diff options
Diffstat (limited to 'src')
202 files changed, 18286 insertions, 6681 deletions
diff --git a/src/ARCodeFile.cpp b/src/ARCodeFile.cpp index d79cd39..a95ebbd 100644 --- a/src/ARCodeFile.cpp +++ b/src/ARCodeFile.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -26,10 +26,9 @@ // TODO: more user-friendly error reporting -ARCodeFile::ARCodeFile(const char* filename) +ARCodeFile::ARCodeFile(std::string filename) { - memset(Filename, 0, sizeof(Filename)); - strncpy(Filename, filename, 1023); + Filename = filename; Error = false; @@ -91,7 +90,7 @@ bool ARCodeFile::Load() if (isincat) Categories.push_back(curcat); isincat = true; - memcpy(curcat.Name, catname, 128); + curcat.Name = catname; curcat.Codes.clear(); } else if (!strncasecmp(start, "CODE", 4)) @@ -118,7 +117,7 @@ bool ARCodeFile::Load() if (isincode) curcat.Codes.push_back(curcode); isincode = true; - memcpy(curcode.Name, codename, 128); + curcode.Name = codename; curcode.Enabled = enable!=0; curcode.CodeLen = 0; } @@ -172,12 +171,12 @@ bool ARCodeFile::Save() ARCodeCat& cat = *it; if (it != Categories.begin()) fprintf(f, "\r\n"); - fprintf(f, "CAT %s\r\n\r\n", cat.Name); + fprintf(f, "CAT %s\r\n\r\n", cat.Name.c_str()); for (ARCodeList::iterator jt = cat.Codes.begin(); jt != cat.Codes.end(); jt++) { ARCode& code = *jt; - fprintf(f, "CODE %d %s\r\n", code.Enabled, code.Name); + fprintf(f, "CODE %d %s\r\n", code.Enabled, code.Name.c_str()); for (u32 i = 0; i < code.CodeLen; i+=2) { diff --git a/src/ARCodeFile.h b/src/ARCodeFile.h index a3c36e4..f9a1c13 100644 --- a/src/ARCodeFile.h +++ b/src/ARCodeFile.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,13 +19,14 @@ #ifndef ARCODEFILE_H #define ARCODEFILE_H +#include <string> #include <list> #include "types.h" struct ARCode { - char Name[128]; + std::string Name; bool Enabled; u32 CodeLen; u32 Code[2*64]; @@ -35,7 +36,7 @@ typedef std::list<ARCode> ARCodeList; struct ARCodeCat { - char Name[128]; + std::string Name; ARCodeList Codes; }; @@ -45,7 +46,7 @@ typedef std::list<ARCodeCat> ARCodeCatList; class ARCodeFile { public: - ARCodeFile(const char* filename); + ARCodeFile(std::string filename); ~ARCodeFile(); bool Error; @@ -56,7 +57,7 @@ public: ARCodeCatList Categories; private: - char Filename[1024]; + std::string Filename; }; #endif // ARCODEFILE_H diff --git a/src/AREngine.cpp b/src/AREngine.cpp index ec3b70e..0605d28 100644 --- a/src/AREngine.cpp +++ b/src/AREngine.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -50,8 +50,6 @@ void DeInit() void Reset() { - CodeFile = nullptr; - if (NDS::ConsoleType == 1) { BusRead8 = DSi::ARM7Read8; @@ -73,6 +71,11 @@ void Reset() } +ARCodeFile* GetCodeFile() +{ + return CodeFile; +} + void SetCodeFile(ARCodeFile* file) { CodeFile = file; @@ -145,7 +148,9 @@ void RunCheat(ARCode& arcode) condstack <<= 1; condstack |= cond; - u32 chk = BusRead32(a & 0x0FFFFFFF); + u32 addr = a & 0x0FFFFFFF; + if (!addr) addr = offset; + u32 chk = BusRead32(addr); cond = (b > chk) ? 1:0; } @@ -156,7 +161,9 @@ void RunCheat(ARCode& arcode) condstack <<= 1; condstack |= cond; - u32 chk = BusRead32(a & 0x0FFFFFFF); + u32 addr = a & 0x0FFFFFFF; + if (!addr) addr = offset; + u32 chk = BusRead32(addr); cond = (b < chk) ? 1:0; } @@ -167,7 +174,9 @@ void RunCheat(ARCode& arcode) condstack <<= 1; condstack |= cond; - u32 chk = BusRead32(a & 0x0FFFFFFF); + u32 addr = a & 0x0FFFFFFF; + if (!addr) addr = offset; + u32 chk = BusRead32(addr); cond = (b == chk) ? 1:0; } @@ -178,7 +187,9 @@ void RunCheat(ARCode& arcode) condstack <<= 1; condstack |= cond; - u32 chk = BusRead32(a & 0x0FFFFFFF); + u32 addr = a & 0x0FFFFFFF; + if (!addr) addr = offset; + u32 chk = BusRead32(addr); cond = (b != chk) ? 1:0; } @@ -189,7 +200,9 @@ void RunCheat(ARCode& arcode) condstack <<= 1; condstack |= cond; - u16 val = BusRead16(a & 0x0FFFFFFF); + u32 addr = a & 0x0FFFFFFF; + if (!addr) addr = offset; + u16 val = BusRead16(addr); u16 chk = ~(b >> 16); chk &= val; @@ -202,7 +215,9 @@ void RunCheat(ARCode& arcode) condstack <<= 1; condstack |= cond; - u16 val = BusRead16(a & 0x0FFFFFFF); + u32 addr = a & 0x0FFFFFFF; + if (!addr) addr = offset; + u16 val = BusRead16(addr); u16 chk = ~(b >> 16); chk &= val; @@ -215,7 +230,9 @@ void RunCheat(ARCode& arcode) condstack <<= 1; condstack |= cond; - u16 val = BusRead16(a & 0x0FFFFFFF); + u32 addr = a & 0x0FFFFFFF; + if (!addr) addr = offset; + u16 val = BusRead16(addr); u16 chk = ~(b >> 16); chk &= val; @@ -228,7 +245,9 @@ void RunCheat(ARCode& arcode) condstack <<= 1; condstack |= cond; - u16 val = BusRead16(a & 0x0FFFFFFF); + u32 addr = a & 0x0FFFFFFF; + if (!addr) addr = offset; + u16 val = BusRead16(addr); u16 chk = ~(b >> 16); chk &= val; diff --git a/src/AREngine.h b/src/AREngine.h index c142711..3e6e9dd 100644 --- a/src/AREngine.h +++ b/src/AREngine.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -28,6 +28,7 @@ bool Init(); void DeInit(); void Reset(); +ARCodeFile* GetCodeFile(); void SetCodeFile(ARCodeFile* file); void RunCheats(); diff --git a/src/ARM.cpp b/src/ARM.cpp index 55f60bf..e19cb00 100644 --- a/src/ARM.cpp +++ b/src/ARM.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter.cpp b/src/ARMInterpreter.cpp index dfcb801..aaa1e7b 100644 --- a/src/ARMInterpreter.cpp +++ b/src/ARMInterpreter.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter.h b/src/ARMInterpreter.h index 7b82593..9a0fc28 100644 --- a/src/ARMInterpreter.h +++ b/src/ARMInterpreter.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_ALU.cpp b/src/ARMInterpreter_ALU.cpp index 92d1571..3949bb1 100644 --- a/src/ARMInterpreter_ALU.cpp +++ b/src/ARMInterpreter_ALU.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_ALU.h b/src/ARMInterpreter_ALU.h index a904462..8dcc311 100644 --- a/src/ARMInterpreter_ALU.h +++ b/src/ARMInterpreter_ALU.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_Branch.cpp b/src/ARMInterpreter_Branch.cpp index f810ced..848c03b 100644 --- a/src/ARMInterpreter_Branch.cpp +++ b/src/ARMInterpreter_Branch.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_Branch.h b/src/ARMInterpreter_Branch.h index 70b0775..775e895 100644 --- a/src/ARMInterpreter_Branch.h +++ b/src/ARMInterpreter_Branch.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_LoadStore.cpp b/src/ARMInterpreter_LoadStore.cpp index 1c81900..e7b83eb 100644 --- a/src/ARMInterpreter_LoadStore.cpp +++ b/src/ARMInterpreter_LoadStore.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_LoadStore.h b/src/ARMInterpreter_LoadStore.h index 80f33b2..efd4f18 100644 --- a/src/ARMInterpreter_LoadStore.h +++ b/src/ARMInterpreter_LoadStore.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT.cpp b/src/ARMJIT.cpp index 2f9cad9..32f20d5 100644 --- a/src/ARMJIT.cpp +++ b/src/ARMJIT.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -1086,11 +1086,34 @@ void InvalidateByAddr(u32 localAddr) void CheckAndInvalidateITCM() { - for (u32 i = 0; i < ITCMPhysicalSize; i+=16) + for (u32 i = 0; i < ITCMPhysicalSize; i+=512) { - if (CodeIndexITCM[i / 512].Code & (1 << ((i & 0x1FF) / 16))) + if (CodeIndexITCM[i / 512].Code) { - InvalidateByAddr(i | (ARMJIT_Memory::memregion_ITCM << 27)); + // maybe using bitscan would be better here? + // The thing is that in densely populated sets + // The old fashioned way can actually be faster + for (u32 j = 0; j < 512; j += 16) + { + if (CodeIndexITCM[i / 512].Code & (1 << ((j & 0x1FF) / 16))) + InvalidateByAddr((i+j) | (ARMJIT_Memory::memregion_ITCM << 27)); + } + } + } +} + +void CheckAndInvalidateWVRAM(int bank) +{ + u32 start = bank == 1 ? 0x20000 : 0; + for (u32 i = start; i < start+0x20000; i+=512) + { + if (CodeIndexARM7WVRAM[i / 512].Code) + { + for (u32 j = 0; j < 512; j += 16) + { + if (CodeIndexARM7WVRAM[i / 512].Code & (1 << ((j & 0x1FF) / 16))) + InvalidateByAddr((i+j) | (ARMJIT_Memory::memregion_VWRAM << 27)); + } } } } diff --git a/src/ARMJIT.h b/src/ARMJIT.h index 09ba5d2..97c79cd 100644 --- a/src/ARMJIT.h +++ b/src/ARMJIT.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -44,6 +44,7 @@ void DeInit(); void Reset(); void CheckAndInvalidateITCM(); +void CheckAndInvalidateWVRAM(int bank); void InvalidateByAddr(u32 pseudoPhysical); diff --git a/src/ARMJIT_A64/ARMJIT_ALU.cpp b/src/ARMJIT_A64/ARMJIT_ALU.cpp index 1bd427f..3d52f28 100644 --- a/src/ARMJIT_A64/ARMJIT_ALU.cpp +++ b/src/ARMJIT_A64/ARMJIT_ALU.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_Branch.cpp b/src/ARMJIT_A64/ARMJIT_Branch.cpp index 2f640c8..1cdefea 100644 --- a/src/ARMJIT_A64/ARMJIT_Branch.cpp +++ b/src/ARMJIT_A64/ARMJIT_Branch.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_Compiler.cpp b/src/ARMJIT_A64/ARMJIT_Compiler.cpp index 1ad24b1..cc5275c 100644 --- a/src/ARMJIT_A64/ARMJIT_Compiler.cpp +++ b/src/ARMJIT_A64/ARMJIT_Compiler.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_Compiler.h b/src/ARMJIT_A64/ARMJIT_Compiler.h index 235155f..5045cb5 100644 --- a/src/ARMJIT_A64/ARMJIT_Compiler.h +++ b/src/ARMJIT_A64/ARMJIT_Compiler.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_Linkage.S b/src/ARMJIT_A64/ARMJIT_Linkage.S index 37a83e9..c1ecd7c 100644 --- a/src/ARMJIT_A64/ARMJIT_Linkage.S +++ b/src/ARMJIT_A64/ARMJIT_Linkage.S @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_LoadStore.cpp b/src/ARMJIT_A64/ARMJIT_LoadStore.cpp index 7ce6d24..f159d88 100644 --- a/src/ARMJIT_A64/ARMJIT_LoadStore.cpp +++ b/src/ARMJIT_A64/ARMJIT_LoadStore.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_Compiler.h b/src/ARMJIT_Compiler.h index 200972d..c5348f4 100644 --- a/src/ARMJIT_Compiler.h +++ b/src/ARMJIT_Compiler.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_Internal.h b/src/ARMJIT_Internal.h index 6988db0..fb80307 100644 --- a/src/ARMJIT_Internal.h +++ b/src/ARMJIT_Internal.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_Memory.cpp b/src/ARMJIT_Memory.cpp index 0f1dd25..d8a8c18 100644 --- a/src/ARMJIT_Memory.cpp +++ b/src/ARMJIT_Memory.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -55,7 +55,7 @@ and map the memory regions as they're structured on the DS in it. - On most systems you have a single piece of main ram, + On most systems you have a single piece of main ram, maybe some video ram and faster cache RAM and that's about it. Here we have not only a lot more different memory regions, but also two address spaces. Not only that but they all have @@ -119,7 +119,7 @@ void HandleFault(u64 pc, u64 lr, u64 fp, u64 faultAddr, u32 desc); extern "C" { - + void ARM_RestoreContext(u64* registers) __attribute__((noreturn)); extern char __start__; @@ -146,7 +146,7 @@ void __libnx_exception_handler(ThreadExceptionDump* ctx) { integerRegisters[32] = (u64)desc.FaultPC; - ARM_RestoreContext(integerRegisters); + ARM_RestoreContext(integerRegisters); } HandleFault(ctx->pc.x, ctx->lr.x, ctx->fp.x, ctx->far.x, ctx->error_desc); @@ -311,7 +311,7 @@ bool MapIntoRange(u32 addr, u32 num, u32 offset, u32 size) { u8* dst = (u8*)(num == 0 ? FastMem9Start : FastMem7Start) + addr; #ifdef __SWITCH__ - Result r = (svcMapProcessMemory(dst, envGetOwnProcessHandle(), + Result r = (svcMapProcessMemory(dst, envGetOwnProcessHandle(), (u64)(MemoryBaseCodeMem + offset), size)); return R_SUCCEEDED(r); #elif defined(_WIN32) @@ -452,7 +452,7 @@ void SetCodeProtection(int region, u32 offset, bool protect) u32 effectiveAddr = mapping.Addr + (offset - mapping.LocalOffset); if (mapping.Num == 0 - && region != memregion_DTCM + && region != memregion_DTCM && (effectiveAddr & NDS::ARM9->DTCMMask) == NDS::ARM9->DTCMBase) continue; @@ -621,7 +621,7 @@ bool MapAtAddress(u32 addr) // this overcomplicated piece of code basically just finds whole pieces of code memory // which can be mapped/protected - u32 offset = 0; + u32 offset = 0; bool skipDTCM = num == 0 && region != memregion_DTCM; while (offset < mirrorSize) { @@ -700,10 +700,10 @@ void Init() virtmemLock(); MemoryBaseCodeMem = (u8*)virtmemFindCodeMemory(MemoryTotalSize, 0x1000); - bool succeded = R_SUCCEEDED(svcMapProcessCodeMemory(envGetOwnProcessHandle(), (u64)MemoryBaseCodeMem, + bool succeded = R_SUCCEEDED(svcMapProcessCodeMemory(envGetOwnProcessHandle(), (u64)MemoryBaseCodeMem, (u64)MemoryBase, MemoryTotalSize)); assert(succeded); - succeded = R_SUCCEEDED(svcSetProcessMemoryPermission(envGetOwnProcessHandle(), (u64)MemoryBaseCodeMem, + succeded = R_SUCCEEDED(svcSetProcessMemoryPermission(envGetOwnProcessHandle(), (u64)MemoryBaseCodeMem, MemoryTotalSize, Perm_Rw)); assert(succeded); @@ -740,7 +740,7 @@ void Init() u8* basePtr = MemoryBase; #else // this used to be allocated with three different mmaps - // The idea was to give the OS more freedom where to position the buffers, + // The idea was to give the OS more freedom where to position the buffers, // but something was bad about this so instead we take this vmem eating monster // which seems to work better. MemoryBase = (u8*)mmap(NULL, AddrSpaceSize*4, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); @@ -855,7 +855,7 @@ bool IsFastmemCompatible(int region) TODO: with some hacks, the smaller shared WRAM regions could be mapped in some occaisons as well */ - if (region == memregion_DTCM + if (region == memregion_DTCM || region == memregion_SharedWRAM || region == memregion_NewSharedWRAM_B || region == memregion_NewSharedWRAM_C) @@ -1071,7 +1071,7 @@ int ClassifyAddress9(u32 addr) { return memregion_DTCM; } - else + else { if (NDS::ConsoleType == 1 && addr >= 0xFFFF0000 && !(DSi::SCFG_BIOS & (1<<1))) { @@ -1107,6 +1107,8 @@ int ClassifyAddress9(u32 addr) return memregion_IO9; case 0x06000000: return memregion_VRAM; + case 0x0C000000: + return (NDS::ConsoleType==1) ? memregion_MainRAM : memregion_Other; default: return memregion_Other; } @@ -1156,7 +1158,9 @@ int ClassifyAddress7(u32 addr) case 0x06000000: case 0x06800000: return memregion_VWRAM; - + case 0x0C000000: + case 0x0C800000: + return (NDS::ConsoleType==1) ? memregion_MainRAM : memregion_Other; default: return memregion_Other; } @@ -1219,8 +1223,8 @@ void* GetFuncForAddr(ARM* cpu, u32 addr, bool store, int size) { switch (size | store) { - case 8: return (void*)GPU3D::Read8; - case 9: return (void*)GPU3D::Write8; + case 8: return (void*)GPU3D::Read8; + case 9: return (void*)GPU3D::Write8; case 16: return (void*)GPU3D::Read16; case 17: return (void*)GPU3D::Write16; case 32: return (void*)GPU3D::Read32; @@ -1256,7 +1260,7 @@ void* GetFuncForAddr(ARM* cpu, u32 addr, bool store, int size) case 0x06000000: switch (size | store) { - case 8: return (void*)VRAMRead<u8>; + case 8: return (void*)VRAMRead<u8>; case 9: return NULL; case 16: return (void*)VRAMRead<u16>; case 17: return (void*)VRAMWrite<u16>; @@ -1275,8 +1279,8 @@ void* GetFuncForAddr(ARM* cpu, u32 addr, bool store, int size) { switch (size | store) { - case 8: return (void*)SPU::Read8; - case 9: return (void*)SPU::Write8; + case 8: return (void*)SPU::Read8; + case 9: return (void*)SPU::Write8; case 16: return (void*)SPU::Read16; case 17: return (void*)SPU::Write16; case 32: return (void*)SPU::Read32; @@ -1289,7 +1293,7 @@ void* GetFuncForAddr(ARM* cpu, u32 addr, bool store, int size) switch (size | store) { case 8: return (void*)NDS::ARM7IORead8; - case 9: return (void*)NDS::ARM7IOWrite8; + case 9: return (void*)NDS::ARM7IOWrite8; case 16: return (void*)NDS::ARM7IORead16; case 17: return (void*)NDS::ARM7IOWrite16; case 32: return (void*)NDS::ARM7IORead32; @@ -1301,7 +1305,7 @@ void* GetFuncForAddr(ARM* cpu, u32 addr, bool store, int size) switch (size | store) { case 8: return (void*)DSi::ARM7IORead8; - case 9: return (void*)DSi::ARM7IOWrite8; + case 9: return (void*)DSi::ARM7IOWrite8; case 16: return (void*)DSi::ARM7IORead16; case 17: return (void*)DSi::ARM7IOWrite16; case 32: return (void*)DSi::ARM7IORead32; diff --git a/src/ARMJIT_Memory.h b/src/ARMJIT_Memory.h index a1e089e..726b710 100644 --- a/src/ARMJIT_Memory.h +++ b/src/ARMJIT_Memory.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -78,4 +78,4 @@ void* GetFuncForAddr(ARM* cpu, u32 addr, bool store, int size); } -#endif
\ No newline at end of file +#endif diff --git a/src/ARMJIT_RegisterCache.h b/src/ARMJIT_RegisterCache.h index 1eb4c4b..e0c4a15 100644 --- a/src/ARMJIT_RegisterCache.h +++ b/src/ARMJIT_RegisterCache.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. @@ -20,6 +20,7 @@ #define ARMJIT_REGCACHE_H #include "ARMJIT.h" +#include "ARMJIT_Internal.h" // TODO: replace this in the future #include "dolphin/BitSet.h" @@ -41,7 +42,7 @@ public: { for (int i = 0; i < 16; i++) Mapping[i] = (Reg)-1; - + PCAllocatableAsSrc = ~(pcAllocatableAsSrc ? 0 : (1 << 15)); diff --git a/src/ARMJIT_x64/ARMJIT_ALU.cpp b/src/ARMJIT_x64/ARMJIT_ALU.cpp index 16fb22a..0a364ea 100644 --- a/src/ARMJIT_x64/ARMJIT_ALU.cpp +++ b/src/ARMJIT_x64/ARMJIT_ALU.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -364,7 +364,7 @@ void Compiler::A_Comp_Mul_Long() { BSR(32, RSCRATCH, R(RSCRATCH3)); } - + SHR(32, R(RSCRATCH), Imm8(3)); SetJumpTarget(zeroBSR); // fortunately that's even right Comp_AddCycles_CI(RSCRATCH, 2); @@ -617,7 +617,7 @@ void Compiler::T_Comp_AddSub_() int op = (CurInstr.Instr >> 9) & 0x3; OpArg rn = op >= 2 ? Imm32((CurInstr.Instr >> 6) & 0x7) : MapReg(CurInstr.T_Reg(6)); - + Comp_AddCycles_C(); // special case for thumb mov being alias to add rd, rn, #0 diff --git a/src/ARMJIT_x64/ARMJIT_Branch.cpp b/src/ARMJIT_x64/ARMJIT_Branch.cpp index a89cf99..8451665 100644 --- a/src/ARMJIT_x64/ARMJIT_Branch.cpp +++ b/src/ARMJIT_x64/ARMJIT_Branch.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -30,7 +30,7 @@ int squeezePointer(T* ptr) assert((T*)((u64)truncated) == ptr); return truncated; } - + void Compiler::Comp_JumpTo(u32 addr, bool forceNonConstantCycles) { // we can simplify constant branches by a lot diff --git a/src/ARMJIT_x64/ARMJIT_Compiler.cpp b/src/ARMJIT_x64/ARMJIT_Compiler.cpp index 6fffcec..fa015f4 100644 --- a/src/ARMJIT_x64/ARMJIT_Compiler.cpp +++ b/src/ARMJIT_x64/ARMJIT_Compiler.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_Compiler.h b/src/ARMJIT_x64/ARMJIT_Compiler.h index 5046c88..f5817a9 100644 --- a/src/ARMJIT_x64/ARMJIT_Compiler.h +++ b/src/ARMJIT_x64/ARMJIT_Compiler.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -121,7 +121,7 @@ public: void A_Comp_Mul_Long(); void A_Comp_CLZ(); - + void A_Comp_MemWB(); void A_Comp_MemHalf(); void A_Comp_LDM_STM(); @@ -170,7 +170,7 @@ public: s32 Comp_MemAccessBlock(int rn, BitSet16 regs, bool store, bool preinc, bool decrement, bool usermode, bool skipLoadingRn); bool Comp_MemLoadLiteral(int size, bool signExtend, int rd, u32 addr); - void Comp_ArithTriOp(void (Compiler::*op)(int, const Gen::OpArg&, const Gen::OpArg&), + void Comp_ArithTriOp(void (Compiler::*op)(int, const Gen::OpArg&, const Gen::OpArg&), Gen::OpArg rd, Gen::OpArg rn, Gen::OpArg op2, bool carryUsed, int opFlags); void Comp_ArithTriOpReverse(void (Compiler::*op)(int, const Gen::OpArg&, const Gen::OpArg&), Gen::OpArg rd, Gen::OpArg rn, Gen::OpArg op2, bool carryUsed, int opFlags); diff --git a/src/ARMJIT_x64/ARMJIT_GenOffsets.cpp b/src/ARMJIT_x64/ARMJIT_GenOffsets.cpp index 6d0dd21..0961fdd 100644 --- a/src/ARMJIT_x64/ARMJIT_GenOffsets.cpp +++ b/src/ARMJIT_x64/ARMJIT_GenOffsets.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_Linkage.S b/src/ARMJIT_x64/ARMJIT_Linkage.S index 650c93b..fe7b143 100644 --- a/src/ARMJIT_x64/ARMJIT_Linkage.S +++ b/src/ARMJIT_x64/ARMJIT_Linkage.S @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_LoadStore.cpp b/src/ARMJIT_x64/ARMJIT_LoadStore.cpp index 07cade4..7b28e68 100644 --- a/src/ARMJIT_x64/ARMJIT_LoadStore.cpp +++ b/src/ARMJIT_x64/ARMJIT_LoadStore.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_Offsets.h b/src/ARMJIT_x64/ARMJIT_Offsets.h index ade28b6..9d2a952 100644 --- a/src/ARMJIT_x64/ARMJIT_Offsets.h +++ b/src/ARMJIT_x64/ARMJIT_Offsets.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARM_InstrInfo.cpp b/src/ARM_InstrInfo.cpp index afd11da..a7171ba 100644 --- a/src/ARM_InstrInfo.cpp +++ b/src/ARM_InstrInfo.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/ARM_InstrInfo.h b/src/ARM_InstrInfo.h index 2f22e8e..0104027 100644 --- a/src/ARM_InstrInfo.h +++ b/src/ARM_InstrInfo.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -222,7 +222,7 @@ enum tk_POP, tk_LDMIA, tk_STMIA, - + tk_BCOND, tk_BX, tk_BLX_REG, @@ -278,4 +278,4 @@ Info Decode(bool thumb, u32 num, u32 instr); } -#endif
\ No newline at end of file +#endif diff --git a/src/ARM_InstrTable.h b/src/ARM_InstrTable.h index 5f476f5..e4b2906 100644 --- a/src/ARM_InstrTable.h +++ b/src/ARM_InstrTable.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d1f47dc..cdb0587 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,142 +1,145 @@ -project(core) - set (CMAKE_CXX_STANDARD 17) +include(FixInterfaceIncludes) + add_library(core STATIC - ARCodeFile.cpp - AREngine.cpp - ARM.cpp - ARM_InstrTable.h - ARMInterpreter.cpp - ARMInterpreter_ALU.cpp - ARMInterpreter_Branch.cpp - ARMInterpreter_LoadStore.cpp - CP15.cpp - CRC32.cpp - DMA.cpp - DMA_Timings.h - DSi.cpp - DSi_AES.cpp - DSi_Camera.cpp - DSi_DSP.cpp - DSi_I2C.cpp - DSi_NAND.cpp - DSi_NDMA.cpp - DSi_NWifi.cpp - DSi_SD.cpp - DSi_SPI_TSC.cpp - FATStorage.cpp - FIFO.h - GBACart.cpp - GPU.cpp - GPU2D.cpp - GPU2D_Soft.cpp - GPU3D.cpp - GPU3D_Soft.cpp - melonDLDI.h - NDS.cpp - NDSCart.cpp - NDSCart_SRAMManager.cpp - Platform.h - ROMList.h - FreeBIOS.h - RTC.cpp - Savestate.cpp - SPI.cpp - SPU.cpp - types.h - version.h - Wifi.cpp - WifiAP.cpp - - fatfs/diskio.c - fatfs/ff.c - fatfs/ffsystem.c - fatfs/ffunicode.c - fatfs/ffconf.h - - sha1/sha1.c - tiny-AES-c/aes.c - xxhash/xxhash.c -) + ARCodeFile.cpp + AREngine.cpp + ARM.cpp + ARM_InstrTable.h + ARMInterpreter.cpp + ARMInterpreter_ALU.cpp + ARMInterpreter_Branch.cpp + ARMInterpreter_LoadStore.cpp + CP15.cpp + CRC32.cpp + DMA.cpp + DMA_Timings.h + DSi.cpp + DSi_AES.cpp + DSi_Camera.cpp + DSi_DSP.cpp + DSi_I2C.cpp + DSi_NAND.cpp + DSi_NDMA.cpp + DSi_NWifi.cpp + DSi_SD.cpp + DSi_SPI_TSC.cpp + FATStorage.cpp + FIFO.h + GBACart.cpp + GPU.cpp + GPU2D.cpp + GPU2D_Soft.cpp + GPU3D.cpp + GPU3D_Soft.cpp + melonDLDI.h + NDS.cpp + NDSCart.cpp + Platform.h + ROMList.h + FreeBIOS.h + RTC.cpp + Savestate.cpp + SPI.cpp + SPU.cpp + types.h + version.h + Wifi.cpp + WifiAP.cpp + + fatfs/diskio.c + fatfs/ff.c + fatfs/ffsystem.c + fatfs/ffunicode.c + fatfs/ffconf.h + + sha1/sha1.c + tiny-AES-c/aes.c + xxhash/xxhash.c) if (ENABLE_OGLRENDERER) - target_sources(core PRIVATE - GPU_OpenGL.cpp - GPU_OpenGL_shaders.h - GPU3D_OpenGL.cpp - GPU3D_OpenGL_shaders.h - OpenGLSupport.cpp - ) + target_sources(core PRIVATE + GPU_OpenGL.cpp + GPU_OpenGL_shaders.h + GPU3D_OpenGL.cpp + GPU3D_OpenGL_shaders.h + OpenGLSupport.cpp) endif() if (ENABLE_JIT) - enable_language(ASM) - - target_sources(core PRIVATE - ARM_InstrInfo.cpp - - ARMJIT.cpp - ARMJIT_Memory.cpp - - dolphin/CommonFuncs.cpp - ) - - if (ARCHITECTURE STREQUAL x86_64) - target_sources(core PRIVATE - dolphin/x64ABI.cpp - dolphin/x64CPUDetect.cpp - dolphin/x64Emitter.cpp - - ARMJIT_x64/ARMJIT_Compiler.cpp - ARMJIT_x64/ARMJIT_ALU.cpp - ARMJIT_x64/ARMJIT_LoadStore.cpp - ARMJIT_x64/ARMJIT_Branch.cpp - - ARMJIT_x64/ARMJIT_Linkage.S - ) - endif() - if (ARCHITECTURE STREQUAL ARM64) - target_sources(core PRIVATE - dolphin/Arm64Emitter.cpp - dolphin/MathUtil.cpp - - ARMJIT_A64/ARMJIT_Compiler.cpp - ARMJIT_A64/ARMJIT_ALU.cpp - ARMJIT_A64/ARMJIT_LoadStore.cpp - ARMJIT_A64/ARMJIT_Branch.cpp - - ARMJIT_A64/ARMJIT_Linkage.S - ) - endif() + enable_language(ASM) + + target_sources(core PRIVATE + ARM_InstrInfo.cpp + + ARMJIT.cpp + ARMJIT_Memory.cpp + + dolphin/CommonFuncs.cpp) + + if (ARCHITECTURE STREQUAL x86_64) + target_sources(core PRIVATE + dolphin/x64ABI.cpp + dolphin/x64CPUDetect.cpp + dolphin/x64Emitter.cpp + + ARMJIT_x64/ARMJIT_Compiler.cpp + ARMJIT_x64/ARMJIT_ALU.cpp + ARMJIT_x64/ARMJIT_LoadStore.cpp + ARMJIT_x64/ARMJIT_Branch.cpp + + ARMJIT_x64/ARMJIT_Linkage.S) + endif() + if (ARCHITECTURE STREQUAL ARM64) + target_sources(core PRIVATE + dolphin/Arm64Emitter.cpp + dolphin/MathUtil.cpp + + ARMJIT_A64/ARMJIT_Compiler.cpp + ARMJIT_A64/ARMJIT_ALU.cpp + ARMJIT_A64/ARMJIT_LoadStore.cpp + ARMJIT_A64/ARMJIT_Branch.cpp + + ARMJIT_A64/ARMJIT_Linkage.S) + endif() endif() add_subdirectory(teakra EXCLUDE_FROM_ALL) -target_link_libraries(core teakra) +target_link_libraries(core PRIVATE teakra) +find_library(m MATH_LIBRARY) + +if (MATH_LIBRARY) + target_link_libraries(core PRIVATE ${MATH_LIBRARY}) +endif() if (ENABLE_OGLRENDERER) find_package(PkgConfig REQUIRED) - pkg_check_modules(EPOXY REQUIRED epoxy) - - target_include_directories(core PRIVATE ${EPOXY_INCLUDE_DIRS}) - if (WIN32) - target_link_libraries(core ole32 comctl32 ws2_32 ${EPOXY_LIBRARIES}) - elseif (APPLE) - target_link_libraries(core ${EPOXY_LIBRARIES}) - else() - target_link_libraries(core rt ${EPOXY_LIBRARIES}) + pkg_check_modules(Epoxy REQUIRED IMPORTED_TARGET epoxy) + fix_interface_includes(PkgConfig::Epoxy) + + target_link_libraries(core PUBLIC PkgConfig::Epoxy) + + target_compile_definitions(core PUBLIC OGLRENDERER_ENABLED) +endif() + +if (ENABLE_JIT) + target_compile_definitions(core PUBLIC JIT_ENABLED) + + + if (ENABLE_JIT_PROFILING) + include(cmake/FindVTune.cmake) + add_definitions(-DJIT_PROFILING_ENABLED) endif() -else() - if (WIN32) - target_link_libraries(core ole32 comctl32 ws2_32) - elseif (APPLE) - target_link_libraries(core) - else() - target_link_libraries(core rt) - endif() +endif() + +if (WIN32) + target_link_libraries(core PRIVATE ole32 comctl32 ws2_32) +elseif(NOT APPLE) + target_link_libraries(core PRIVATE rt) endif() if (ENABLE_JIT_PROFILING) - target_link_libraries(core jitprofiling) + target_link_libraries(core PRIVATE jitprofiling) endif() diff --git a/src/CP15.cpp b/src/CP15.cpp index e75201b..4fe91cf 100644 --- a/src/CP15.cpp +++ b/src/CP15.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -134,6 +134,9 @@ void ARMv5::UpdateITCMSetting() if (CP15Control & (1<<18)) { ITCMSize = 0x200 << ((ITCMSetting >> 1) & 0x1F); +#ifdef JIT_ENABLED + FastBlockLookupSize = 0; +#endif } else { diff --git a/src/CRC32.cpp b/src/CRC32.cpp index b51b171..c2cc6c0 100644 --- a/src/CRC32.cpp +++ b/src/CRC32.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -52,7 +52,7 @@ void _inittable() } } -u32 CRC32(u8 *data, int len) +u32 CRC32(u8 *data, int len, u32 start) { if (!tableinited) { @@ -60,7 +60,7 @@ u32 CRC32(u8 *data, int len) tableinited = true; } - u32 crc = 0xFFFFFFFF; + u32 crc = start ^ 0xFFFFFFFF; while (len--) crc = (crc >> 8) ^ crctable[(crc & 0xFF) ^ *data++]; diff --git a/src/CRC32.h b/src/CRC32.h index 2107533..1be5a8a 100644 --- a/src/CRC32.h +++ b/src/CRC32.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -21,6 +21,6 @@ #include "types.h" -u32 CRC32(u8* data, int len); +u32 CRC32(u8* data, int len, u32 start=0); #endif // CRC32_H diff --git a/src/DMA.cpp b/src/DMA.cpp index e601871..f45efc5 100644 --- a/src/DMA.cpp +++ b/src/DMA.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -20,6 +20,7 @@ #define DMA_H #include "types.h" +#include "Savestate.h" class DMA { @@ -96,7 +97,7 @@ private: bool IsGXFIFODMA; u32 MRAMBurstCount; - u8* MRAMBurstTable; + const u8* MRAMBurstTable; }; #endif diff --git a/src/DMA_Timings.h b/src/DMA_Timings.h index 1d86750..4281c78 100644 --- a/src/DMA_Timings.h +++ b/src/DMA_Timings.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,6 +19,8 @@ #ifndef DMA_TIMINGS_H #define DMA_TIMINGS_H +#include "types.h" + namespace DMATiming { @@ -43,9 +45,9 @@ namespace DMATiming // setting. Timings are such that the nonseq setting only matters for the first // access, and minor edge cases (like the last of a 0x20000-byte block). -u8 MRAMDummy[1] = {0}; +constexpr u8 MRAMDummy[1] = {0}; -u8 MRAMRead16Bursts[][256] = +constexpr u8 MRAMRead16Bursts[][256] = { // main RAM to regular 16bit or 32bit bus (similar) {7, 3, 2, 2, 2, 2, 2, 2, 2, 2, @@ -119,7 +121,7 @@ u8 MRAMRead16Bursts[][256] = 0}, }; -u8 MRAMRead32Bursts[][256] = +constexpr u8 MRAMRead32Bursts[][256] = { // main RAM to regular 16bit bus {9, 4, 3, 3, 3, 3, 3, 3, 3, 3, @@ -178,7 +180,7 @@ u8 MRAMRead32Bursts[][256] = 0}, }; -u8 MRAMWrite16Bursts[][256] = +constexpr u8 MRAMWrite16Bursts[][256] = { // regular 16bit or 32bit bus to main RAM (similar) {8, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -209,7 +211,7 @@ u8 MRAMWrite16Bursts[][256] = 0}, }; -u8 MRAMWrite32Bursts[][256] = +constexpr u8 MRAMWrite32Bursts[][256] = { // regular 16bit bus to main RAM {9, 4, 4, 4, 4, 4, 4, 4, 4, 4, diff --git a/src/DSi.cpp b/src/DSi.cpp index 9267f14..cfce0ac 100644 --- a/src/DSi.cpp +++ b/src/DSi.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -47,8 +47,6 @@ namespace DSi { -u32 BootAddr[2]; - u16 SCFG_BIOS; u16 SCFG_Clock9; u16 SCFG_Clock7; @@ -79,16 +77,12 @@ DSi_NDMA* NDMAs[8]; DSi_SDHost* SDMMC; DSi_SDHost* SDIO; -FILE* SDMMCFile = nullptr; - u64 ConsoleID; u8 eMMC_CID[16]; -u8 ITCMInit[0x8000]; -u8 ARM7Init[0x3C00]; - void Set_SCFG_Clock9(u16 val); +void Set_SCFG_MC(u32 val); bool Init() @@ -100,6 +94,7 @@ bool Init() #endif if (!DSi_I2C::Init()) return false; + if (!DSi_CamModule::Init()) return false; if (!DSi_AES::Init()) return false; if (!DSi_DSP::Init()) return false; @@ -127,6 +122,7 @@ void DeInit() #endif DSi_I2C::DeInit(); + DSi_CamModule::DeInit(); DSi_AES::DeInit(); DSi_DSP::DeInit(); @@ -134,8 +130,6 @@ void DeInit() delete SDMMC; delete SDIO; - - CloseDSiNAND(); } void Reset() @@ -144,27 +138,31 @@ void Reset() //NDS::ARM9->CP15Write(0x911, 0x00000020); //NDS::ARM9->CP15Write(0x100, NDS::ARM9->CP15Read(0x100) | 0x00050000); - NDS::ARM9->JumpTo(BootAddr[0]); - NDS::ARM7->JumpTo(BootAddr[1]); + NDS::MapSharedWRAM(3); NDMACnt[0] = 0; NDMACnt[1] = 0; for (int i = 0; i < 8; i++) NDMAs[i]->Reset(); - memcpy(NDS::ARM9->ITCM, ITCMInit, 0x8000); - DSi_I2C::Reset(); - DSi_AES::Reset(); + DSi_CamModule::Reset(); DSi_DSP::Reset(); + SDMMC->CloseHandles(); + SDIO->CloseHandles(); + + LoadNAND(); + SDMMC->Reset(); SDIO->Reset(); + DSi_AES::Reset(); + SCFG_BIOS = 0x0101; // TODO: should be zero when booting from BIOS SCFG_Clock9 = 0x0187; // CHECKME SCFG_Clock7 = 0x0187; SCFG_EXT[0] = 0x8307F100; SCFG_EXT[1] = 0x93FFFB06; - SCFG_MC = 0x0010;//0x0011; + SCFG_MC = 0x0010 | (~((u32)NDSCart::CartInserted)&1);//0x0011; SCFG_RST = 0; DSi_DSP::SetRstLine(false); @@ -172,22 +170,98 @@ void Reset() // LCD init flag GPU::DispStat[0] |= (1<<6); GPU::DispStat[1] |= (1<<6); +} - NDS::MapSharedWRAM(3); +void Stop() +{ + DSi_CamModule::Stop(); +} - for (u32 i = 0; i < 0x3C00; i+=4) - ARM7Write32(0x03FFC400+i, *(u32*)&ARM7Init[i]); +void DoSavestate(Savestate* file) +{ + file->Section("DSIG"); - u32 eaddr = 0x03FFE6E4; - ARM7Write32(eaddr+0x00, *(u32*)&eMMC_CID[0]); - ARM7Write32(eaddr+0x04, *(u32*)&eMMC_CID[4]); - ARM7Write32(eaddr+0x08, *(u32*)&eMMC_CID[8]); - ARM7Write32(eaddr+0x0C, *(u32*)&eMMC_CID[12]); - ARM7Write16(eaddr+0x2C, 0x0001); - ARM7Write16(eaddr+0x2E, 0x0001); - ARM7Write16(eaddr+0x3C, 0x0100); - ARM7Write16(eaddr+0x3E, 0x40E0); - ARM7Write16(eaddr+0x42, 0x0001); + file->Var16(&SCFG_BIOS); + file->Var16(&SCFG_Clock9); + file->Var16(&SCFG_Clock7); + file->VarArray(&SCFG_EXT[0], sizeof(u32)*2); + file->Var32(&SCFG_MC); + file->Var16(&SCFG_RST); + + //file->VarArray(ARM9iBIOS, 0x10000); + //file->VarArray(ARM7iBIOS, 0x10000); + + if (file->Saving) + { + file->VarArray(&MBK[0][0], sizeof(u32)*8); + file->VarArray(&MBK[1][5], sizeof(u32)*3); + file->Var32(&MBK[0][8]); + } + else + { + Set_SCFG_Clock9(SCFG_Clock9); + Set_SCFG_MC(SCFG_MC); + DSi_DSP::SetRstLine(SCFG_RST & 0x0001); + + MBK[0][8] = 0; + MBK[1][8] = 0; + + u32 mbk[12]; + file->VarArray(&mbk, sizeof(u32)*12); + + MapNWRAM_A(0, mbk[0] & 0xFF); + MapNWRAM_A(1, (mbk[0] >> 8) & 0xFF); + MapNWRAM_A(2, (mbk[0] >> 16) & 0xFF); + MapNWRAM_A(3, mbk[0] >> 24); + + MapNWRAM_B(0, mbk[1] & 0xFF); + MapNWRAM_B(1, (mbk[1] >> 8) & 0xFF); + MapNWRAM_B(2, (mbk[1] >> 16) & 0xFF); + MapNWRAM_B(3, mbk[1] >> 24); + MapNWRAM_B(4, mbk[2] & 0xFF); + MapNWRAM_B(5, (mbk[2] >> 8) & 0xFF); + MapNWRAM_B(6, (mbk[2] >> 16) & 0xFF); + MapNWRAM_B(7, mbk[2] >> 24); + + MapNWRAM_C(0, mbk[3] & 0xFF); + MapNWRAM_C(1, (mbk[3] >> 8) & 0xFF); + MapNWRAM_C(2, (mbk[3] >> 16) & 0xFF); + MapNWRAM_C(3, mbk[3] >> 24); + MapNWRAM_C(4, mbk[4] & 0xFF); + MapNWRAM_C(5, (mbk[4] >> 8) & 0xFF); + MapNWRAM_C(6, (mbk[4] >> 16) & 0xFF); + MapNWRAM_C(7, mbk[4] >> 24); + + MapNWRAMRange(0, 0, mbk[5]); + MapNWRAMRange(0, 1, mbk[6]); + MapNWRAMRange(0, 2, mbk[7]); + + MapNWRAMRange(1, 0, mbk[8]); + MapNWRAMRange(1, 1, mbk[9]); + MapNWRAMRange(1, 2, mbk[10]); + + mbk[11] &= 0x00FFFF0F; + MBK[0][8] = mbk[11]; + MBK[1][8] = mbk[11]; + } + + for (int i = 0; i < 8; i++) + NDMAs[i]->DoSavestate(file); + + DSi_AES::DoSavestate(file); + DSi_CamModule::DoSavestate(file); + DSi_DSP::DoSavestate(file); + DSi_I2C::DoSavestate(file); + SDMMC->DoSavestate(file); + SDIO->DoSavestate(file); +} + +void SetCartInserted(bool inserted) +{ + if (inserted) + SCFG_MC &= ~1; + else + SCFG_MC |= 1; } void DecryptModcryptArea(u32 offset, u32 size, u8* iv) @@ -445,7 +519,7 @@ void SetupDirectBoot() ARM9Write32(0x02FFE000+i, tmp); } - if (DSi_NAND::Init(SDMMCFile, &DSi::ARM7iBIOS[0x8308])) + if (DSi_NAND::Init(&DSi::ARM7iBIOS[0x8308])) { u8 userdata[0x1B0]; DSi_NAND::ReadUserData(userdata); @@ -544,21 +618,22 @@ void SoftReset() NDS::ARM9->CP15Reset(); - memcpy(NDS::ARM9->ITCM, ITCMInit, 0x8000); + NDS::MapSharedWRAM(3); - DSi_AES::Reset(); // TODO: does the DSP get reset? NWRAM doesn't, so I'm assuming no // *HOWEVER*, the bootrom (which does get rerun) does remap NWRAM, and thus // the DSP most likely gets reset DSi_DSP::Reset(); + SDMMC->CloseHandles(); + SDIO->CloseHandles(); + LoadNAND(); SDMMC->Reset(); SDIO->Reset(); - NDS::ARM9->JumpTo(BootAddr[0]); - NDS::ARM7->JumpTo(BootAddr[1]); + DSi_AES::Reset(); SCFG_BIOS = 0x0101; // TODO: should be zero when booting from BIOS SCFG_Clock9 = 0x0187; // CHECKME @@ -574,22 +649,6 @@ void SoftReset() // LCD init flag GPU::DispStat[0] |= (1<<6); GPU::DispStat[1] |= (1<<6); - - NDS::MapSharedWRAM(3); - - for (u32 i = 0; i < 0x3C00; i+=4) - ARM7Write32(0x03FFC400+i, *(u32*)&ARM7Init[i]); - - u32 eaddr = 0x03FFE6E4; - ARM7Write32(eaddr+0x00, *(u32*)&eMMC_CID[0]); - ARM7Write32(eaddr+0x04, *(u32*)&eMMC_CID[4]); - ARM7Write32(eaddr+0x08, *(u32*)&eMMC_CID[8]); - ARM7Write32(eaddr+0x0C, *(u32*)&eMMC_CID[12]); - ARM7Write16(eaddr+0x2C, 0x0001); - ARM7Write16(eaddr+0x2E, 0x0001); - ARM7Write16(eaddr+0x3C, 0x0100); - ARM7Write16(eaddr+0x3E, 0x40E0); - ARM7Write16(eaddr+0x42, 0x0001); } bool LoadBIOS() @@ -650,12 +709,14 @@ bool LoadNAND() { printf("Loading DSi NAND\n"); - if (!DSi_NAND::Init(SDMMCFile, &DSi::ARM7iBIOS[0x8308])) + if (!DSi_NAND::Init(&DSi::ARM7iBIOS[0x8308])) { printf("Failed to load DSi NAND\n"); return false; } + FILE* nand = DSi_NAND::GetFile(); + // Make sure NWRAM is accessible. // The Bits are set to the startup values in Reset() and we might // still have them on default (0) or some bits cleared by the previous @@ -676,8 +737,8 @@ bool LoadNAND() memset(NWRAMMask, 0, sizeof(NWRAMMask)); u32 bootparams[8]; - fseek(SDMMCFile, 0x220, SEEK_SET); - fread(bootparams, 4, 8, SDMMCFile); + fseek(nand, 0x220, SEEK_SET); + fread(bootparams, 4, 8, nand); printf("ARM9: offset=%08X size=%08X RAM=%08X size_aligned=%08X\n", bootparams[0], bootparams[1], bootparams[2], bootparams[3]); @@ -690,8 +751,8 @@ bool LoadNAND() MBK[1][8] = 0; u32 mbk[12]; - fseek(SDMMCFile, 0x380, SEEK_SET); - fread(mbk, 4, 12, SDMMCFile); + fseek(nand, 0x380, SEEK_SET); + fread(mbk, 4, 12, nand); MapNWRAM_A(0, mbk[0] & 0xFF); MapNWRAM_A(1, (mbk[0] >> 8) & 0xFF); @@ -745,12 +806,12 @@ bool LoadNAND() AES_init_ctx_iv(&ctx, boot2key, boot2iv); - fseek(SDMMCFile, bootparams[0], SEEK_SET); + fseek(nand, bootparams[0], SEEK_SET); dstaddr = bootparams[2]; for (u32 i = 0; i < bootparams[3]; i += 16) { u8 data[16]; - fread(data, 16, 1, SDMMCFile); + fread(data, 16, 1, nand); for (int j = 0; j < 16; j++) tmp[j] = data[15-j]; AES_CTR_xcrypt_buffer(&ctx, tmp, 16); @@ -770,12 +831,12 @@ bool LoadNAND() AES_init_ctx_iv(&ctx, boot2key, boot2iv); - fseek(SDMMCFile, bootparams[4], SEEK_SET); + fseek(nand, bootparams[4], SEEK_SET); dstaddr = bootparams[6]; for (u32 i = 0; i < bootparams[7]; i += 16) { u8 data[16]; - fread(data, 16, 1, SDMMCFile); + fread(data, 16, 1, nand); for (int j = 0; j < 16; j++) tmp[j] = data[15-j]; AES_CTR_xcrypt_buffer(&ctx, tmp, 16); @@ -787,11 +848,6 @@ bool LoadNAND() ARM7Write32(dstaddr, *(u32*)&data[12]); dstaddr += 4; } - // repoint the CPUs to the boot2 binaries - - BootAddr[0] = bootparams[2]; - BootAddr[1] = bootparams[6]; - #define printhex(str, size) { for (int z = 0; z < (size); z++) printf("%02X", (str)[z]); printf("\n"); } #define printhex_rev(str, size) { for (int z = (size)-1; z >= 0; z--) printf("%02X", (str)[z]); printf("\n"); } @@ -800,18 +856,36 @@ bool LoadNAND() printf("eMMC CID: "); printhex(eMMC_CID, 16); printf("Console ID: %" PRIx64 "\n", ConsoleID); - memset(ITCMInit, 0, 0x8000); - memcpy(&ITCMInit[0x4400], &ARM9iBIOS[0x87F4], 0x400); - memcpy(&ITCMInit[0x4800], &ARM9iBIOS[0x9920], 0x80); - memcpy(&ITCMInit[0x4894], &ARM9iBIOS[0x99A0], 0x1048); - memcpy(&ITCMInit[0x58DC], &ARM9iBIOS[0xA9E8], 0x1048); + u32 eaddr = 0x03FFE6E4; + ARM7Write32(eaddr+0x00, *(u32*)&eMMC_CID[0]); + ARM7Write32(eaddr+0x04, *(u32*)&eMMC_CID[4]); + ARM7Write32(eaddr+0x08, *(u32*)&eMMC_CID[8]); + ARM7Write32(eaddr+0x0C, *(u32*)&eMMC_CID[12]); + ARM7Write16(eaddr+0x2C, 0x0001); + ARM7Write16(eaddr+0x2E, 0x0001); + ARM7Write16(eaddr+0x3C, 0x0100); + ARM7Write16(eaddr+0x3E, 0x40E0); + ARM7Write16(eaddr+0x42, 0x0001); + + memcpy(&NDS::ARM9->ITCM[0x4400], &ARM9iBIOS[0x87F4], 0x400); + memcpy(&NDS::ARM9->ITCM[0x4800], &ARM9iBIOS[0x9920], 0x80); + memcpy(&NDS::ARM9->ITCM[0x4894], &ARM9iBIOS[0x99A0], 0x1048); + memcpy(&NDS::ARM9->ITCM[0x58DC], &ARM9iBIOS[0xA9E8], 0x1048); + u8 ARM7Init[0x3C00]; memset(ARM7Init, 0, 0x3C00); memcpy(&ARM7Init[0x0000], &ARM7iBIOS[0x8188], 0x200); memcpy(&ARM7Init[0x0200], &ARM7iBIOS[0xB5D8], 0x40); memcpy(&ARM7Init[0x0254], &ARM7iBIOS[0xC6D0], 0x1048); memcpy(&ARM7Init[0x129C], &ARM7iBIOS[0xD718], 0x1048); + for (u32 i = 0; i < 0x3C00; i+=4) + ARM7Write32(0x03FFC400+i, *(u32*)&ARM7Init[i]); + + // repoint the CPUs to the boot2 binaries + NDS::ARM9->JumpTo(bootparams[2]); + NDS::ARM7->JumpTo(bootparams[6]); + DSi_NAND::PatchUserData(); DSi_NAND::DeInit(); @@ -819,12 +893,6 @@ bool LoadNAND() return true; } -void CloseDSiNAND() -{ - if (DSi::SDMMCFile) - fclose(DSi::SDMMCFile); - DSi::SDMMCFile = nullptr; -} void RunNDMAs(u32 cpu) { @@ -1190,7 +1258,6 @@ u8 ARM9Read8(u32 addr) switch (addr & 0xFF000000) { case 0x03000000: - case 0x03800000: if (SCFG_EXT[0] & (1 << 25)) { if (addr >= NWRAMStart[0][0] && addr < NWRAMEnd[0][0]) @@ -1218,6 +1285,9 @@ u8 ARM9Read8(u32 addr) case 0x09000000: case 0x0A000000: return (NDS::ExMemCnt[0] & (1<<7)) ? 0 : 0xFF; + + case 0x0C000000: + return *(u8*)&NDS::MainRAM[addr & NDS::MainRAMMask]; } return NDS::ARM9Read8(addr); @@ -1236,7 +1306,6 @@ u16 ARM9Read16(u32 addr) switch (addr & 0xFF000000) { case 0x03000000: - case 0x03800000: if (SCFG_EXT[0] & (1 << 25)) { if (addr >= NWRAMStart[0][0] && addr < NWRAMEnd[0][0]) @@ -1264,6 +1333,9 @@ u16 ARM9Read16(u32 addr) case 0x09000000: case 0x0A000000: return (NDS::ExMemCnt[0] & (1<<7)) ? 0 : 0xFFFF; + + case 0x0C000000: + return *(u16*)&NDS::MainRAM[addr & NDS::MainRAMMask]; } return NDS::ARM9Read16(addr); @@ -1287,7 +1359,6 @@ u32 ARM9Read32(u32 addr) break; case 0x03000000: - case 0x03800000: if (SCFG_EXT[0] & (1 << 25)) { if (addr >= NWRAMStart[0][0] && addr < NWRAMEnd[0][0]) @@ -1315,6 +1386,9 @@ u32 ARM9Read32(u32 addr) case 0x09000000: case 0x0A000000: return (NDS::ExMemCnt[0] & (1<<7)) ? 0 : 0xFFFFFFFF; + + case 0x0C000000: + return *(u32*)&NDS::MainRAM[addr & NDS::MainRAMMask]; } return NDS::ARM9Read32(addr); @@ -1325,7 +1399,6 @@ void ARM9Write8(u32 addr, u8 val) switch (addr & 0xFF000000) { case 0x03000000: - case 0x03800000: if (SCFG_EXT[0] & (1 << 25)) { if (addr >= NWRAMStart[0][0] && addr < NWRAMEnd[0][0]) @@ -1413,6 +1486,13 @@ void ARM9Write8(u32 addr, u8 val) case 0x09000000: case 0x0A000000: return; + + case 0x0C000000: +#ifdef JIT_ENABLED + ARMJIT::CheckAndInvalidate<0, ARMJIT_Memory::memregion_MainRAM>(addr); +#endif + *(u8*)&NDS::MainRAM[addr & NDS::MainRAMMask] = val; + return; } return NDS::ARM9Write8(addr, val); @@ -1423,7 +1503,6 @@ void ARM9Write16(u32 addr, u16 val) switch (addr & 0xFF000000) { case 0x03000000: - case 0x03800000: if (SCFG_EXT[0] & (1 << 25)) { if (addr >= NWRAMStart[0][0] && addr < NWRAMEnd[0][0]) @@ -1497,6 +1576,13 @@ void ARM9Write16(u32 addr, u16 val) case 0x09000000: case 0x0A000000: return; + + case 0x0C000000: +#ifdef JIT_ENABLED + ARMJIT::CheckAndInvalidate<0, ARMJIT_Memory::memregion_MainRAM>(addr); +#endif + *(u16*)&NDS::MainRAM[addr & NDS::MainRAMMask] = val; + return; } return NDS::ARM9Write16(addr, val); @@ -1507,7 +1593,6 @@ void ARM9Write32(u32 addr, u32 val) switch (addr & 0xFF000000) { case 0x03000000: - case 0x03800000: if (SCFG_EXT[0] & (1 << 25)) { if (addr >= NWRAMStart[0][0] && addr < NWRAMEnd[0][0]) @@ -1581,6 +1666,13 @@ void ARM9Write32(u32 addr, u32 val) case 0x09000000: case 0x0A000000: return; + + case 0x0C000000: +#ifdef JIT_ENABLED + ARMJIT::CheckAndInvalidate<0, ARMJIT_Memory::memregion_MainRAM>(addr); +#endif + *(u32*)&NDS::MainRAM[addr & NDS::MainRAMMask] = val; + return; } return NDS::ARM9Write32(addr, val); @@ -1591,6 +1683,7 @@ bool ARM9GetMemRegion(u32 addr, bool write, NDS::MemRegion* region) switch (addr & 0xFF000000) { case 0x02000000: + case 0x0C000000: region->Mem = NDS::MainRAM; region->Mask = NDS::MainRAMMask; return true; @@ -1671,6 +1764,10 @@ u8 ARM7Read8(u32 addr) case 0x0A000000: case 0x0A800000: return (NDS::ExMemCnt[0] & (1<<7)) ? 0xFF : 0; + + case 0x0C000000: + case 0x0C800000: + return *(u8*)&NDS::MainRAM[addr & NDS::MainRAMMask]; } return NDS::ARM7Read8(addr); @@ -1724,6 +1821,10 @@ u16 ARM7Read16(u32 addr) case 0x0A000000: case 0x0A800000: return (NDS::ExMemCnt[0] & (1<<7)) ? 0xFFFF : 0; + + case 0x0C000000: + case 0x0C800000: + return *(u16*)&NDS::MainRAM[addr & NDS::MainRAMMask]; } return NDS::ARM7Read16(addr); @@ -1777,6 +1878,10 @@ u32 ARM7Read32(u32 addr) case 0x0A000000: case 0x0A800000: return (NDS::ExMemCnt[0] & (1<<7)) ? 0xFFFFFFFF : 0; + + case 0x0C000000: + case 0x0C800000: + return *(u32*)&NDS::MainRAM[addr & NDS::MainRAMMask]; } return NDS::ARM7Read32(addr); @@ -1864,6 +1969,14 @@ void ARM7Write8(u32 addr, u8 val) case 0x0A000000: case 0x0A800000: return; + + case 0x0C000000: + case 0x0C800000: +#ifdef JIT_ENABLED + ARMJIT::CheckAndInvalidate<1, ARMJIT_Memory::memregion_MainRAM>(addr); +#endif + *(u8*)&NDS::MainRAM[addr & NDS::MainRAMMask] = val; + return; } return NDS::ARM7Write8(addr, val); @@ -1951,6 +2064,14 @@ void ARM7Write16(u32 addr, u16 val) case 0x0A000000: case 0x0A800000: return; + + case 0x0C000000: + case 0x0C800000: +#ifdef JIT_ENABLED + ARMJIT::CheckAndInvalidate<1, ARMJIT_Memory::memregion_MainRAM>(addr); +#endif + *(u16*)&NDS::MainRAM[addr & NDS::MainRAMMask] = val; + return; } return NDS::ARM7Write16(addr, val); @@ -2038,6 +2159,14 @@ void ARM7Write32(u32 addr, u32 val) case 0x0A000000: case 0x0A800000: return; + + case 0x0C000000: + case 0x0C800000: +#ifdef JIT_ENABLED + ARMJIT::CheckAndInvalidate<1, ARMJIT_Memory::memregion_MainRAM>(addr); +#endif + *(u32*)&NDS::MainRAM[addr & NDS::MainRAMMask] = val; + return; } return NDS::ARM7Write32(addr, val); @@ -2049,6 +2178,8 @@ bool ARM7GetMemRegion(u32 addr, bool write, NDS::MemRegion* region) { case 0x02000000: case 0x02800000: + case 0x0C000000: + case 0x0C800000: region->Mem = NDS::MainRAM; region->Mask = NDS::MainRAMMask; return true; @@ -2107,7 +2238,7 @@ u8 ARM9IORead8(u32 addr) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return 0; - return DSi_Camera::Read8(addr); + return DSi_CamModule::Read8(addr); } if (addr >= 0x04004300 && addr <= 0x04004400) @@ -2139,7 +2270,7 @@ u16 ARM9IORead16(u32 addr) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return 0; - return DSi_Camera::Read16(addr); + return DSi_CamModule::Read16(addr); } if (addr >= 0x04004300 && addr <= 0x04004400) @@ -2201,7 +2332,7 @@ u32 ARM9IORead32(u32 addr) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return 0; - return DSi_Camera::Read32(addr); + return DSi_CamModule::Read32(addr); } return NDS::ARM9IORead32(addr); @@ -2265,7 +2396,7 @@ void ARM9IOWrite8(u32 addr, u8 val) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return; - return DSi_Camera::Write8(addr, val); + return DSi_CamModule::Write8(addr, val); } if (addr >= 0x04004300 && addr <= 0x04004400) @@ -2325,7 +2456,7 @@ void ARM9IOWrite16(u32 addr, u16 val) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return; - return DSi_Camera::Write16(addr, val); + return DSi_CamModule::Write16(addr, val); } if (addr >= 0x04004300 && addr <= 0x04004400) @@ -2475,7 +2606,7 @@ void ARM9IOWrite32(u32 addr, u32 val) if ((addr & 0xFFFFFF00) == 0x04004200) { if (!(SCFG_EXT[0] & (1<<17))) return; - return DSi_Camera::Write32(addr, val); + return DSi_CamModule::Write32(addr, val); } return NDS::ARM9IOWrite32(addr, val); @@ -2512,6 +2643,9 @@ u8 ARM7IORead8(u32 addr) case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return (ConsoleID >> 48) & 0xFF; case 0x04004D07: if (SCFG_BIOS & (1<<10)) return 0; return ConsoleID >> 56; case 0x04004D08: return 0; + + case 0x4004700: return DSi_DSP::SNDExCnt; + case 0x4004701: return DSi_DSP::SNDExCnt >> 8; } return NDS::ARM7IORead8(addr); @@ -2544,6 +2678,8 @@ u16 ARM7IORead16(u32 addr) case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (ConsoleID >> 32) & 0xFFFF; case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return ConsoleID >> 48; case 0x04004D08: return 0; + + case 0x4004700: return DSi_DSP::SNDExCnt; } if (addr >= 0x04004800 && addr < 0x04004A00) @@ -2615,6 +2751,10 @@ u32 ARM7IORead32(u32 addr) case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return ConsoleID & 0xFFFFFFFF; case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return ConsoleID >> 32; case 0x04004D08: return 0; + + case 0x4004700: + printf("32-Bit SNDExCnt read? %08X\n", NDS::ARM7->R[15]); + return DSi_DSP::SNDExCnt; } if (addr >= 0x04004800 && addr < 0x04004A00) @@ -2662,6 +2802,46 @@ void ARM7IOWrite8(u32 addr, u8 val) case 0x04004500: DSi_I2C::WriteData(val); return; case 0x04004501: DSi_I2C::WriteCnt(val); return; + + case 0x4004700: + DSi_DSP::WriteSNDExCnt((u16)val | (DSi_DSP::SNDExCnt & 0xFF00)); + return; + case 0x4004701: + DSi_DSP::WriteSNDExCnt(((u16)val << 8) | (DSi_DSP::SNDExCnt & 0x00FF)); + return; + } + + if (addr >= 0x04004420 && addr < 0x04004430) + { + u32 shift = (addr&3)*8; + addr -= 0x04004420; + addr &= ~3; + DSi_AES::WriteIV(addr, (u32)val << shift, 0xFF << shift); + return; + } + if (addr >= 0x04004430 && addr < 0x04004440) + { + u32 shift = (addr&3)*8; + addr -= 0x04004430; + addr &= ~3; + DSi_AES::WriteMAC(addr, (u32)val << shift, 0xFF << shift); + return; + } + if (addr >= 0x04004440 && addr < 0x04004500) + { + u32 shift = (addr&3)*8; + addr -= 0x04004440; + addr &= ~3; + + int n = 0; + while (addr >= 0x30) { addr -= 0x30; n++; } + + switch (addr >> 4) + { + case 0: DSi_AES::WriteKeyNormal(n, addr&0xF, (u32)val << shift, 0xFF << shift); return; + case 1: DSi_AES::WriteKeyX(n, addr&0xF, (u32)val << shift, 0xFF << shift); return; + case 2: DSi_AES::WriteKeyY(n, addr&0xF, (u32)val << shift, 0xFF << shift); return; + } } return NDS::ARM7IOWrite8(addr, val); @@ -2693,12 +2873,51 @@ void ARM7IOWrite16(u32 addr, u16 val) case 0x04004062: if (!(SCFG_EXT[1] & (1 << 31))) /* no access to SCFG Registers if disabled*/ return; - u32 tmp = MBK[0][8]; - tmp &= ~(0xffff << ((addr % 4) * 8)); - tmp |= (val << ((addr % 4) * 8)); - MBK[0][8] = tmp & 0x00FFFF0F; - MBK[1][8] = MBK[0][8]; + { + u32 tmp = MBK[0][8]; + tmp &= ~(0xffff << ((addr % 4) * 8)); + tmp |= (val << ((addr % 4) * 8)); + MBK[0][8] = tmp & 0x00FFFF0F; + MBK[1][8] = MBK[0][8]; + } return; + + case 0x4004700: + DSi_DSP::WriteSNDExCnt(val); + return; + } + + if (addr >= 0x04004420 && addr < 0x04004430) + { + u32 shift = (addr&1)*16; + addr -= 0x04004420; + addr &= ~1; + DSi_AES::WriteIV(addr, (u32)val << shift, 0xFFFF << shift); + return; + } + if (addr >= 0x04004430 && addr < 0x04004440) + { + u32 shift = (addr&1)*16; + addr -= 0x04004430; + addr &= ~1; + DSi_AES::WriteMAC(addr, (u32)val << shift, 0xFFFF << shift); + return; + } + if (addr >= 0x04004440 && addr < 0x04004500) + { + u32 shift = (addr&1)*16; + addr -= 0x04004440; + addr &= ~1; + + int n = 0; + while (addr >= 0x30) { addr -= 0x30; n++; } + + switch (addr >> 4) + { + case 0: DSi_AES::WriteKeyNormal(n, addr&0xF, (u32)val << shift, 0xFFFF << shift); return; + case 1: DSi_AES::WriteKeyX(n, addr&0xF, (u32)val << shift, 0xFFFF << shift); return; + case 2: DSi_AES::WriteKeyY(n, addr&0xF, (u32)val << shift, 0xFFFF << shift); return; + } } if (addr >= 0x04004800 && addr < 0x04004A00) @@ -2798,6 +3017,11 @@ void ARM7IOWrite32(u32 addr, u32 val) case 0x04004400: DSi_AES::WriteCnt(val); return; case 0x04004404: DSi_AES::WriteBlkCnt(val); return; case 0x04004408: DSi_AES::WriteInputFIFO(val); return; + + case 0x4004700: + printf("32-Bit SNDExCnt write? %08X %08X\n", val, NDS::ARM7->R[15]); + DSi_DSP::WriteSNDExCnt(val); + return; } if (addr >= 0x04004420 && addr < 0x04004430) @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -39,8 +39,6 @@ extern u64 ConsoleID; extern DSi_SDHost* SDMMC; extern DSi_SDHost* SDIO; -extern FILE* SDMMCFile; - const u32 NWRAMSize = 0x40000; extern u8* NWRAM_A; @@ -58,6 +56,11 @@ extern u32 NWRAMMask[2][3]; bool Init(); void DeInit(); void Reset(); +void Stop(); + +void DoSavestate(Savestate* file); + +void SetCartInserted(bool inserted); void SetupDirectBoot(); void SoftReset(); @@ -65,8 +68,6 @@ void SoftReset(); bool LoadBIOS(); bool LoadNAND(); -void CloseDSiNAND(); - void RunNDMAs(u32 cpu); void StallNDMAs(); bool NDMAsInMode(u32 cpu, u32 mode); diff --git a/src/DSiCrypto.cpp b/src/DSiCrypto.cpp deleted file mode 100644 index 26262e3..0000000 --- a/src/DSiCrypto.cpp +++ /dev/null @@ -1,17 +0,0 @@ -/* - Copyright 2016-2021 Arisotura - - This file is part of melonDS. - - melonDS is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - melonDS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with melonDS. If not, see http://www.gnu.org/licenses/. -*/ diff --git a/src/DSiCrypto.h b/src/DSiCrypto.h deleted file mode 100644 index 2828666..0000000 --- a/src/DSiCrypto.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - Copyright 2016-2021 Arisotura - - This file is part of melonDS. - - melonDS is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - melonDS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#ifndef DSICRYPTO_H -#define DSICRYPTO_H - -// - -#endif // DSICRYPTO_H diff --git a/src/DSi_AES.cpp b/src/DSi_AES.cpp index 02e4f1c..f84db11 100644 --- a/src/DSi_AES.cpp +++ b/src/DSi_AES.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -155,6 +155,43 @@ void Reset() *(u32*)&KeyY[3][8] = 0x202DDD1D; } +void DoSavestate(Savestate* file) +{ + file->Section("AESi"); + + file->Var32(&Cnt); + + file->Var32(&BlkCnt); + file->Var32(&RemExtra); + file->Var32(&RemBlocks); + + file->Bool32(&OutputFlush); + + file->Var32(&InputDMASize); + file->Var32(&OutputDMASize); + file->Var32(&AESMode); + + InputFIFO.DoSavestate(file); + OutputFIFO.DoSavestate(file); + + file->VarArray(IV, 16); + + file->VarArray(MAC, 16); + + file->VarArray(KeyNormal, 4*16); + file->VarArray(KeyX, 4*16); + file->VarArray(KeyY, 4*16); + + file->VarArray(CurKey, 16); + file->VarArray(CurMAC, 16); + + file->VarArray(OutputMAC, 16); + file->Bool32(&OutputMACDue); + + file->VarArray(Ctx.RoundKey, AES_keyExpSize); + file->VarArray(Ctx.Iv, AES_BLOCKLEN); +} + void ProcessBlock_CCM_Extra() { diff --git a/src/DSi_AES.h b/src/DSi_AES.h index 27f8e64..d8ef98a 100644 --- a/src/DSi_AES.h +++ b/src/DSi_AES.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -20,6 +20,7 @@ #define DSI_AES_H #include "types.h" +#include "Savestate.h" namespace DSi_AES { @@ -30,6 +31,8 @@ bool Init(); void DeInit(); void Reset(); +void DoSavestate(Savestate* file); + u32 ReadCnt(); void WriteCnt(u32 val); void WriteBlkCnt(u32 val); diff --git a/src/DSi_Camera.cpp b/src/DSi_Camera.cpp index d8cc335..20f15cf 100644 --- a/src/DSi_Camera.cpp +++ b/src/DSi_Camera.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -20,17 +20,25 @@ #include <string.h> #include "DSi.h" #include "DSi_Camera.h" +#include "Platform.h" -DSi_Camera* DSi_Camera0; // 78 / facing outside -DSi_Camera* DSi_Camera1; // 7A / selfie cam +namespace DSi_CamModule +{ + +Camera* Camera0; // 78 / facing outside +Camera* Camera1; // 7A / selfie cam + +u16 ModuleCnt; +u16 Cnt; -u16 DSi_Camera::ModuleCnt; -u16 DSi_Camera::Cnt; +u32 CropStart, CropEnd; -u8 DSi_Camera::FrameBuffer[640*480*4]; -u32 DSi_Camera::FrameLength; -u32 DSi_Camera::TransferPos; +// pixel data buffer holds a maximum of 512 words, regardless of how long scanlines are +u32 DataBuffer[512]; +u32 BufferReadPos, BufferWritePos; +u32 BufferNumLines; +Camera* CurCamera; // note on camera data/etc intervals // on hardware those are likely affected by several factors @@ -41,120 +49,383 @@ const u32 kIRQInterval = 1120000; // ~30 FPS const u32 kTransferStart = 60000; -bool DSi_Camera::Init() +bool Init() { - DSi_Camera0 = new DSi_Camera(0); - DSi_Camera1 = new DSi_Camera(1); + Camera0 = new Camera(0); + Camera1 = new Camera(1); return true; } -void DSi_Camera::DeInit() +void DeInit() { - delete DSi_Camera0; - delete DSi_Camera1; + delete Camera0; + delete Camera1; } -void DSi_Camera::Reset() +void Reset() { - DSi_Camera0->ResetCam(); - DSi_Camera1->ResetCam(); + Camera0->Reset(); + Camera1->Reset(); ModuleCnt = 0; // CHECKME Cnt = 0; - memset(FrameBuffer, 0, 640*480*4); - TransferPos = 0; - FrameLength = 256*192*2; // TODO: make it check frame size, data type, etc + CropStart = 0; + CropEnd = 0; + + memset(DataBuffer, 0, 512*sizeof(u32)); + BufferReadPos = 0; + BufferWritePos = 0; + BufferNumLines = 0; + CurCamera = nullptr; NDS::ScheduleEvent(NDS::Event_DSi_CamIRQ, true, kIRQInterval, IRQ, 0); } +void Stop() +{ + Camera0->Stop(); + Camera1->Stop(); +} + +void DoSavestate(Savestate* file) +{ + file->Section("CAMi"); + + file->Var16(&ModuleCnt); + file->Var16(&Cnt); + + /*file->VarArray(FrameBuffer, sizeof(FrameBuffer)); + file->Var32(&TransferPos); + file->Var32(&FrameLength);*/ + + Camera0->DoSavestate(file); + Camera1->DoSavestate(file); +} + -void DSi_Camera::IRQ(u32 param) +void IRQ(u32 param) { - DSi_Camera* activecam = nullptr; + Camera* activecam = nullptr; - // TODO: check which camera has priority if both are activated - // (or does it just jumble both data sources together, like it - // does for, say, overlapping VRAM?) - if (DSi_Camera0->IsActivated()) activecam = DSi_Camera0; - else if (DSi_Camera1->IsActivated()) activecam = DSi_Camera1; + // TODO: cameras don't have any priority! + // activating both together will jumble the image data together + if (Camera0->IsActivated()) activecam = Camera0; + else if (Camera1->IsActivated()) activecam = Camera1; if (activecam) { - RequestFrame(activecam->Num); + activecam->StartTransfer(); if (Cnt & (1<<11)) NDS::SetIRQ(0, NDS::IRQ_DSi_Camera); if (Cnt & (1<<15)) - NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, kTransferStart, Transfer, 0); + { + BufferReadPos = 0; + BufferWritePos = 0; + BufferNumLines = 0; + CurCamera = activecam; + NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, kTransferStart, TransferScanline, 0); + } } NDS::ScheduleEvent(NDS::Event_DSi_CamIRQ, true, kIRQInterval, IRQ, 0); } -void DSi_Camera::RequestFrame(u32 cam) +void TransferScanline(u32 line) { - if (!(Cnt & (1<<13))) printf("CAMERA: !! REQUESTING YUV FRAME\n"); + u32* dstbuf = &DataBuffer[BufferWritePos]; + int maxlen = 512 - BufferWritePos; - // TODO: picture size, data type, cropping, etc - // generate test pattern - // TODO: get picture from platform (actual camera, video file, whatever source) - for (u32 y = 0; y < 192; y++) + u32 tmpbuf[512]; + int datalen = CurCamera->TransferScanline(tmpbuf, 512); + + // TODO: must be tweaked such that each block has enough time to transfer + u32 delay = datalen*4 + 16; + + int copystart = 0; + int copylen = datalen; + + if (Cnt & (1<<14)) { - for (u32 x = 0; x < 256; x++) + // crop picture + + int ystart = (CropStart >> 16) & 0x1FF; + int yend = (CropEnd >> 16) & 0x1FF; + if (line < ystart || line > yend) { - u16* px = (u16*)&FrameBuffer[((y*256) + x) * 2]; + if (!CurCamera->TransferDone()) + NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, delay, TransferScanline, line+1); - if ((x & 0x8) ^ (y & 0x8)) - *px = 0x8000; - else - *px = 0xFC00 | ((y >> 3) << 5); + return; + } + + int xstart = (CropStart >> 1) & 0x1FF; + int xend = (CropEnd >> 1) & 0x1FF; + + copystart = xstart; + copylen = xend+1 - xstart; + + if ((copystart + copylen) > datalen) + copylen = datalen - copystart; + if (copylen < 0) + copylen = 0; + } + + if (copylen > maxlen) + { + copylen = maxlen; + Cnt |= (1<<4); + } + + if (Cnt & (1<<13)) + { + // convert to RGB + + for (u32 i = 0; i < copylen; i++) + { + u32 val = tmpbuf[copystart + i]; + + int y1 = val & 0xFF; + int u = (val >> 8) & 0xFF; + int y2 = (val >> 16) & 0xFF; + int v = (val >> 24) & 0xFF; + + u -= 128; v -= 128; + + int r1 = y1 + ((v * 91881) >> 16); + int g1 = y1 - ((v * 46793) >> 16) - ((u * 22544) >> 16); + int b1 = y1 + ((u * 116129) >> 16); + + int r2 = y2 + ((v * 91881) >> 16); + int g2 = y2 - ((v * 46793) >> 16) - ((u * 22544) >> 16); + int b2 = y2 + ((u * 116129) >> 16); + + r1 = std::clamp(r1, 0, 255); g1 = std::clamp(g1, 0, 255); b1 = std::clamp(b1, 0, 255); + r2 = std::clamp(r2, 0, 255); g2 = std::clamp(g2, 0, 255); b2 = std::clamp(b2, 0, 255); + + u32 col1 = (r1 >> 3) | ((g1 >> 3) << 5) | ((b1 >> 3) << 10) | 0x8000; + u32 col2 = (r2 >> 3) | ((g2 >> 3) << 5) | ((b2 >> 3) << 10) | 0x8000; + + dstbuf[i] = col1 | (col2 << 16); } } + else + { + // return raw data + + memcpy(dstbuf, &tmpbuf[copystart], copylen*sizeof(u32)); + } + + u32 numscan = Cnt & 0x000F; + if (BufferNumLines >= numscan) + { + BufferReadPos = 0; // checkme + BufferWritePos = 0; + BufferNumLines = 0; + DSi::CheckNDMAs(0, 0x0B); + } + else + { + BufferWritePos += copylen; + if (BufferWritePos > 512) BufferWritePos = 512; + BufferNumLines++; + } + + if (CurCamera->TransferDone()) + return; + + NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, delay, TransferScanline, line+1); } -void DSi_Camera::Transfer(u32 pos) + +u8 Read8(u32 addr) { - u32 numscan = (Cnt & 0x000F) + 1; - u32 numpix = numscan * 256; // CHECKME + // - // TODO: present data - //printf("CAM TRANSFER POS=%d/%d\n", pos, 0x6000*2); + printf("unknown DSi cam read8 %08X\n", addr); + return 0; +} - DSi::CheckNDMAs(0, 0x0B); +u16 Read16(u32 addr) +{ + switch (addr) + { + case 0x04004200: return ModuleCnt; + case 0x04004202: return Cnt; + } + + printf("unknown DSi cam read16 %08X\n", addr); + return 0; +} - pos += numpix; - if (pos >= 0x6000*2) // HACK +u32 Read32(u32 addr) +{ + switch (addr) { - // transfer done + case 0x04004204: + { + u32 ret = DataBuffer[BufferReadPos]; + if (Cnt & (1<<15)) + { + if (BufferReadPos < 511) + BufferReadPos++; + // CHECKME!!!! + // also presumably we should set bit4 in Cnt if there's no new data to be read + } + + return ret; + } + + case 0x04004210: return CropStart; + case 0x04004214: return CropEnd; } - else + + printf("unknown DSi cam read32 %08X\n", addr); + return 0; +} + +void Write8(u32 addr, u8 val) +{ + // + + printf("unknown DSi cam write8 %08X %02X\n", addr, val); +} + +void Write16(u32 addr, u16 val) +{ + switch (addr) { - // keep going + case 0x04004200: + { + u16 oldcnt = ModuleCnt; + ModuleCnt = val; + + if ((ModuleCnt & (1<<1)) && !(oldcnt & (1<<1))) + { + // reset shit to zero + // CHECKME + + Cnt = 0; + } + + if ((ModuleCnt & (1<<5)) && !(oldcnt & (1<<5))) + { + // TODO: reset I2C?? + } + } + return; + + case 0x04004202: + { + // TODO: during a transfer, clearing bit15 does not reflect immediately + // maybe it needs to finish the trasnfer or atleast the current block + + // checkme + u16 oldmask; + if (Cnt & 0x8000) + { + val &= 0x8F20; + oldmask = 0x601F; + } + else + { + val &= 0xEF2F; + oldmask = 0x0010; + } + + Cnt = (Cnt & oldmask) | (val & ~0x0020); + if (val & (1<<5)) + { + Cnt &= ~(1<<4); + BufferReadPos = 0; + BufferWritePos = 0; + } - // TODO: must be tweaked such that each block has enough time to transfer - u32 delay = numpix*2 + 16; + if ((val & (1<<15)) && !(Cnt & (1<<15))) + { + // start transfer + //DSi::CheckNDMAs(0, 0x0B); + } + } + return; - NDS::ScheduleEvent(NDS::Event_DSi_CamTransfer, false, delay, Transfer, pos); + case 0x04004210: + if (Cnt & (1<<15)) return; + CropStart = (CropStart & 0x01FF0000) | (val & 0x03FE); + return; + case 0x04004212: + if (Cnt & (1<<15)) return; + CropStart = (CropStart & 0x03FE) | ((val & 0x01FF) << 16); + return; + case 0x04004214: + if (Cnt & (1<<15)) return; + CropEnd = (CropEnd & 0x01FF0000) | (val & 0x03FE); + return; + case 0x04004216: + if (Cnt & (1<<15)) return; + CropEnd = (CropEnd & 0x03FE) | ((val & 0x01FF) << 16); + return; } + + printf("unknown DSi cam write16 %08X %04X\n", addr, val); +} + +void Write32(u32 addr, u32 val) +{ + switch (addr) + { + case 0x04004210: + if (Cnt & (1<<15)) return; + CropStart = val & 0x01FF03FE; + return; + case 0x04004214: + if (Cnt & (1<<15)) return; + CropEnd = val & 0x01FF03FE; + return; + } + + printf("unknown DSi cam write32 %08X %08X\n", addr, val); } -DSi_Camera::DSi_Camera(u32 num) + +Camera::Camera(u32 num) { Num = num; } -DSi_Camera::~DSi_Camera() +Camera::~Camera() { - // } -void DSi_Camera::ResetCam() +void Camera::DoSavestate(Savestate* file) +{ + char magic[5] = "CAMx"; + magic[3] = '0' + Num; + file->Section(magic); + + file->Var32(&DataPos); + file->Var32(&RegAddr); + file->Var16(&RegData); + + file->Var16(&PLLDiv); + file->Var16(&PLLPDiv); + file->Var16(&PLLCnt); + file->Var16(&ClocksCnt); + file->Var16(&StandbyCnt); + file->Var16(&MiscCnt); + + file->Var16(&MCUAddr); + file->VarArray(MCURegs, 0x8000); +} + +void Camera::Reset() { + Platform::Camera_Stop(Num); + DataPos = 0; RegAddr = 0; RegData = 0; @@ -165,9 +436,23 @@ void DSi_Camera::ResetCam() ClocksCnt = 0; StandbyCnt = 0x4029; // checkme MiscCnt = 0; + + MCUAddr = 0; + memset(MCURegs, 0, 0x8000); + + // default state is preview mode (checkme) + MCURegs[0x2104] = 3; + + TransferY = 0; + memset(FrameBuffer, 0, (640*480/2)*sizeof(u32)); } -bool DSi_Camera::IsActivated() +void Camera::Stop() +{ + Platform::Camera_Stop(Num); +} + +bool Camera::IsActivated() { if (StandbyCnt & (1<<14)) return false; // standby if (!(MiscCnt & (1<<9))) return false; // data transfer not enabled @@ -176,40 +461,121 @@ bool DSi_Camera::IsActivated() } -void DSi_Camera::I2C_Start() +void Camera::StartTransfer() { + TransferY = 0; + + u8 state = MCURegs[0x2104]; + if (state == 3) // preview + { + FrameWidth = *(u16*)&MCURegs[0x2703]; + FrameHeight = *(u16*)&MCURegs[0x2705]; + FrameReadMode = *(u16*)&MCURegs[0x2717]; + FrameFormat = *(u16*)&MCURegs[0x2755]; + } + else if (state == 7) // capture + { + FrameWidth = *(u16*)&MCURegs[0x2707]; + FrameHeight = *(u16*)&MCURegs[0x2709]; + FrameReadMode = *(u16*)&MCURegs[0x272D]; + FrameFormat = *(u16*)&MCURegs[0x2757]; + } + else + { + FrameWidth = 0; + FrameHeight = 0; + FrameReadMode = 0; + FrameFormat = 0; + } + + Platform::Camera_CaptureFrame(Num, FrameBuffer, 640, 480, true); } -u8 DSi_Camera::I2C_Read(bool last) +bool Camera::TransferDone() { - u8 ret; + return TransferY >= FrameHeight; +} - if (DataPos < 2) +int Camera::TransferScanline(u32* buffer, int maxlen) +{ + if (TransferY >= FrameHeight) + return 0; + + if (FrameWidth > 640 || FrameHeight > 480 || + FrameWidth < 2 || FrameHeight < 2 || + (FrameWidth & 1)) { - printf("DSi_Camera: WHAT??\n"); - ret = 0; + // TODO work out something for these cases? + printf("CAM%d: invalid resolution %dx%d\n", Num, FrameWidth, FrameHeight); + //memset(buffer, 0, width*height*sizeof(u16)); + return 0; } - else + + // TODO: non-YUV pixel formats and all + + int retlen = FrameWidth >> 1; + int sy = (TransferY * 480) / FrameHeight; + if (FrameReadMode & (1<<1)) + sy = 479 - sy; + + if (FrameReadMode & (1<<0)) { - if (DataPos & 0x1) + for (int dx = 0; dx < retlen; dx++) { - ret = RegData & 0xFF; - RegAddr += 2; // checkme + if (dx >= maxlen) break; + + int sx = (dx * 640) / FrameWidth; + + u32 val = FrameBuffer[sy*320 + sx]; + buffer[dx] = val; } - else + } + else + { + for (int dx = 0; dx < retlen; dx++) { - RegData = I2C_ReadReg(RegAddr); - ret = RegData >> 8; + if (dx >= maxlen) break; + + int sx = 319 - ((dx * 640) / FrameWidth); + + u32 val = FrameBuffer[sy*320 + sx]; + buffer[dx] = (val & 0xFF00FF00) | ((val >> 16) & 0xFF) | ((val & 0xFF) << 16); } } + TransferY++; + + return retlen; +} + + +void Camera::I2C_Start() +{ + DataPos = 0; +} + +u8 Camera::I2C_Read(bool last) +{ + u8 ret; + + if (DataPos & 0x1) + { + ret = RegData & 0xFF; + RegAddr += 2; // checkme + } + else + { + RegData = I2C_ReadReg(RegAddr); + ret = RegData >> 8; + } + if (last) DataPos = 0; else DataPos++; return ret; } -void DSi_Camera::I2C_Write(u8 val, bool last) +void Camera::I2C_Write(u8 val, bool last) { if (DataPos < 2) { @@ -238,7 +604,7 @@ void DSi_Camera::I2C_Write(u8 val, bool last) else DataPos++; } -u16 DSi_Camera::I2C_ReadReg(u16 addr) +u16 Camera::I2C_ReadReg(u16 addr) { switch (addr) { @@ -250,6 +616,23 @@ u16 DSi_Camera::I2C_ReadReg(u16 addr) case 0x0018: return StandbyCnt; case 0x001A: return MiscCnt; + case 0x098C: return MCUAddr; + case 0x0990: + case 0x0992: + case 0x0994: + case 0x0996: + case 0x0998: + case 0x099A: + case 0x099C: + case 0x099E: + { + addr -= 0x0990; + u16 ret = MCU_Read((MCUAddr & 0x7FFF) + addr); + if (!(MCUAddr & (1<<15))) + ret |= (MCU_Read((MCUAddr & 0x7FFF) + addr+1) << 8); + return ret; + } + case 0x301A: return ((~StandbyCnt) & 0x4000) >> 12; } @@ -257,7 +640,7 @@ u16 DSi_Camera::I2C_ReadReg(u16 addr) return 0; } -void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) +void Camera::I2C_WriteReg(u16 addr, u16 val) { switch (addr) { @@ -275,18 +658,47 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) return; case 0x0016: ClocksCnt = val; - printf("ClocksCnt=%04X\n", val); + //printf("ClocksCnt=%04X\n", val); return; case 0x0018: - // TODO: this shouldn't be instant, but uh - val &= 0x003F; - val |= ((val & 0x0001) << 14); - StandbyCnt = val; - printf("CAM%d STBCNT=%04X (%04X)\n", Num, StandbyCnt, val); + { + bool wasactive = IsActivated(); + // TODO: this shouldn't be instant, but uh + val &= 0x003F; + val |= ((val & 0x0001) << 14); + 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); + } return; case 0x001A: - MiscCnt = val & 0x0B7B; - printf("CAM%d MISCCNT=%04X (%04X)\n", Num, MiscCnt, val); + { + bool wasactive = IsActivated(); + 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); + } + return; + + case 0x098C: + MCUAddr = val; + return; + case 0x0990: + case 0x0992: + case 0x0994: + case 0x0996: + case 0x0998: + case 0x099A: + case 0x099C: + case 0x099E: + addr -= 0x0990; + MCU_Write((MCUAddr & 0x7FFF) + addr, val&0xFF); + if (!(MCUAddr & (1<<15))) + MCU_Write((MCUAddr & 0x7FFF) + addr+1, val>>8); return; } @@ -294,117 +706,122 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) } -u8 DSi_Camera::Read8(u32 addr) +// TODO: not sure at all what is the accessible range +// or if there is any overlap in the address range + +u8 Camera::MCU_Read(u16 addr) { - // + addr &= 0x7FFF; - printf("unknown DSi cam read8 %08X\n", addr); - return 0; + return MCURegs[addr]; } -u16 DSi_Camera::Read16(u32 addr) +void Camera::MCU_Write(u16 addr, u8 val) { + addr &= 0x7FFF; + switch (addr) { - case 0x04004200: return ModuleCnt; - case 0x04004202: return Cnt; + case 0x2103: // SEQ_CMD + MCURegs[addr] = 0; + if (val == 2) MCURegs[0x2104] = 7; // capture mode + else if (val == 1) MCURegs[0x2104] = 3; // preview mode + else if (val != 5 && val != 6) + printf("CAM%d: atypical SEQ_CMD %04X\n", Num, val); + return; + + case 0x2104: // SEQ_STATE, read-only + return; } - printf("unknown DSi cam read16 %08X\n", addr); - return 0; + MCURegs[addr] = val; } -u32 DSi_Camera::Read32(u32 addr) + +void Camera::InputFrame(u32* data, int width, int height, bool rgb) { - switch (addr) + // TODO: double-buffering? + + if (width == 640 && height == 480 && !rgb) { - case 0x04004204: + memcpy(FrameBuffer, data, (640*480/2)*sizeof(u32)); + return; + } + + if (rgb) + { + for (int dy = 0; dy < 480; dy++) { - // TODO - return 0xFC00801F; - /*if (!(Cnt & (1<<15))) return 0; // CHECKME - u32 ret = *(u32*)&FrameBuffer[TransferPos]; - TransferPos += 4; - if (TransferPos >= FrameLength) TransferPos = 0; - dorp += 4; - //if (dorp >= (256*4*2)) - if (TransferPos == 0) + int sy = (dy * height) / 480; + + for (int dx = 0; dx < 640; dx+=2) { - dorp = 0; - Cnt &= ~(1<<4); - } - return ret;*/ - } - } + int sx; - printf("unknown DSi cam read32 %08X\n", addr); - return 0; -} + sx = (dx * width) / 640; + u32 pixel1 = data[sy*width + sx]; -void DSi_Camera::Write8(u32 addr, u8 val) -{ - // + sx = ((dx+1) * width) / 640; + u32 pixel2 = data[sy*width + sx]; - printf("unknown DSi cam write8 %08X %02X\n", addr, val); -} + int r1 = (pixel1 >> 16) & 0xFF; + int g1 = (pixel1 >> 8) & 0xFF; + int b1 = pixel1 & 0xFF; -void DSi_Camera::Write16(u32 addr, u16 val) -{ - switch (addr) - { - case 0x04004200: - { - u16 oldcnt = ModuleCnt; - ModuleCnt = val; + int r2 = (pixel2 >> 16) & 0xFF; + int g2 = (pixel2 >> 8) & 0xFF; + int b2 = pixel2 & 0xFF; - if ((ModuleCnt & (1<<1)) && !(oldcnt & (1<<1))) - { - // reset shit to zero - // CHECKME + int y1 = ((r1 * 19595) + (g1 * 38470) + (b1 * 7471)) >> 16; + int u1 = ((b1 - y1) * 32244) >> 16; + int v1 = ((r1 - y1) * 57475) >> 16; - Cnt = 0; - } + int y2 = ((r2 * 19595) + (g2 * 38470) + (b2 * 7471)) >> 16; + int u2 = ((b2 - y2) * 32244) >> 16; + int v2 = ((r2 - y2) * 57475) >> 16; - if ((ModuleCnt & (1<<5)) && !(oldcnt & (1<<5))) - { - // TODO: reset I2C?? + u1 += 128; v1 += 128; + u2 += 128; v2 += 128; + + y1 = std::clamp(y1, 0, 255); u1 = std::clamp(u1, 0, 255); v1 = std::clamp(v1, 0, 255); + y2 = std::clamp(y2, 0, 255); u2 = std::clamp(u2, 0, 255); v2 = std::clamp(v2, 0, 255); + + // huh + u1 = (u1 + u2) >> 1; + v1 = (v1 + v2) >> 1; + + FrameBuffer[(dy*640 + dx) / 2] = y1 | (u1 << 8) | (y2 << 16) | (v1 << 24); } } - return; - - case 0x04004202: + } + else + { + for (int dy = 0; dy < 480; dy++) { - // checkme - u16 oldmask; - if (Cnt & 0x8000) - { - val &= 0x8F20; - oldmask = 0x601F; - } - else - { - val &= 0xEF2F; - oldmask = 0x0010; - } - - Cnt = (Cnt & oldmask) | (val & ~0x0020); - if (val & (1<<5)) Cnt &= ~(1<<4); + int sy = (dy * height) / 480; - if ((val & (1<<15)) && !(Cnt & (1<<15))) + for (int dx = 0; dx < 640; dx+=2) { - // start transfer - //DSi::CheckNDMAs(0, 0x0B); + int sx = (dx * width) / 640; + + FrameBuffer[(dy*640 + dx) / 2] = data[(sy*width + sx) / 2]; } } - return; } +} - printf("unknown DSi cam write16 %08X %04X\n", addr, val); } -void DSi_Camera::Write32(u32 addr, u32 val) -{ - // - printf("unknown DSi cam write32 %08X %08X\n", addr, val); -} + + + + + + + + + + + + diff --git a/src/DSi_Camera.h b/src/DSi_Camera.h index 3f39e3a..bf18e59 100644 --- a/src/DSi_Camera.h +++ b/src/DSi_Camera.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -20,35 +20,57 @@ #define DSI_CAMERA_H #include "types.h" +#include "Savestate.h" -class DSi_Camera +namespace DSi_CamModule { -public: - static bool Init(); - static void DeInit(); - static void Reset(); - static void IRQ(u32 param); - static void RequestFrame(u32 cam); +class Camera; + +extern Camera* Camera0; +extern Camera* Camera1; + +bool Init(); +void DeInit(); +void Reset(); +void Stop(); + +void DoSavestate(Savestate* file); - static void Transfer(u32 pos); +void IRQ(u32 param); - DSi_Camera(u32 num); - ~DSi_Camera(); +void TransferScanline(u32 line); - void ResetCam(); +u8 Read8(u32 addr); +u16 Read16(u32 addr); +u32 Read32(u32 addr); +void Write8(u32 addr, u8 val); +void Write16(u32 addr, u16 val); +void Write32(u32 addr, u32 val); + +class Camera +{ +public: + Camera(u32 num); + ~Camera(); + + void DoSavestate(Savestate* file); + + void Reset(); + void Stop(); bool IsActivated(); + void StartTransfer(); + bool TransferDone(); + + // lengths in words + int TransferScanline(u32* buffer, int maxlen); + void I2C_Start(); u8 I2C_Read(bool last); void I2C_Write(u8 val, bool last); - static u8 Read8(u32 addr); - static u16 Read16(u32 addr); - static u32 Read32(u32 addr); - static void Write8(u32 addr, u8 val); - static void Write16(u32 addr, u16 val); - static void Write32(u32 addr, u32 val); + void InputFrame(u32* data, int width, int height, bool rgb); u32 Num; @@ -68,20 +90,17 @@ private: u16 MiscCnt; u16 MCUAddr; - u16* MCUData; - u8 MCURegs[0x8000]; - static u16 ModuleCnt; - static u16 Cnt; + u8 MCU_Read(u16 addr); + void MCU_Write(u16 addr, u8 val); - static u8 FrameBuffer[640*480*4]; - static u32 TransferPos; - static u32 FrameLength; + u16 FrameWidth, FrameHeight; + u16 FrameReadMode, FrameFormat; + int TransferY; + u32 FrameBuffer[640*480/2]; // YUYV framebuffer, two pixels per word }; - -extern DSi_Camera* DSi_Camera0; -extern DSi_Camera* DSi_Camera1; +} #endif // DSI_CAMERA_H diff --git a/src/DSi_DSP.cpp b/src/DSi_DSP.cpp index 328f01d..0525366 100644 --- a/src/DSi_DSP.cpp +++ b/src/DSi_DSP.cpp @@ -27,6 +27,9 @@ namespace DSi_DSP { +// not sure whether to not rather put it somewhere else +u16 SNDExCnt; + Teakra::Teakra* TeakraCore; bool SCFG_RST; @@ -151,6 +154,8 @@ void Reset() TeakraCore->Reset(); NDS::CancelEvent(NDS::Event_DSi_DSP); + + SNDExCnt = 0; } bool IsRstReleased() @@ -548,6 +553,21 @@ void Write32(u32 addr, u32 val) Write16(addr, val & 0xFFFF); } +void WriteSNDExCnt(u16 val) +{ + // it can be written even in NDS mode + + // mic frequency can only be changed if it was disabled + // before the write + if (SNDExCnt & 0x8000) + { + val &= ~0x2000; + val |= SNDExCnt & 0x2000; + } + + SNDExCnt = val & 0xE00F; +} + void Run(u32 cycles) { if (!IsDSPCoreEnabled()) @@ -587,6 +607,8 @@ void DoSavestate(Savestate* file) file->Var16(&DSP_REP[1]); file->Var16(&DSP_REP[2]); file->Var8((u8*)&SCFG_RST); + + // TODO: save the Teakra state!!! } } diff --git a/src/DSi_DSP.h b/src/DSi_DSP.h index f5264b4..5d3427f 100644 --- a/src/DSi_DSP.h +++ b/src/DSi_DSP.h @@ -19,13 +19,17 @@ #ifndef DSI_DSP_H #define DSI_DSP_H +#include "types.h" +#include "Savestate.h" + // TODO: for actual sound output // * audio callbacks -// * SNDEXCNT namespace DSi_DSP { +extern u16 SNDExCnt; + extern u16 DSP_PDATA; extern u16 DSP_PADR; extern u16 DSP_PCFG; @@ -41,10 +45,10 @@ bool Init(); void DeInit(); void Reset(); -// TODO: needs to be called! -// however, no DSi savestate stuff seems to be actually implemented?! void DoSavestate(Savestate* file); +void DSPCatchUpU32(u32 _); + // SCFG_RST bit0 bool IsRstReleased(); void SetRstLine(bool release); @@ -62,6 +66,8 @@ void Write16(u32 addr, u16 val); u32 Read32(u32 addr); void Write32(u32 addr, u32 val); +void WriteSNDExCnt(u16 val); + // NOTE: checks SCFG_CLK9 void Run(u32 cycles); diff --git a/src/DSi_I2C.cpp b/src/DSi_I2C.cpp index 6f5f2e5..5889bef 100644 --- a/src/DSi_I2C.cpp +++ b/src/DSi_I2C.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -22,6 +22,7 @@ #include "DSi_I2C.h" #include "DSi_Camera.h" #include "ARM.h" +#include "SPI.h" namespace DSi_BPTWL @@ -72,8 +73,29 @@ void Reset() Registers[0x81] = 0x64; } +void DoSavestate(Savestate* file) +{ + file->Section("I2BP"); + + file->VarArray(Registers, 0x100); + file->Var32(&CurPos); +} + u8 GetBootFlag() { return Registers[0x70]; } +bool GetBatteryCharging() { return Registers[0x20] >> 7; } +void SetBatteryCharging(bool charging) +{ + Registers[0x20] = (((charging ? 0x8 : 0x0) << 4) | (Registers[0x20] & 0x0F)); +} + +u8 GetBatteryLevel() { return Registers[0x20] & 0xF; } +void SetBatteryLevel(u8 batteryLevel) +{ + Registers[0x20] = ((Registers[0x20] & 0xF0) | (batteryLevel & 0x0F)); + SPI_Powerman::SetBatteryLevelOkay(batteryLevel > batteryLevel_Low ? true : false); +} + void Start() { //printf("BPTWL: start\n"); @@ -147,7 +169,6 @@ u32 Device; bool Init() { if (!DSi_BPTWL::Init()) return false; - if (!DSi_Camera::Init()) return false; return true; } @@ -155,7 +176,6 @@ bool Init() void DeInit() { DSi_BPTWL::DeInit(); - DSi_Camera::DeInit(); } void Reset() @@ -166,12 +186,22 @@ void Reset() Device = -1; DSi_BPTWL::Reset(); - DSi_Camera::Reset(); +} + +void DoSavestate(Savestate* file) +{ + file->Section("I2Ci"); + + file->Var8(&Cnt); + file->Var8(&Data); + file->Var32(&Device); + + DSi_BPTWL::DoSavestate(file); } void WriteCnt(u8 val) { - //printf("I2C: write CNT %02X, %08X\n", val, NDS::GetPC(1)); + //printf("I2C: write CNT %02X, %02X, %08X\n", val, Data, NDS::GetPC(1)); // TODO: check ACK flag // TODO: transfer delay @@ -190,8 +220,8 @@ void WriteCnt(u8 val) switch (Device) { case 0x4A: Data = DSi_BPTWL::Read(islast); break; - case 0x78: Data = DSi_Camera0->I2C_Read(islast); break; - case 0x7A: Data = DSi_Camera1->I2C_Read(islast); break; + case 0x78: Data = DSi_CamModule::Camera0->I2C_Read(islast); break; + case 0x7A: Data = DSi_CamModule::Camera1->I2C_Read(islast); break; case 0xA0: case 0xE0: Data = 0xFF; break; default: @@ -216,8 +246,8 @@ void WriteCnt(u8 val) switch (Device) { case 0x4A: DSi_BPTWL::Start(); break; - case 0x78: DSi_Camera0->I2C_Start(); break; - case 0x7A: DSi_Camera1->I2C_Start(); break; + case 0x78: DSi_CamModule::Camera0->I2C_Start(); break; + case 0x7A: DSi_CamModule::Camera1->I2C_Start(); break; case 0xA0: case 0xE0: ack = false; break; default: @@ -233,8 +263,8 @@ void WriteCnt(u8 val) switch (Device) { case 0x4A: DSi_BPTWL::Write(Data, islast); break; - case 0x78: DSi_Camera0->I2C_Write(Data, islast); break; - case 0x7A: DSi_Camera1->I2C_Write(Data, islast); break; + case 0x78: DSi_CamModule::Camera0->I2C_Write(Data, islast); break; + case 0x7A: DSi_CamModule::Camera1->I2C_Write(Data, islast); break; case 0xA0: case 0xE0: ack = false; break; default: diff --git a/src/DSi_I2C.h b/src/DSi_I2C.h index c064366..48c8e88 100644 --- a/src/DSi_I2C.h +++ b/src/DSi_I2C.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,11 +19,29 @@ #ifndef DSI_I2C_H #define DSI_I2C_H +#include "types.h" +#include "Savestate.h" + namespace DSi_BPTWL { u8 GetBootFlag(); +bool GetBatteryCharging(); +void SetBatteryCharging(bool charging); + +enum +{ + batteryLevel_Critical = 0x0, + batteryLevel_AlmostEmpty = 0x1, + batteryLevel_Low = 0x3, + batteryLevel_Half = 0x7, + batteryLevel_ThreeQuarters = 0xB, + batteryLevel_Full = 0xF +}; + +u8 GetBatteryLevel(); +void SetBatteryLevel(u8 batteryLevel); } namespace DSi_I2C @@ -34,7 +52,7 @@ extern u8 Cnt; bool Init(); void DeInit(); void Reset(); -//void DoSavestate(Savestate* file); +void DoSavestate(Savestate* file); void WriteCnt(u8 val); diff --git a/src/DSi_NAND.cpp b/src/DSi_NAND.cpp index 4366ce6..912fee4 100644 --- a/src/DSi_NAND.cpp +++ b/src/DSi_NAND.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -49,8 +49,48 @@ UINT FF_ReadNAND(BYTE* buf, LBA_t sector, UINT num); UINT FF_WriteNAND(BYTE* buf, LBA_t sector, UINT num); -bool Init(FILE* nandfile, u8* es_keyY) +bool Init(u8* es_keyY) { + CurFile = nullptr; + + std::string nandpath = Platform::GetConfigString(Platform::DSi_NANDPath); + std::string instnand = nandpath + Platform::InstanceFileSuffix(); + + FILE* nandfile = Platform::OpenLocalFile(instnand, "r+b"); + if ((!nandfile) && (Platform::InstanceID() > 0)) + { + FILE* orig = Platform::OpenLocalFile(nandpath, "rb"); + if (!orig) + { + printf("Failed to open DSi NAND\n"); + return false; + } + + fseek(orig, 0, SEEK_END); + long len = ftell(orig); + fseek(orig, 0, SEEK_SET); + + nandfile = Platform::OpenLocalFile(instnand, "w+b"); + if (nandfile) + { + u8* tmpbuf = new u8[0x10000]; + for (long i = 0; i < len; i+=0x10000) + { + long blklen = 0x10000; + if ((i+blklen) > len) blklen = len-i; + + fread(tmpbuf, blklen, 1, orig); + fwrite(tmpbuf, blklen, 1, nandfile); + } + delete[] tmpbuf; + } + + fclose(orig); + fclose(nandfile); + + nandfile = Platform::OpenLocalFile(instnand, "r+b"); + } + if (!nandfile) return false; @@ -138,10 +178,17 @@ void DeInit() f_unmount("0:"); ff_disk_close(); + if (CurFile) fclose(CurFile); CurFile = nullptr; } +FILE* GetFile() +{ + return CurFile; +} + + void GetIDs(u8* emmc_cid, u64& consoleid) { memcpy(emmc_cid, eMMC_CID, 16); @@ -919,29 +966,44 @@ bool CreateSaveFile(const char* path, u32 len) u32 clustersize, maxfiles, totsec16, fatsz16; // CHECKME! - // code inspired from https://github.com/JeffRuLz/TMFH/blob/master/arm9/src/sav.c - if (len < 573440) - { - clustersize = 512; - maxfiles = 16; - } - else if (len < 5472256) + // code inspired from https://github.com/Epicpkmn11/NTM/blob/master/arm9/src/sav.c + const u16 sectorsize = 0x200; + + // fit maximum sectors for the size + const u16 maxsectors = len / sectorsize; + u16 tracksize = 1; + u16 headcount = 1; + u16 totsec16next = 0; + while (totsec16next <= maxsectors) { - clustersize = 2048; - maxfiles = 256; + totsec16next = tracksize * (headcount + 1) * (headcount + 1); + if (totsec16next <= maxsectors) + { + headcount++; + totsec16 = totsec16next; + + tracksize++; + totsec16next = tracksize * headcount * headcount; + if (totsec16next <= maxsectors) + { + totsec16 = totsec16next; + } + } } - else + totsec16next = (tracksize + 1) * headcount * headcount; + if (totsec16next <= maxsectors) { - clustersize = 4096; - maxfiles = 256; + tracksize++; + totsec16 = totsec16next; } - if (len <= 0x4000) fatsz16 = 1; - else if (len <= 0x200000) fatsz16 = 3; - else fatsz16 = 6; + maxfiles = len < 0x8C000 ? 0x20 : 0x200; + clustersize = (totsec16 > (8 << 10)) ? 8 : (totsec16 > (1 << 10) ? 4 : 1); - if (len == 0x4000) totsec16 = 27; - else totsec16 = len >> 9; + #define ALIGN(v, a) (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v)) + u16 totalclusters = ALIGN(totsec16, clustersize) / clustersize; + u32 fatbytes = (ALIGN(totalclusters, 2) / 2) * 3; // 2 sectors -> 3 byte + fatsz16 = ALIGN(fatbytes, sectorsize) / sectorsize; FF_FIL file; FRESULT res; @@ -960,17 +1022,19 @@ bool CreateSaveFile(const char* path, u32 len) // create FAT header data[0x000] = 0xE9; memcpy(&data[0x003], "MSWIN4.1", 8); - *(u16*)&data[0x00B] = 512; // bytes per sector - data[0x00D] = clustersize >> 9; + *(u16*)&data[0x00B] = sectorsize; // bytes per sector + data[0x00D] = clustersize; *(u16*)&data[0x00E] = 1; // reserved sectors data[0x010] = 2; // num FATs - *(u16*)&data[0x011] = maxfiles << 1; + *(u16*)&data[0x011] = maxfiles; *(u16*)&data[0x013] = totsec16; data[0x015] = 0xF8; *(u16*)&data[0x016] = fatsz16; - data[0x024] = 0x07; + *(u16*)&data[0x018] = tracksize; + *(u16*)&data[0x01A] = headcount; + data[0x024] = 0x05; data[0x026] = 0x29; - *(u32*)&data[0x027] = 305419896; + *(u32*)&data[0x027] = 0x12345678; memcpy(&data[0x02B], "VOLUMELABEL", 11); memcpy(&data[0x036], "FAT12 ", 8); *(u16*)&data[0x1FE] = 0xAA55; diff --git a/src/DSi_NAND.h b/src/DSi_NAND.h index 64c40fa..a23e62f 100644 --- a/src/DSi_NAND.h +++ b/src/DSi_NAND.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -34,9 +34,11 @@ enum TitleData_BannerSav, }; -bool Init(FILE* nand, u8* es_keyY); +bool Init(u8* es_keyY); void DeInit(); +FILE* GetFile(); + void GetIDs(u8* emmc_cid, u64& consoleid); void ReadHardwareInfo(u8* dataS, u8* dataN); diff --git a/src/DSi_NDMA.cpp b/src/DSi_NDMA.cpp index 37d5018..ca834eb 100644 --- a/src/DSi_NDMA.cpp +++ b/src/DSi_NDMA.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -63,7 +63,31 @@ void DSi_NDMA::Reset() void DSi_NDMA::DoSavestate(Savestate* file) { - // TODO! + char magic[5] = "NDMx"; + magic[3] = '0' + Num + (CPU*4); + file->Section(magic); + + file->Var32(&SrcAddr); + file->Var32(&DstAddr); + file->Var32(&TotalLength); + file->Var32(&BlockLength); + file->Var32(&SubblockTimer); + file->Var32(&FillData); + file->Var32(&Cnt); + + file->Var32(&StartMode); + file->Var32(&CurSrcAddr); + file->Var32(&CurDstAddr); + file->Var32(&SubblockLength); + file->Var32(&RemCount); + file->Var32(&IterCount); + file->Var32(&TotalRemCount); + file->Var32(&SrcAddrInc); + file->Var32(&DstAddrInc); + + file->Var32(&Running); + file->Bool32(&InProgress); + file->Bool32(&IsGXFIFODMA); } void DSi_NDMA::WriteCnt(u32 val) @@ -99,10 +123,17 @@ void DSi_NDMA::WriteCnt(u32 val) if ((StartMode & 0x1F) == 0x10) Start(); + else if (StartMode == 0x0A) + GPU3D::CheckFIFODMA(); - if (StartMode != 0x10 && StartMode != 0x30 && - StartMode != 0x04 && StartMode != 0x06 && StartMode != 0x07 && StartMode != 0x08 && StartMode != 0x09 && StartMode != 0x0B && - StartMode != 0x24 && StartMode != 0x26 && StartMode != 0x28 && StartMode != 0x29 && StartMode != 0x2A && StartMode != 0x2B) + // TODO: unsupported start modes: + // * timers (00-03) + // * camera (ARM9 0B) + // * microphone (ARM7 0C) + // * NDS-wifi?? (ARM7 07, likely not working) + + if (StartMode <= 0x03 || StartMode == 0x05 || (StartMode >= 0x0C && StartMode <= 0x0F) || + (StartMode >= 0x20 && StartMode <= 0x23) || StartMode == 0x25 || StartMode == 0x27 || (StartMode >= 0x2C && StartMode <= 0x2F)) printf("UNIMPLEMENTED ARM%d NDMA%d START MODE %02X, %08X->%08X LEN=%d BLK=%d CNT=%08X\n", CPU?7:9, Num, StartMode, SrcAddr, DstAddr, TotalLength, BlockLength, Cnt); } @@ -119,8 +150,12 @@ void DSi_NDMA::Start() RemCount = 0x1000000; } - // TODO: how does GXFIFO DMA work with all the block shito? - IterCount = RemCount; + // CHECKME: this is assumed to work the same as the old DMA version + // also not really certain how this interacts with the block subdivision system here + if (StartMode == 0x0A && RemCount > 112) + IterCount = 112; + else + IterCount = RemCount; if (((StartMode & 0x1F) != 0x10) && !(Cnt & (1<<29))) { diff --git a/src/DSi_NDMA.h b/src/DSi_NDMA.h index d4d0a8b..b1ea4c9 100644 --- a/src/DSi_NDMA.h +++ b/src/DSi_NDMA.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -20,6 +20,7 @@ #define DSI_NDMA_H #include "types.h" +#include "Savestate.h" class DSi_NDMA { diff --git a/src/DSi_NWifi.cpp b/src/DSi_NWifi.cpp index 57bd93c..7f8be83 100644 --- a/src/DSi_NWifi.cpp +++ b/src/DSi_NWifi.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -223,6 +223,45 @@ void DSi_NWifi::Reset() NDS::CancelEvent(NDS::Event_DSi_NWifi); } +void DSi_NWifi::DoSavestate(Savestate* file) +{ + file->Section("NWFi"); + + for (int i = 0; i < 9; i++) + Mailbox[i].DoSavestate(file); + + file->Var8(&F0_IRQEnable); + file->Var8(&F0_IRQStatus); + + file->Var8(&F1_IRQEnable); + file->Var8(&F1_IRQEnable_CPU); + file->Var8(&F1_IRQEnable_Error); + file->Var8(&F1_IRQEnable_Counter); + file->Var8(&F1_IRQStatus); + file->Var8(&F1_IRQStatus_CPU); + file->Var8(&F1_IRQStatus_Error); + file->Var8(&F1_IRQStatus_Counter); + + file->Var32(&WindowData); + file->Var32(&WindowReadAddr); + file->Var32(&WindowWriteAddr); + + file->Var32(&ROMID); + file->Var32(&ChipID); + file->Var32(&HostIntAddr); + + file->VarArray(EEPROM, 0x400); + file->Var32(&EEPROMReady); + + file->Var32(&BootPhase); + + file->Var32(&ErrorMask); + file->Var32(&ScanTimer); + + file->Var64(&BeaconTimer); + file->Var32(&ConnectionStatus); +} + // CHECKME // can IRQ status bits be set when the corresponding IRQs are disabled in the enable register? diff --git a/src/DSi_NWifi.h b/src/DSi_NWifi.h index da6597d..ffd5647 100644 --- a/src/DSi_NWifi.h +++ b/src/DSi_NWifi.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -21,6 +21,7 @@ #include "DSi_SD.h" #include "FIFO.h" +#include "Savestate.h" class DSi_NWifi : public DSi_SDDevice { @@ -30,6 +31,8 @@ public: void Reset(); + void DoSavestate(Savestate* file); + void SendCMD(u8 cmd, u32 param); void SendACMD(u8 cmd, u32 param); diff --git a/src/DSi_SD.cpp b/src/DSi_SD.cpp index 4c2b085..e603347 100644 --- a/src/DSi_SD.cpp +++ b/src/DSi_SD.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -51,8 +51,8 @@ DSi_SDHost::DSi_SDHost(u32 num) { Num = num; - Ports[0] = NULL; - Ports[1] = NULL; + Ports[0] = nullptr; + Ports[1] = nullptr; } DSi_SDHost::~DSi_SDHost() @@ -61,6 +61,14 @@ DSi_SDHost::~DSi_SDHost() if (Ports[1]) delete Ports[1]; } +void DSi_SDHost::CloseHandles() +{ + if (Ports[0]) delete Ports[0]; + if (Ports[1]) delete Ports[1]; + Ports[0] = nullptr; + Ports[1] = nullptr; +} + void DSi_SDHost::Reset() { if (Num == 0) @@ -101,10 +109,7 @@ void DSi_SDHost::Reset() TXReq = false; - if (Ports[0]) delete Ports[0]; - if (Ports[1]) delete Ports[1]; - Ports[0] = nullptr; - Ports[1] = nullptr; + CloseHandles(); if (Num == 0) { @@ -131,7 +136,10 @@ void DSi_SDHost::Reset() else sd = nullptr; - mmc = new DSi_MMCStorage(this, true, DSi::SDMMCFile); + std::string nandpath = Platform::GetConfigString(Platform::DSi_NANDPath); + std::string instnand = nandpath + Platform::InstanceFileSuffix(); + + mmc = new DSi_MMCStorage(this, true, instnand); mmc->SetCID(DSi::eMMC_CID); Ports[0] = sd; @@ -150,7 +158,41 @@ void DSi_SDHost::Reset() void DSi_SDHost::DoSavestate(Savestate* file) { - // TODO! + file->Section(Num ? "SDIO" : "SDMM"); + + file->Var16(&PortSelect); + file->Var16(&SoftReset); + file->Var16(&SDClock); + file->Var16(&SDOption); + + file->Var32(&IRQStatus); + file->Var32(&IRQMask); + + file->Var16(&CardIRQStatus); + file->Var16(&CardIRQMask); + file->Var16(&CardIRQCtl); + + file->Var16(&DataCtl); + file->Var16(&Data32IRQ); + file->Var32(&DataMode); + file->Var16(&BlockCount16); + file->Var16(&BlockCount32); + file->Var16(&BlockCountInternal); + file->Var16(&BlockLen16); + file->Var16(&BlockLen32); + file->Var16(&StopAction); + + file->Var16(&Command); + file->Var32(&Param); + file->VarArray(ResponseBuffer, 8); + + file->Var32(&CurFIFO); + DataFIFO[0].DoSavestate(file); + DataFIFO[1].DoSavestate(file); + DataFIFO32.DoSavestate(file); + + if (Ports[0]) Ports[0]->DoSavestate(file); + if (Ports[1]) Ports[1]->DoSavestate(file); } @@ -438,16 +480,14 @@ u16 DSi_SDHost::Read(u32 addr) { if (Ports[0]) // basic check of whether the SD card is inserted { - ret |= 0x0030; + ret |= 0x0020; if (!Ports[0]->ReadOnly) ret |= 0x0080; } - else - ret |= 0x0008; } else { // SDIO wifi is always inserted, I guess - ret |= 0x00B0; + ret |= 0x00A0; } return ret; } @@ -727,12 +767,15 @@ void DSi_SDHost::CheckSwapFIFO() #define MMC_DESC (Internal?"NAND":"SDcard") -DSi_MMCStorage::DSi_MMCStorage(DSi_SDHost* host, bool internal, FILE* file) +DSi_MMCStorage::DSi_MMCStorage(DSi_SDHost* host, bool internal, std::string filename) : DSi_SDDevice(host) { Internal = internal; - File = file; + File = Platform::OpenLocalFile(filename, "r+b"); + SD = nullptr; + + ReadOnly = false; } DSi_MMCStorage::DSi_MMCStorage(DSi_SDHost* host, bool internal, std::string filename, u64 size, bool readonly, std::string sourcedir) @@ -754,6 +797,10 @@ DSi_MMCStorage::~DSi_MMCStorage() SD->Close(); delete SD; } + if (File) + { + fclose(File); + } } void DSi_MMCStorage::Reset() @@ -781,6 +828,26 @@ void DSi_MMCStorage::Reset() RWCommand = 0; } +void DSi_MMCStorage::DoSavestate(Savestate* file) +{ + file->Section(Internal ? "NAND" : "SDCR"); + + file->VarArray(CID, 16); + file->VarArray(CSD, 16); + + file->Var32(&CSR); + file->Var32(&OCR); + file->Var32(&RCA); + file->VarArray(SCR, 8); + file->VarArray(SSR, 64); + + file->Var32(&BlockSize); + file->Var64(&RWAddress); + file->Var32(&RWCommand); + + // TODO: what about the file contents? +} + void DSi_MMCStorage::SendCMD(u8 cmd, u32 param) { if (CSR & (1<<5)) diff --git a/src/DSi_SD.h b/src/DSi_SD.h index 1c0f7ce..5f6dbcd 100644 --- a/src/DSi_SD.h +++ b/src/DSi_SD.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,10 +19,10 @@ #ifndef DSI_SD_H #define DSI_SD_H -#include <string.h> +#include <cstring> #include "FIFO.h" #include "FATStorage.h" - +#include "Savestate.h" class DSi_SDDevice; @@ -33,6 +33,7 @@ public: DSi_SDHost(u32 num); ~DSi_SDHost(); + void CloseHandles(); void Reset(); void DoSavestate(Savestate* file); @@ -108,6 +109,8 @@ public: virtual void Reset() = 0; + virtual void DoSavestate(Savestate* file) = 0; + virtual void SendCMD(u8 cmd, u32 param) = 0; virtual void ContinueTransfer() = 0; @@ -122,12 +125,14 @@ protected: class DSi_MMCStorage : public DSi_SDDevice { public: - DSi_MMCStorage(DSi_SDHost* host, bool internal, FILE* file); + DSi_MMCStorage(DSi_SDHost* host, bool internal, std::string filename); DSi_MMCStorage(DSi_SDHost* host, bool internal, std::string filename, u64 size, bool readonly, std::string sourcedir); ~DSi_MMCStorage(); void Reset(); + void DoSavestate(Savestate* file); + void SetCID(u8* cid) { memcpy(CID, cid, 16); } void SendCMD(u8 cmd, u32 param); diff --git a/src/DSi_SPI_TSC.cpp b/src/DSi_SPI_TSC.cpp index 061feb1..73d792b 100644 --- a/src/DSi_SPI_TSC.cpp +++ b/src/DSi_SPI_TSC.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -71,14 +71,15 @@ void Reset() void DoSavestate(Savestate* file) { - /*file->Section("SPTi"); + file->Section("SPTi"); file->Var32(&DataPos); - file->Var8(&ControlByte); + file->Var8(&Index); + file->Var8(&Bank); file->Var8(&Data); - file->Var16(&ConvResult);*/ - // TODO!! + file->VarArray(Bank3Regs, 0x80); + file->Var8(&TSCMode); } void SetMode(u8 mode) diff --git a/src/DSi_SPI_TSC.h b/src/DSi_SPI_TSC.h index 13b7883..7a3acf4 100644 --- a/src/DSi_SPI_TSC.h +++ b/src/DSi_SPI_TSC.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,6 +19,9 @@ #ifndef DSI_SPI_TSC #define DSI_SPI_TSC +#include "types.h" +#include "Savestate.h" + namespace DSi_SPI_TSC { diff --git a/src/FATStorage.cpp b/src/FATStorage.cpp index 80bc46e..5764524 100644 --- a/src/FATStorage.cpp +++ b/src/FATStorage.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/FATStorage.h b/src/FATStorage.h index 153c95a..29bcbc4 100644 --- a/src/FATStorage.h +++ b/src/FATStorage.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -20,6 +20,7 @@ #define FIFO_H #include "types.h" +#include "Savestate.h" template<typename T, u32 NumEntries> class FIFO diff --git a/src/FreeBIOS.h b/src/FreeBIOS.h index 3d54c78..965d43b 100644 --- a/src/FreeBIOS.h +++ b/src/FreeBIOS.h @@ -31,7 +31,7 @@ unsigned char bios_arm7_bin[] = { 0x1c, 0x04, 0x00, 0xea, 0x1c, 0x04, 0x00, 0xea, 0x1c, 0x04, 0x00, 0xea, 0x1a, 0x04, 0x00, 0xea, 0x19, 0x04, 0x00, 0xea, 0x18, 0x04, 0x00, 0xea, - 0xb8, 0x07, 0x00, 0xea, 0x16, 0x04, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, + 0xe3, 0x07, 0x00, 0xea, 0x16, 0x04, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -381,22 +381,22 @@ unsigned char bios_arm7_bin[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, - 0x10, 0x50, 0x2d, 0xe9, 0x00, 0x40, 0x4f, 0xe1, 0x04, 0x40, 0x2d, 0xe5, + 0x10, 0x50, 0x2d, 0xe9, 0x00, 0x40, 0x4f, 0xe1, 0x10, 0x00, 0x2d, 0xe9, 0x80, 0x40, 0x04, 0xe2, 0x1f, 0x40, 0x84, 0xe3, 0x02, 0xc0, 0x5e, 0xe5, - 0x04, 0xf0, 0x29, 0xe1, 0x04, 0xe0, 0x2d, 0xe5, 0x20, 0x00, 0x5c, 0xe3, + 0x04, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x20, 0x00, 0x5c, 0xe3, 0x01, 0xc0, 0xa0, 0xa3, 0x0c, 0xf1, 0x9f, 0xe7, 0x00, 0x00, 0xa0, 0xe1, - 0x28, 0x1f, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, + 0xd4, 0x1f, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x5c, 0x11, 0x00, 0x00, 0x90, 0x11, 0x00, 0x00, 0x88, 0x11, 0x00, 0x00, 0x4c, 0x11, 0x00, 0x00, 0xbc, 0x11, 0x00, 0x00, 0xcc, 0x11, 0x00, 0x00, 0xe8, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x44, 0x12, 0x00, 0x00, 0xc4, 0x12, 0x00, 0x00, 0x04, 0x13, 0x00, 0x00, 0x50, 0x13, 0x00, 0x00, 0xe8, 0x13, 0x00, 0x00, 0xf0, 0x13, 0x00, 0x00, 0x84, 0x14, 0x00, 0x00, - 0x84, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x04, 0x15, 0x00, 0x00, - 0x04, 0x15, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, - 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0xdc, 0x15, 0x00, 0x00, - 0xf0, 0x1b, 0x00, 0x00, 0x18, 0x1f, 0x00, 0x00, 0xd8, 0x1e, 0x00, 0x00, - 0xf4, 0x1e, 0x00, 0x00, 0x04, 0xe0, 0x9d, 0xe4, 0xd3, 0x40, 0xa0, 0xe3, - 0x04, 0xf0, 0x29, 0xe1, 0x04, 0x40, 0x9d, 0xe4, 0x04, 0xf0, 0x69, 0xe1, + 0x00, 0x15, 0x00, 0x00, 0xac, 0x15, 0x00, 0x00, 0xb0, 0x15, 0x00, 0x00, + 0xb0, 0x15, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, + 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x88, 0x16, 0x00, 0x00, + 0x9c, 0x1c, 0x00, 0x00, 0xc4, 0x1f, 0x00, 0x00, 0x84, 0x1f, 0x00, 0x00, + 0xa0, 0x1f, 0x00, 0x00, 0x00, 0x40, 0xbd, 0xe8, 0xd3, 0x40, 0xa0, 0xe3, + 0x04, 0xf0, 0x29, 0xe1, 0x10, 0x00, 0xbd, 0xe8, 0x04, 0xf0, 0x69, 0xe1, 0x10, 0x50, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x20, 0xa0, 0xe3, 0x01, 0x23, 0xc0, 0xe5, 0xf3, 0xff, 0xff, 0xea, 0x01, 0x00, 0x50, 0xe2, 0xfd, 0xff, 0xff, 0xca, @@ -441,7 +441,7 @@ unsigned char bios_arm7_bin[] = { 0x7e, 0xff, 0xff, 0xea, 0x00, 0x00, 0x01, 0xcc, 0x01, 0xd8, 0x00, 0x14, 0x01, 0xf0, 0x00, 0x3c, 0x00, 0x28, 0x01, 0xe4, 0x01, 0xa0, 0x00, 0x6c, 0x00, 0x78, 0x01, 0xb4, 0x00, 0x50, 0x01, 0x9c, 0x01, 0x88, 0x00, 0x44, - 0x04, 0x50, 0x2d, 0xe5, 0x1e, 0x40, 0xa0, 0xe3, 0x30, 0xe0, 0x4f, 0xe2, + 0x20, 0x00, 0x2d, 0xe9, 0x1e, 0x40, 0xa0, 0xe3, 0x30, 0xe0, 0x4f, 0xe2, 0xa2, 0x20, 0xb0, 0xe1, 0x1e, 0x00, 0x00, 0x0a, 0xb2, 0x30, 0xd1, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x83, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, @@ -453,7 +453,7 @@ unsigned char bios_arm7_bin[] = { 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x55, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x01, 0x20, 0x52, 0xe2, 0xe0, 0xff, 0xff, 0x1a, - 0x04, 0x50, 0x9d, 0xe4, 0x50, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, + 0x20, 0x00, 0xbd, 0xe8, 0x50, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, 0x4e, 0xff, 0xff, 0xea, 0xe0, 0x03, 0x2d, 0xe9, 0xb0, 0x30, 0xd2, 0xe1, 0x02, 0xc0, 0xd2, 0xe5, 0x03, 0xe0, 0xd2, 0xe5, 0x04, 0x40, 0x92, 0xe5, 0xa4, 0x2f, 0xa0, 0xe1, 0x02, 0x41, 0xc4, 0xe3, 0x01, 0x50, 0xa0, 0xe3, @@ -477,156 +477,170 @@ unsigned char bios_arm7_bin[] = { 0xed, 0xff, 0xff, 0x5a, 0xea, 0xff, 0xff, 0xea, 0x01, 0x40, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, 0x01, 0x40, 0xc1, 0xe4, 0x0d, 0xff, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, 0xe6, 0xff, 0xff, 0x5a, 0xe3, 0xff, 0xff, 0xea, - 0x09, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xa0, 0xe1, - 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, 0x7f, 0x30, 0x03, 0xe2, - 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, 0x03, 0x30, 0x83, 0xe2, - 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, 0xfe, 0xfe, 0xff, 0x0a, - 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, 0xf3, 0xff, 0xff, 0xea, - 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, - 0x01, 0xc0, 0xc1, 0xe4, 0xf6, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, - 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, 0x00, 0x00, 0x24, 0x03, - 0x48, 0x06, 0x6a, 0x09, 0x8c, 0x0c, 0xab, 0x0f, 0xc8, 0x12, 0xe2, 0x15, - 0xf9, 0x18, 0x0b, 0x1c, 0x1a, 0x1f, 0x23, 0x22, 0x28, 0x25, 0x26, 0x28, - 0x1f, 0x2b, 0x11, 0x2e, 0xfb, 0x30, 0xdf, 0x33, 0xba, 0x36, 0x8c, 0x39, - 0x56, 0x3c, 0x17, 0x3f, 0xce, 0x41, 0x7a, 0x44, 0x1c, 0x47, 0xb4, 0x49, - 0x3f, 0x4c, 0xbf, 0x4e, 0x33, 0x51, 0x9b, 0x53, 0xf5, 0x55, 0x42, 0x58, - 0x82, 0x5a, 0xb3, 0x5c, 0xd7, 0x5e, 0xeb, 0x60, 0xf1, 0x62, 0xe8, 0x64, - 0xcf, 0x66, 0xa6, 0x68, 0x6d, 0x6a, 0x23, 0x6c, 0xc9, 0x6d, 0x5e, 0x6f, - 0xe2, 0x70, 0x54, 0x72, 0xb5, 0x73, 0x04, 0x75, 0x41, 0x76, 0x6b, 0x77, - 0x84, 0x78, 0x89, 0x79, 0x7c, 0x7a, 0x5c, 0x7b, 0x29, 0x7c, 0xe3, 0x7c, - 0x89, 0x7d, 0x1d, 0x7e, 0x9c, 0x7e, 0x09, 0x7f, 0x61, 0x7f, 0xa6, 0x7f, - 0xd8, 0x7f, 0xf5, 0x7f, 0x00, 0x00, 0x80, 0xe0, 0x8c, 0x10, 0x4f, 0xe2, - 0xb0, 0x00, 0x91, 0xe1, 0xcf, 0xfe, 0xff, 0xea, 0x00, 0x00, 0x3b, 0x00, - 0x76, 0x00, 0xb2, 0x00, 0xed, 0x00, 0x28, 0x01, 0x64, 0x01, 0x9f, 0x01, - 0xdb, 0x01, 0x17, 0x02, 0x52, 0x02, 0x8e, 0x02, 0xca, 0x02, 0x05, 0x03, - 0x41, 0x03, 0x7d, 0x03, 0xb9, 0x03, 0xf5, 0x03, 0x31, 0x04, 0x6e, 0x04, - 0xaa, 0x04, 0xe6, 0x04, 0x22, 0x05, 0x5f, 0x05, 0x9b, 0x05, 0xd8, 0x05, - 0x14, 0x06, 0x51, 0x06, 0x8d, 0x06, 0xca, 0x06, 0x07, 0x07, 0x43, 0x07, - 0x80, 0x07, 0xbd, 0x07, 0xfa, 0x07, 0x37, 0x08, 0x74, 0x08, 0xb1, 0x08, - 0xef, 0x08, 0x2c, 0x09, 0x69, 0x09, 0xa7, 0x09, 0xe4, 0x09, 0x21, 0x0a, - 0x5f, 0x0a, 0x9c, 0x0a, 0xda, 0x0a, 0x18, 0x0b, 0x56, 0x0b, 0x93, 0x0b, - 0xd1, 0x0b, 0x0f, 0x0c, 0x4d, 0x0c, 0x8b, 0x0c, 0xc9, 0x0c, 0x07, 0x0d, - 0x45, 0x0d, 0x84, 0x0d, 0xc2, 0x0d, 0x00, 0x0e, 0x3f, 0x0e, 0x7d, 0x0e, - 0xbc, 0x0e, 0xfa, 0x0e, 0x39, 0x0f, 0x78, 0x0f, 0xb6, 0x0f, 0xf5, 0x0f, - 0x34, 0x10, 0x73, 0x10, 0xb2, 0x10, 0xf1, 0x10, 0x30, 0x11, 0x6f, 0x11, - 0xae, 0x11, 0xee, 0x11, 0x2d, 0x12, 0x6c, 0x12, 0xac, 0x12, 0xeb, 0x12, - 0x2b, 0x13, 0x6b, 0x13, 0xaa, 0x13, 0xea, 0x13, 0x2a, 0x14, 0x6a, 0x14, - 0xa9, 0x14, 0xe9, 0x14, 0x29, 0x15, 0x69, 0x15, 0xaa, 0x15, 0xea, 0x15, - 0x2a, 0x16, 0x6a, 0x16, 0xab, 0x16, 0xeb, 0x16, 0x2c, 0x17, 0x6c, 0x17, - 0xad, 0x17, 0xed, 0x17, 0x2e, 0x18, 0x6f, 0x18, 0xb0, 0x18, 0xf0, 0x18, - 0x31, 0x19, 0x72, 0x19, 0xb3, 0x19, 0xf5, 0x19, 0x36, 0x1a, 0x77, 0x1a, - 0xb8, 0x1a, 0xfa, 0x1a, 0x3b, 0x1b, 0x7d, 0x1b, 0xbe, 0x1b, 0x00, 0x1c, - 0x41, 0x1c, 0x83, 0x1c, 0xc5, 0x1c, 0x07, 0x1d, 0x48, 0x1d, 0x8a, 0x1d, - 0xcc, 0x1d, 0x0e, 0x1e, 0x51, 0x1e, 0x93, 0x1e, 0xd5, 0x1e, 0x17, 0x1f, - 0x5a, 0x1f, 0x9c, 0x1f, 0xdf, 0x1f, 0x21, 0x20, 0x64, 0x20, 0xa6, 0x20, - 0xe9, 0x20, 0x2c, 0x21, 0x6f, 0x21, 0xb2, 0x21, 0xf5, 0x21, 0x38, 0x22, - 0x7b, 0x22, 0xbe, 0x22, 0x01, 0x23, 0x44, 0x23, 0x88, 0x23, 0xcb, 0x23, - 0x0e, 0x24, 0x52, 0x24, 0x96, 0x24, 0xd9, 0x24, 0x1d, 0x25, 0x61, 0x25, - 0xa4, 0x25, 0xe8, 0x25, 0x2c, 0x26, 0x70, 0x26, 0xb4, 0x26, 0xf8, 0x26, - 0x3d, 0x27, 0x81, 0x27, 0xc5, 0x27, 0x0a, 0x28, 0x4e, 0x28, 0x92, 0x28, - 0xd7, 0x28, 0x1c, 0x29, 0x60, 0x29, 0xa5, 0x29, 0xea, 0x29, 0x2f, 0x2a, - 0x74, 0x2a, 0xb9, 0x2a, 0xfe, 0x2a, 0x43, 0x2b, 0x88, 0x2b, 0xcd, 0x2b, - 0x13, 0x2c, 0x58, 0x2c, 0x9d, 0x2c, 0xe3, 0x2c, 0x28, 0x2d, 0x6e, 0x2d, - 0xb4, 0x2d, 0xf9, 0x2d, 0x3f, 0x2e, 0x85, 0x2e, 0xcb, 0x2e, 0x11, 0x2f, - 0x57, 0x2f, 0x9d, 0x2f, 0xe3, 0x2f, 0x2a, 0x30, 0x70, 0x30, 0xb6, 0x30, - 0xfd, 0x30, 0x43, 0x31, 0x8a, 0x31, 0xd0, 0x31, 0x17, 0x32, 0x5e, 0x32, - 0xa5, 0x32, 0xec, 0x32, 0x32, 0x33, 0x79, 0x33, 0xc1, 0x33, 0x08, 0x34, - 0x4f, 0x34, 0x96, 0x34, 0xdd, 0x34, 0x25, 0x35, 0x6c, 0x35, 0xb4, 0x35, - 0xfb, 0x35, 0x43, 0x36, 0x8b, 0x36, 0xd3, 0x36, 0x1a, 0x37, 0x62, 0x37, - 0xaa, 0x37, 0xf2, 0x37, 0x3a, 0x38, 0x83, 0x38, 0xcb, 0x38, 0x13, 0x39, - 0x5c, 0x39, 0xa4, 0x39, 0xed, 0x39, 0x35, 0x3a, 0x7e, 0x3a, 0xc6, 0x3a, - 0x0f, 0x3b, 0x58, 0x3b, 0xa1, 0x3b, 0xea, 0x3b, 0x33, 0x3c, 0x7c, 0x3c, - 0xc5, 0x3c, 0x0e, 0x3d, 0x58, 0x3d, 0xa1, 0x3d, 0xea, 0x3d, 0x34, 0x3e, - 0x7d, 0x3e, 0xc7, 0x3e, 0x11, 0x3f, 0x5a, 0x3f, 0xa4, 0x3f, 0xee, 0x3f, - 0x38, 0x40, 0x82, 0x40, 0xcc, 0x40, 0x16, 0x41, 0x61, 0x41, 0xab, 0x41, - 0xf5, 0x41, 0x40, 0x42, 0x8a, 0x42, 0xd5, 0x42, 0x1f, 0x43, 0x6a, 0x43, - 0xb5, 0x43, 0x00, 0x44, 0x4b, 0x44, 0x95, 0x44, 0xe1, 0x44, 0x2c, 0x45, - 0x77, 0x45, 0xc2, 0x45, 0x0d, 0x46, 0x59, 0x46, 0xa4, 0x46, 0xf0, 0x46, - 0x3b, 0x47, 0x87, 0x47, 0xd3, 0x47, 0x1e, 0x48, 0x6a, 0x48, 0xb6, 0x48, - 0x02, 0x49, 0x4e, 0x49, 0x9a, 0x49, 0xe6, 0x49, 0x33, 0x4a, 0x7f, 0x4a, - 0xcb, 0x4a, 0x18, 0x4b, 0x64, 0x4b, 0xb1, 0x4b, 0xfe, 0x4b, 0x4a, 0x4c, - 0x97, 0x4c, 0xe4, 0x4c, 0x31, 0x4d, 0x7e, 0x4d, 0xcb, 0x4d, 0x18, 0x4e, - 0x66, 0x4e, 0xb3, 0x4e, 0x00, 0x4f, 0x4e, 0x4f, 0x9b, 0x4f, 0xe9, 0x4f, - 0x36, 0x50, 0x84, 0x50, 0xd2, 0x50, 0x20, 0x51, 0x6e, 0x51, 0xbc, 0x51, - 0x0a, 0x52, 0x58, 0x52, 0xa6, 0x52, 0xf4, 0x52, 0x43, 0x53, 0x91, 0x53, - 0xe0, 0x53, 0x2e, 0x54, 0x7d, 0x54, 0xcc, 0x54, 0x1a, 0x55, 0x69, 0x55, - 0xb8, 0x55, 0x07, 0x56, 0x56, 0x56, 0xa5, 0x56, 0xf4, 0x56, 0x44, 0x57, - 0x93, 0x57, 0xe2, 0x57, 0x32, 0x58, 0x82, 0x58, 0xd1, 0x58, 0x21, 0x59, - 0x71, 0x59, 0xc1, 0x59, 0x10, 0x5a, 0x60, 0x5a, 0xb0, 0x5a, 0x01, 0x5b, - 0x51, 0x5b, 0xa1, 0x5b, 0xf1, 0x5b, 0x42, 0x5c, 0x92, 0x5c, 0xe3, 0x5c, - 0x34, 0x5d, 0x84, 0x5d, 0xd5, 0x5d, 0x26, 0x5e, 0x77, 0x5e, 0xc8, 0x5e, - 0x19, 0x5f, 0x6a, 0x5f, 0xbb, 0x5f, 0x0d, 0x60, 0x5e, 0x60, 0xb0, 0x60, - 0x01, 0x61, 0x53, 0x61, 0xa4, 0x61, 0xf6, 0x61, 0x48, 0x62, 0x9a, 0x62, - 0xec, 0x62, 0x3e, 0x63, 0x90, 0x63, 0xe2, 0x63, 0x34, 0x64, 0x87, 0x64, - 0xd9, 0x64, 0x2c, 0x65, 0x7e, 0x65, 0xd1, 0x65, 0x24, 0x66, 0x76, 0x66, - 0xc9, 0x66, 0x1c, 0x67, 0x6f, 0x67, 0xc2, 0x67, 0x15, 0x68, 0x69, 0x68, - 0xbc, 0x68, 0x0f, 0x69, 0x63, 0x69, 0xb6, 0x69, 0x0a, 0x6a, 0x5e, 0x6a, - 0xb1, 0x6a, 0x05, 0x6b, 0x59, 0x6b, 0xad, 0x6b, 0x01, 0x6c, 0x55, 0x6c, - 0xaa, 0x6c, 0xfe, 0x6c, 0x52, 0x6d, 0xa7, 0x6d, 0xfb, 0x6d, 0x50, 0x6e, - 0xa4, 0x6e, 0xf9, 0x6e, 0x4e, 0x6f, 0xa3, 0x6f, 0xf8, 0x6f, 0x4d, 0x70, - 0xa2, 0x70, 0xf7, 0x70, 0x4d, 0x71, 0xa2, 0x71, 0xf7, 0x71, 0x4d, 0x72, - 0xa2, 0x72, 0xf8, 0x72, 0x4e, 0x73, 0xa4, 0x73, 0xfa, 0x73, 0x50, 0x74, - 0xa6, 0x74, 0xfc, 0x74, 0x52, 0x75, 0xa8, 0x75, 0xff, 0x75, 0x55, 0x76, - 0xac, 0x76, 0x02, 0x77, 0x59, 0x77, 0xb0, 0x77, 0x07, 0x78, 0x5e, 0x78, - 0xb4, 0x78, 0x0c, 0x79, 0x63, 0x79, 0xba, 0x79, 0x11, 0x7a, 0x69, 0x7a, - 0xc0, 0x7a, 0x18, 0x7b, 0x6f, 0x7b, 0xc7, 0x7b, 0x1f, 0x7c, 0x77, 0x7c, - 0xcf, 0x7c, 0x27, 0x7d, 0x7f, 0x7d, 0xd7, 0x7d, 0x2f, 0x7e, 0x88, 0x7e, - 0xe0, 0x7e, 0x38, 0x7f, 0x91, 0x7f, 0xea, 0x7f, 0x42, 0x80, 0x9b, 0x80, - 0xf4, 0x80, 0x4d, 0x81, 0xa6, 0x81, 0xff, 0x81, 0x59, 0x82, 0xb2, 0x82, - 0x0b, 0x83, 0x65, 0x83, 0xbe, 0x83, 0x18, 0x84, 0x72, 0x84, 0xcb, 0x84, - 0x25, 0x85, 0x7f, 0x85, 0xd9, 0x85, 0x33, 0x86, 0x8e, 0x86, 0xe8, 0x86, - 0x42, 0x87, 0x9d, 0x87, 0xf7, 0x87, 0x52, 0x88, 0xac, 0x88, 0x07, 0x89, - 0x62, 0x89, 0xbd, 0x89, 0x18, 0x8a, 0x73, 0x8a, 0xce, 0x8a, 0x2a, 0x8b, - 0x85, 0x8b, 0xe0, 0x8b, 0x3c, 0x8c, 0x97, 0x8c, 0xf3, 0x8c, 0x4f, 0x8d, - 0xab, 0x8d, 0x07, 0x8e, 0x63, 0x8e, 0xbf, 0x8e, 0x1b, 0x8f, 0x77, 0x8f, - 0xd4, 0x8f, 0x30, 0x90, 0x8c, 0x90, 0xe9, 0x90, 0x46, 0x91, 0xa2, 0x91, - 0xff, 0x91, 0x5c, 0x92, 0xb9, 0x92, 0x16, 0x93, 0x73, 0x93, 0xd1, 0x93, - 0x2e, 0x94, 0x8c, 0x94, 0xe9, 0x94, 0x47, 0x95, 0xa4, 0x95, 0x02, 0x96, - 0x60, 0x96, 0xbe, 0x96, 0x1c, 0x97, 0x7a, 0x97, 0xd8, 0x97, 0x36, 0x98, - 0x95, 0x98, 0xf3, 0x98, 0x52, 0x99, 0xb0, 0x99, 0x0f, 0x9a, 0x6e, 0x9a, - 0xcd, 0x9a, 0x2c, 0x9b, 0x8b, 0x9b, 0xea, 0x9b, 0x49, 0x9c, 0xa8, 0x9c, - 0x08, 0x9d, 0x67, 0x9d, 0xc7, 0x9d, 0x26, 0x9e, 0x86, 0x9e, 0xe6, 0x9e, - 0x46, 0x9f, 0xa6, 0x9f, 0x06, 0xa0, 0x66, 0xa0, 0xc6, 0xa0, 0x27, 0xa1, - 0x87, 0xa1, 0xe8, 0xa1, 0x48, 0xa2, 0xa9, 0xa2, 0x0a, 0xa3, 0x6b, 0xa3, - 0xcc, 0xa3, 0x2d, 0xa4, 0x8e, 0xa4, 0xef, 0xa4, 0x50, 0xa5, 0xb2, 0xa5, - 0x13, 0xa6, 0x75, 0xa6, 0xd6, 0xa6, 0x38, 0xa7, 0x9a, 0xa7, 0xfc, 0xa7, - 0x5e, 0xa8, 0xc0, 0xa8, 0x22, 0xa9, 0x84, 0xa9, 0xe7, 0xa9, 0x49, 0xaa, - 0xac, 0xaa, 0x0e, 0xab, 0x71, 0xab, 0xd4, 0xab, 0x37, 0xac, 0x9a, 0xac, - 0xfd, 0xac, 0x60, 0xad, 0xc3, 0xad, 0x27, 0xae, 0x8a, 0xae, 0xed, 0xae, - 0x51, 0xaf, 0xb5, 0xaf, 0x19, 0xb0, 0x7c, 0xb0, 0xe0, 0xb0, 0x45, 0xb1, - 0xa9, 0xb1, 0x0d, 0xb2, 0x71, 0xb2, 0xd6, 0xb2, 0x3a, 0xb3, 0x9f, 0xb3, - 0x03, 0xb4, 0x68, 0xb4, 0xcd, 0xb4, 0x32, 0xb5, 0x97, 0xb5, 0xfc, 0xb5, - 0x62, 0xb6, 0xc7, 0xb6, 0x2c, 0xb7, 0x92, 0xb7, 0xf7, 0xb7, 0x5d, 0xb8, - 0xc3, 0xb8, 0x29, 0xb9, 0x8f, 0xb9, 0xf5, 0xb9, 0x5b, 0xba, 0xc1, 0xba, - 0x28, 0xbb, 0x8e, 0xbb, 0xf5, 0xbb, 0x5b, 0xbc, 0xc2, 0xbc, 0x29, 0xbd, - 0x90, 0xbd, 0xf7, 0xbd, 0x5e, 0xbe, 0xc5, 0xbe, 0x2c, 0xbf, 0x94, 0xbf, - 0xfb, 0xbf, 0x63, 0xc0, 0xca, 0xc0, 0x32, 0xc1, 0x9a, 0xc1, 0x02, 0xc2, - 0x6a, 0xc2, 0xd2, 0xc2, 0x3a, 0xc3, 0xa2, 0xc3, 0x0b, 0xc4, 0x73, 0xc4, - 0xdc, 0xc4, 0x44, 0xc5, 0xad, 0xc5, 0x16, 0xc6, 0x7f, 0xc6, 0xe8, 0xc6, - 0x51, 0xc7, 0xbb, 0xc7, 0x24, 0xc8, 0x8d, 0xc8, 0xf7, 0xc8, 0x60, 0xc9, - 0xca, 0xc9, 0x34, 0xca, 0x9e, 0xca, 0x08, 0xcb, 0x72, 0xcb, 0xdc, 0xcb, - 0x47, 0xcc, 0xb1, 0xcc, 0x1b, 0xcd, 0x86, 0xcd, 0xf1, 0xcd, 0x5b, 0xce, - 0xc6, 0xce, 0x31, 0xcf, 0x9c, 0xcf, 0x08, 0xd0, 0x73, 0xd0, 0xde, 0xd0, - 0x4a, 0xd1, 0xb5, 0xd1, 0x21, 0xd2, 0x8d, 0xd2, 0xf8, 0xd2, 0x64, 0xd3, - 0xd0, 0xd3, 0x3d, 0xd4, 0xa9, 0xd4, 0x15, 0xd5, 0x82, 0xd5, 0xee, 0xd5, - 0x5b, 0xd6, 0xc7, 0xd6, 0x34, 0xd7, 0xa1, 0xd7, 0x0e, 0xd8, 0x7b, 0xd8, - 0xe9, 0xd8, 0x56, 0xd9, 0xc3, 0xd9, 0x31, 0xda, 0x9e, 0xda, 0x0c, 0xdb, - 0x7a, 0xdb, 0xe8, 0xdb, 0x56, 0xdc, 0xc4, 0xdc, 0x32, 0xdd, 0xa0, 0xdd, - 0x0f, 0xde, 0x7d, 0xde, 0xec, 0xde, 0x5b, 0xdf, 0xc9, 0xdf, 0x38, 0xe0, - 0xa7, 0xe0, 0x16, 0xe1, 0x86, 0xe1, 0xf5, 0xe1, 0x64, 0xe2, 0xd4, 0xe2, - 0x43, 0xe3, 0xb3, 0xe3, 0x23, 0xe4, 0x93, 0xe4, 0x03, 0xe5, 0x73, 0xe5, - 0xe3, 0xe5, 0x54, 0xe6, 0xc4, 0xe6, 0x35, 0xe7, 0xa5, 0xe7, 0x16, 0xe8, - 0x87, 0xe8, 0xf8, 0xe8, 0x69, 0xe9, 0xda, 0xe9, 0x4b, 0xea, 0xbc, 0xea, - 0x2e, 0xeb, 0x9f, 0xeb, 0x11, 0xec, 0x83, 0xec, 0xf5, 0xec, 0x66, 0xed, - 0xd9, 0xed, 0x4b, 0xee, 0xbd, 0xee, 0x2f, 0xef, 0xa2, 0xef, 0x14, 0xf0, - 0x87, 0xf0, 0xfa, 0xf0, 0x6d, 0xf1, 0xe0, 0xf1, 0x53, 0xf2, 0xc6, 0xf2, - 0x39, 0xf3, 0xad, 0xf3, 0x20, 0xf4, 0x94, 0xf4, 0x07, 0xf5, 0x7b, 0xf5, - 0xef, 0xf5, 0x63, 0xf6, 0xd7, 0xf6, 0x4c, 0xf7, 0xc0, 0xf7, 0x34, 0xf8, - 0xa9, 0xf8, 0x1e, 0xf9, 0x92, 0xf9, 0x07, 0xfa, 0x7c, 0xfa, 0xf1, 0xfa, - 0x66, 0xfb, 0xdc, 0xfb, 0x51, 0xfc, 0xc7, 0xfc, 0x3c, 0xfd, 0xb2, 0xfd, - 0x28, 0xfe, 0x9e, 0xfe, 0x14, 0xff, 0x8a, 0xff, 0xec, 0x15, 0x00, 0x00, - 0x00, 0x00, 0x80, 0xe0, 0x10, 0x10, 0x1f, 0xe5, 0xb0, 0x00, 0x91, 0xe1, - 0x4a, 0xfd, 0xff, 0xea, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xb0, 0xe1, 0x07, 0xff, 0xff, 0x0a, + 0x20, 0x00, 0x2d, 0xe9, 0x00, 0x50, 0xa0, 0xe3, 0x01, 0x30, 0xd0, 0xe4, + 0x02, 0x35, 0x83, 0xe3, 0x80, 0x00, 0x13, 0xe3, 0x15, 0x00, 0x00, 0x0a, + 0x01, 0x40, 0xd0, 0xe4, 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, + 0x24, 0xe6, 0xa0, 0xe1, 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, + 0x0c, 0xc0, 0x41, 0xe0, 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x00, 0x11, 0xe3, + 0x01, 0x50, 0xdc, 0x04, 0x01, 0x40, 0xdc, 0x14, 0x04, 0x44, 0x85, 0x11, + 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, + 0x20, 0x00, 0xbd, 0x08, 0xf0, 0xfe, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, + 0xf4, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, 0xe8, 0xff, 0xff, 0x5a, + 0xe5, 0xff, 0xff, 0xea, 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xd0, 0x04, + 0x01, 0x40, 0xd0, 0x14, 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, + 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, + 0xe2, 0xfe, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, 0xdc, 0xff, 0xff, 0x5a, + 0xd9, 0xff, 0xff, 0xea, 0xde, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, + 0x23, 0x24, 0xa0, 0xe1, 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, + 0x7f, 0x30, 0x03, 0xe2, 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, + 0x03, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0xd3, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, + 0xf3, 0xff, 0xff, 0xea, 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, + 0x01, 0x20, 0x52, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0xcb, 0xfe, 0xff, 0x0a, + 0x01, 0x30, 0x53, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, + 0x00, 0x00, 0x24, 0x03, 0x48, 0x06, 0x6a, 0x09, 0x8c, 0x0c, 0xab, 0x0f, + 0xc8, 0x12, 0xe2, 0x15, 0xf9, 0x18, 0x0b, 0x1c, 0x1a, 0x1f, 0x23, 0x22, + 0x28, 0x25, 0x26, 0x28, 0x1f, 0x2b, 0x11, 0x2e, 0xfb, 0x30, 0xdf, 0x33, + 0xba, 0x36, 0x8c, 0x39, 0x56, 0x3c, 0x17, 0x3f, 0xce, 0x41, 0x7a, 0x44, + 0x1c, 0x47, 0xb4, 0x49, 0x3f, 0x4c, 0xbf, 0x4e, 0x33, 0x51, 0x9b, 0x53, + 0xf5, 0x55, 0x42, 0x58, 0x82, 0x5a, 0xb3, 0x5c, 0xd7, 0x5e, 0xeb, 0x60, + 0xf1, 0x62, 0xe8, 0x64, 0xcf, 0x66, 0xa6, 0x68, 0x6d, 0x6a, 0x23, 0x6c, + 0xc9, 0x6d, 0x5e, 0x6f, 0xe2, 0x70, 0x54, 0x72, 0xb5, 0x73, 0x04, 0x75, + 0x41, 0x76, 0x6b, 0x77, 0x84, 0x78, 0x89, 0x79, 0x7c, 0x7a, 0x5c, 0x7b, + 0x29, 0x7c, 0xe3, 0x7c, 0x89, 0x7d, 0x1d, 0x7e, 0x9c, 0x7e, 0x09, 0x7f, + 0x61, 0x7f, 0xa6, 0x7f, 0xd8, 0x7f, 0xf5, 0x7f, 0x00, 0x00, 0x80, 0xe0, + 0x8c, 0x10, 0x4f, 0xe2, 0xb0, 0x00, 0x91, 0xe1, 0xa4, 0xfe, 0xff, 0xea, + 0x00, 0x00, 0x3b, 0x00, 0x76, 0x00, 0xb2, 0x00, 0xed, 0x00, 0x28, 0x01, + 0x64, 0x01, 0x9f, 0x01, 0xdb, 0x01, 0x17, 0x02, 0x52, 0x02, 0x8e, 0x02, + 0xca, 0x02, 0x05, 0x03, 0x41, 0x03, 0x7d, 0x03, 0xb9, 0x03, 0xf5, 0x03, + 0x31, 0x04, 0x6e, 0x04, 0xaa, 0x04, 0xe6, 0x04, 0x22, 0x05, 0x5f, 0x05, + 0x9b, 0x05, 0xd8, 0x05, 0x14, 0x06, 0x51, 0x06, 0x8d, 0x06, 0xca, 0x06, + 0x07, 0x07, 0x43, 0x07, 0x80, 0x07, 0xbd, 0x07, 0xfa, 0x07, 0x37, 0x08, + 0x74, 0x08, 0xb1, 0x08, 0xef, 0x08, 0x2c, 0x09, 0x69, 0x09, 0xa7, 0x09, + 0xe4, 0x09, 0x21, 0x0a, 0x5f, 0x0a, 0x9c, 0x0a, 0xda, 0x0a, 0x18, 0x0b, + 0x56, 0x0b, 0x93, 0x0b, 0xd1, 0x0b, 0x0f, 0x0c, 0x4d, 0x0c, 0x8b, 0x0c, + 0xc9, 0x0c, 0x07, 0x0d, 0x45, 0x0d, 0x84, 0x0d, 0xc2, 0x0d, 0x00, 0x0e, + 0x3f, 0x0e, 0x7d, 0x0e, 0xbc, 0x0e, 0xfa, 0x0e, 0x39, 0x0f, 0x78, 0x0f, + 0xb6, 0x0f, 0xf5, 0x0f, 0x34, 0x10, 0x73, 0x10, 0xb2, 0x10, 0xf1, 0x10, + 0x30, 0x11, 0x6f, 0x11, 0xae, 0x11, 0xee, 0x11, 0x2d, 0x12, 0x6c, 0x12, + 0xac, 0x12, 0xeb, 0x12, 0x2b, 0x13, 0x6b, 0x13, 0xaa, 0x13, 0xea, 0x13, + 0x2a, 0x14, 0x6a, 0x14, 0xa9, 0x14, 0xe9, 0x14, 0x29, 0x15, 0x69, 0x15, + 0xaa, 0x15, 0xea, 0x15, 0x2a, 0x16, 0x6a, 0x16, 0xab, 0x16, 0xeb, 0x16, + 0x2c, 0x17, 0x6c, 0x17, 0xad, 0x17, 0xed, 0x17, 0x2e, 0x18, 0x6f, 0x18, + 0xb0, 0x18, 0xf0, 0x18, 0x31, 0x19, 0x72, 0x19, 0xb3, 0x19, 0xf5, 0x19, + 0x36, 0x1a, 0x77, 0x1a, 0xb8, 0x1a, 0xfa, 0x1a, 0x3b, 0x1b, 0x7d, 0x1b, + 0xbe, 0x1b, 0x00, 0x1c, 0x41, 0x1c, 0x83, 0x1c, 0xc5, 0x1c, 0x07, 0x1d, + 0x48, 0x1d, 0x8a, 0x1d, 0xcc, 0x1d, 0x0e, 0x1e, 0x51, 0x1e, 0x93, 0x1e, + 0xd5, 0x1e, 0x17, 0x1f, 0x5a, 0x1f, 0x9c, 0x1f, 0xdf, 0x1f, 0x21, 0x20, + 0x64, 0x20, 0xa6, 0x20, 0xe9, 0x20, 0x2c, 0x21, 0x6f, 0x21, 0xb2, 0x21, + 0xf5, 0x21, 0x38, 0x22, 0x7b, 0x22, 0xbe, 0x22, 0x01, 0x23, 0x44, 0x23, + 0x88, 0x23, 0xcb, 0x23, 0x0e, 0x24, 0x52, 0x24, 0x96, 0x24, 0xd9, 0x24, + 0x1d, 0x25, 0x61, 0x25, 0xa4, 0x25, 0xe8, 0x25, 0x2c, 0x26, 0x70, 0x26, + 0xb4, 0x26, 0xf8, 0x26, 0x3d, 0x27, 0x81, 0x27, 0xc5, 0x27, 0x0a, 0x28, + 0x4e, 0x28, 0x92, 0x28, 0xd7, 0x28, 0x1c, 0x29, 0x60, 0x29, 0xa5, 0x29, + 0xea, 0x29, 0x2f, 0x2a, 0x74, 0x2a, 0xb9, 0x2a, 0xfe, 0x2a, 0x43, 0x2b, + 0x88, 0x2b, 0xcd, 0x2b, 0x13, 0x2c, 0x58, 0x2c, 0x9d, 0x2c, 0xe3, 0x2c, + 0x28, 0x2d, 0x6e, 0x2d, 0xb4, 0x2d, 0xf9, 0x2d, 0x3f, 0x2e, 0x85, 0x2e, + 0xcb, 0x2e, 0x11, 0x2f, 0x57, 0x2f, 0x9d, 0x2f, 0xe3, 0x2f, 0x2a, 0x30, + 0x70, 0x30, 0xb6, 0x30, 0xfd, 0x30, 0x43, 0x31, 0x8a, 0x31, 0xd0, 0x31, + 0x17, 0x32, 0x5e, 0x32, 0xa5, 0x32, 0xec, 0x32, 0x32, 0x33, 0x79, 0x33, + 0xc1, 0x33, 0x08, 0x34, 0x4f, 0x34, 0x96, 0x34, 0xdd, 0x34, 0x25, 0x35, + 0x6c, 0x35, 0xb4, 0x35, 0xfb, 0x35, 0x43, 0x36, 0x8b, 0x36, 0xd3, 0x36, + 0x1a, 0x37, 0x62, 0x37, 0xaa, 0x37, 0xf2, 0x37, 0x3a, 0x38, 0x83, 0x38, + 0xcb, 0x38, 0x13, 0x39, 0x5c, 0x39, 0xa4, 0x39, 0xed, 0x39, 0x35, 0x3a, + 0x7e, 0x3a, 0xc6, 0x3a, 0x0f, 0x3b, 0x58, 0x3b, 0xa1, 0x3b, 0xea, 0x3b, + 0x33, 0x3c, 0x7c, 0x3c, 0xc5, 0x3c, 0x0e, 0x3d, 0x58, 0x3d, 0xa1, 0x3d, + 0xea, 0x3d, 0x34, 0x3e, 0x7d, 0x3e, 0xc7, 0x3e, 0x11, 0x3f, 0x5a, 0x3f, + 0xa4, 0x3f, 0xee, 0x3f, 0x38, 0x40, 0x82, 0x40, 0xcc, 0x40, 0x16, 0x41, + 0x61, 0x41, 0xab, 0x41, 0xf5, 0x41, 0x40, 0x42, 0x8a, 0x42, 0xd5, 0x42, + 0x1f, 0x43, 0x6a, 0x43, 0xb5, 0x43, 0x00, 0x44, 0x4b, 0x44, 0x95, 0x44, + 0xe1, 0x44, 0x2c, 0x45, 0x77, 0x45, 0xc2, 0x45, 0x0d, 0x46, 0x59, 0x46, + 0xa4, 0x46, 0xf0, 0x46, 0x3b, 0x47, 0x87, 0x47, 0xd3, 0x47, 0x1e, 0x48, + 0x6a, 0x48, 0xb6, 0x48, 0x02, 0x49, 0x4e, 0x49, 0x9a, 0x49, 0xe6, 0x49, + 0x33, 0x4a, 0x7f, 0x4a, 0xcb, 0x4a, 0x18, 0x4b, 0x64, 0x4b, 0xb1, 0x4b, + 0xfe, 0x4b, 0x4a, 0x4c, 0x97, 0x4c, 0xe4, 0x4c, 0x31, 0x4d, 0x7e, 0x4d, + 0xcb, 0x4d, 0x18, 0x4e, 0x66, 0x4e, 0xb3, 0x4e, 0x00, 0x4f, 0x4e, 0x4f, + 0x9b, 0x4f, 0xe9, 0x4f, 0x36, 0x50, 0x84, 0x50, 0xd2, 0x50, 0x20, 0x51, + 0x6e, 0x51, 0xbc, 0x51, 0x0a, 0x52, 0x58, 0x52, 0xa6, 0x52, 0xf4, 0x52, + 0x43, 0x53, 0x91, 0x53, 0xe0, 0x53, 0x2e, 0x54, 0x7d, 0x54, 0xcc, 0x54, + 0x1a, 0x55, 0x69, 0x55, 0xb8, 0x55, 0x07, 0x56, 0x56, 0x56, 0xa5, 0x56, + 0xf4, 0x56, 0x44, 0x57, 0x93, 0x57, 0xe2, 0x57, 0x32, 0x58, 0x82, 0x58, + 0xd1, 0x58, 0x21, 0x59, 0x71, 0x59, 0xc1, 0x59, 0x10, 0x5a, 0x60, 0x5a, + 0xb0, 0x5a, 0x01, 0x5b, 0x51, 0x5b, 0xa1, 0x5b, 0xf1, 0x5b, 0x42, 0x5c, + 0x92, 0x5c, 0xe3, 0x5c, 0x34, 0x5d, 0x84, 0x5d, 0xd5, 0x5d, 0x26, 0x5e, + 0x77, 0x5e, 0xc8, 0x5e, 0x19, 0x5f, 0x6a, 0x5f, 0xbb, 0x5f, 0x0d, 0x60, + 0x5e, 0x60, 0xb0, 0x60, 0x01, 0x61, 0x53, 0x61, 0xa4, 0x61, 0xf6, 0x61, + 0x48, 0x62, 0x9a, 0x62, 0xec, 0x62, 0x3e, 0x63, 0x90, 0x63, 0xe2, 0x63, + 0x34, 0x64, 0x87, 0x64, 0xd9, 0x64, 0x2c, 0x65, 0x7e, 0x65, 0xd1, 0x65, + 0x24, 0x66, 0x76, 0x66, 0xc9, 0x66, 0x1c, 0x67, 0x6f, 0x67, 0xc2, 0x67, + 0x15, 0x68, 0x69, 0x68, 0xbc, 0x68, 0x0f, 0x69, 0x63, 0x69, 0xb6, 0x69, + 0x0a, 0x6a, 0x5e, 0x6a, 0xb1, 0x6a, 0x05, 0x6b, 0x59, 0x6b, 0xad, 0x6b, + 0x01, 0x6c, 0x55, 0x6c, 0xaa, 0x6c, 0xfe, 0x6c, 0x52, 0x6d, 0xa7, 0x6d, + 0xfb, 0x6d, 0x50, 0x6e, 0xa4, 0x6e, 0xf9, 0x6e, 0x4e, 0x6f, 0xa3, 0x6f, + 0xf8, 0x6f, 0x4d, 0x70, 0xa2, 0x70, 0xf7, 0x70, 0x4d, 0x71, 0xa2, 0x71, + 0xf7, 0x71, 0x4d, 0x72, 0xa2, 0x72, 0xf8, 0x72, 0x4e, 0x73, 0xa4, 0x73, + 0xfa, 0x73, 0x50, 0x74, 0xa6, 0x74, 0xfc, 0x74, 0x52, 0x75, 0xa8, 0x75, + 0xff, 0x75, 0x55, 0x76, 0xac, 0x76, 0x02, 0x77, 0x59, 0x77, 0xb0, 0x77, + 0x07, 0x78, 0x5e, 0x78, 0xb4, 0x78, 0x0c, 0x79, 0x63, 0x79, 0xba, 0x79, + 0x11, 0x7a, 0x69, 0x7a, 0xc0, 0x7a, 0x18, 0x7b, 0x6f, 0x7b, 0xc7, 0x7b, + 0x1f, 0x7c, 0x77, 0x7c, 0xcf, 0x7c, 0x27, 0x7d, 0x7f, 0x7d, 0xd7, 0x7d, + 0x2f, 0x7e, 0x88, 0x7e, 0xe0, 0x7e, 0x38, 0x7f, 0x91, 0x7f, 0xea, 0x7f, + 0x42, 0x80, 0x9b, 0x80, 0xf4, 0x80, 0x4d, 0x81, 0xa6, 0x81, 0xff, 0x81, + 0x59, 0x82, 0xb2, 0x82, 0x0b, 0x83, 0x65, 0x83, 0xbe, 0x83, 0x18, 0x84, + 0x72, 0x84, 0xcb, 0x84, 0x25, 0x85, 0x7f, 0x85, 0xd9, 0x85, 0x33, 0x86, + 0x8e, 0x86, 0xe8, 0x86, 0x42, 0x87, 0x9d, 0x87, 0xf7, 0x87, 0x52, 0x88, + 0xac, 0x88, 0x07, 0x89, 0x62, 0x89, 0xbd, 0x89, 0x18, 0x8a, 0x73, 0x8a, + 0xce, 0x8a, 0x2a, 0x8b, 0x85, 0x8b, 0xe0, 0x8b, 0x3c, 0x8c, 0x97, 0x8c, + 0xf3, 0x8c, 0x4f, 0x8d, 0xab, 0x8d, 0x07, 0x8e, 0x63, 0x8e, 0xbf, 0x8e, + 0x1b, 0x8f, 0x77, 0x8f, 0xd4, 0x8f, 0x30, 0x90, 0x8c, 0x90, 0xe9, 0x90, + 0x46, 0x91, 0xa2, 0x91, 0xff, 0x91, 0x5c, 0x92, 0xb9, 0x92, 0x16, 0x93, + 0x73, 0x93, 0xd1, 0x93, 0x2e, 0x94, 0x8c, 0x94, 0xe9, 0x94, 0x47, 0x95, + 0xa4, 0x95, 0x02, 0x96, 0x60, 0x96, 0xbe, 0x96, 0x1c, 0x97, 0x7a, 0x97, + 0xd8, 0x97, 0x36, 0x98, 0x95, 0x98, 0xf3, 0x98, 0x52, 0x99, 0xb0, 0x99, + 0x0f, 0x9a, 0x6e, 0x9a, 0xcd, 0x9a, 0x2c, 0x9b, 0x8b, 0x9b, 0xea, 0x9b, + 0x49, 0x9c, 0xa8, 0x9c, 0x08, 0x9d, 0x67, 0x9d, 0xc7, 0x9d, 0x26, 0x9e, + 0x86, 0x9e, 0xe6, 0x9e, 0x46, 0x9f, 0xa6, 0x9f, 0x06, 0xa0, 0x66, 0xa0, + 0xc6, 0xa0, 0x27, 0xa1, 0x87, 0xa1, 0xe8, 0xa1, 0x48, 0xa2, 0xa9, 0xa2, + 0x0a, 0xa3, 0x6b, 0xa3, 0xcc, 0xa3, 0x2d, 0xa4, 0x8e, 0xa4, 0xef, 0xa4, + 0x50, 0xa5, 0xb2, 0xa5, 0x13, 0xa6, 0x75, 0xa6, 0xd6, 0xa6, 0x38, 0xa7, + 0x9a, 0xa7, 0xfc, 0xa7, 0x5e, 0xa8, 0xc0, 0xa8, 0x22, 0xa9, 0x84, 0xa9, + 0xe7, 0xa9, 0x49, 0xaa, 0xac, 0xaa, 0x0e, 0xab, 0x71, 0xab, 0xd4, 0xab, + 0x37, 0xac, 0x9a, 0xac, 0xfd, 0xac, 0x60, 0xad, 0xc3, 0xad, 0x27, 0xae, + 0x8a, 0xae, 0xed, 0xae, 0x51, 0xaf, 0xb5, 0xaf, 0x19, 0xb0, 0x7c, 0xb0, + 0xe0, 0xb0, 0x45, 0xb1, 0xa9, 0xb1, 0x0d, 0xb2, 0x71, 0xb2, 0xd6, 0xb2, + 0x3a, 0xb3, 0x9f, 0xb3, 0x03, 0xb4, 0x68, 0xb4, 0xcd, 0xb4, 0x32, 0xb5, + 0x97, 0xb5, 0xfc, 0xb5, 0x62, 0xb6, 0xc7, 0xb6, 0x2c, 0xb7, 0x92, 0xb7, + 0xf7, 0xb7, 0x5d, 0xb8, 0xc3, 0xb8, 0x29, 0xb9, 0x8f, 0xb9, 0xf5, 0xb9, + 0x5b, 0xba, 0xc1, 0xba, 0x28, 0xbb, 0x8e, 0xbb, 0xf5, 0xbb, 0x5b, 0xbc, + 0xc2, 0xbc, 0x29, 0xbd, 0x90, 0xbd, 0xf7, 0xbd, 0x5e, 0xbe, 0xc5, 0xbe, + 0x2c, 0xbf, 0x94, 0xbf, 0xfb, 0xbf, 0x63, 0xc0, 0xca, 0xc0, 0x32, 0xc1, + 0x9a, 0xc1, 0x02, 0xc2, 0x6a, 0xc2, 0xd2, 0xc2, 0x3a, 0xc3, 0xa2, 0xc3, + 0x0b, 0xc4, 0x73, 0xc4, 0xdc, 0xc4, 0x44, 0xc5, 0xad, 0xc5, 0x16, 0xc6, + 0x7f, 0xc6, 0xe8, 0xc6, 0x51, 0xc7, 0xbb, 0xc7, 0x24, 0xc8, 0x8d, 0xc8, + 0xf7, 0xc8, 0x60, 0xc9, 0xca, 0xc9, 0x34, 0xca, 0x9e, 0xca, 0x08, 0xcb, + 0x72, 0xcb, 0xdc, 0xcb, 0x47, 0xcc, 0xb1, 0xcc, 0x1b, 0xcd, 0x86, 0xcd, + 0xf1, 0xcd, 0x5b, 0xce, 0xc6, 0xce, 0x31, 0xcf, 0x9c, 0xcf, 0x08, 0xd0, + 0x73, 0xd0, 0xde, 0xd0, 0x4a, 0xd1, 0xb5, 0xd1, 0x21, 0xd2, 0x8d, 0xd2, + 0xf8, 0xd2, 0x64, 0xd3, 0xd0, 0xd3, 0x3d, 0xd4, 0xa9, 0xd4, 0x15, 0xd5, + 0x82, 0xd5, 0xee, 0xd5, 0x5b, 0xd6, 0xc7, 0xd6, 0x34, 0xd7, 0xa1, 0xd7, + 0x0e, 0xd8, 0x7b, 0xd8, 0xe9, 0xd8, 0x56, 0xd9, 0xc3, 0xd9, 0x31, 0xda, + 0x9e, 0xda, 0x0c, 0xdb, 0x7a, 0xdb, 0xe8, 0xdb, 0x56, 0xdc, 0xc4, 0xdc, + 0x32, 0xdd, 0xa0, 0xdd, 0x0f, 0xde, 0x7d, 0xde, 0xec, 0xde, 0x5b, 0xdf, + 0xc9, 0xdf, 0x38, 0xe0, 0xa7, 0xe0, 0x16, 0xe1, 0x86, 0xe1, 0xf5, 0xe1, + 0x64, 0xe2, 0xd4, 0xe2, 0x43, 0xe3, 0xb3, 0xe3, 0x23, 0xe4, 0x93, 0xe4, + 0x03, 0xe5, 0x73, 0xe5, 0xe3, 0xe5, 0x54, 0xe6, 0xc4, 0xe6, 0x35, 0xe7, + 0xa5, 0xe7, 0x16, 0xe8, 0x87, 0xe8, 0xf8, 0xe8, 0x69, 0xe9, 0xda, 0xe9, + 0x4b, 0xea, 0xbc, 0xea, 0x2e, 0xeb, 0x9f, 0xeb, 0x11, 0xec, 0x83, 0xec, + 0xf5, 0xec, 0x66, 0xed, 0xd9, 0xed, 0x4b, 0xee, 0xbd, 0xee, 0x2f, 0xef, + 0xa2, 0xef, 0x14, 0xf0, 0x87, 0xf0, 0xfa, 0xf0, 0x6d, 0xf1, 0xe0, 0xf1, + 0x53, 0xf2, 0xc6, 0xf2, 0x39, 0xf3, 0xad, 0xf3, 0x20, 0xf4, 0x94, 0xf4, + 0x07, 0xf5, 0x7b, 0xf5, 0xef, 0xf5, 0x63, 0xf6, 0xd7, 0xf6, 0x4c, 0xf7, + 0xc0, 0xf7, 0x34, 0xf8, 0xa9, 0xf8, 0x1e, 0xf9, 0x92, 0xf9, 0x07, 0xfa, + 0x7c, 0xfa, 0xf1, 0xfa, 0x66, 0xfb, 0xdc, 0xfb, 0x51, 0xfc, 0xc7, 0xfc, + 0x3c, 0xfd, 0xb2, 0xfd, 0x28, 0xfe, 0x9e, 0xfe, 0x14, 0xff, 0x8a, 0xff, + 0x98, 0x16, 0x00, 0x00, 0x00, 0x00, 0x80, 0xe0, 0x10, 0x10, 0x1f, 0xe5, + 0xb0, 0x00, 0x91, 0xe1, 0x1f, 0xfd, 0xff, 0xea, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, @@ -634,88 +648,74 @@ unsigned char bios_arm7_bin[] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, - 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, - 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, - 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, - 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, - 0x0f, 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, - 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, - 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x1a, - 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1e, - 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x20, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, - 0x22, 0x23, 0x23, 0x24, 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, - 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, 0x2b, 0x2c, 0x2c, 0x2d, - 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x33, - 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3a, 0x3a, 0x3b, - 0x3c, 0x3c, 0x3d, 0x3e, 0x3f, 0x3f, 0x40, 0x41, 0x42, 0x42, 0x43, 0x44, - 0x45, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, - 0x4f, 0x50, 0x51, 0x52, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x5b, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x67, - 0x68, 0x69, 0x6a, 0x6b, 0x6d, 0x6e, 0x6f, 0x71, 0x72, 0x73, 0x75, 0x76, - 0x77, 0x79, 0x7a, 0x7b, 0x7d, 0x7e, 0x7f, 0x20, 0x21, 0x21, 0x21, 0x22, - 0x22, 0x23, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x26, 0x27, - 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, 0x2b, 0x2c, 0x2c, 0x2d, - 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x30, 0x31, 0x31, 0x32, 0x33, 0x33, - 0x34, 0x34, 0x35, 0x36, 0x36, 0x37, 0x37, 0x38, 0x39, 0x39, 0x3a, 0x3b, - 0x3b, 0x3c, 0x3d, 0x3e, 0x3e, 0x3f, 0x40, 0x40, 0x41, 0x42, 0x43, 0x43, - 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4d, - 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x62, 0x63, 0x64, 0x65, 0x66, - 0x67, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6f, 0x70, 0x71, 0x73, 0x74, 0x75, - 0x77, 0x78, 0x79, 0x7b, 0x7c, 0x7e, 0x7e, 0x40, 0x41, 0x42, 0x43, 0x43, - 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4c, 0x4d, - 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x64, 0x65, 0x66, - 0x67, 0x68, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x70, 0x71, 0x72, 0x74, 0x75, - 0x76, 0x78, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x40, 0x41, 0x42, 0x42, 0x43, - 0x44, 0x45, 0x46, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4b, 0x4c, 0x4d, - 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x65, 0x66, - 0x67, 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, 0x6f, 0x71, 0x72, 0x73, 0x75, - 0x76, 0x77, 0x79, 0x7a, 0x7c, 0x7d, 0x7e, 0x7f, 0x00, 0x00, 0x00, 0x00, - 0x2e, 0x00, 0xa0, 0xe3, 0x3c, 0x10, 0xa0, 0xe3, 0xff, 0x20, 0xa0, 0xe3, - 0x0a, 0x0c, 0x80, 0xe3, 0x0b, 0x1b, 0x81, 0xe3, 0x05, 0x2c, 0x82, 0xe3, - 0x8d, 0xfc, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, 0x01, 0x03, 0xc1, 0xe5, - 0x8a, 0xfc, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3, - 0x0f, 0xe0, 0xa0, 0xe1, 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, - 0x04, 0xf0, 0x5e, 0xe2, 0x32, 0x1e, 0x4f, 0xe2, 0x00, 0x00, 0xd1, 0xe7, - 0x81, 0xfc, 0xff, 0xea, 0xdc, 0xff, 0x80, 0x03, 0x01, 0x03, 0xa0, 0xe3, - 0x80, 0x10, 0xa0, 0xe3, 0x00, 0x20, 0xa0, 0xe3, 0x04, 0x20, 0x20, 0xe5, - 0x01, 0x10, 0x51, 0xe2, 0xfc, 0xff, 0xff, 0x1a, 0x24, 0x10, 0x1f, 0xe5, - 0xd3, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, 0x01, 0xd0, 0xa0, 0xe1, - 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, 0xd2, 0x30, 0xa0, 0xe3, - 0x03, 0xf0, 0x2f, 0xe1, 0x2c, 0xd0, 0x41, 0xe2, 0x02, 0xe0, 0xa0, 0xe1, - 0x02, 0xf0, 0x6f, 0xe1, 0x5f, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, - 0xdc, 0xd0, 0x41, 0xe2, 0xff, 0x1f, 0x90, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, + 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, + 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, + 0x19, 0x19, 0x19, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, + 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x20, 0x20, 0x20, + 0x21, 0x21, 0x22, 0x22, 0x22, 0x23, 0x23, 0x24, 0x24, 0x24, 0x25, 0x25, + 0x26, 0x26, 0x27, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, + 0x2b, 0x2c, 0x2c, 0x2d, 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x31, 0x31, + 0x32, 0x32, 0x33, 0x33, 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x38, 0x38, + 0x39, 0x3a, 0x3a, 0x3b, 0x3c, 0x3c, 0x3d, 0x3e, 0x3f, 0x3f, 0x40, 0x41, + 0x42, 0x42, 0x43, 0x44, 0x45, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4a, + 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6d, 0x6e, 0x6f, 0x71, + 0x72, 0x73, 0x75, 0x76, 0x77, 0x79, 0x7a, 0x7b, 0x7d, 0x7e, 0x7f, 0x20, + 0x21, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, + 0x26, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, + 0x2b, 0x2c, 0x2c, 0x2d, 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x30, 0x31, + 0x31, 0x32, 0x33, 0x33, 0x34, 0x34, 0x35, 0x36, 0x36, 0x37, 0x37, 0x38, + 0x39, 0x39, 0x3a, 0x3b, 0x3b, 0x3c, 0x3d, 0x3e, 0x3e, 0x3f, 0x40, 0x40, + 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, + 0x4b, 0x4c, 0x4d, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6f, 0x70, + 0x71, 0x73, 0x74, 0x75, 0x77, 0x78, 0x79, 0x7b, 0x7c, 0x7e, 0x7e, 0x40, + 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, + 0x4b, 0x4c, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, + 0x62, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x70, + 0x71, 0x72, 0x74, 0x75, 0x76, 0x78, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x40, + 0x41, 0x42, 0x42, 0x43, 0x44, 0x45, 0x46, 0x46, 0x47, 0x48, 0x49, 0x4a, + 0x4b, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, + 0x62, 0x63, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, 0x6f, + 0x71, 0x72, 0x73, 0x75, 0x76, 0x77, 0x79, 0x7a, 0x7c, 0x7d, 0x7e, 0x7f, + 0x00, 0x00, 0x00, 0x00, 0x2e, 0x00, 0xa0, 0xe3, 0x3c, 0x10, 0xa0, 0xe3, + 0xff, 0x20, 0xa0, 0xe3, 0x0a, 0x0c, 0x80, 0xe3, 0x0b, 0x1b, 0x81, 0xe3, + 0x05, 0x2c, 0x82, 0xe3, 0x62, 0xfc, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, + 0x01, 0x03, 0xc1, 0xe5, 0x5f, 0xfc, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, + 0x01, 0x03, 0xa0, 0xe3, 0x0f, 0xe0, 0xa0, 0xe1, 0x04, 0xf0, 0x10, 0xe5, + 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, 0x32, 0x1e, 0x4f, 0xe2, + 0x00, 0x00, 0xd1, 0xe7, 0x56, 0xfc, 0xff, 0xea, 0xdc, 0xff, 0x80, 0x03, + 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x10, 0xa0, 0xe3, 0x00, 0x20, 0xa0, 0xe3, + 0x04, 0x20, 0x20, 0xe5, 0x01, 0x10, 0x51, 0xe2, 0xfc, 0xff, 0xff, 0x1a, + 0x24, 0x10, 0x1f, 0xe5, 0xd3, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, + 0x01, 0xd0, 0xa0, 0xe1, 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, + 0xd2, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, 0x2c, 0xd0, 0x41, 0xe2, + 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, 0x5f, 0x30, 0xa0, 0xe3, + 0x03, 0xf0, 0x2f, 0xe1, 0xdc, 0xd0, 0x41, 0xe2, 0xff, 0x1f, 0x90, 0xe8, + 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1401,7 +1401,7 @@ unsigned int bios_arm7_bin_len = 16384; unsigned char bios_arm9_bin[] = { 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, 0x3c, 0x00, 0x00, 0xea, 0x3b, 0x00, 0x00, 0xea, 0x3a, 0x00, 0x00, 0xea, - 0x82, 0x01, 0x00, 0xea, 0x38, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, + 0xad, 0x01, 0x00, 0xea, 0x38, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1421,22 +1421,22 @@ unsigned char bios_arm9_bin[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, - 0x10, 0x50, 0x2d, 0xe9, 0x00, 0x40, 0x4f, 0xe1, 0x04, 0x40, 0x2d, 0xe5, + 0x10, 0x50, 0x2d, 0xe9, 0x00, 0x40, 0x4f, 0xe1, 0x10, 0x00, 0x2d, 0xe9, 0x80, 0x40, 0x04, 0xe2, 0x1f, 0x40, 0x84, 0xe3, 0x02, 0xc0, 0x5e, 0xe5, - 0x04, 0xf0, 0x29, 0xe1, 0x04, 0xe0, 0x2d, 0xe5, 0x20, 0x00, 0x5c, 0xe3, + 0x04, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x20, 0x00, 0x5c, 0xe3, 0x01, 0xc0, 0xa0, 0xa3, 0x0c, 0xf1, 0x9f, 0xe7, 0x00, 0x00, 0xa0, 0xe1, - 0x4c, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0xf8, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0x20, 0x02, 0xff, 0xff, 0x18, 0x02, 0xff, 0xff, 0xd4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0x4c, 0x02, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xa8, 0x02, 0xff, 0xff, 0x28, 0x03, 0xff, 0xff, 0x68, 0x03, 0xff, 0xff, 0xb4, 0x03, 0xff, 0xff, 0x4c, 0x04, 0xff, 0xff, 0x54, 0x04, 0xff, 0xff, 0xe8, 0x04, 0xff, 0xff, - 0xe8, 0x04, 0xff, 0xff, 0x64, 0x05, 0xff, 0xff, 0x68, 0x05, 0xff, 0xff, - 0x68, 0x05, 0xff, 0xff, 0xc0, 0x05, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0xec, 0x05, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0x64, 0x05, 0xff, 0xff, 0x10, 0x06, 0xff, 0xff, 0x14, 0x06, 0xff, 0xff, + 0x14, 0x06, 0xff, 0xff, 0x6c, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0x98, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0x1c, 0x06, 0xff, 0xff, 0x04, 0xe0, 0x9d, 0xe4, 0xd3, 0x40, 0xa0, 0xe3, - 0x04, 0xf0, 0x29, 0xe1, 0x04, 0x40, 0x9d, 0xe4, 0x04, 0xf0, 0x69, 0xe1, + 0xc8, 0x06, 0xff, 0xff, 0x00, 0x40, 0xbd, 0xe8, 0xd3, 0x40, 0xa0, 0xe3, + 0x04, 0xf0, 0x29, 0xe1, 0x10, 0x00, 0xbd, 0xe8, 0x04, 0xf0, 0x69, 0xe1, 0x10, 0x50, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xe3, 0x90, 0x0f, 0x07, 0xee, 0xf4, 0xff, 0xff, 0xea, 0x01, 0x00, 0x50, 0xe2, 0xfd, 0xff, 0xff, 0xca, 0xf1, 0xff, 0xff, 0xea, @@ -1478,7 +1478,7 @@ unsigned char bios_arm9_bin[] = { 0x87, 0xff, 0xff, 0xea, 0x00, 0x00, 0x01, 0xcc, 0x01, 0xd8, 0x00, 0x14, 0x01, 0xf0, 0x00, 0x3c, 0x00, 0x28, 0x01, 0xe4, 0x01, 0xa0, 0x00, 0x6c, 0x00, 0x78, 0x01, 0xb4, 0x00, 0x50, 0x01, 0x9c, 0x01, 0x88, 0x00, 0x44, - 0x04, 0x50, 0x2d, 0xe5, 0x1e, 0x40, 0xa0, 0xe3, 0x30, 0xe0, 0x4f, 0xe2, + 0x20, 0x00, 0x2d, 0xe9, 0x1e, 0x40, 0xa0, 0xe3, 0x30, 0xe0, 0x4f, 0xe2, 0xa2, 0x20, 0xb0, 0xe1, 0x1e, 0x00, 0x00, 0x0a, 0xb2, 0x30, 0xd1, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x83, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, @@ -1490,7 +1490,7 @@ unsigned char bios_arm9_bin[] = { 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x55, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x01, 0x20, 0x52, 0xe2, 0xe0, 0xff, 0xff, 0x1a, - 0x04, 0x50, 0x9d, 0xe4, 0x59, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, + 0x20, 0x00, 0xbd, 0xe8, 0x59, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, 0x57, 0xff, 0xff, 0xea, 0xe0, 0x03, 0x2d, 0xe9, 0xb0, 0x30, 0xd2, 0xe1, 0x02, 0xc0, 0xd2, 0xe5, 0x03, 0xe0, 0xd2, 0xe5, 0x04, 0x40, 0x92, 0xe5, 0xa4, 0x2f, 0xa0, 0xe1, 0x02, 0x41, 0xc4, 0xe3, 0x01, 0x50, 0xa0, 0xe3, @@ -1514,47 +1514,47 @@ unsigned char bios_arm9_bin[] = { 0xed, 0xff, 0xff, 0x5a, 0xea, 0xff, 0xff, 0xea, 0x01, 0x40, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, 0x01, 0x40, 0xc1, 0xe4, 0x16, 0xff, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, 0xe6, 0xff, 0xff, 0x5a, 0xe3, 0xff, 0xff, 0xea, - 0x12, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xa0, 0xe1, - 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, 0x7f, 0x30, 0x03, 0xe2, - 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, 0x03, 0x30, 0x83, 0xe2, - 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, 0x07, 0xff, 0xff, 0x0a, - 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, 0xf3, 0xff, 0xff, 0xea, - 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, - 0x01, 0xc0, 0xc1, 0xe4, 0xff, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, - 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0x01, 0xc0, 0xd0, 0xe4, 0x23, 0x24, 0xa0, 0xe1, 0x01, 0xc0, 0xc1, 0xe4, - 0x01, 0x20, 0x42, 0xe2, 0x01, 0x30, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, - 0x03, 0xc0, 0x8c, 0xe0, 0x01, 0xc0, 0xc1, 0xe4, 0xfa, 0xff, 0xff, 0x1a, - 0xf1, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0xb2, 0xc0, 0xd0, 0xe0, - 0x23, 0x24, 0xa0, 0xe1, 0xb2, 0xc0, 0xc1, 0xe0, 0x01, 0x20, 0xc2, 0xe3, - 0x02, 0x20, 0x42, 0xe2, 0xb2, 0x30, 0xd0, 0xe0, 0x02, 0x20, 0x52, 0xe2, - 0x03, 0xc0, 0x8c, 0xe0, 0xb2, 0xc0, 0xc1, 0xe0, 0xfa, 0xff, 0xff, 0x1a, - 0xe5, 0xfe, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, 0x00, 0x03, 0xc1, 0xe5, - 0xe2, 0xfe, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, 0x11, 0x0f, 0x19, 0xee, - 0xff, 0x00, 0xc0, 0xe3, 0x01, 0x09, 0x80, 0xe2, 0x0f, 0xe0, 0xa0, 0xe1, - 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, - 0xdc, 0xff, 0x80, 0x03, 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x10, 0xa0, 0xe3, - 0x00, 0x20, 0xa0, 0xe3, 0x04, 0x20, 0x20, 0xe5, 0x01, 0x10, 0x51, 0xe2, - 0xfc, 0xff, 0xff, 0x1a, 0x24, 0x10, 0x1f, 0xe5, 0xd3, 0x30, 0xa0, 0xe3, - 0x03, 0xf0, 0x2f, 0xe1, 0x01, 0xd0, 0xa0, 0xe1, 0x02, 0xe0, 0xa0, 0xe1, - 0x02, 0xf0, 0x6f, 0xe1, 0xd2, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, - 0x2c, 0xd0, 0x41, 0xe2, 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, - 0x5f, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, 0xdc, 0xd0, 0x41, 0xe2, - 0xff, 0x1f, 0x90, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xb0, 0xe1, 0x10, 0xff, 0xff, 0x0a, + 0x20, 0x00, 0x2d, 0xe9, 0x00, 0x50, 0xa0, 0xe3, 0x01, 0x30, 0xd0, 0xe4, + 0x02, 0x35, 0x83, 0xe3, 0x80, 0x00, 0x13, 0xe3, 0x15, 0x00, 0x00, 0x0a, + 0x01, 0x40, 0xd0, 0xe4, 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, + 0x24, 0xe6, 0xa0, 0xe1, 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, + 0x0c, 0xc0, 0x41, 0xe0, 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x00, 0x11, 0xe3, + 0x01, 0x50, 0xdc, 0x04, 0x01, 0x40, 0xdc, 0x14, 0x04, 0x44, 0x85, 0x11, + 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, + 0x20, 0x00, 0xbd, 0x08, 0xf9, 0xfe, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, + 0xf4, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, 0xe8, 0xff, 0xff, 0x5a, + 0xe5, 0xff, 0xff, 0xea, 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xd0, 0x04, + 0x01, 0x40, 0xd0, 0x14, 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, + 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, + 0xeb, 0xfe, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, 0xdc, 0xff, 0xff, 0x5a, + 0xd9, 0xff, 0xff, 0xea, 0xe7, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, + 0x23, 0x24, 0xa0, 0xe1, 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, + 0x7f, 0x30, 0x03, 0xe2, 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, + 0x03, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0xdc, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, + 0xf3, 0xff, 0xff, 0xea, 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, + 0x01, 0x20, 0x52, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0xd4, 0xfe, 0xff, 0x0a, + 0x01, 0x30, 0x53, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, + 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0xd0, 0xe4, 0x23, 0x24, 0xa0, 0xe1, + 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x42, 0xe2, 0x01, 0x30, 0xd0, 0xe4, + 0x01, 0x20, 0x52, 0xe2, 0x03, 0xc0, 0x8c, 0xe0, 0x01, 0xc0, 0xc1, 0xe4, + 0xfa, 0xff, 0xff, 0x1a, 0xc6, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, + 0xb2, 0xc0, 0xd0, 0xe0, 0x23, 0x24, 0xa0, 0xe1, 0xb2, 0xc0, 0xc1, 0xe0, + 0x01, 0x20, 0xc2, 0xe3, 0x02, 0x20, 0x42, 0xe2, 0xb2, 0x30, 0xd0, 0xe0, + 0x02, 0x20, 0x52, 0xe2, 0x03, 0xc0, 0x8c, 0xe0, 0xb2, 0xc0, 0xc1, 0xe0, + 0xfa, 0xff, 0xff, 0x1a, 0xba, 0xfe, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, + 0x00, 0x03, 0xc1, 0xe5, 0xb7, 0xfe, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, + 0x11, 0x0f, 0x19, 0xee, 0xff, 0x00, 0xc0, 0xe3, 0x01, 0x09, 0x80, 0xe2, + 0x0f, 0xe0, 0xa0, 0xe1, 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, + 0x04, 0xf0, 0x5e, 0xe2, 0xdc, 0xff, 0x80, 0x03, 0x01, 0x03, 0xa0, 0xe3, + 0x80, 0x10, 0xa0, 0xe3, 0x00, 0x20, 0xa0, 0xe3, 0x04, 0x20, 0x20, 0xe5, + 0x01, 0x10, 0x51, 0xe2, 0xfc, 0xff, 0xff, 0x1a, 0x24, 0x10, 0x1f, 0xe5, + 0xd3, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, 0x01, 0xd0, 0xa0, 0xe1, + 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, 0xd2, 0x30, 0xa0, 0xe3, + 0x03, 0xf0, 0x2f, 0xe1, 0x2c, 0xd0, 0x41, 0xe2, 0x02, 0xe0, 0xa0, 0xe1, + 0x02, 0xf0, 0x6f, 0xe1, 0x5f, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, + 0xdc, 0xd0, 0x41, 0xe2, 0xff, 0x1f, 0x90, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/src/GBACart.cpp b/src/GBACart.cpp index e5b6e4f..68507bc 100644 --- a/src/GBACart.cpp +++ b/src/GBACart.cpp @@ -1,5 +1,5 @@ /*
- Copyright 2016-2021 Arisotura, Raphaël Zumer
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -18,6 +18,7 @@ #include <stdio.h>
#include <string.h>
+#include "NDS.h"
#include "GBACart.h"
#include "CRC32.h"
#include "Platform.h"
@@ -42,7 +43,6 @@ const char SOLAR_SENSOR_GAMECODES[10][5] = bool CartInserted;
u8* CartROM;
u32 CartROMSize;
-u32 CartCRC;
u32 CartID;
CartCommon* Cart;
@@ -58,16 +58,20 @@ CartCommon::~CartCommon() {
}
+void CartCommon::Reset()
+{
+}
+
void CartCommon::DoSavestate(Savestate* file)
{
file->Section("GBCS");
}
-void CartCommon::LoadSave(const char* path, u32 type)
+void CartCommon::SetupSave(u32 type)
{
}
-void CartCommon::RelocateSave(const char* path, bool write)
+void CartCommon::LoadSave(const u8* savedata, u32 savelen)
{
}
@@ -100,10 +104,7 @@ CartGame::CartGame(u8* rom, u32 len) : CartCommon() ROM = rom;
ROMLength = len;
- memset(&GPIO, 0, sizeof(GPIO));
-
SRAM = nullptr;
- SRAMFile = nullptr;
SRAMLength = 0;
SRAMType = S_NULL;
SRAMFlashState = {};
@@ -111,10 +112,23 @@ CartGame::CartGame(u8* rom, u32 len) : CartCommon() CartGame::~CartGame()
{
- if (SRAMFile) fclose(SRAMFile);
if (SRAM) delete[] SRAM;
}
+u32 CartGame::Checksum()
+{
+ u32 crc = CRC32(ROM, 0xC0, 0);
+
+ // TODO: hash more contents?
+
+ return crc;
+}
+
+void CartGame::Reset()
+{
+ memset(&GPIO, 0, sizeof(GPIO));
+}
+
void CartGame::DoSavestate(Savestate* file)
{
CartCommon::DoSavestate(file);
@@ -123,8 +137,6 @@ void CartGame::DoSavestate(Savestate* file) file->Var16(&GPIO.data);
file->Var16(&GPIO.direction);
- // logic mostly copied from NDSCart_SRAM
-
u32 oldlen = SRAMLength;
file->Var32(&SRAMLength);
@@ -133,6 +145,7 @@ void CartGame::DoSavestate(Savestate* file) {
// reallocate save memory
if (oldlen) delete[] SRAM;
+ SRAM = nullptr;
if (SRAMLength) SRAM = new u8[SRAMLength];
}
if (SRAMLength)
@@ -144,9 +157,7 @@ void CartGame::DoSavestate(Savestate* file) {
// no save data, clear the current state
SRAMType = SaveType::S_NULL;
- if (SRAMFile) fclose(SRAMFile);
SRAM = nullptr;
- SRAMFile = nullptr;
return;
}
@@ -158,27 +169,24 @@ void CartGame::DoSavestate(Savestate* file) file->Var8(&SRAMFlashState.state);
file->Var8((u8*)&SRAMType);
+
+ if ((!file->Saving) && SRAM)
+ Platform::WriteGBASave(SRAM, SRAMLength, 0, SRAMLength);
}
-void CartGame::LoadSave(const char* path, u32 type)
+void CartGame::SetupSave(u32 type)
{
if (SRAM) delete[] SRAM;
+ SRAM = nullptr;
- strncpy(SRAMPath, path, 1023);
- SRAMPath[1023] = '\0';
- SRAMLength = 0;
+ // TODO: have type be determined from some list, like in NDSCart
+ // and not this gross hack!!
+ SRAMLength = type;
- FILE* f = Platform::OpenFile(SRAMPath, "r+b");
- if (f)
+ if (SRAMLength)
{
- fseek(f, 0, SEEK_END);
- SRAMLength = (u32)ftell(f);
SRAM = new u8[SRAMLength];
-
- fseek(f, 0, SEEK_SET);
- fread(SRAM, SRAMLength, 1, f);
-
- SRAMFile = f;
+ memset(SRAM, 0xFF, SRAMLength);
}
switch (SRAMLength)
@@ -219,26 +227,13 @@ void CartGame::LoadSave(const char* path, u32 type) }
}
-void CartGame::RelocateSave(const char* path, bool write)
+void CartGame::LoadSave(const u8* savedata, u32 savelen)
{
- if (!write)
- {
- LoadSave(path, 0); // lazy
- return;
- }
-
- strncpy(SRAMPath, path, 1023);
- SRAMPath[1023] = '\0';
-
- FILE *f = Platform::OpenFile(path, "r+b");
- if (!f)
- {
- printf("GBACart_SRAM::RelocateSave: failed to create new file. fuck\n");
- return;
- }
+ if (!SRAM) return;
- SRAMFile = f;
- fwrite(SRAM, SRAMLength, 1, SRAMFile);
+ u32 len = std::min(savelen, SRAMLength);
+ memcpy(SRAM, savedata, len);
+ Platform::WriteGBASave(savedata, len, 0, len);
}
u16 CartGame::ROMRead(u32 addr)
@@ -469,11 +464,7 @@ void CartGame::SRAMWrite_FLASH(u32 addr, u8 val) u32 start_addr = addr + 0x10000 * SRAMFlashState.bank;
memset((u8*)&SRAM[start_addr], 0xFF, 0x1000);
- if (SRAMFile)
- {
- fseek(SRAMFile, start_addr, SEEK_SET);
- fwrite((u8*)&SRAM[start_addr], 1, 0x1000, SRAMFile);
- }
+ Platform::WriteGBASave(SRAM, SRAMLength, start_addr, 0x1000);
}
SRAMFlashState.state = 0;
SRAMFlashState.cmd = 0;
@@ -531,11 +522,8 @@ void CartGame::SRAMWrite_SRAM(u32 addr, u8 val) {
*(u8*)&SRAM[addr] = val;
- if (SRAMFile)
- {
- fseek(SRAMFile, addr, SEEK_SET);
- fwrite((u8*)&SRAM[addr], 1, 1, SRAMFile);
- }
+ // TODO: optimize this!!
+ Platform::WriteGBASave(SRAM, SRAMLength, addr, 1);
}
}
@@ -544,16 +532,20 @@ const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 1 CartGameSolarSensor::CartGameSolarSensor(u8* rom, u32 len) : CartGame(rom, len)
{
- LightEdge = false;
- LightCounter = 0;
- LightSample = 0xFF;
- LightLevel = 0;
}
CartGameSolarSensor::~CartGameSolarSensor()
{
}
+void CartGameSolarSensor::Reset()
+{
+ LightEdge = false;
+ LightCounter = 0;
+ LightSample = 0xFF;
+ LightLevel = 0;
+}
+
void CartGameSolarSensor::DoSavestate(Savestate* file)
{
CartGame::DoSavestate(file);
@@ -608,104 +600,163 @@ void CartGameSolarSensor::ProcessGPIO() }
-bool Init()
+CartRAMExpansion::CartRAMExpansion() : CartCommon()
{
- CartROM = nullptr;
+}
- Cart = nullptr;
+CartRAMExpansion::~CartRAMExpansion()
+{
+}
- return true;
+void CartRAMExpansion::Reset()
+{
+ memset(RAM, 0xFF, sizeof(RAM));
+ RAMEnable = 1;
}
-void DeInit()
+void CartRAMExpansion::DoSavestate(Savestate* file)
{
- if (CartROM) delete[] CartROM;
+ CartCommon::DoSavestate(file);
- if (Cart) delete Cart;
+ file->VarArray(RAM, sizeof(RAM));
+ file->Var16(&RAMEnable);
}
-void Reset()
+u16 CartRAMExpansion::ROMRead(u32 addr)
{
- // Do not reset cartridge ROM.
- // Prefer keeping the inserted cartridge on reset.
- // This allows resetting a DS game without losing GBA state,
- // and resetting to firmware without the slot being emptied.
- // The Stop function will clear the cartridge state via Eject().
+ addr &= 0x01FFFFFF;
+
+ if (addr < 0x01000000)
+ {
+ switch (addr)
+ {
+ case 0xB0: return 0xFFFF;
+ case 0xB2: return 0x0000;
+ case 0xB4: return 0x2400;
+ case 0xB6: return 0x2424;
+ case 0xB8: return 0xFFFF;
+ case 0xBA: return 0xFFFF;
+ case 0xBC: return 0xFFFF;
+ case 0xBE: return 0x7FFF;
+
+ case 0x1FFFC: return 0xFFFF;
+ case 0x1FFFE: return 0x7FFF;
+
+ case 0x240000: return RAMEnable;
+ case 0x240002: return 0x0000;
+ }
+
+ return 0xFFFF;
+ }
+ else if (addr < 0x01800000)
+ {
+ if (!RAMEnable) return 0xFFFF;
+
+ return *(u16*)&RAM[addr & 0x7FFFFF];
+ }
- // OpenBusDecay doesn't need to be reset, either, as it will be set
- // through NDS::SetGBASlotTimings().
+ return 0xFFFF;
}
-void Eject()
+void CartRAMExpansion::ROMWrite(u32 addr, u16 val)
{
- if (CartROM) delete[] CartROM;
+ addr &= 0x01FFFFFF;
- CartInserted = false;
- CartROM = NULL;
- CartROMSize = 0;
- CartCRC = 0;
- CartID = 0;
+ if (addr < 0x01000000)
+ {
+ switch (addr)
+ {
+ case 0x240000:
+ RAMEnable = val & 0x0001;
+ return;
+ }
+ }
+ else if (addr < 0x01800000)
+ {
+ if (!RAMEnable) return;
+
+ *(u16*)&RAM[addr & 0x7FFFFF] = val;
+ }
+}
+
+
+bool Init()
+{
+ CartROM = nullptr;
- if (Cart) delete Cart;
Cart = nullptr;
- Reset();
+ return true;
+}
+
+void DeInit()
+{
+ if (CartROM) delete[] CartROM;
+
+ if (Cart) delete Cart;
+}
+
+void Reset()
+{
+ if (Cart) Cart->Reset();
}
void DoSavestate(Savestate* file)
{
file->Section("GBAC"); // Game Boy Advance Cartridge
- // logic mostly copied from NDSCart
+ // little state here
+ // no need to save OpenBusDecay, it will be set later
- // first we need to reload the cart itself,
- // since unlike with DS, it's not loaded in advance
-
- file->Var32(&CartROMSize);
- if (!CartROMSize) // no GBA cartridge state? nothing to do here
+ u32 carttype = 0;
+ u32 cartchk = 0;
+ if (Cart)
{
- // do eject the cartridge if something is inserted
- Eject();
- return;
+ carttype = Cart->Type();
+ cartchk = Cart->Checksum();
}
- u32 oldCRC = CartCRC;
- file->Var32(&CartCRC);
-
- if (CartCRC != oldCRC)
+ if (file->Saving)
{
- // delete and reallocate ROM so that it is zero-padded to its full length
- if (CartROM) delete[] CartROM;
- CartROM = new u8[CartROMSize];
+ file->Var32(&carttype);
+ file->Var32(&cartchk);
}
+ else
+ {
+ u32 savetype;
+ file->Var32(&savetype);
+ if (savetype != carttype) return;
- // only save/load the cartridge header
- //
- // GBA connectivity on DS mainly involves identifying the title currently
- // inserted, reading save data, and issuing commands intercepted here
- // (e.g. solar sensor signals). we don't know of any case where GBA ROM is
- // read directly from DS software. therefore, it is more practical, both
- // from the development and user experience perspectives, to avoid dealing
- // with file dependencies, and store a small portion of ROM data that should
- // satisfy the needs of all known software that reads from the GBA slot.
- //
- // note: in case of a state load, only the cartridge header is restored, but
- // the rest of the ROM data is only cleared (zero-initialized) if the CRC
- // differs. Therefore, loading the GBA cartridge associated with the save state
- // in advance will maintain access to the full ROM contents.
- file->VarArray(CartROM, 192);
-
- CartInserted = true; // known, because CartROMSize > 0
- file->Var32(&CartCRC);
- file->Var32(&CartID);
-
- // now do the rest
+ u32 savechk;
+ file->Var32(&savechk);
+ if (savechk != cartchk) return;
+ }
if (Cart) Cart->DoSavestate(file);
}
-void LoadROMCommon(const char *sram)
+bool LoadROM(const u8* romdata, u32 romlen)
{
+ if (CartInserted)
+ EjectCart();
+
+ CartROMSize = 0x200;
+ while (CartROMSize < romlen)
+ CartROMSize <<= 1;
+
+ try
+ {
+ CartROM = new u8[CartROMSize];
+ }
+ catch (const std::bad_alloc& e)
+ {
+ printf("GBACart: failed to allocate memory for ROM (%d bytes)\n", CartROMSize);
+ return false;
+ }
+
+ memset(CartROM, 0, CartROMSize);
+ memcpy(CartROM, romdata, romlen);
+
char gamecode[5] = { '\0' };
memcpy(&gamecode, CartROM + 0xAC, 4);
printf("GBA game code: %s\n", gamecode);
@@ -722,9 +773,6 @@ void LoadROMCommon(const char *sram) printf("GBA solar sensor support detected!\n");
}
- CartCRC = CRC32(CartROM, CartROMSize);
- printf("GBA ROM CRC32: %08X\n", CartCRC);
-
CartInserted = true;
if (solarsensor)
@@ -732,61 +780,61 @@ void LoadROMCommon(const char *sram) else
Cart = new CartGame(CartROM, CartROMSize);
+ if (Cart)
+ Cart->Reset();
+
// save
- printf("GBA save file: %s\n", sram);
+ //printf("GBA save file: %s\n", sram);
// TODO: have a list of sorts like in NDSCart? to determine the savemem type
- if (Cart) Cart->LoadSave(sram, 0);
+ //if (Cart) Cart->LoadSave(sram, 0);
+
+ // TODO: setup cart save here! from a list or something
+
+ return true;
}
-bool LoadROM(const char* path, const char* sram)
+void LoadSave(const u8* savedata, u32 savelen)
{
- FILE* f = Platform::OpenFile(path, "rb");
- if (!f)
+ if (Cart)
{
- return false;
- }
+ // gross hack
+ Cart->SetupSave(savelen);
- if (CartInserted)
- {
- Reset();
+ Cart->LoadSave(savedata, savelen);
}
-
- fseek(f, 0, SEEK_END);
- u32 len = (u32)ftell(f);
-
- CartROMSize = 0x200;
- while (CartROMSize < len)
- CartROMSize <<= 1;
-
- CartROM = new u8[CartROMSize];
- memset(CartROM, 0, CartROMSize);
- fseek(f, 0, SEEK_SET);
- fread(CartROM, 1, len, f);
- fclose(f);
-
- LoadROMCommon(sram);
-
- return true;
}
-bool LoadROM(const u8* romdata, u32 filelength, const char *sram)
+void LoadAddon(int type)
{
- CartROMSize = 0x200;
- while (CartROMSize < filelength)
- CartROMSize <<= 1;
+ CartROMSize = 0;
+ CartROM = nullptr;
- CartROM = new u8[CartROMSize];
- memcpy(CartROM, romdata, filelength);
+ switch (type)
+ {
+ case NDS::GBAAddon_RAMExpansion:
+ Cart = new CartRAMExpansion();
+ break;
- LoadROMCommon(sram);
+ default:
+ printf("GBACart: !! invalid addon type %d\n", type);
+ return;
+ }
- return true;
+ CartInserted = true;
}
-void RelocateSave(const char* path, bool write)
+void EjectCart()
{
- if (Cart) Cart->RelocateSave(path, write);
+ if (Cart) delete Cart;
+ Cart = nullptr;
+
+ if (CartROM) delete[] CartROM;
+
+ CartInserted = false;
+ CartROM = nullptr;
+ CartROMSize = 0;
+ CartID = 0;
}
diff --git a/src/GBACart.h b/src/GBACart.h index 8698e25..8610714 100644 --- a/src/GBACart.h +++ b/src/GBACart.h @@ -1,5 +1,5 @@ /*
- Copyright 2016-2021 Arisotura, Raphaël Zumer
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -32,10 +32,15 @@ public: CartCommon();
virtual ~CartCommon();
+ virtual u32 Type() { return 0x001; }
+ virtual u32 Checksum() { return 0; }
+
+ virtual void Reset();
+
virtual void DoSavestate(Savestate* file);
- virtual void LoadSave(const char* path, u32 type);
- virtual void RelocateSave(const char* path, bool write);
+ virtual void SetupSave(u32 type);
+ virtual void LoadSave(const u8* savedata, u32 savelen);
virtual int SetInput(int num, bool pressed);
@@ -53,10 +58,15 @@ public: CartGame(u8* rom, u32 len);
virtual ~CartGame() override;
+ virtual u32 Type() override { return 0x101; }
+ virtual u32 Checksum() override;
+
+ virtual void Reset() override;
+
virtual void DoSavestate(Savestate* file) override;
- virtual void LoadSave(const char* path, u32 type) override;
- virtual void RelocateSave(const char* path, bool write) override;
+ virtual void SetupSave(u32 type) override;
+ virtual void LoadSave(const u8* savedata, u32 savelen) override;
virtual u16 ROMRead(u32 addr) override;
virtual void ROMWrite(u32 addr, u16 val) override;
@@ -107,11 +117,8 @@ protected: } SRAMFlashState;
u8* SRAM;
- FILE* SRAMFile;
u32 SRAMLength;
SaveType SRAMType;
-
- char SRAMPath[1024];
};
// CartGameSolarSensor -- Boktai game cart
@@ -121,6 +128,10 @@ public: CartGameSolarSensor(u8* rom, u32 len);
virtual ~CartGameSolarSensor() override;
+ virtual u32 Type() override { return 0x102; }
+
+ virtual void Reset() override;
+
virtual void DoSavestate(Savestate* file) override;
virtual int SetInput(int num, bool pressed) override;
@@ -136,6 +147,27 @@ private: u8 LightLevel;
};
+// CartRAMExpansion -- RAM expansion cart (DS browser, ...)
+class CartRAMExpansion : public CartCommon
+{
+public:
+ CartRAMExpansion();
+ ~CartRAMExpansion() override;
+
+ virtual u32 Type() override { return 0x201; }
+
+ void Reset() override;
+
+ void DoSavestate(Savestate* file) override;
+
+ u16 ROMRead(u32 addr) override;
+ void ROMWrite(u32 addr, u16 val) override;
+
+private:
+ u8 RAM[0x800000];
+ u16 RAMEnable;
+};
+
// possible inputs for GBA carts that might accept user input
enum
{
@@ -146,17 +178,23 @@ enum extern bool CartInserted;
extern u8* CartROM;
extern u32 CartROMSize;
-extern u32 CartCRC;
bool Init();
void DeInit();
void Reset();
-void Eject();
void DoSavestate(Savestate* file);
-bool LoadROM(const char* path, const char* sram);
-bool LoadROM(const u8* romdata, u32 filelength, const char *sram);
-void RelocateSave(const char* path, bool write);
+
+bool LoadROM(const u8* romdata, u32 romlen);
+void LoadSave(const u8* savedata, u32 savelen);
+
+void LoadAddon(int type);
+
+void EjectCart();
+
+//bool LoadROM(const char* path, const char* sram);
+//bool LoadROM(const u8* romdata, u32 filelength, const char *sram);
+//void RelocateSave(const char* path, bool write);
// TODO: make more flexible, support nonbinary inputs
int SetInput(int num, bool pressed);
diff --git a/src/GPU.cpp b/src/GPU.cpp index de229b3..f54d771 100644 --- a/src/GPU.cpp +++ b/src/GPU.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -21,6 +21,10 @@ #include "NDS.h" #include "GPU.h" +#ifdef JIT_ENABLED +#include "ARMJIT.h" +#endif + #include "GPU2D_Soft.h" namespace GPU @@ -529,6 +533,8 @@ u8* GetUniqueBankPtr(u32 mask, u32 offset) void MapVRAM_AB(u32 bank, u8 cnt) { + cnt &= 0x9B; + u8 oldcnt = VRAMCNT[bank]; VRAMCNT[bank] = cnt; @@ -587,6 +593,8 @@ void MapVRAM_AB(u32 bank, u8 cnt) void MapVRAM_CD(u32 bank, u8 cnt) { + cnt &= 0x9F; + u8 oldcnt = VRAMCNT[bank]; VRAMCNT[bank] = cnt; @@ -649,6 +657,9 @@ void MapVRAM_CD(u32 bank, u8 cnt) VRAMMap_ARM7[ofs] |= bankmask; memset(VRAMDirty[bank].Data, 0xFF, sizeof(VRAMDirty[bank].Data)); VRAMSTAT |= (1 << (bank-2)); +#ifdef JIT_ENABLED + ARMJIT::CheckAndInvalidateWVRAM(ofs); +#endif break; case 3: // texture @@ -671,6 +682,8 @@ void MapVRAM_CD(u32 bank, u8 cnt) void MapVRAM_E(u32 bank, u8 cnt) { + cnt &= 0x87; + u8 oldcnt = VRAMCNT[bank]; VRAMCNT[bank] = cnt; @@ -733,6 +746,8 @@ void MapVRAM_E(u32 bank, u8 cnt) void MapVRAM_FG(u32 bank, u8 cnt) { + cnt &= 0x9F; + u8 oldcnt = VRAMCNT[bank]; VRAMCNT[bank] = cnt; @@ -831,6 +846,8 @@ void MapVRAM_FG(u32 bank, u8 cnt) void MapVRAM_H(u32 bank, u8 cnt) { + cnt &= 0x83; + u8 oldcnt = VRAMCNT[bank]; VRAMCNT[bank] = cnt; @@ -891,6 +908,8 @@ void MapVRAM_H(u32 bank, u8 cnt) void MapVRAM_I(u32 bank, u8 cnt) { + cnt &= 0x83; + u8 oldcnt = VRAMCNT[bank]; VRAMCNT[bank] = cnt; @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/GPU2D.cpp b/src/GPU2D.cpp index 2dd606a..eda3e32 100644 --- a/src/GPU2D.cpp +++ b/src/GPU2D.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -521,8 +521,6 @@ void Unit::Write32(u32 addr, u32 val) return; case 0x064: - // TODO: check what happens when writing to it during display - // esp. if a capture is happening CaptureCnt = val & 0xEF3F1F1F; return; @@ -534,31 +532,32 @@ void Unit::Write32(u32 addr, u32 val) return; } - if (!Enabled) return; - - switch (addr & 0x00000FFF) + if (Enabled) { - case 0x028: - if (val & 0x08000000) val |= 0xF0000000; - BGXRef[0] = val; - if (GPU::VCount < 192) BGXRefInternal[0] = BGXRef[0]; - return; - case 0x02C: - if (val & 0x08000000) val |= 0xF0000000; - BGYRef[0] = val; - if (GPU::VCount < 192) BGYRefInternal[0] = BGYRef[0]; - return; - - case 0x038: - if (val & 0x08000000) val |= 0xF0000000; - BGXRef[1] = val; - if (GPU::VCount < 192) BGXRefInternal[1] = BGXRef[1]; - return; - case 0x03C: - if (val & 0x08000000) val |= 0xF0000000; - BGYRef[1] = val; - if (GPU::VCount < 192) BGYRefInternal[1] = BGYRef[1]; - return; + switch (addr & 0x00000FFF) + { + case 0x028: + if (val & 0x08000000) val |= 0xF0000000; + BGXRef[0] = val; + if (GPU::VCount < 192) BGXRefInternal[0] = BGXRef[0]; + return; + case 0x02C: + if (val & 0x08000000) val |= 0xF0000000; + BGYRef[0] = val; + if (GPU::VCount < 192) BGYRefInternal[0] = BGYRef[0]; + return; + + case 0x038: + if (val & 0x08000000) val |= 0xF0000000; + BGXRef[1] = val; + if (GPU::VCount < 192) BGXRefInternal[1] = BGXRef[1]; + return; + case 0x03C: + if (val & 0x08000000) val |= 0xF0000000; + BGYRef[1] = val; + if (GPU::VCount < 192) BGYRefInternal[1] = BGYRef[1]; + return; + } } Write16(addr, val&0xFFFF); @@ -721,4 +720,4 @@ void Unit::GetOBJVRAM(u8*& data, u32& mask) } } -}
\ No newline at end of file +} diff --git a/src/GPU2D.h b/src/GPU2D.h index 5a387fd..5edc0a2 100644 --- a/src/GPU2D.h +++ b/src/GPU2D.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/GPU2D_Soft.cpp b/src/GPU2D_Soft.cpp index f7adc53..66095e1 100644 --- a/src/GPU2D_Soft.cpp +++ b/src/GPU2D_Soft.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -38,9 +38,9 @@ SoftRenderer::SoftRenderer() u32 SoftRenderer::ColorBlend4(u32 val1, u32 val2, u32 eva, u32 evb) { - u32 r = (((val1 & 0x00003F) * eva) + ((val2 & 0x00003F) * evb)) >> 4; - u32 g = ((((val1 & 0x003F00) * eva) + ((val2 & 0x003F00) * evb)) >> 4) & 0x007F00; - u32 b = ((((val1 & 0x3F0000) * eva) + ((val2 & 0x3F0000) * evb)) >> 4) & 0x7F0000; + u32 r = (((val1 & 0x00003F) * eva) + ((val2 & 0x00003F) * evb) + 0x000008) >> 4; + u32 g = ((((val1 & 0x003F00) * eva) + ((val2 & 0x003F00) * evb) + 0x000800) >> 4) & 0x007F00; + u32 b = ((((val1 & 0x3F0000) * eva) + ((val2 & 0x3F0000) * evb) + 0x080000) >> 4) & 0x7F0000; if (r > 0x00003F) r = 0x00003F; if (g > 0x003F00) g = 0x003F00; @@ -56,16 +56,9 @@ u32 SoftRenderer::ColorBlend5(u32 val1, u32 val2) if (eva == 32) return val1; - u32 r = (((val1 & 0x00003F) * eva) + ((val2 & 0x00003F) * evb)) >> 5; - u32 g = ((((val1 & 0x003F00) * eva) + ((val2 & 0x003F00) * evb)) >> 5) & 0x007F00; - u32 b = ((((val1 & 0x3F0000) * eva) + ((val2 & 0x3F0000) * evb)) >> 5) & 0x7F0000; - - if (eva <= 16) - { - r += 0x000001; - g += 0x000100; - b += 0x010000; - } + u32 r = (((val1 & 0x00003F) * eva) + ((val2 & 0x00003F) * evb) + 0x000010) >> 5; + u32 g = ((((val1 & 0x003F00) * eva) + ((val2 & 0x003F00) * evb) + 0x001000) >> 5) & 0x007F00; + u32 b = ((((val1 & 0x3F0000) * eva) + ((val2 & 0x3F0000) * evb) + 0x100000) >> 5) & 0x7F0000; if (r > 0x00003F) r = 0x00003F; if (g > 0x003F00) g = 0x003F00; @@ -74,24 +67,24 @@ u32 SoftRenderer::ColorBlend5(u32 val1, u32 val2) return r | g | b | 0xFF000000; } -u32 SoftRenderer::ColorBrightnessUp(u32 val, u32 factor) +u32 SoftRenderer::ColorBrightnessUp(u32 val, u32 factor, u32 bias) { u32 rb = val & 0x3F003F; u32 g = val & 0x003F00; - rb += ((((0x3F003F - rb) * factor) >> 4) & 0x3F003F); - g += ((((0x003F00 - g) * factor) >> 4) & 0x003F00); + rb += (((((0x3F003F - rb) * factor) + (bias*0x010001)) >> 4) & 0x3F003F); + g += (((((0x003F00 - g ) * factor) + (bias*0x000100)) >> 4) & 0x003F00); return rb | g | 0xFF000000; } -u32 SoftRenderer::ColorBrightnessDown(u32 val, u32 factor) +u32 SoftRenderer::ColorBrightnessDown(u32 val, u32 factor, u32 bias) { u32 rb = val & 0x3F003F; u32 g = val & 0x003F00; - rb -= (((rb * factor) >> 4) & 0x3F003F); - g -= (((g * factor) >> 4) & 0x003F00); + rb -= ((((rb * factor) + (bias*0x010001)) >> 4) & 0x3F003F); + g -= ((((g * factor) + (bias*0x000100)) >> 4) & 0x003F00); return rb | g | 0xFF000000; } @@ -160,8 +153,8 @@ u32 SoftRenderer::ColorComposite(int i, u32 val1, u32 val2) { case 0: return val1; case 1: return ColorBlend4(val1, val2, eva, evb); - case 2: return ColorBrightnessUp(val1, CurUnit->EVY); - case 3: return ColorBrightnessDown(val1, CurUnit->EVY); + case 2: return ColorBrightnessUp(val1, CurUnit->EVY, 0x8); + case 3: return ColorBrightnessDown(val1, CurUnit->EVY, 0x7); case 4: return ColorBlend5(val1, val2); } @@ -335,7 +328,7 @@ void SoftRenderer::DrawScanline(u32 line, Unit* unit) for (int i = 0; i < 256; i++) { - dst[i] = ColorBrightnessUp(dst[i], factor); + dst[i] = ColorBrightnessUp(dst[i], factor, 0x0); } } else if ((masterBrightness >> 14) == 2) @@ -346,7 +339,7 @@ void SoftRenderer::DrawScanline(u32 line, Unit* unit) for (int i = 0; i < 256; i++) { - dst[i] = ColorBrightnessDown(dst[i], factor); + dst[i] = ColorBrightnessDown(dst[i], factor, 0xF); } } } @@ -452,8 +445,8 @@ void SoftRenderer::DoCapture(u32 line, u32 width) u32 evy = (val3 >> 8) & 0x1F; val1 = _3dval; - if (compmode == 2) val1 = ColorBrightnessUp(val1, evy); - else if (compmode == 3) val1 = ColorBrightnessDown(val1, evy); + if (compmode == 2) val1 = ColorBrightnessUp(val1, evy, 0x8); + else if (compmode == 3) val1 = ColorBrightnessDown(val1, evy, 0x7); } else val1 = val2; @@ -561,9 +554,9 @@ void SoftRenderer::DoCapture(u32 line, u32 width) u32 bB = (val >> 10) & 0x1F; u32 aB = val >> 15; - u32 rD = ((rA * aA * eva) + (rB * aB * evb)) >> 4; - u32 gD = ((gA * aA * eva) + (gB * aB * evb)) >> 4; - u32 bD = ((bA * aA * eva) + (bB * aB * evb)) >> 4; + u32 rD = ((rA * aA * eva) + (rB * aB * evb) + 8) >> 4; + u32 gD = ((gA * aA * eva) + (gB * aB * evb) + 8) >> 4; + u32 bD = ((bA * aA * eva) + (bB * aB * evb) + 8) >> 4; u32 aD = (eva>0 ? aA : 0) | (evb>0 ? aB : 0); if (rD > 0x1F) rD = 0x1F; @@ -588,9 +581,9 @@ void SoftRenderer::DoCapture(u32 line, u32 width) u32 bA = (val >> 17) & 0x1F; u32 aA = ((val >> 24) != 0) ? 1 : 0; - u32 rD = (rA * aA * eva) >> 4; - u32 gD = (gA * aA * eva) >> 4; - u32 bD = (bA * aA * eva) >> 4; + u32 rD = ((rA * aA * eva) + 8) >> 4; + u32 gD = ((gA * aA * eva) + 8) >> 4; + u32 bD = ((bA * aA * eva) + 8) >> 4; u32 aD = (eva>0 ? aA : 0); dst[dstaddr] = rD | (gD << 5) | (bD << 10) | (aD << 15); @@ -2258,4 +2251,4 @@ void SoftRenderer::DrawSprite_Normal(u32 num, u32 width, u32 height, s32 xpos, s } } -}
\ No newline at end of file +} diff --git a/src/GPU2D_Soft.h b/src/GPU2D_Soft.h index b7043ee..d7a417b 100644 --- a/src/GPU2D_Soft.h +++ b/src/GPU2D_Soft.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -49,8 +49,8 @@ private: u32 ColorBlend4(u32 val1, u32 val2, u32 eva, u32 evb); u32 ColorBlend5(u32 val1, u32 val2); - u32 ColorBrightnessUp(u32 val, u32 factor); - u32 ColorBrightnessDown(u32 val, u32 factor); + u32 ColorBrightnessUp(u32 val, u32 factor, u32 bias); + u32 ColorBrightnessDown(u32 val, u32 factor, u32 bias); u32 ColorComposite(int i, u32 val1, u32 val2); template<u32 bgmode> void DrawScanlineBGMode(u32 line); @@ -78,4 +78,4 @@ private: void DoCapture(u32 line, u32 width); }; -}
\ No newline at end of file +} diff --git a/src/GPU3D.cpp b/src/GPU3D.cpp index 7fa04b3..b4b9eb2 100644 --- a/src/GPU3D.cpp +++ b/src/GPU3D.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/GPU3D.h b/src/GPU3D.h index 23f55eb..4a7bfdc 100644 --- a/src/GPU3D.h +++ b/src/GPU3D.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/GPU3D_OpenGL.cpp b/src/GPU3D_OpenGL.cpp index df7fc0b..ebabd51 100644 --- a/src/GPU3D_OpenGL.cpp +++ b/src/GPU3D_OpenGL.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/GPU3D_OpenGL.h b/src/GPU3D_OpenGL.h index 0752cf1..a9dd62c 100644 --- a/src/GPU3D_OpenGL.h +++ b/src/GPU3D_OpenGL.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/GPU3D_OpenGL_shaders.h b/src/GPU3D_OpenGL_shaders.h index db15df3..6e17c35 100644 --- a/src/GPU3D_OpenGL_shaders.h +++ b/src/GPU3D_OpenGL_shaders.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/GPU3D_Soft.cpp b/src/GPU3D_Soft.cpp index fa22afa..0f8c602 100644 --- a/src/GPU3D_Soft.cpp +++ b/src/GPU3D_Soft.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/GPU3D_Soft.h b/src/GPU3D_Soft.h index 61bf368..c792f13 100644 --- a/src/GPU3D_Soft.h +++ b/src/GPU3D_Soft.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/GPU_OpenGL.cpp b/src/GPU_OpenGL.cpp index 0dd25f0..79adf7a 100644 --- a/src/GPU_OpenGL.cpp +++ b/src/GPU_OpenGL.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -153,14 +153,14 @@ void GLCompositor::SetRenderSettings(RenderSettings& settings) glBindTexture(GL_TEXTURE_2D, CompScreenOutputTex[i]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ScreenW, ScreenH, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); // fill the padding - u8 zeroPixels[ScreenW*2*scale*4]; - memset(zeroPixels, 0, sizeof(zeroPixels)); + u8* zeroPixels = (u8*) calloc(1, ScreenW*2*scale*4); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192*scale, ScreenW, 2*scale, GL_RGBA, GL_UNSIGNED_BYTE, zeroPixels); GLenum fbassign[] = {GL_COLOR_ATTACHMENT0}; glBindFramebuffer(GL_FRAMEBUFFER, CompScreenOutputFB[i]); glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, CompScreenOutputTex[i], 0); glDrawBuffers(1, fbassign); + free(zeroPixels); } glBindFramebuffer(GL_FRAMEBUFFER, 0); diff --git a/src/GPU_OpenGL.h b/src/GPU_OpenGL.h index 08ea4a6..d27ae20 100644 --- a/src/GPU_OpenGL.h +++ b/src/GPU_OpenGL.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/GPU_OpenGL_shaders.h b/src/GPU_OpenGL_shaders.h index ba3b4bd..b1460cd 100644 --- a/src/GPU_OpenGL_shaders.h +++ b/src/GPU_OpenGL_shaders.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -82,8 +82,7 @@ void main() eva = (_3dpix.a & 0x1F) + 1; evb = 32 - eva; - val1 = ((_3dpix * eva) + (val1 * evb)) >> 5; - if (eva <= 16) val1 += ivec4(1,1,1,0); + val1 = ((_3dpix * eva) + (val1 * evb) + 0x10) >> 5; val1 = min(val1, 0x3F); } else @@ -103,7 +102,7 @@ void main() eva = val3.g; evb = val3.b; - val1 = ((val1 * eva) + (_3dpix * evb)) >> 4; + val1 = ((val1 * eva) + (_3dpix * evb) + 0x8) >> 4; val1 = min(val1, 0x3F); } else @@ -123,8 +122,8 @@ void main() evy = val3.g; val1 = _3dpix; - if (compmode == 2) val1 += ((ivec4(0x3F,0x3F,0x3F,0) - val1) * evy) >> 4; - else if (compmode == 3) val1 -= (val1 * evy) >> 4; + if (compmode == 2) val1 += (((0x3F - val1) * evy) + 0x8) >> 4; + else if (compmode == 3) val1 -= ((val1 * evy) + 0x7) >> 4; } else val1 = val2; @@ -142,7 +141,7 @@ void main() int evy = mbright.r & 0x1F; if (evy > 16) evy = 16; - pixel += ((ivec4(0x3F,0x3F,0x3F,0) - pixel) * evy) >> 4; + pixel += ((0x3F - pixel) * evy) >> 4; } else if (brightmode == 2) { @@ -150,7 +149,7 @@ void main() int evy = mbright.r & 0x1F; if (evy > 16) evy = 16; - pixel -= (pixel * evy) >> 4; + pixel -= ((pixel * evy) + 0xF) >> 4; } } diff --git a/src/NDS.cpp b/src/NDS.cpp index e809313..966b252 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -32,7 +32,6 @@ #include "Wifi.h" #include "AREngine.h" #include "Platform.h" -#include "NDSCart_SRAMManager.h" #include "FreeBIOS.h" #ifdef JIT_ENABLED @@ -42,6 +41,9 @@ #include "DSi.h" #include "DSi_SPI_TSC.h" +#include "DSi_NWifi.h" +#include "DSi_Camera.h" +#include "DSi_DSP.h" namespace NDS @@ -174,6 +176,7 @@ bool RunningGame; void DivDone(u32 param); void SqrtDone(u32 param); void RunTimer(u32 tid, s32 cycles); +void UpdateWifiTimings(); void SetWifiWaitCnt(u16 val); void SetGBASlotTimings(); @@ -200,7 +203,6 @@ bool Init() DMAs[6] = new DMA(1, 2); DMAs[7] = new DMA(1, 3); - if (!NDSCart_SRAMManager::Init()) return false; if (!NDSCart::Init()) return false; if (!GBACart::Init()) return false; if (!GPU::Init()) return false; @@ -228,7 +230,6 @@ void DeInit() for (int i = 0; i < 8; i++) delete DMAs[i]; - NDSCart_SRAMManager::DeInit(); NDSCart::DeInit(); GBACart::DeInit(); GPU::DeInit(); @@ -353,7 +354,28 @@ void InitTimings() // handled later: GBA slot, wifi } -void SetupDirectBoot() +bool NeedsDirectBoot() +{ + if (ConsoleType == 1) + { + // for now, DSi mode requires original BIOS/NAND + return false; + } + else + { + // internal BIOS does not support direct boot + if (!Platform::GetConfigBool(Platform::ExternalBIOSEnable)) + return true; + + // DSi/3DS firmwares aren't bootable + if (SPI_Firmware::GetFirmwareLength() == 0x20000) + return true; + + return false; + } +} + +void SetupDirectBoot(std::string romname) { if (ConsoleType == 1) { @@ -444,6 +466,8 @@ void SetupDirectBoot() ARM9->CP15Write(0x911, 0x00000020); } + NDSCart::SetupDirectBoot(romname); + ARM9->R[12] = NDSCart::Header.ARM9EntryAddress; ARM9->R[13] = 0x03002F7C; ARM9->R[14] = NDSCart::Header.ARM9EntryAddress; @@ -542,7 +566,6 @@ void Reset() if (ConsoleType == 1) { DSi::LoadBIOS(); - DSi::LoadNAND(); ARM9ClockShift = 2; MainRAMMask = 0xFFFFFF; @@ -658,6 +681,11 @@ void Reset() AREngine::Reset(); } +void Start() +{ + Running = true; +} + void Stop() { printf("Stopping: shutdown\n"); @@ -665,6 +693,9 @@ void Stop() Platform::StopEmu(); GPU::Stop(); SPU::Stop(); + + if (ConsoleType == 1) + DSi::Stop(); } bool DoSavestate_Scheduler(Savestate* file) @@ -692,7 +723,14 @@ bool DoSavestate_Scheduler(Savestate* file) DivDone, SqrtDone, - NULL + DSi_SDHost::FinishRX, + DSi_SDHost::FinishTX, + DSi_NWifi::MSTimer, + DSi_CamModule::IRQ, + DSi_CamModule::TransferScanline, + DSi_DSP::DSPCatchUpU32, + + nullptr }; int len = Event_MAX; @@ -702,7 +740,7 @@ bool DoSavestate_Scheduler(Savestate* file) { SchedEvent* evt = &SchedList[i]; - u32 funcid = -1; + u32 funcid = 0xFFFFFFFF; if (evt->Func) { for (int j = 0; eventfuncs[j]; j++) @@ -749,7 +787,7 @@ bool DoSavestate_Scheduler(Savestate* file) evt->Func = eventfuncs[funcid]; } else - evt->Func = NULL; + evt->Func = nullptr; file->Var64(&evt->Timestamp); file->Var32(&evt->Param); @@ -763,15 +801,26 @@ bool DoSavestate(Savestate* file) { file->Section("NDSG"); - // TODO: - // * do something for bool's (sizeof=1) - // * do something for 'loading DSi-mode savestate in DS mode' and vice-versa - // * add IE2/IF2 there + if (file->Saving) + { + u32 console = ConsoleType; + file->Var32(&console); + } + else + { + u32 console; + file->Var32(&console); + if (console != ConsoleType) + return false; + } - file->VarArray(MainRAM, 0x400000); - file->VarArray(SharedWRAM, 0x8000); + file->VarArray(MainRAM, MainRAMMaxSize); + file->VarArray(SharedWRAM, SharedWRAMSize); file->VarArray(ARM7WRAM, ARM7WRAMSize); + //file->VarArray(ARM9BIOS, 0x1000); + //file->VarArray(ARM7BIOS, 0x4000); + file->VarArray(ExMemCnt, 2*sizeof(u16)); file->VarArray(ROMSeed0, 2*8); file->VarArray(ROMSeed1, 2*8); @@ -781,6 +830,8 @@ bool DoSavestate(Savestate* file) file->VarArray(IME, 2*sizeof(u32)); file->VarArray(IE, 2*sizeof(u32)); file->VarArray(IF, 2*sizeof(u32)); + file->Var32(&IE2); + file->Var32(&IF2); file->Var8(&PostFlag9); file->Var8(&PostFlag7); @@ -825,11 +876,8 @@ bool DoSavestate(Savestate* file) file->Var64(&LastSysClockCycles); file->Var64(&FrameStartTimestamp); file->Var32(&NumFrames); - if (file->IsAtleastVersion(7, 1)) - { - file->Var32(&NumLagFrames); - file->Bool32(&LagFrameFlag); - } + file->Var32(&NumLagFrames); + file->Bool32(&LagFrameFlag); // TODO: save KeyInput???? file->Var16(&KeyCnt); @@ -848,9 +896,7 @@ bool DoSavestate(Savestate* file) InitTimings(); SetGBASlotTimings(); - u16 tmp = WifiWaitCnt; - WifiWaitCnt = 0xFFFF; - SetWifiWaitCnt(tmp); // force timing table update + UpdateWifiTimings(); } for (int i = 0; i < 8; i++) @@ -860,16 +906,23 @@ bool DoSavestate(Savestate* file) ARM7->DoSavestate(file); NDSCart::DoSavestate(file); - GBACart::DoSavestate(file); + if (ConsoleType == 0) + GBACart::DoSavestate(file); GPU::DoSavestate(file); SPU::DoSavestate(file); SPI::DoSavestate(file); RTC::DoSavestate(file); Wifi::DoSavestate(file); + if (ConsoleType == 1) + DSi::DoSavestate(file); + if (!file->Saving) { GPU::SetPowerCnt(PowerControl9); + + SPU::SetPowerCnt(PowerControl7 & 0x0001); + Wifi::SetPowerCnt(PowerControl7 & 0x0002); } #ifdef JIT_ENABLED @@ -888,72 +941,58 @@ void SetConsoleType(int type) ConsoleType = type; } -bool LoadROM(const u8* romdata, u32 filelength, const char *sram, bool direct) +bool LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) { - if (NDSCart::LoadROM(romdata, filelength, sram, direct)) - { - Running = true; - return true; - } - else - { - printf("Failed to load ROM from archive\n"); + if (!NDSCart::LoadROM(romdata, romlen)) return false; - } + + if (savedata && savelen) + NDSCart::LoadSave(savedata, savelen); + + return true; } -bool LoadROM(const char* path, const char* sram, bool direct) +void LoadSave(const u8* savedata, u32 savelen) { - if (NDSCart::LoadROM(path, sram, direct)) - { - Running = true; - return true; - } - else - { - printf("Failed to load ROM %s\n", path); - return false; - } + if (savedata && savelen) + NDSCart::LoadSave(savedata, savelen); } -bool LoadGBAROM(const char* path, const char* sram) +void EjectCart() { - if (GBACart::LoadROM(path, sram)) - { - return true; - } - else - { - printf("Failed to load ROM %s\n", path); - return false; - } + NDSCart::EjectCart(); } -bool LoadGBAROM(const u8* romdata, u32 filelength, const char *filename, const char *sram) +bool CartInserted() { - if (GBACart::LoadROM(romdata, filelength, sram)) - { - return true; - } - else - { - printf("Failed to load ROM %s from archive\n", filename); + return NDSCart::CartInserted; +} + +bool LoadGBACart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) +{ + if (!GBACart::LoadROM(romdata, romlen)) return false; - } + + if (savedata && savelen) + GBACart::LoadSave(savedata, savelen); + + return true; } -void LoadBIOS() +void LoadGBAAddon(int type) { - Reset(); - Running = true; + GBACart::LoadAddon(type); } -void RelocateSave(const char* path, bool write) +void EjectGBACart() { - printf("SRAM: relocating to %s (write=%s)\n", path, write?"true":"false"); - NDSCart::RelocateSave(path, write); + GBACart::EjectCart(); } +void LoadBIOS() +{ + Reset(); +} u64 NextTarget() @@ -1094,8 +1133,6 @@ u32 RunFrame() GPU3D::Timestamp-SysTimestamp); #endif SPU::TransferOutput(); - - NDSCart::FlushSRAMFile(); } // In the context of TASes, frame count is traditionally the primary measure of emulated time, @@ -1166,6 +1203,25 @@ void ScheduleEvent(u32 id, bool periodic, s32 delay, void (*func)(u32), u32 para Reschedule(evt->Timestamp); } +void ScheduleEvent(u32 id, u64 timestamp, void (*func)(u32), u32 param) +{ + if (SchedListMask & (1<<id)) + { + printf("!! EVENT %d ALREADY SCHEDULED\n", id); + return; + } + + SchedEvent* evt = &SchedList[id]; + + evt->Timestamp = timestamp; + evt->Func = func; + evt->Param = param; + + SchedListMask |= (1<<id); + + Reschedule(evt->Timestamp); +} + void CancelEvent(u32 id) { SchedListMask &= ~(1<<id); @@ -1229,15 +1285,30 @@ void SetLidClosed(bool closed) } } +void CamInputFrame(int cam, u32* data, int width, int height, bool rgb) +{ + // TODO: support things like the GBA-slot camera addon + // whenever these are emulated + + if (ConsoleType == 1) + { + switch (cam) + { + case 0: return DSi_CamModule::Camera0->InputFrame(data, width, height, rgb); + case 1: return DSi_CamModule::Camera1->InputFrame(data, width, height, rgb); + } + } +} + void MicInputFrame(s16* data, int samples) { return SPI_TSC::MicInputFrame(data, samples); } -int ImportSRAM(u8* data, u32 length) +/*int ImportSRAM(u8* data, u32 length) { return NDSCart::ImportSRAM(data, length); -} +}*/ void Halt() @@ -1291,15 +1362,29 @@ void MapSharedWRAM(u8 val) } +void UpdateWifiTimings() +{ + if (PowerControl7 & 0x0002) + { + const int ntimings[4] = {10, 8, 6, 18}; + u16 val = WifiWaitCnt; + + SetARM7RegionTimings(0x04800, 0x04808, Mem7_Wifi0, 16, ntimings[val & 0x3], (val & 0x4) ? 4 : 6); + SetARM7RegionTimings(0x04808, 0x04810, Mem7_Wifi1, 16, ntimings[(val>>3) & 0x3], (val & 0x20) ? 4 : 10); + } + else + { + SetARM7RegionTimings(0x04800, 0x04808, Mem7_Wifi0, 32, 1, 1); + SetARM7RegionTimings(0x04808, 0x04810, Mem7_Wifi1, 32, 1, 1); + } +} + void SetWifiWaitCnt(u16 val) { if (WifiWaitCnt == val) return; WifiWaitCnt = val; - - const int ntimings[4] = {10, 8, 6, 18}; - SetARM7RegionTimings(0x04800, 0x04808, Mem7_Wifi0, 16, ntimings[val & 0x3], (val & 0x4) ? 4 : 6); - SetARM7RegionTimings(0x04808, 0x04810, Mem7_Wifi1, 16, ntimings[(val>>3) & 0x3], (val & 0x20) ? 4 : 10); + UpdateWifiTimings(); } void SetGBASlotTimings() @@ -1909,8 +1994,8 @@ void debug(u32 param) //for (int i = 0; i < 9; i++) // printf("VRAM %c: %02X\n", 'A'+i, GPU::VRAMCNT[i]); - /*FILE* - shit = fopen("debug/construct.bin", "wb"); + FILE* + shit = fopen("debug/inazuma.bin", "wb"); fwrite(ARM9->ITCM, 0x8000, 1, shit); for (u32 i = 0x02000000; i < 0x02400000; i+=4) { @@ -1922,9 +2007,14 @@ void debug(u32 param) u32 val = ARM7Read32(i); fwrite(&val, 4, 1, shit); } - fclose(shit);*/ + for (u32 i = 0x06000000; i < 0x06040000; i+=4) + { + u32 val = ARM7Read32(i); + fwrite(&val, 4, 1, shit); + } + fclose(shit); - FILE* + /*FILE* shit = fopen("debug/directboot9.bin", "wb"); for (u32 i = 0x02000000; i < 0x04000000; i+=4) { @@ -1932,13 +2022,13 @@ void debug(u32 param) fwrite(&val, 4, 1, shit); } fclose(shit); - shit = fopen("debug/directboot7.bin", "wb"); + shit = fopen("debug/camera7.bin", "wb"); for (u32 i = 0x02000000; i < 0x04000000; i+=4) { u32 val = DSi::ARM7Read32(i); fwrite(&val, 4, 1, shit); } - fclose(shit); + fclose(shit);*/ } @@ -2364,6 +2454,7 @@ u8 ARM7Read8(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; if (addr & 0x1) return Wifi::Read(addr-1) >> 8; return Wifi::Read(addr) & 0xFF; } @@ -2428,6 +2519,7 @@ u16 ARM7Read16(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; return Wifi::Read(addr); } break; @@ -2491,6 +2583,7 @@ u32 ARM7Read32(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; return Wifi::Read(addr) | (Wifi::Read(addr+2) << 16); } break; @@ -2582,7 +2675,8 @@ void ARM7Write8(u32 addr, u8 val) return; } - if (ARM7->R[15] > 0x00002F30) // ARM7 BIOS bug + //if (ARM7->R[15] > 0x00002F30) // ARM7 BIOS bug + if (addr >= 0x01000000) printf("unknown arm7 write8 %08X %02X @ %08X\n", addr, val, ARM7->R[15]); } @@ -2630,6 +2724,7 @@ void ARM7Write16(u32 addr, u16 val) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return; Wifi::Write(addr, val); return; } @@ -2659,7 +2754,8 @@ void ARM7Write16(u32 addr, u16 val) return; } - printf("unknown arm7 write16 %08X %04X @ %08X\n", addr, val, ARM7->R[15]); + if (addr >= 0x01000000) + printf("unknown arm7 write16 %08X %04X @ %08X\n", addr, val, ARM7->R[15]); } void ARM7Write32(u32 addr, u32 val) @@ -2706,6 +2802,7 @@ void ARM7Write32(u32 addr, u32 val) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return; Wifi::Write(addr, val & 0xFFFF); Wifi::Write(addr+2, val >> 16); return; @@ -2739,7 +2836,8 @@ void ARM7Write32(u32 addr, u32 val) return; } - printf("unknown arm7 write32 %08X %08X @ %08X\n", addr, val, ARM7->R[15]); + if (addr >= 0x01000000) + printf("unknown arm7 write32 %08X %08X @ %08X\n", addr, val, ARM7->R[15]); } bool ARM7GetMemRegion(u32 addr, bool write, MemRegion* region) @@ -2899,7 +2997,8 @@ u8 ARM9IORead8(u32 addr) return (u8)(emuID[idx]); } - printf("unknown ARM9 IO read8 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read8 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3045,7 +3144,8 @@ u16 ARM9IORead16(u32 addr) return GPU3D::Read16(addr); } - printf("unknown ARM9 IO read16 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read16 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3188,7 +3288,8 @@ u32 ARM9IORead32(u32 addr) return GPU3D::Read32(addr); } - printf("unknown ARM9 IO read32 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read32 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3716,6 +3817,7 @@ u8 ARM7IORead8(u32 addr) case 0x04000241: return WRAMCnt; case 0x04000300: return PostFlag7; + case 0x04000304: return PowerControl7; } if (addr >= 0x04000400 && addr < 0x04000520) @@ -3723,7 +3825,8 @@ u8 ARM7IORead8(u32 addr) return SPU::Read8(addr); } - printf("unknown ARM7 IO read8 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read8 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -3798,7 +3901,9 @@ u16 ARM7IORead16(u32 addr) case 0x040001C2: return SPI::ReadData(); case 0x04000204: return ExMemCnt[1]; - case 0x04000206: return WifiWaitCnt; + case 0x04000206: + if (!(PowerControl7 & (1<<1))) return 0; + return WifiWaitCnt; case 0x04000208: return IME[1]; case 0x04000210: return IE[1] & 0xFFFF; @@ -3814,7 +3919,8 @@ u16 ARM7IORead16(u32 addr) return SPU::Read16(addr); } - printf("unknown ARM7 IO read16 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read16 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -3880,6 +3986,7 @@ u32 ARM7IORead32(u32 addr) case 0x04000210: return IE[1]; case 0x04000214: return IF[1]; + case 0x04000304: return PowerControl7; case 0x04000308: return ARM7BIOSProt; case 0x04100000: @@ -3913,7 +4020,8 @@ u32 ARM7IORead32(u32 addr) return SPU::Read32(addr); } - printf("unknown ARM7 IO read32 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read32 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -4108,6 +4216,7 @@ void ARM7IOWrite16(u32 addr, u16 val) return; } case 0x04000206: + if (!(PowerControl7 & (1<<1))) return; SetWifiWaitCnt(val); return; @@ -4123,7 +4232,15 @@ void ARM7IOWrite16(u32 addr, u16 val) PostFlag7 = val & 0x01; return; - case 0x04000304: PowerControl7 = val; return; + case 0x04000304: + { + u16 change = PowerControl7 ^ val; + PowerControl7 = val & 0x0003; + SPU::SetPowerCnt(val & 0x0001); + Wifi::SetPowerCnt(val & 0x0002); + if (change & 0x0002) UpdateWifiTimings(); + } + return; case 0x04000308: if (ARM7BIOSProt == 0) @@ -4245,7 +4362,15 @@ void ARM7IOWrite32(u32 addr, u32 val) case 0x04000210: IE[1] = val; UpdateIRQ(1); return; case 0x04000214: IF[1] &= ~val; UpdateIRQ(1); return; - case 0x04000304: PowerControl7 = val & 0xFFFF; return; + case 0x04000304: + { + u16 change = PowerControl7 ^ val; + PowerControl7 = val & 0x0003; + SPU::SetPowerCnt(val & 0x0001); + Wifi::SetPowerCnt(val & 0x0002); + if (change & 0x0002) UpdateWifiTimings(); + } + return; case 0x04000308: if (ARM7BIOSProt == 0) @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,6 +19,8 @@ #ifndef NDS_H #define NDS_H +#include <string> + #include "Savestate.h" #include "types.h" @@ -48,8 +50,6 @@ enum Event_DSi_NWifi, Event_DSi_CamIRQ, Event_DSi_CamTransfer, - - Event_DSi_RAMSizeChange, Event_DSi_DSP, Event_MAX @@ -162,6 +162,12 @@ struct MemRegion u32 Mask; }; +// supported GBA slot addon types +enum +{ + GBAAddon_RAMExpansion = 1, +}; + #ifdef JIT_ENABLED extern bool EnableJIT; #endif @@ -219,6 +225,7 @@ extern u8* ARM7WRAM; bool Init(); void DeInit(); void Reset(); +void Start(); void Stop(); bool DoSavestate(Savestate* file); @@ -229,13 +236,19 @@ void SetARM7RegionTimings(u32 addrstart, u32 addrend, u32 region, int buswidth, // 0=DS 1=DSi void SetConsoleType(int type); -bool LoadROM(const char* path, const char* sram, bool direct); -bool LoadROM(const u8* romdata, u32 filelength, const char *sram, bool direct); -bool LoadGBAROM(const char* path, const char* sram); -bool LoadGBAROM(const u8* romdata, u32 filelength, const char *filename, const char *sram); void LoadBIOS(); -void SetupDirectBoot(); -void RelocateSave(const char* path, bool write); + +bool LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen); +void LoadSave(const u8* savedata, u32 savelen); +void EjectCart(); +bool CartInserted(); + +bool NeedsDirectBoot(); +void SetupDirectBoot(std::string romname); + +bool LoadGBACart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen); +void LoadGBAAddon(int type); +void EjectGBACart(); u32 RunFrame(); @@ -247,11 +260,11 @@ void SetKeyMask(u32 mask); bool IsLidClosed(); void SetLidClosed(bool closed); +void CamInputFrame(int cam, u32* data, int width, int height, bool rgb); void MicInputFrame(s16* data, int samples); -int ImportSRAM(u8* data, u32 length); - void ScheduleEvent(u32 id, bool periodic, s32 delay, void (*func)(u32), u32 param); +void ScheduleEvent(u32 id, u64 timestamp, void (*func)(u32), u32 param); void CancelEvent(u32 id); void debug(u32 p); diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index 3d231ac..cdc26ef 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -22,11 +22,11 @@ #include "DSi.h" #include "NDSCart.h" #include "ARM.h" +#include "CRC32.h" #include "DSi_AES.h" #include "Platform.h" #include "ROMList.h" #include "melonDLDI.h" -#include "NDSCart_SRAMManager.h" namespace NDSCart @@ -51,12 +51,9 @@ u32 TransferDir; u8 TransferCmd[8]; bool CartInserted; -char CartName[256]; u8* CartROM; u32 CartROMSize; u32 CartID; -bool CartIsHomebrew; -bool CartIsDSi; NDSHeader Header; NDSBanner Banner; @@ -135,13 +132,32 @@ void Key1_ApplyKeycode(u32* keycode, u32 mod) } } -void Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod) +void Key1_LoadKeyBuf(bool dsi) { - // TODO: source the key data from different possible places - if (dsi && NDS::ConsoleType==1) - memcpy(Key1_KeyBuf, &DSi::ARM7iBIOS[0xC6D0], 0x1048); // hax + // it is possible that this gets called before the BIOSes are loaded + // so we will read from the BIOS files directly + + if (Platform::GetConfigBool(Platform::ExternalBIOSEnable)) + { + std::string path = Platform::GetConfigString(dsi ? Platform::DSi_BIOS7Path : Platform::BIOS7Path); + FILE* f = Platform::OpenLocalFile(path, "rb"); + if (f) + { + fseek(f, dsi ? 0xC6D0 : 0x0030, SEEK_SET); + fread(Key1_KeyBuf, 0x1048, 1, f); + fclose(f); + } + } else - memcpy(Key1_KeyBuf, &NDS::ARM7BIOS[0x30], 0x1048); // hax + { + // well + memset(Key1_KeyBuf, 0, 0x1048); + } +} + +void Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod) +{ + Key1_LoadKeyBuf(dsi); u32 keycode[3] = {idcode, idcode>>1, idcode<<1}; if (level >= 1) Key1_ApplyKeycode(keycode, mod); @@ -191,6 +207,22 @@ CartCommon::~CartCommon() { } +u32 CartCommon::Checksum() +{ + u32 crc = CRC32(ROM, 0x40); + + crc = CRC32(&ROM[Header.ARM9ROMOffset], Header.ARM9Size, crc); + crc = CRC32(&ROM[Header.ARM7ROMOffset], Header.ARM7Size, crc); + + if (IsDSi) + { + crc = CRC32(&ROM[Header.DSiARM9iROMOffset], Header.DSiARM9iSize, crc); + crc = CRC32(&ROM[Header.DSiARM7iROMOffset], Header.DSiARM7iSize, crc); + } + + return crc; +} + void CartCommon::Reset() { CmdEncMode = 0; @@ -198,11 +230,11 @@ void CartCommon::Reset() DSiMode = false; } -void CartCommon::SetupDirectBoot() +void CartCommon::SetupDirectBoot(std::string romname) { CmdEncMode = 2; DataEncMode = 2; - DSiMode = IsDSi && NDS::ConsoleType==1; + DSiMode = IsDSi && (NDS::ConsoleType==1); } void CartCommon::DoSavestate(Savestate* file) @@ -214,20 +246,11 @@ void CartCommon::DoSavestate(Savestate* file) file->Bool32(&DSiMode); } -void CartCommon::LoadSave(const char* path, u32 type) -{ -} - -void CartCommon::RelocateSave(const char* path, bool write) +void CartCommon::SetupSave(u32 type) { } -int CartCommon::ImportSRAM(const u8* data, u32 length) -{ - return 0; -} - -void CartCommon::FlushSRAMFile() +void CartCommon::LoadSave(const u8* savedata, u32 savelen) { } @@ -392,11 +415,7 @@ void CartRetail::DoSavestate(Savestate* file) CartCommon::DoSavestate(file); // we reload the SRAM contents. - // it should be the same file (as it should be the same ROM, duh) - // but the contents may change - - //if (!file->Saving && SRAMLength) - // delete[] SRAM; + // it should be the same file, but the contents may change u32 oldlen = SRAMLength; @@ -407,13 +426,11 @@ void CartRetail::DoSavestate(Savestate* file) printf("oh well. loading it anyway. adsfgdsf\n"); if (oldlen) delete[] SRAM; + SRAM = nullptr; if (SRAMLength) SRAM = new u8[SRAMLength]; } if (SRAMLength) { - //if (!file->Saving) - // SRAM = new u8[SRAMLength]; - file->VarArray(SRAM, SRAMLength); } @@ -423,20 +440,14 @@ void CartRetail::DoSavestate(Savestate* file) file->Var32(&SRAMAddr); file->Var8(&SRAMStatus); - // SRAMManager might now have an old buffer (or one from the future or alternate timeline!) - if (!file->Saving) - { - SRAMFileDirty = false; - NDSCart_SRAMManager::RequestFlush(); - } + if ((!file->Saving) && SRAM) + Platform::WriteNDSSave(SRAM, SRAMLength, 0, SRAMLength); } -void CartRetail::LoadSave(const char* path, u32 type) +void CartRetail::SetupSave(u32 type) { if (SRAM) delete[] SRAM; - - strncpy(SRAMPath, path, 1023); - SRAMPath[1023] = '\0'; + SRAM = nullptr; if (type > 10) type = 0; int sramlen[] = @@ -455,18 +466,6 @@ void CartRetail::LoadSave(const char* path, u32 type) memset(SRAM, 0xFF, SRAMLength); } - FILE* f = Platform::OpenFile(path, "rb"); - if (f) - { - fseek(f, 0, SEEK_SET); - fread(SRAM, 1, SRAMLength, f); - - fclose(f); - } - - SRAMFileDirty = false; - NDSCart_SRAMManager::Setup(path, SRAM, SRAMLength); - switch (type) { case 1: SRAMType = 1; break; // EEPROM, small @@ -483,47 +482,13 @@ void CartRetail::LoadSave(const char* path, u32 type) } } -void CartRetail::RelocateSave(const char* path, bool write) -{ - if (!write) - { - LoadSave(path, 0); // lazy - return; - } - - strncpy(SRAMPath, path, 1023); - SRAMPath[1023] = '\0'; - - FILE* f = Platform::OpenFile(path, "wb"); - if (!f) - { - printf("NDSCart_SRAM::RelocateSave: failed to create new file. fuck\n"); - return; - } - - fwrite(SRAM, SRAMLength, 1, f); - fclose(f); -} - -int CartRetail::ImportSRAM(const u8* data, u32 length) -{ - memcpy(SRAM, data, std::min(length, SRAMLength)); - FILE* f = Platform::OpenFile(SRAMPath, "wb"); - if (f) - { - fwrite(SRAM, SRAMLength, 1, f); - fclose(f); - } - - return length - SRAMLength; -} - -void CartRetail::FlushSRAMFile() +void CartRetail::LoadSave(const u8* savedata, u32 savelen) { - if (!SRAMFileDirty) return; + if (!SRAM) return; - SRAMFileDirty = false; - NDSCart_SRAMManager::RequestFlush(); + u32 len = std::min(savelen, SRAMLength); + memcpy(SRAM, savedata, len); + Platform::WriteNDSSave(savedata, len, 0, len); } int CartRetail::ROMCommandStart(u8* cmd, u8* data, u32 len) @@ -564,17 +529,17 @@ u8 CartRetail::SPIWrite(u8 val, u32 pos, bool last) { case 0x04: // write disable SRAMStatus &= ~(1<<1); - break; + return 0; case 0x06: // write enable SRAMStatus |= (1<<1); - break; + return 0; default: SRAMCmd = val; SRAMAddr = 0; } - return 0; + return 0xFF; } switch (SRAMType) @@ -582,7 +547,7 @@ u8 CartRetail::SPIWrite(u8 val, u32 pos, bool last) case 1: return SRAMWrite_EEPROMTiny(val, pos, last); case 2: return SRAMWrite_EEPROM(val, pos, last); case 3: return SRAMWrite_FLASH(val, pos, last); - default: return 0; + default: return 0xFF; } } @@ -624,6 +589,7 @@ u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last) if (pos < 2) { SRAMAddr = val; + SRAMFirstAddr = SRAMAddr; } else { @@ -631,11 +597,15 @@ u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last) if (SRAMStatus & (1<<1)) { SRAM[(SRAMAddr + ((SRAMCmd==0x0A)?0x100:0)) & 0x1FF] = val; - SRAMFileDirty |= last; } SRAMAddr++; } - if (last) SRAMStatus &= ~(1<<1); + if (last) + { + SRAMStatus &= ~(1<<1); + Platform::WriteNDSSave(SRAM, SRAMLength, + (SRAMFirstAddr + ((SRAMCmd==0x0A)?0x100:0)) & 0x1FF, SRAMAddr-SRAMFirstAddr); + } return 0; case 0x03: // read low @@ -658,7 +628,7 @@ u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last) default: if (pos == 1) printf("unknown tiny EEPROM save command %02X\n", SRAMCmd); - return 0; + return 0xFF; } } @@ -683,6 +653,7 @@ u8 CartRetail::SRAMWrite_EEPROM(u8 val, u32 pos, bool last) { SRAMAddr <<= 8; SRAMAddr |= val; + SRAMFirstAddr = SRAMAddr; } else { @@ -690,11 +661,15 @@ u8 CartRetail::SRAMWrite_EEPROM(u8 val, u32 pos, bool last) if (SRAMStatus & (1<<1)) { SRAM[SRAMAddr & (SRAMLength-1)] = val; - SRAMFileDirty |= last; } SRAMAddr++; } - if (last) SRAMStatus &= ~(1<<1); + if (last) + { + SRAMStatus &= ~(1<<1); + Platform::WriteNDSSave(SRAM, SRAMLength, + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + } return 0; case 0x03: // read @@ -719,7 +694,7 @@ u8 CartRetail::SRAMWrite_EEPROM(u8 val, u32 pos, bool last) default: if (pos == 1) printf("unknown EEPROM save command %02X\n", SRAMCmd); - return 0; + return 0xFF; } } @@ -735,6 +710,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMAddr <<= 8; SRAMAddr |= val; + SRAMFirstAddr = SRAMAddr; } else { @@ -742,11 +718,15 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { // CHECKME: should it be &=~val ?? SRAM[SRAMAddr & (SRAMLength-1)] = 0; - SRAMFileDirty |= last; } SRAMAddr++; } - if (last) SRAMStatus &= ~(1<<1); + if (last) + { + SRAMStatus &= ~(1<<1); + Platform::WriteNDSSave(SRAM, SRAMLength, + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + } return 0; case 0x03: // read @@ -768,17 +748,22 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMAddr <<= 8; SRAMAddr |= val; + SRAMFirstAddr = SRAMAddr; } else { if (SRAMStatus & (1<<1)) { SRAM[SRAMAddr & (SRAMLength-1)] = val; - SRAMFileDirty |= last; } SRAMAddr++; } - if (last) SRAMStatus &= ~(1<<1); + if (last) + { + SRAMStatus &= ~(1<<1); + Platform::WriteNDSSave(SRAM, SRAMLength, + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + } return 0; case 0x0B: // fast read @@ -809,6 +794,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMAddr <<= 8; SRAMAddr |= val; + SRAMFirstAddr = SRAMAddr; } if ((pos == 3) && (SRAMStatus & (1<<1))) { @@ -817,9 +803,13 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) SRAM[SRAMAddr & (SRAMLength-1)] = 0; SRAMAddr++; } - SRAMFileDirty = true; } - if (last) SRAMStatus &= ~(1<<1); + if (last) + { + SRAMStatus &= ~(1<<1); + Platform::WriteNDSSave(SRAM, SRAMLength, + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + } return 0; case 0xDB: // page erase @@ -827,6 +817,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMAddr <<= 8; SRAMAddr |= val; + SRAMFirstAddr = SRAMAddr; } if ((pos == 3) && (SRAMStatus & (1<<1))) { @@ -835,15 +826,19 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) SRAM[SRAMAddr & (SRAMLength-1)] = 0; SRAMAddr++; } - SRAMFileDirty = true; } - if (last) SRAMStatus &= ~(1<<1); + if (last) + { + SRAMStatus &= ~(1<<1); + Platform::WriteNDSSave(SRAM, SRAMLength, + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + } return 0; default: if (pos == 1) printf("unknown FLASH save command %02X\n", SRAMCmd); - return 0; + return 0xFF; } } @@ -884,19 +879,12 @@ void CartRetailNAND::DoSavestate(Savestate* file) BuildSRAMID(); } -void CartRetailNAND::LoadSave(const char* path, u32 type) +void CartRetailNAND::LoadSave(const u8* savedata, u32 savelen) { - CartRetail::LoadSave(path, type); + CartRetail::LoadSave(savedata, savelen); BuildSRAMID(); } -int CartRetailNAND::ImportSRAM(const u8* data, u32 length) -{ - int ret = CartRetail::ImportSRAM(data, length); - BuildSRAMID(); - return ret; -} - int CartRetailNAND::ROMCommandStart(u8* cmd, u8* data, u32 len) { if (CmdEncMode != 2) return CartCommon::ROMCommandStart(cmd, data, len); @@ -926,7 +914,7 @@ int CartRetailNAND::ROMCommandStart(u8* cmd, u8* data, u32 len) if (SRAMLength && SRAMAddr < (SRAMBase+SRAMLength-0x20000)) { memcpy(&SRAM[SRAMAddr - SRAMBase], SRAMWriteBuffer, 0x800); - SRAMFileDirty = true; + Platform::WriteNDSSave(SRAM, SRAMLength, SRAMAddr - SRAMBase, 0x800); } SRAMAddr = 0; @@ -1164,8 +1152,30 @@ u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last) CartHomebrew::CartHomebrew(u8* rom, u32 len, u32 chipid) : CartCommon(rom, len, chipid) { + SD = nullptr; +} + +CartHomebrew::~CartHomebrew() +{ + if (SD) + { + SD->Close(); + delete SD; + } +} + +void CartHomebrew::Reset() +{ + CartCommon::Reset(); + ReadOnly = Platform::GetConfigBool(Platform::DLDI_ReadOnly); + if (SD) + { + SD->Close(); + delete SD; + } + if (Platform::GetConfigBool(Platform::DLDI_Enable)) { std::string folderpath; @@ -1185,29 +1195,15 @@ CartHomebrew::CartHomebrew(u8* rom, u32 len, u32 chipid) : CartCommon(rom, len, SD = nullptr; } -CartHomebrew::~CartHomebrew() -{ - if (SD) - { - SD->Close(); - delete SD; - } -} - -void CartHomebrew::Reset() -{ - CartCommon::Reset(); -} - -void CartHomebrew::SetupDirectBoot() +void CartHomebrew::SetupDirectBoot(std::string romname) { - CartCommon::SetupDirectBoot(); + CartCommon::SetupDirectBoot(romname); if (SD) { // add the ROM to the SD volume - if (!SD->InjectFile(CartName, CartROM, CartROMSize)) + if (!SD->InjectFile(romname, CartROM, CartROMSize)) return; // setup argv command line @@ -1216,7 +1212,7 @@ void CartHomebrew::SetupDirectBoot() int argvlen; strncpy(argv, "fat:/", 511); - strncat(argv, CartName, 511); + strncat(argv, romname.c_str(), 511); argvlen = strlen(argv); void (*writefn)(u32,u32) = (NDS::ConsoleType==1) ? DSi::ARM9Write32 : NDS::ARM9Write32; @@ -1295,40 +1291,8 @@ void CartHomebrew::ROMCommandFinish(u8* cmd, u8* data, u32 len) } } -void CartHomebrew::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly) +void CartHomebrew::ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) { - u32 offset = *(u32*)&ROM[0x20]; - u32 size = *(u32*)&ROM[0x2C]; - - u8* binary = &ROM[offset]; - u32 dldioffset = 0; - - for (u32 i = 0; i < size; i++) - { - if (*(u32*)&binary[i ] == 0xBF8DA5ED && - *(u32*)&binary[i+4] == 0x69684320 && - *(u32*)&binary[i+8] == 0x006D6873) - { - dldioffset = i; - break; - } - } - - if (!dldioffset) - { - return; - } - - printf("DLDI structure found at %08X (%08X)\n", dldioffset, offset+dldioffset); - - if (*(u32*)&patch[0] != 0xBF8DA5ED || - *(u32*)&patch[4] != 0x69684320 || - *(u32*)&patch[8] != 0x006D6873) - { - printf("bad DLDI patch\n"); - return; - } - if (patch[0x0D] > binary[dldioffset+0x0F]) { printf("DLDI driver ain't gonna fit, sorry\n"); @@ -1425,7 +1389,37 @@ void CartHomebrew::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly) *(u32*)&binary[writesec_addr+0x04] = 0xE12FFF1E; // bx lr } - printf("applied DLDI patch\n"); + printf("applied DLDI patch at %08X\n", dldioffset); +} + +void CartHomebrew::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly) +{ + if (*(u32*)&patch[0] != 0xBF8DA5ED || + *(u32*)&patch[4] != 0x69684320 || + *(u32*)&patch[8] != 0x006D6873) + { + printf("bad DLDI patch\n"); + return; + } + + u32 offset = *(u32*)&ROM[0x20]; + u32 size = *(u32*)&ROM[0x2C]; + + u8* binary = &ROM[offset]; + + for (u32 i = 0; i < size; ) + { + if (*(u32*)&binary[i ] == 0xBF8DA5ED && + *(u32*)&binary[i+4] == 0x69684320 && + *(u32*)&binary[i+8] == 0x006D6873) + { + printf("DLDI structure found at %08X (%08X)\n", i, offset+i); + ApplyDLDIPatchAt(binary, i, patch, patchlen, readonly); + i += patchlen; + } + else + i++; + } } void CartHomebrew::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) @@ -1441,6 +1435,7 @@ void CartHomebrew::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) bool Init() { + CartInserted = false; CartROM = nullptr; Cart = nullptr; @@ -1455,17 +1450,6 @@ void DeInit() void Reset() { - CartInserted = false; - if (CartROM) delete[] CartROM; - CartROM = nullptr; - CartROMSize = 0; - CartID = 0; - CartIsHomebrew = false; - CartIsDSi = false; - - if (Cart) delete Cart; - Cart = nullptr; - ResetCart(); } @@ -1494,6 +1478,30 @@ void DoSavestate(Savestate* file) // (TODO: system to verify that indeed the right ROM is loaded) // (what to CRC? whole ROM? code binaries? latter would be more convenient for ie. romhaxing) + u32 carttype = 0; + u32 cartchk = 0; + if (Cart) + { + carttype = Cart->Type(); + cartchk = Cart->Checksum(); + } + + if (file->Saving) + { + file->Var32(&carttype); + file->Var32(&cartchk); + } + else + { + u32 savetype; + file->Var32(&savetype); + if (savetype != carttype) return; + + u32 savechk; + file->Var32(&savechk); + if (savechk != cartchk) return; + } + if (Cart) Cart->DoSavestate(file); } @@ -1542,11 +1550,6 @@ bool ReadROMParams(u32 gamecode, ROMListEntry* params) void DecryptSecureArea(u8* out) { - // TODO: source decryption data from different possible sources - // * original DS-mode ARM7 BIOS has the key data at 0x30 - // * .srl ROMs (VC dumps) have encrypted secure areas but have precomputed - // decryption data at 0x1000 (and at the beginning of the DSi region if any) - u32 gamecode = (u32)Header.GameCode[3] << 24 | (u32)Header.GameCode[2] << 16 | (u32)Header.GameCode[1] << 8 | @@ -1576,10 +1579,41 @@ void DecryptSecureArea(u8* out) } } -bool LoadROMCommon(u32 filelength, const char *sram, bool direct) +bool LoadROM(const u8* romdata, u32 romlen) { + if (CartInserted) + EjectCart(); + + memset(&Header, 0, sizeof(Header)); + memset(&Banner, 0, sizeof(Banner)); + + CartROMSize = 0x200; + while (CartROMSize < romlen) + CartROMSize <<= 1; + + try + { + CartROM = new u8[CartROMSize]; + } + catch (const std::bad_alloc& e) + { + printf("NDSCart: failed to allocate memory for ROM (%d bytes)\n", CartROMSize); + return false; + } + + memset(CartROM, 0, CartROMSize); + memcpy(CartROM, romdata, romlen); + memcpy(&Header, CartROM, sizeof(Header)); - memcpy(&Banner, CartROM + Header.BannerOffset, sizeof(Banner)); + + u8 unitcode = Header.UnitCode; + bool dsi = (unitcode & 0x02) != 0; + + size_t bannersize = dsi ? 0x23C0 : 0xA40; + if (Header.BannerOffset >= 0x200 && Header.BannerOffset < (CartROMSize - bannersize)) + { + memcpy(&Banner, CartROM + Header.BannerOffset, bannersize); + } printf("Game code: %.4s\n", Header.GameCode); @@ -1588,8 +1622,8 @@ bool LoadROMCommon(u32 filelength, const char *sram, bool direct) (u32)Header.GameCode[1] << 8 | (u32)Header.GameCode[0]; - u8 unitcode = Header.UnitCode; - CartIsDSi = (unitcode & 0x02) != 0; + u32 arm9base = Header.ARM9ROMOffset; + bool homebrew = (arm9base < 0x4000) || (gamecode == 0x23232323); ROMListEntry romparams; if (!ReadROMParams(gamecode, &romparams)) @@ -1599,7 +1633,7 @@ bool LoadROMCommon(u32 filelength, const char *sram, bool direct) romparams.GameCode = gamecode; romparams.ROMSize = CartROMSize; - if (*(u32*)&CartROM[0x20] < 0x4000) + if (homebrew) romparams.SaveMemType = 0; // no saveRAM for homebrew else romparams.SaveMemType = 2; // assume EEPROM 64k (TODO FIXME) @@ -1607,7 +1641,8 @@ bool LoadROMCommon(u32 filelength, const char *sram, bool direct) else printf("ROM entry: %08X %08X\n", romparams.ROMSize, romparams.SaveMemType); - if (romparams.ROMSize != filelength) printf("!! bad ROM size %d (expected %d) rounded to %d\n", filelength, romparams.ROMSize, CartROMSize); + if (romparams.ROMSize != romlen) + printf("!! bad ROM size %d (expected %d) rounded to %d\n", romlen, romparams.ROMSize, CartROMSize); // generate a ROM ID // note: most games don't check the actual value @@ -1622,7 +1657,7 @@ bool LoadROMCommon(u32 filelength, const char *sram, bool direct) if (romparams.SaveMemType >= 8 && romparams.SaveMemType <= 10) CartID |= 0x08000000; // NAND flag - if (CartIsDSi) + if (dsi) CartID |= 0x40000000; // cart ID for Jam with the Band @@ -1633,35 +1668,26 @@ bool LoadROMCommon(u32 filelength, const char *sram, bool direct) printf("Cart ID: %08X\n", CartID); - u32 arm9base = *(u32*)&CartROM[0x20]; - - if (arm9base < 0x8000) + if (arm9base >= 0x4000 && arm9base < 0x8000) { - if (arm9base >= 0x4000) + // reencrypt secure area if needed + if (*(u32*)&CartROM[arm9base] == 0xE7FFDEFF && *(u32*)&CartROM[arm9base+0x10] != 0xE7FFDEFF) { - // reencrypt secure area if needed - if (*(u32*)&CartROM[arm9base] == 0xE7FFDEFF && *(u32*)&CartROM[arm9base+0x10] != 0xE7FFDEFF) - { - printf("Re-encrypting cart secure area\n"); + printf("Re-encrypting cart secure area\n"); - strncpy((char*)&CartROM[arm9base], "encryObj", 8); + strncpy((char*)&CartROM[arm9base], "encryObj", 8); - Key1_InitKeycode(false, gamecode, 3, 2); - for (u32 i = 0; i < 0x800; i += 8) - Key1_Encrypt((u32*)&CartROM[arm9base + i]); + Key1_InitKeycode(false, gamecode, 3, 2); + for (u32 i = 0; i < 0x800; i += 8) + Key1_Encrypt((u32*)&CartROM[arm9base + i]); - Key1_InitKeycode(false, gamecode, 2, 2); - Key1_Encrypt((u32*)&CartROM[arm9base]); - } + Key1_InitKeycode(false, gamecode, 2, 2); + Key1_Encrypt((u32*)&CartROM[arm9base]); } } - if ((arm9base < 0x4000) || (gamecode == 0x23232323)) - { - CartIsHomebrew = true; - } - CartInserted = true; + DSi::SetCartInserted(true); u32 irversion = 0; if ((gamecode & 0xFF) == 'I') @@ -1672,7 +1698,7 @@ bool LoadROMCommon(u32 filelength, const char *sram, bool direct) irversion = 2; // Pokémon HG/SS, B/W, B2/W2 } - if (CartIsHomebrew) + if (homebrew) Cart = new CartHomebrew(CartROM, CartROMSize, CartID); else if (CartID & 0x08000000) Cart = new CartRetailNAND(CartROM, CartROMSize, CartID); @@ -1684,99 +1710,46 @@ bool LoadROMCommon(u32 filelength, const char *sram, bool direct) Cart = new CartRetail(CartROM, CartROMSize, CartID); if (Cart) - { Cart->Reset(); - if (direct) - { - NDS::SetupDirectBoot(); - Cart->SetupDirectBoot(); - } - } - - // encryption - Key1_InitKeycode(false, gamecode, 2, 2); - // save - printf("Save file: %s\n", sram); - if (Cart) Cart->LoadSave(sram, romparams.SaveMemType); + if (Cart && romparams.SaveMemType > 0) + Cart->SetupSave(romparams.SaveMemType); return true; } -bool LoadROM(const char* path, const char* sram, bool direct) +void LoadSave(const u8* savedata, u32 savelen) { - // TODO: streaming mode? for really big ROMs or systems with limited RAM - // for now we're lazy - // also TODO: validate what we're loading!! - - FILE* f = Platform::OpenFile(path, "rb"); - if (!f) - { - return false; - } - - NDS::Reset(); - - char* romname = strrchr((char*)path, '/'); - if (!romname) - { - romname = strrchr((char*)path, '\\'); - if (!romname) - romname = (char*)&path[-1]; - } - romname++; - strncpy(CartName, romname, 255); CartName[255] = '\0'; - - fseek(f, 0, SEEK_END); - u32 len = (u32)ftell(f); - - CartROMSize = 0x200; - while (CartROMSize < len) - CartROMSize <<= 1; - - CartROM = new u8[CartROMSize]; - memset(CartROM, 0, CartROMSize); - fseek(f, 0, SEEK_SET); - fread(CartROM, 1, len, f); - - fclose(f); - - return LoadROMCommon(len, sram, direct); + if (Cart) + Cart->LoadSave(savedata, savelen); } -bool LoadROM(const u8* romdata, u32 filelength, const char *sram, bool direct) +void SetupDirectBoot(std::string romname) { - NDS::Reset(); - - // TODO: make it more meaningful? - strncpy(CartName, "rom.nds", 256); + if (Cart) + Cart->SetupDirectBoot(romname); +} - u32 len = filelength; - CartROMSize = 0x200; - while (CartROMSize < len) - CartROMSize <<= 1; +void EjectCart() +{ + if (!CartInserted) return; - CartROM = new u8[CartROMSize]; - memset(CartROM, 0, CartROMSize); - memcpy(CartROM, romdata, filelength); + // ejecting the cart triggers the gamecard IRQ + NDS::SetIRQ(0, NDS::IRQ_CartIREQMC); + NDS::SetIRQ(1, NDS::IRQ_CartIREQMC); - return LoadROMCommon(filelength, sram, direct); -} + if (Cart) delete Cart; + Cart = nullptr; -void RelocateSave(const char* path, bool write) -{ - if (Cart) Cart->RelocateSave(path, write); -} + CartInserted = false; + if (CartROM) delete[] CartROM; + CartROM = nullptr; + CartROMSize = 0; + CartID = 0; -void FlushSRAMFile() -{ - if (Cart) Cart->FlushSRAMFile(); -} + DSi::SetCartInserted(false); -int ImportSRAM(const u8* data, u32 length) -{ - if (Cart) return Cart->ImportSRAM(data, length); - return 0; + // CHECKME: does an eject imply anything for the ROM/SPI transfer registers? } void ResetCart() @@ -1840,9 +1813,8 @@ void ROMPrepareData(u32 param) void WriteROMCnt(u32 val) { - ROMCnt = (val & 0xFF7F7FFF) | (ROMCnt & 0x00800000); - - if (!(SPICnt & (1<<15))) return; + u32 xferstart = (val & ~ROMCnt) & (1<<31); + ROMCnt = (val & 0xFF7F7FFF) | (ROMCnt & 0x20800000); // all this junk would only really be useful if melonDS was interfaced to // a DS cart reader @@ -1866,7 +1838,11 @@ void WriteROMCnt(u32 val) printf("key2 Y: %02X%08X\n", (u32)(Key2_Y>>32), (u32)Key2_Y); } - if (!(ROMCnt & (1<<31))) return; + // transfers will only start when bit31 changes from 0 to 1 + // and if AUXSPICNT is configured correctly + if (!(SPICnt & (1<<15))) return; + if (SPICnt & (1<<13)) return; + if (!xferstart) return; u32 datasize = (ROMCnt >> 24) & 0x7; if (datasize == 7) @@ -1880,6 +1856,8 @@ void WriteROMCnt(u32 val) *(u32*)&TransferCmd[0] = *(u32*)&ROMCommand[0]; *(u32*)&TransferCmd[4] = *(u32*)&ROMCommand[4]; + memset(TransferData, 0xFF, TransferLen); + /*printf("ROM COMMAND %04X %08X %02X%02X%02X%02X%02X%02X%02X%02X SIZE %04X\n", SPICnt, ROMCnt, TransferCmd[0], TransferCmd[1], TransferCmd[2], TransferCmd[3], @@ -1903,6 +1881,7 @@ void WriteROMCnt(u32 val) // thus a command would take 8 cycles to be transferred // and it would take 4 cycles to receive a word of data // TODO: advance read position if bit28 is set + // TODO: during a write transfer, bit23 is set immediately when beginning the transfer(?) u32 xfercycle = (ROMCnt & (1<<27)) ? 8 : 5; u32 cmddelay = 8; @@ -1983,6 +1962,10 @@ void WriteSPICnt(u16 val) } SPICnt = (SPICnt & 0x0080) | (val & 0xE043); + + // AUXSPICNT can be changed during a transfer + // in this case, the transfer continues until the end, even if bit13 or bit15 are cleared + // if the transfer speed is changed, the transfer continues at the new speed (TODO) if (SPICnt & (1<<7)) printf("!! CHANGING AUXSPICNT DURING TRANSFER: %04X\n", val); } @@ -2005,8 +1988,7 @@ void WriteSPIData(u8 val) { if (!(SPICnt & (1<<15))) return; if (!(SPICnt & (1<<13))) return; - - if (SPICnt & (1<<7)) printf("!! WRITING AUXSPIDATA DURING PENDING TRANSFER\n"); + if (SPICnt & (1<<7)) return; SPICnt |= (1<<7); diff --git a/src/NDSCart.h b/src/NDSCart.h index 9f39988..10286ac 100644 --- a/src/NDSCart.h +++ b/src/NDSCart.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,7 +19,10 @@ #ifndef NDSCART_H #define NDSCART_H +#include <string> + #include "types.h" +#include "Savestate.h" #include "NDS_Header.h" #include "FATStorage.h" @@ -33,15 +36,16 @@ public: CartCommon(u8* rom, u32 len, u32 chipid); virtual ~CartCommon(); + virtual u32 Type() { return 0x001; } + virtual u32 Checksum(); + virtual void Reset(); - virtual void SetupDirectBoot(); + virtual void SetupDirectBoot(std::string romname); virtual void DoSavestate(Savestate* file); - virtual void LoadSave(const char* path, u32 type); - virtual void RelocateSave(const char* path, bool write); - virtual int ImportSRAM(const u8* data, u32 length); - virtual void FlushSRAMFile(); + virtual void SetupSave(u32 type); + virtual void LoadSave(const u8* savedata, u32 savelen); virtual int ROMCommandStart(u8* cmd, u8* data, u32 len); virtual void ROMCommandFinish(u8* cmd, u8* data, u32 len); @@ -71,14 +75,14 @@ public: CartRetail(u8* rom, u32 len, u32 chipid); virtual ~CartRetail() override; + virtual u32 Type() override { return 0x101; } + virtual void Reset() override; virtual void DoSavestate(Savestate* file) override; - virtual void LoadSave(const char* path, u32 type) override; - virtual void RelocateSave(const char* path, bool write) override; - virtual int ImportSRAM(const u8* data, u32 length) override; - virtual void FlushSRAMFile() override; + virtual void SetupSave(u32 type) override; + virtual void LoadSave(const u8* savedata, u32 savelen) override; virtual int ROMCommandStart(u8* cmd, u8* data, u32 len) override; @@ -95,11 +99,9 @@ protected: u32 SRAMLength; u32 SRAMType; - char SRAMPath[1024]; - bool SRAMFileDirty; - u8 SRAMCmd; u32 SRAMAddr; + u32 SRAMFirstAddr; u8 SRAMStatus; }; @@ -110,12 +112,13 @@ public: CartRetailNAND(u8* rom, u32 len, u32 chipid); ~CartRetailNAND() override; + virtual u32 Type() override { return 0x102; } + void Reset() override; void DoSavestate(Savestate* file) override; - void LoadSave(const char* path, u32 type) override; - int ImportSRAM(const u8* data, u32 length) override; + void LoadSave(const u8* savedata, u32 savelen) override; int ROMCommandStart(u8* cmd, u8* data, u32 len) override; void ROMCommandFinish(u8* cmd, u8* data, u32 len) override; @@ -139,6 +142,8 @@ public: CartRetailIR(u8* rom, u32 len, u32 chipid, u32 irversion); ~CartRetailIR() override; + virtual u32 Type() override { return 0x103; } + void Reset() override; void DoSavestate(Savestate* file) override; @@ -157,6 +162,8 @@ public: CartRetailBT(u8* rom, u32 len, u32 chipid); ~CartRetailBT() override; + virtual u32 Type() override { return 0x104; } + void Reset() override; void DoSavestate(Savestate* file) override; @@ -171,8 +178,10 @@ public: CartHomebrew(u8* rom, u32 len, u32 chipid); ~CartHomebrew() override; + virtual u32 Type() override { return 0x201; } + void Reset() override; - void SetupDirectBoot() override; + void SetupDirectBoot(std::string romname) override; void DoSavestate(Savestate* file) override; @@ -180,6 +189,7 @@ public: void ROMCommandFinish(u8* cmd, u8* data, u32 len) override; private: + void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly); void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly); void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset); @@ -192,6 +202,7 @@ extern u32 ROMCnt; extern u8 ROMCommand[8]; +extern bool CartInserted; extern u8* CartROM; extern u32 CartROMSize; @@ -207,14 +218,12 @@ void Reset(); void DoSavestate(Savestate* file); void DecryptSecureArea(u8* out); -bool LoadROM(const char* path, const char* sram, bool direct); -bool LoadROM(const u8* romdata, u32 filelength, const char *sram, bool direct); - -void FlushSRAMFile(); -void RelocateSave(const char* path, bool write); +bool LoadROM(const u8* romdata, u32 romlen); +void LoadSave(const u8* savedata, u32 savelen); +void SetupDirectBoot(std::string romname); -int ImportSRAM(const u8* data, u32 length); +void EjectCart(); void ResetCart(); diff --git a/src/NDSCart_SRAMManager.cpp b/src/NDSCart_SRAMManager.cpp deleted file mode 100644 index addd122..0000000 --- a/src/NDSCart_SRAMManager.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/* - Copyright 2016-2021 Arisotura - - This file is part of melonDS. - - melonDS is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - melonDS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#include <stdio.h> -#include <string.h> -#include <unistd.h> -#include <time.h> -#include <atomic> -#include "NDSCart_SRAMManager.h" -#include "Platform.h" - -namespace NDSCart_SRAMManager -{ - -Platform::Thread* FlushThread; -std::atomic_bool FlushThreadRunning; -Platform::Mutex* SecondaryBufferLock; - -char Path[1024]; - -u8* Buffer; -u32 Length; - -u8* SecondaryBuffer; -u32 SecondaryBufferLength; - -time_t TimeAtLastFlushRequest; - -// We keep versions in case the user closes the application before -// a flush cycle is finished. -u32 PreviousFlushVersion; -u32 FlushVersion; - -void FlushThreadFunc(); - -bool Init() -{ - SecondaryBufferLock = Platform::Mutex_Create(); - - return true; -} - -void DeInit() -{ - if (FlushThreadRunning) - { - FlushThreadRunning = false; - Platform::Thread_Wait(FlushThread); - Platform::Thread_Free(FlushThread); - FlushSecondaryBuffer(); - } - - if (SecondaryBuffer) delete[] SecondaryBuffer; - SecondaryBuffer = NULL; - - Platform::Mutex_Free(SecondaryBufferLock); -} - -void Setup(const char* path, u8* buffer, u32 length) -{ - // Flush SRAM in case there is unflushed data from previous state. - FlushSecondaryBuffer(); - - Platform::Mutex_Lock(SecondaryBufferLock); - - strncpy(Path, path, 1023); - Path[1023] = '\0'; - - Buffer = buffer; - Length = length; - - if(SecondaryBuffer) delete[] SecondaryBuffer; // Delete secondary buffer, there might be previous state. - - SecondaryBuffer = new u8[length]; - SecondaryBufferLength = length; - - FlushVersion = 0; - PreviousFlushVersion = 0; - TimeAtLastFlushRequest = 0; - - Platform::Mutex_Unlock(SecondaryBufferLock); - - if (path[0] != '\0' && !FlushThreadRunning) - { - FlushThread = Platform::Thread_Create(FlushThreadFunc); - FlushThreadRunning = true; - } - else if (path[0] == '\0' && FlushThreadRunning) - { - FlushThreadRunning = false; - Platform::Thread_Wait(FlushThread); - Platform::Thread_Free(FlushThread); - } -} - -void RequestFlush() -{ - Platform::Mutex_Lock(SecondaryBufferLock); - printf("NDS SRAM: Flush requested\n"); - memcpy(SecondaryBuffer, Buffer, Length); - FlushVersion++; - TimeAtLastFlushRequest = time(NULL); - Platform::Mutex_Unlock(SecondaryBufferLock); -} - -void FlushThreadFunc() -{ - for (;;) - { - Platform::Sleep(100 * 1000); // 100ms - - if (!FlushThreadRunning) return; - - // We debounce for two seconds after last flush request to ensure that writing has finished. - if (TimeAtLastFlushRequest == 0 || difftime(time(NULL), TimeAtLastFlushRequest) < 2) - { - continue; - } - - FlushSecondaryBuffer(); - } -} - -void FlushSecondaryBuffer(u8* dst, s32 dstLength) -{ - // When flushing to a file, there's no point in re-writing the exact same data. - if (!dst && !NeedsFlush()) return; - // When flushing to memory, we don't know if dst already has any data so we only check that we CAN flush. - if (dst && dstLength < SecondaryBufferLength) return; - - Platform::Mutex_Lock(SecondaryBufferLock); - if (dst) - { - memcpy(dst, SecondaryBuffer, SecondaryBufferLength); - } - else - { - FILE* f = Platform::OpenFile(Path, "wb"); - if (f) - { - printf("NDS SRAM: Written\n"); - fwrite(SecondaryBuffer, SecondaryBufferLength, 1, f); - fclose(f); - } - } - PreviousFlushVersion = FlushVersion; - TimeAtLastFlushRequest = 0; - Platform::Mutex_Unlock(SecondaryBufferLock); -} - -bool NeedsFlush() -{ - return FlushVersion != PreviousFlushVersion; -} - -void UpdateBuffer(u8* src, s32 srcLength) -{ - if (!src || srcLength != Length) return; - - // should we create a lock for the primary buffer? this method is not intended to be called from a secondary thread in the way Flush is - memcpy(Buffer, src, srcLength); - Platform::Mutex_Lock(SecondaryBufferLock); - memcpy(SecondaryBuffer, src, srcLength); - Platform::Mutex_Unlock(SecondaryBufferLock); - - PreviousFlushVersion = FlushVersion; -} - -} diff --git a/src/NDS_Header.h b/src/NDS_Header.h index c2079bf..2c94f4c 100644 --- a/src/NDS_Header.h +++ b/src/NDS_Header.h @@ -1,5 +1,5 @@ /*
- Copyright 2016-2021 Arisotura, WaluigiWare64
+ Copyright 2016-2022 melonDS team, WaluigiWare64
This file is part of melonDS.
diff --git a/src/NonStupidBitfield.h b/src/NonStupidBitfield.h index 45b160e..badefaf 100644 --- a/src/NonStupidBitfield.h +++ b/src/NonStupidBitfield.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, RSDuck + Copyright 2016-2022 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/OpenGLSupport.cpp b/src/OpenGLSupport.cpp index 9df814b..aee107a 100644 --- a/src/OpenGLSupport.cpp +++ b/src/OpenGLSupport.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/OpenGLSupport.h b/src/OpenGLSupport.h index cbbb34e..d40246c 100644 --- a/src/OpenGLSupport.h +++ b/src/OpenGLSupport.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/Platform.h b/src/Platform.h index bbdc245..f2997ef 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -32,6 +32,10 @@ void DeInit(); void StopEmu(); +// instance ID, for local multiplayer +int InstanceID(); +std::string InstanceFileSuffix(); + // configuration values enum ConfigEntry @@ -77,7 +81,6 @@ enum ConfigEntry Firm_Color, Firm_Message, Firm_MAC, - Firm_RandomizeMAC, AudioBitrate, }; @@ -144,13 +147,30 @@ void Mutex_Lock(Mutex* mutex); void Mutex_Unlock(Mutex* mutex); bool Mutex_TryLock(Mutex* mutex); +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); + // local multiplayer comm interface // packet type: DS-style TX header (12 bytes) + original 802.11 frame bool MP_Init(); void MP_DeInit(); -int MP_SendPacket(u8* data, int len); -int MP_RecvPacket(u8* data, bool block); +void MP_Begin(); +void MP_End(); +int MP_SendPacket(u8* data, int len, u64 timestamp); +int MP_RecvPacket(u8* data, u64* timestamp); +int MP_SendCmd(u8* data, int len, u64 timestamp); +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid); +int MP_SendAck(u8* data, int len, u64 timestamp); +int MP_RecvHostPacket(u8* data, u64* timestamp); +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask); + // LAN comm interface // packet type: Ethernet (802.3) @@ -159,7 +179,15 @@ void LAN_DeInit(); int LAN_SendPacket(u8* data, int len); int LAN_RecvPacket(u8* data); -void Sleep(u64 usecs); + +// interface for camera emulation +// camera numbers: +// 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); } diff --git a/src/ROMList.h b/src/ROMList.h index 2f168e5..ab23112 100644 --- a/src/ROMList.h +++ b/src/ROMList.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,6 +19,8 @@ #ifndef ROMLIST_H #define ROMLIST_H +#include "types.h" + struct ROMListEntry { u32 GameCode; diff --git a/src/RTC.cpp b/src/RTC.cpp index 905a8d8..1c02cbb 100644 --- a/src/RTC.cpp +++ b/src/RTC.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/SPI.cpp b/src/SPI.cpp index 80ef336..e990b3a 100644 --- a/src/SPI.cpp +++ b/src/SPI.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -127,8 +127,8 @@ void LoadDefaultFirmware() if (NDS::ConsoleType == 1) { Firmware[0x1D] = 0x57; // DSi - Firmware[0x2F] = 0x18; - Firmware[0x1FD] = 0x02; + Firmware[0x2F] = 0x0F; + Firmware[0x1FD] = 0x01; Firmware[0x1FE] = 0x20; } else @@ -215,41 +215,52 @@ void LoadDefaultFirmware() // wifi access points // TODO: WFC ID?? - u32 apdata = userdata - 0x400; - memset(&Firmware[apdata], 0, 0x300); - - strcpy((char*)&Firmware[apdata+0x40], "melonAP"); - if (NDS::ConsoleType == 1) *(u16*)&Firmware[apdata+0xEA] = 1400; - Firmware[apdata+0xEF] = 0x01; - *(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000); - - apdata += 0x100; - Firmware[apdata+0xE7] = 0xFF; - Firmware[apdata+0xEF] = 0x01; - *(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000); - - apdata += 0x100; - Firmware[apdata+0xE7] = 0xFF; - Firmware[apdata+0xEF] = 0x01; - *(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000); - - if (NDS::ConsoleType == 1) + FILE* f = Platform::OpenLocalFile("wfcsettings.bin"+Platform::InstanceFileSuffix(), "rb"); + if (!f) f = Platform::OpenLocalFile("wfcsettings.bin", "rb"); + if (f) { - apdata = userdata - 0xA00; - Firmware[apdata+0xE7] = 0xFF; + u32 apdata = userdata - 0xA00; + fread(&Firmware[apdata], 0x900, 1, f); + fclose(f); + } + else + { + u32 apdata = userdata - 0x400; + memset(&Firmware[apdata], 0, 0x300); + + strcpy((char*)&Firmware[apdata+0x40], "melonAP"); + if (NDS::ConsoleType == 1) *(u16*)&Firmware[apdata+0xEA] = 1400; + Firmware[apdata+0xEF] = 0x01; *(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000); - apdata += 0x200; + apdata += 0x100; Firmware[apdata+0xE7] = 0xFF; + Firmware[apdata+0xEF] = 0x01; *(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000); - apdata += 0x200; + apdata += 0x100; Firmware[apdata+0xE7] = 0xFF; + Firmware[apdata+0xEF] = 0x01; *(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000); + + if (NDS::ConsoleType == 1) + { + apdata = userdata - 0xA00; + Firmware[apdata+0xE7] = 0xFF; + *(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000); + + apdata += 0x200; + Firmware[apdata+0xE7] = 0xFF; + *(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000); + + apdata += 0x200; + Firmware[apdata+0xE7] = 0xFF; + *(u16*)&Firmware[apdata+0xFE] = CRC16(&Firmware[apdata], 0xFE, 0x0000); + } } } -void LoadFirmwareFromFile(FILE* f) +void LoadFirmwareFromFile(FILE* f, bool makecopy) { fseek(f, 0, SEEK_END); @@ -261,7 +272,9 @@ void LoadFirmwareFromFile(FILE* f) fread(Firmware, 1, FirmwareLength, f); // take a backup - std::string fwBackupPath = FirmwarePath + ".bak"; + std::string fwBackupPath; + if (!makecopy) fwBackupPath = FirmwarePath + ".bak"; + else fwBackupPath = FirmwarePath; FILE* bf = Platform::OpenLocalFile(fwBackupPath, "rb"); if (!bf) { @@ -323,15 +336,24 @@ void Reset() else FirmwarePath = Platform::GetConfigString(Platform::FirmwarePath); + bool makecopy = false; + std::string origpath = FirmwarePath; + FirmwarePath += Platform::InstanceFileSuffix(); + FILE* f = Platform::OpenLocalFile(FirmwarePath, "rb"); if (!f) { + f = Platform::OpenLocalFile(origpath, "rb"); + makecopy = true; + } + if (!f) + { printf("Firmware not found! Generating default firmware.\n"); FirmwarePath = ""; } else { - LoadFirmwareFromFile(f); + LoadFirmwareFromFile(f, makecopy); fclose(f); } } @@ -375,28 +397,28 @@ void Reset() *(u16*)&Firmware[userdata+0x72] = CRC16(&Firmware[userdata], 0x70, 0xFFFF); - if (firmoverride) + //if (firmoverride) { u8 mac[6]; - bool rep; + bool rep = false; + + memcpy(mac, &Firmware[0x36], 6); + + if (firmoverride) + rep = Platform::GetConfigArray(Platform::Firm_MAC, mac); - if (Platform::GetConfigBool(Platform::Firm_RandomizeMAC)) + int inst = Platform::InstanceID(); + if (inst > 0) { - mac[0] = 0x00; - mac[1] = 0x09; - mac[2] = 0xBF; - mac[3] = rand()&0xFF; - mac[4] = rand()&0xFF; - mac[5] = rand()&0xFF; rep = true; - } - else - { - rep = Platform::GetConfigArray(Platform::Firm_MAC, mac); + mac[3] += inst; + mac[4] += inst*0x44; + mac[5] += inst*0x10; } if (rep) { + mac[0] &= 0xFC; // ensure the MAC isn't a broadcast MAC memcpy(&Firmware[0x36], mac, 6); *(u16*)&Firmware[0x2A] = CRC16(&Firmware[0x2C], *(u16*)&Firmware[0x2C], 0x0000); @@ -463,6 +485,7 @@ void SetupDirectBoot(bool dsi) } } +u32 GetFirmwareLength() { return FirmwareLength; } u8 GetConsoleType() { return Firmware[0x1D]; } u8 GetWifiVersion() { return Firmware[0x2F]; } u8 GetNWifiVersion() { return Firmware[0x1FD]; } // for DSi; will return 0xFF on a DS @@ -563,6 +586,7 @@ void Write(u8 val, u32 hold) default: printf("unknown firmware SPI command %02X\n", CurCmd); + Data = 0xFF; break; } @@ -573,12 +597,27 @@ void Write(u8 val, u32 hold) FILE* f = Platform::OpenLocalFile(FirmwarePath, "r+b"); if (f) { - u32 cutoff = 0x7FA00 & FirmwareMask; + u32 cutoff = ((NDS::ConsoleType==1) ? 0x7F400 : 0x7FA00) & FirmwareMask; fseek(f, cutoff, SEEK_SET); fwrite(&Firmware[cutoff], FirmwareLength-cutoff, 1, f); fclose(f); } } + else + { + char wfcfile[50] = {0}; + int inst = Platform::InstanceID(); + if (inst > 0) snprintf(wfcfile, 49, "wfcsettings.bin", Platform::InstanceID()); + else strncpy(wfcfile, "wfcsettings.bin", 49); + + FILE* f = Platform::OpenLocalFile(wfcfile, "wb"); + if (f) + { + u32 cutoff = 0x7F400 & FirmwareMask; + fwrite(&Firmware[cutoff], 0x900, 1, f); + fclose(f); + } + } } } @@ -617,12 +656,15 @@ void Reset() Registers[4] = 0x40; RegMasks[0] = 0x7F; - RegMasks[1] = 0x01; + RegMasks[1] = 0x00; RegMasks[2] = 0x01; RegMasks[3] = 0x03; RegMasks[4] = 0x0F; } +bool GetBatteryLevelOkay() { return !Registers[1]; } +void SetBatteryLevelOkay(bool okay) { Registers[1] = okay ? 0x00 : 0x01; } + void DoSavestate(Savestate* file) { file->Section("SPPW"); @@ -659,6 +701,7 @@ void Write(u8 val, u32 hold) if (DataPos == 1) { + // TODO: DSi-specific registers in DSi mode u32 regid = Index & 0x07; if (Index & 0x80) @@ -891,6 +934,8 @@ void WriteCnt(u16 val) } } + // TODO: presumably the transfer speed can be changed during a transfer + // like with the NDSCart SPI interface Cnt = (Cnt & 0x0080) | (val & 0xCF03); if (val & 0x0400) printf("!! CRAPOED 16BIT SPI MODE\n"); if (Cnt & (1<<7)) printf("!! CHANGING SPICNT DURING TRANSFER: %04X\n", val); @@ -925,8 +970,7 @@ u8 ReadData() void WriteData(u8 val) { if (!(Cnt & (1<<15))) return; - - if (Cnt & (1<<7)) printf("!! WRITING AUXSPIDATA DURING PENDING TRANSFER\n"); + if (Cnt & (1<<7)) return; Cnt |= (1<<7); switch (Cnt & 0x0300) @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -28,6 +28,7 @@ void SetupDirectBoot(bool dsi); u32 FixFirmwareLength(u32 originalLength); +u32 GetFirmwareLength(); u8 GetConsoleType(); u8 GetWifiVersion(); u8 GetNWifiVersion(); @@ -36,6 +37,14 @@ u8* GetWifiMAC(); } +namespace SPI_Powerman +{ + +bool GetBatteryLevelOkay(); +void SetBatteryLevelOkay(bool okay); + +} + namespace SPI_TSC { diff --git a/src/SPU.cpp b/src/SPU.cpp index 1de6a26..9f245a2 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -184,6 +184,12 @@ void DoSavestate(Savestate* file) } +void SetPowerCnt(u32 val) +{ + // TODO +} + + void SetInterpolation(int type) { InterpType = type; @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -31,6 +31,8 @@ void Stop(); void DoSavestate(Savestate* file); +void SetPowerCnt(u32 val); + // 0=none 1=linear 2=cosine 3=cubic void SetInterpolation(int type); diff --git a/src/Savestate.cpp b/src/Savestate.cpp index 1bfe937..9128939 100644 --- a/src/Savestate.cpp +++ b/src/Savestate.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -43,7 +43,10 @@ * different minor means adjustments may have to be made */ -Savestate::Savestate(const char* filename, bool save) +// TODO: buffering system! or something of that sort +// repeated fread/fwrite is slow on Switch + +Savestate::Savestate(std::string filename, bool save) { const char* magic = "MELN"; @@ -55,7 +58,7 @@ Savestate::Savestate(const char* filename, bool save) file = Platform::OpenLocalFile(filename, "wb"); if (!file) { - printf("savestate: file %s doesn't exist\n", filename); + printf("savestate: file %s doesn't exist\n", filename.c_str()); Error = true; return; } @@ -74,7 +77,7 @@ Savestate::Savestate(const char* filename, bool save) file = Platform::OpenFile(filename, "rb"); if (!file) { - printf("savestate: file %s doesn't exist\n", filename); + printf("savestate: file %s doesn't exist\n", filename.c_str()); Error = true; return; } diff --git a/src/Savestate.h b/src/Savestate.h index 60d34cc..275b402 100644 --- a/src/Savestate.h +++ b/src/Savestate.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,6 +19,7 @@ #ifndef SAVESTATE_H #define SAVESTATE_H +#include <string> #include <stdio.h> #include "types.h" @@ -28,7 +29,7 @@ class Savestate { public: - Savestate(const char* filename, bool save); + Savestate(std::string filename, bool save); ~Savestate(); bool Error; diff --git a/src/Wifi.cpp b/src/Wifi.cpp index e6fdeed..c2614e7 100644 --- a/src/Wifi.cpp +++ b/src/Wifi.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -23,6 +23,8 @@ #include "Wifi.h" #include "WifiAP.h" #include "Platform.h" +#include "ARM.h" +#include "GPU.h" namespace Wifi @@ -31,37 +33,52 @@ namespace Wifi //#define WIFI_LOG printf #define WIFI_LOG(...) {} +#define PRINT_MAC(pf, mac) printf("%s: %02X:%02X:%02X:%02X:%02X:%02X\n", pf, (mac)[0], (mac)[1], (mac)[2], (mac)[3], (mac)[4], (mac)[5]); + u8 RAM[0x2000]; u16 IO[0x1000>>1]; #define IOPORT(x) IO[(x)>>1] +#define IOPORT8(x) ((u8*)IO)[x] + +// destination MACs for MP frames +const u8 MPCmdMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x00}; +const u8 MPReplyMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x10}; +const u8 MPAckMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x03}; + +const int kTimerInterval = 8; +const u32 kTimeCheckMask = ~(kTimerInterval - 1); + +bool Enabled; +bool PowerOn; + +s32 TimerError; u16 Random; +// general, always-on microsecond counter +u64 USTimestamp; + u64 USCounter; u64 USCompare; bool BlockBeaconIRQ14; u32 CmdCounter; -u16 BBCnt; -u8 BBWrite; u8 BBRegs[0x100]; u8 BBRegsRO[0x100]; u8 RFVersion; -u16 RFCnt; -u16 RFData1; -u16 RFData2; u32 RFRegs[0x40]; struct TXSlot { + bool Valid; u16 Addr; u16 Length; u8 Rate; u8 CurPhase; - u32 CurPhaseTime; + int CurPhaseTime; u32 HalfwordTimeMask; }; @@ -69,21 +86,28 @@ TXSlot TXSlots[6]; u8 RXBuffer[2048]; u32 RXBufferPtr; -u32 RXTime; +int RXTime; u32 RXHalfwordTimeMask; -u16 RXEndAddr; u32 ComStatus; // 0=waiting for packets 1=receiving 2=sending u32 TXCurSlot; u32 RXCounter; int MPReplyTimer; -int MPNumReplies; +u16 MPClientMask, MPClientFail; + +u8 MPClientReplies[15*1024]; bool MPInited; bool LANInited; +int USUntilPowerOn; +bool ForcePowerOn; +// MULTIPLAYER SYNC APPARATUS +bool IsMPClient; +u64 NextSync; // for clients: timestamp for next sync point +u64 RXTimestamp; // multiplayer host TX sequence: // 1. preamble @@ -110,21 +134,26 @@ bool LANInited; // 4 = switching from TX to RX // 5 = MP host data sent, waiting for replies (RFPINS=0x0084) // 6 = RX -// 7 = ?? +// 7 = switching from RX reply to TX ack // 8 = MP client sending reply, MP host sending ack (RFPINS=0x0046) // 9 = idle // wifi TODO: -// * power saving -// * RXSTAT, multiplay reply errors +// * RXSTAT // * TX errors (if applicable) bool Init() { - MPInited = false; - LANInited = false; + //MPInited = false; + //LANInited = false; + + Platform::MP_Init(); + MPInited = true; + + Platform::LAN_Init(); + LANInited = true; WifiAP::Init(); @@ -146,6 +175,9 @@ void Reset() memset(RAM, 0, 0x2000); memset(IO, 0, 0x1000); + Enabled = false; + PowerOn = false; + Random = 1; memset(BBRegs, 0, 0x100); @@ -198,19 +230,39 @@ void Reset() memset(&IOPORT(0x018), 0xFF, 6); memset(&IOPORT(0x020), 0xFF, 6); + // TODO: find out what the initial values are + IOPORT(W_PowerUS) = 0x0001; + + USTimestamp = 0; + USCounter = 0; USCompare = 0; BlockBeaconIRQ14 = false; + memset(TXSlots, 0, sizeof(TXSlots)); ComStatus = 0; TXCurSlot = -1; RXCounter = 0; + memset(RXBuffer, 0, sizeof(RXBuffer)); + RXBufferPtr = 0; + RXTime = 0; + RXHalfwordTimeMask = 0xFFFFFFFF; + MPReplyTimer = 0; - MPNumReplies = 0; + MPClientMask = 0; + MPClientFail = 0; + memset(MPClientReplies, 0, sizeof(MPClientReplies)); CmdCounter = 0; + USUntilPowerOn = 0; + ForcePowerOn = false; + + IsMPClient = false; + NextSync = 0; + RXTimestamp = 0; + WifiAP::Reset(); } @@ -226,8 +278,13 @@ void DoSavestate(Savestate* file) file->VarArray(RAM, 0x2000); file->VarArray(IO, 0x1000); + file->Bool32(&Enabled); + file->Bool32(&PowerOn); + file->Var16(&Random); + file->Var32((u32*)&TimerError); + file->VarArray(BBRegs, 0x100); file->VarArray(BBRegsRO, 0x100); @@ -238,17 +295,107 @@ void DoSavestate(Savestate* file) file->Var64(&USCompare); file->Bool32(&BlockBeaconIRQ14); + file->Var32(&CmdCounter); + + file->Var64(&USTimestamp); + + for (int i = 0; i < 6; i++) + { + TXSlot* slot = &TXSlots[i]; + + file->Bool32(&slot->Valid); + file->Var16(&slot->Addr); + file->Var16(&slot->Length); + file->Var8(&slot->Rate); + file->Var8(&slot->CurPhase); + file->Var32((u32*)&slot->CurPhaseTime); + file->Var32(&slot->HalfwordTimeMask); + } + + file->VarArray(RXBuffer, sizeof(RXBuffer)); + file->Var32(&RXBufferPtr); + file->Var32((u32*)&RXTime); + file->Var32(&RXHalfwordTimeMask); + file->Var32(&ComStatus); file->Var32(&TXCurSlot); file->Var32(&RXCounter); file->Var32((u32*)&MPReplyTimer); - file->Var32((u32*)&MPNumReplies); + file->Var16(&MPClientMask); + file->Var16(&MPClientFail); - file->Var32(&CmdCounter); + file->VarArray(MPClientReplies, sizeof(MPClientReplies)); + + file->Var32((u32*)&USUntilPowerOn); + file->Bool32(&ForcePowerOn); + + file->Bool32(&IsMPClient); + file->Var64(&NextSync); + file->Var64(&RXTimestamp); } +void ScheduleTimer(bool first) +{ + if (first) TimerError = 0; + + s32 cycles = 33513982 * kTimerInterval; + cycles -= TimerError; + s32 delay = (cycles + 999999) / 1000000; + TimerError = (delay * 1000000) - cycles; + + NDS::ScheduleEvent(NDS::Event_Wifi, !first, delay, USTimer, 0); +} + +void UpdatePowerOn() +{ + bool on = Enabled; + + if (NDS::ConsoleType == 1) + { + // TODO for DSi: + // * W_POWER_US doesn't work (atleast on DWM-W024) + // * other registers like GPIO_WIFI may also control wifi power/clock + // * turning wifi off via POWCNT2 while sending breaks further attempts at sending frames + } + else + { + on = on && ((IOPORT(W_PowerUS) & 0x1) == 0); + } + + if (on == PowerOn) + return; + + PowerOn = on; + if (on) + { + printf("WIFI: ON\n"); + + ScheduleTimer(true); + + Platform::MP_Begin(); + } + else + { + printf("WIFI: OFF\n"); + + NDS::CancelEvent(NDS::Event_Wifi); + + Platform::MP_End(); + } +} + +void SetPowerCnt(u32 val) +{ + Enabled = val & (1<<1); + UpdatePowerOn(); +} + + +void PowerDown(); +void StartTX_Beacon(); + void SetIRQ(u32 irq) { u32 oldflags = IOPORT(W_IF) & IOPORT(W_IE); @@ -267,7 +414,8 @@ void SetIRQ13() if (!(IOPORT(W_PowerTX) & 0x0002)) { IOPORT(0x034) = 0x0002; - // TODO: 03C + //PowerDown(); + // FIXME!! IOPORT(W_RFPins) = 0x0046; IOPORT(W_RFStatus) = 9; } @@ -316,20 +464,32 @@ void SetIRQ15() void SetStatus(u32 status) { - // TODO, eventually: states 2/4, also find out what state 7 is + // TODO, eventually: states 2/4/7 u16 rfpins[10] = {0x04, 0x84, 0, 0x46, 0, 0x84, 0x87, 0, 0x46, 0x04}; IOPORT(W_RFStatus) = status; IOPORT(W_RFPins) = rfpins[status]; } -bool MACEqual(u8* a, u8* b) +void PowerDown() { - return (*(u32*)&a[0] == *(u32*)&b[0]) && (*(u16*)&a[4] == *(u16*)&b[4]); + IOPORT(W_TXReqRead) &= ~0x000F; + IOPORT(W_PowerState) |= 0x0200; + + // if the RF hardware is powered down while still sending or receiving, + // the current frame is completed before going idle + if (!ComStatus) + { + SetStatus(9); + } } -// TODO: set RFSTATUS/RFPINS +bool MACEqual(const u8* a, const u8* b) +{ + return (*(u32*)&a[0] == *(u32*)&b[0]) && (*(u16*)&a[4] == *(u16*)&b[4]); +} + int PreambleLen(int rate) { @@ -338,6 +498,16 @@ int PreambleLen(int rate) return 192; } +u32 NumClients(u16 bitmask) +{ + u32 ret = 0; + for (int i = 1; i < 16; i++) + { + if (bitmask & (1<<i)) ret++; + } + return ret; +} + void IncrementTXCount(TXSlot* slot) { u8 cnt = RAM[slot->Addr + 0x4]; @@ -345,6 +515,19 @@ void IncrementTXCount(TXSlot* slot) *(u16*)&RAM[slot->Addr + 0x4] = cnt; } +void ReportMPReplyErrors(u16 clientfail) +{ + // TODO: do these trigger any IRQ? + + for (int i = 1; i < 16; i++) + { + if (!(clientfail & (1<<i))) + continue; + + IOPORT8(W_CMDStat0 + i)++; + } +} + void StartTX_LocN(int nslot, int loc) { TXSlot* slot = &TXSlots[nslot]; @@ -369,7 +552,8 @@ void StartTX_Cmd() // TODO: cancel the transfer if there isn't enough time left (check CMDCOUNT) - if (IOPORT(W_TXSlotCmd) & 0x7000) printf("wifi: !! unusual TXSLOT_CMD bits set %04X\n", IOPORT(W_TXSlotCmd)); + if (IOPORT(W_TXSlotCmd) & 0x3000) + printf("wifi: !! unusual TXSLOT_CMD bits set %04X\n", IOPORT(W_TXSlotCmd)); slot->Addr = (IOPORT(W_TXSlotCmd) & 0x0FFF) << 1; slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; @@ -399,9 +583,11 @@ void StartTX_Beacon() IOPORT(W_TXBusy) |= 0x0010; } -// TODO eventually: there is a small delay to firing TX void FireTX() { + if (!(IOPORT(W_RXCnt) & 0x8000)) + return; + u16 txbusy = IOPORT(W_TXBusy); u16 txreq = IOPORT(W_TXReqRead); @@ -441,24 +627,89 @@ void FireTX() } } +void SendMPDefaultReply() +{ + u8 reply[12 + 32]; + + *(u16*)&reply[0xA] = 28; // length + + // rate + //if (TXSlots[1].Rate == 2) reply[0x8] = 0x14; + //else reply[0x8] = 0xA; + // TODO + reply[0x8] = 0x14; + + *(u16*)&reply[0xC + 0x00] = 0x0158; + *(u16*)&reply[0xC + 0x02] = 0x00F0;//0; // TODO?? + *(u16*)&reply[0xC + 0x04] = IOPORT(W_BSSID0); + *(u16*)&reply[0xC + 0x06] = IOPORT(W_BSSID1); + *(u16*)&reply[0xC + 0x08] = IOPORT(W_BSSID2); + *(u16*)&reply[0xC + 0x0A] = IOPORT(W_MACAddr0); + *(u16*)&reply[0xC + 0x0C] = IOPORT(W_MACAddr1); + *(u16*)&reply[0xC + 0x0E] = IOPORT(W_MACAddr2); + *(u16*)&reply[0xC + 0x10] = 0x0903; + *(u16*)&reply[0xC + 0x12] = 0x00BF; + *(u16*)&reply[0xC + 0x14] = 0x1000; + *(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)); + WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); +} + void SendMPReply(u16 clienttime, u16 clientmask) { TXSlot* slot = &TXSlots[5]; // mark the last packet as success. dunno what the MSB is, it changes. + //if (slot->Valid) if (IOPORT(W_TXSlotReply2) & 0x8000) *(u16*)&RAM[slot->Addr] = 0x0001; + // CHECKME!! + // can the transfer rate for MP replies be set, or is it determined from the CMD transfer rate? + // how does it work for default empty replies? + slot->Rate = 2; + IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); IOPORT(W_TXSlotReply1) = 0; + if (!(IOPORT(W_TXSlotReply2) & 0x8000)) + { + slot->Valid = false; + } + else + { + slot->Valid = true; + + slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; + slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; + + // the packet is entirely ignored if it lasts longer than the maximum reply time + u32 duration = PreambleLen(slot->Rate) + (slot->Length * (slot->Rate==2 ? 4:8)); + if (duration > clienttime) + slot->Valid = false; + } + + //if (RAM[slot->Addr+4] > 0) + // printf("REPLY RETRY COUNTER %d (%04X)\n", RAM[slot->Addr+4], IOPORT(W_TXSlotReply2)); + // this seems to be set upon IRQ0 // TODO: how does it behave if the packet addr is changed before it gets sent? (maybe just not possible) - if (IOPORT(W_TXSlotReply2) & 0x8000) + if (slot->Valid) { - slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; //*(u16*)&RAM[slot->Addr + 0x4] = 0x0001; IncrementTXCount(slot); + + slot->CurPhase = 0; + int txlen = Platform::MP_SendReply(&RAM[slot->Addr], 12 + slot->Length, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/%d bytes of MP reply\n", txlen, 12 + slot->Length); + } + else + { + slot->CurPhase = 10; + + SendMPDefaultReply(); } u16 clientnum = 0; @@ -468,43 +719,12 @@ void SendMPReply(u16 clienttime, u16 clientmask) clientnum++; } - slot->CurPhase = 0; - slot->CurPhaseTime = 16 + ((clienttime + 10) * clientnum); + slot->CurPhaseTime = 16 + ((clienttime + 10) * clientnum) + PreambleLen(slot->Rate); IOPORT(W_TXBusy) |= 0x0080; } -void SendMPDefaultReply() -{ - u8 reply[12 + 32]; - - *(u16*)&reply[0xA] = 28; // length - - // rate - //if (TXSlots[1].Rate == 2) reply[0x8] = 0x14; - //else reply[0x8] = 0xA; - // TODO - reply[0x8] = 0x14; - - *(u16*)&reply[0xC + 0x00] = 0x0158; - *(u16*)&reply[0xC + 0x02] = 0x00F0;//0; // TODO?? - *(u16*)&reply[0xC + 0x04] = IOPORT(W_BSSID0); - *(u16*)&reply[0xC + 0x06] = IOPORT(W_BSSID1); - *(u16*)&reply[0xC + 0x08] = IOPORT(W_BSSID2); - *(u16*)&reply[0xC + 0x0A] = IOPORT(W_MACAddr0); - *(u16*)&reply[0xC + 0x0C] = IOPORT(W_MACAddr1); - *(u16*)&reply[0xC + 0x0E] = IOPORT(W_MACAddr2); - *(u16*)&reply[0xC + 0x10] = 0x0903; - *(u16*)&reply[0xC + 0x12] = 0x00BF; - *(u16*)&reply[0xC + 0x14] = 0x1000; - *(u16*)&reply[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; - *(u32*)&reply[0xC + 0x18] = 0; - - int txlen = Platform::MP_SendPacket(reply, 12+28); - WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); -} - -void SendMPAck() +void SendMPAck(u16 clientfail) { u8 ack[12 + 32]; @@ -514,41 +734,32 @@ void SendMPAck() if (TXSlots[1].Rate == 2) ack[0x8] = 0x14; else ack[0x8] = 0xA; - *(u16*)&ack[0xC + 0x00] = 0x0218; - *(u16*)&ack[0xC + 0x02] = 0; - *(u16*)&ack[0xC + 0x04] = 0x0903; - *(u16*)&ack[0xC + 0x06] = 0x00BF; - *(u16*)&ack[0xC + 0x08] = 0x0300; - *(u16*)&ack[0xC + 0x0A] = IOPORT(W_BSSID0); - *(u16*)&ack[0xC + 0x0C] = IOPORT(W_BSSID1); - *(u16*)&ack[0xC + 0x0E] = IOPORT(W_BSSID2); - *(u16*)&ack[0xC + 0x10] = IOPORT(W_MACAddr0); - *(u16*)&ack[0xC + 0x12] = IOPORT(W_MACAddr1); - *(u16*)&ack[0xC + 0x14] = IOPORT(W_MACAddr2); - *(u16*)&ack[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; - *(u16*)&ack[0xC + 0x18] = 0x0033; // ??? - *(u16*)&ack[0xC + 0x1A] = 0; - *(u32*)&ack[0xC + 0x1C] = 0; - - int txlen = Platform::MP_SendPacket(ack, 12+32); - WIFI_LOG("wifi: sent %d/44 bytes of MP ack, %d %d\n", txlen, ComStatus, RXTime); -} - -u32 NumClients(u16 bitmask) -{ - u32 ret = 0; - for (int i = 1; i < 16; i++) - { - if (bitmask & (1<<i)) ret++; - } - return ret; + *(u16*)&ack[0xC + 0x00] = 0x0218; + *(u16*)&ack[0xC + 0x02] = 0; + *(u16*)&ack[0xC + 0x04] = 0x0903; + *(u16*)&ack[0xC + 0x06] = 0x00BF; + *(u16*)&ack[0xC + 0x08] = 0x0300; + *(u16*)&ack[0xC + 0x0A] = IOPORT(W_BSSID0); + *(u16*)&ack[0xC + 0x0C] = IOPORT(W_BSSID1); + *(u16*)&ack[0xC + 0x0E] = IOPORT(W_BSSID2); + *(u16*)&ack[0xC + 0x10] = IOPORT(W_MACAddr0); + *(u16*)&ack[0xC + 0x12] = IOPORT(W_MACAddr1); + *(u16*)&ack[0xC + 0x14] = IOPORT(W_MACAddr2); + *(u16*)&ack[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; + *(u16*)&ack[0xC + 0x18] = 0x0033; // ??? + *(u16*)&ack[0xC + 0x1A] = clientfail; + *(u32*)&ack[0xC + 0x1C] = 0; + + int txlen = Platform::MP_SendAck(ack, 12+32, USTimestamp); + WIFI_LOG("wifi: sent %d/44 bytes of MP ack, %d %d\n", txlen, ComStatus, RXTime); } -bool CheckRX(bool block); +bool CheckRX(int type); +void MPClientReplyRX(int client); bool ProcessTX(TXSlot* slot, int num) { - slot->CurPhaseTime--; + slot->CurPhaseTime -= kTimerInterval; if (slot->CurPhaseTime > 0) { if (slot->CurPhase == 1) @@ -558,19 +769,28 @@ bool ProcessTX(TXSlot* slot, int num) } else if (slot->CurPhase == 2) { - MPReplyTimer--; - if (MPReplyTimer == 0 && MPNumReplies > 0) + MPReplyTimer -= kTimerInterval; + if (MPReplyTimer <= 0 && MPClientMask != 0) { - if (CheckRX(true)) + int nclient = 1; + while (!(MPClientMask & (1 << nclient))) nclient++; + + u32 curclient = 1 << nclient; + + /*if (CheckRX(1)) { - ComStatus |= 0x1; - } + // we received a reply, mark it as such + // TODO: is any received packet considered a good reply? + // hardware probably requires a specific frame-control and/or destination MAC - // TODO: properly handle reply errors - // also, if the reply is too big to fit within its window, what happens? + MPClientFail &= ~curclient; + } + else printf("REPLY %04X NOT RECEIVED\n");*/ + if (!(MPClientFail & curclient)) + MPClientReplyRX(nclient); - MPReplyTimer = 10 + IOPORT(W_CmdReplyTime); - MPNumReplies--; + MPReplyTimer += 10 + IOPORT(W_CmdReplyTime); + MPClientMask &= ~curclient; } } @@ -590,31 +810,16 @@ bool ProcessTX(TXSlot* slot, int num) SetStatus(8); - // if no reply is configured, send a default empty reply - if (!(IOPORT(W_TXSlotReply2) & 0x8000)) - { - SendMPDefaultReply(); - - slot->Addr = 0; - slot->Length = 28; - slot->Rate = 2; // TODO - slot->CurPhase = 4; - slot->CurPhaseTime = 28*4; - slot->HalfwordTimeMask = 0xFFFFFFFF; - IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; - break; - } + //slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; + //slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; - slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; - slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; + /*u8 rate = RAM[slot->Addr + 0x8]; + if (rate == 0x14) slot->Rate = 2; + else slot->Rate = 1;*/ // TODO: duration should be set by hardware // doesn't seem to be important //RAM[slot->Addr + 0xC + 2] = 0x00F0; - - u8 rate = RAM[slot->Addr + 0x8]; - if (rate == 0x14) slot->Rate = 2; - else slot->Rate = 1; } else SetStatus(3); @@ -623,17 +828,32 @@ bool ProcessTX(TXSlot* slot, int num) if (slot->Rate == 2) { len *= 4; - slot->HalfwordTimeMask = 0x7; + slot->HalfwordTimeMask = 0x7 & kTimeCheckMask; } else { len *= 8; - slot->HalfwordTimeMask = 0xF; + slot->HalfwordTimeMask = 0xF & kTimeCheckMask; } slot->CurPhase = 1; slot->CurPhaseTime = len; + u16 framectl = *(u16*)&RAM[slot->Addr + 0xC]; + if (framectl & (1<<14)) + { + // WEP frame + // TODO: what happens when sending a WEP frame while WEP processing is off? + // TODO: some form of actual WEP processing? + // for now we just set the WEP FCS to a nonzero value, because some games require it + + if (IOPORT(W_WEPCnt) & (1<<15)) + { + u32 wep_fcs = (slot->Addr + 0xC + slot->Length - 7) & ~0x1; + *(u32*)&RAM[wep_fcs] = 0x22334466; + } + } + u64 oldts; if (num == 4) { @@ -642,28 +862,66 @@ bool ProcessTX(TXSlot* slot, int num) *(u64*)&RAM[slot->Addr + 0xC + 24] = USCounter; } - //u32 noseqno = 0; - //if (num == 1) noseqno = (IOPORT(W_TXSlotCmd) & 0x4000); + u32 noseqno = 0; + if (num == 1) noseqno = (IOPORT(W_TXSlotCmd) & 0x4000); - //if (!noseqno) + if (!noseqno) { *(u16*)&RAM[slot->Addr + 0xC + 22] = IOPORT(W_TXSeqNo) << 4; IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; } + if ((num != 5) && (RAM[slot->Addr+4] > 0)) + printf("SLOT %d RETRY COUNTER %d\n", RAM[slot->Addr+4]); + // set TX addr IOPORT(W_RXTXAddr) = slot->Addr >> 1; - // send - int txlen = Platform::MP_SendPacket(&RAM[slot->Addr], 12 + slot->Length); - WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", - txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], - *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + if (num == 1) + { + // send + int txlen = Platform::MP_SendCmd(&RAM[slot->Addr], 12 + slot->Length, USTimestamp); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + } + else if (num == 5) + { + // send + /*int txlen = Platform::MP_SendReply(&RAM[slot->Addr], 12 + slot->Length, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]);*/ + } + else //if (num != 5) + { + // send + int txlen = Platform::MP_SendPacket(&RAM[slot->Addr], 12 + slot->Length, USTimestamp); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + } // if the packet is being sent via LOC1..3, send it to the AP // any packet sent via CMD/REPLY/BEACON isn't going to have much use outside of local MP if (num == 0 || num == 2 || num == 3) + { + if ((framectl & 0x00FF) == 0x0010) + { + u16 aid = *(u16*)&RAM[slot->Addr + 0xC + 24 + 4]; + if (aid) printf("[HOST] syncing client %04X, sync=%016llX\n", aid, USTimestamp); + } + else if ((framectl & 0x00FF) == 0x00C0) + { + if (IsMPClient) + { + printf("[CLIENT] deauth\n"); + IsMPClient = false; + } + } + WifiAP::SendPacket(&RAM[slot->Addr], 12 + slot->Length); + } if (num == 4) { @@ -672,10 +930,25 @@ bool ProcessTX(TXSlot* slot, int num) } break; + case 10: // preamble done (default empty MP reply) + { + SetIRQ(7); + SetStatus(8); + + //SendMPDefaultReply(); + + //slot->Addr = 0; + //slot->Length = 28; + slot->CurPhase = 4; + slot->CurPhaseTime = 28*4; + slot->HalfwordTimeMask = 0xFFFFFFFF; + } + break; + case 1: // transmit done { - // for the MP reply slot, this is set later - if (num != 5) + // for the MP CMD and reply slots, this is set later + if (num != 1 && num != 5) *(u16*)&RAM[slot->Addr] = 0x0001; RAM[slot->Addr + 5] = 0; @@ -688,12 +961,21 @@ bool ProcessTX(TXSlot* slot, int num) } SetStatus(5); - u16 clientmask = *(u16*)&RAM[slot->Addr + 12 + 24 + 2]; - MPNumReplies = NumClients(clientmask); - MPReplyTimer = 16; + u16 clientmask = *(u16*)&RAM[slot->Addr + 12 + 24 + 2] & 0xFFFE; + //MPNumReplies = NumClients(clientmask); + MPReplyTimer = 16 + PreambleLen(slot->Rate); + MPClientMask = clientmask; + MPClientFail = clientmask; + u16 res = 0; + if (clientmask) + res = Platform::MP_RecvReplies(MPClientReplies, USTimestamp, clientmask); + MPClientFail &= ~res; + + // TODO: 112 likely includes the ack preamble, which needs adjusted + // for long-preamble settings slot->CurPhase = 2; - slot->CurPhaseTime = 112 + ((10 + IOPORT(W_CmdReplyTime)) * MPNumReplies); + slot->CurPhaseTime = 112 + ((10 + IOPORT(W_CmdReplyTime)) * NumClients(clientmask)); break; } @@ -748,7 +1030,10 @@ bool ProcessTX(TXSlot* slot, int num) if (slot->Rate == 2) slot->CurPhaseTime = 32 * 4; else slot->CurPhaseTime = 32 * 8; - SendMPAck(); + ReportMPReplyErrors(MPClientFail); + + // send + SendMPAck(MPClientFail); slot->CurPhase = 3; } @@ -760,11 +1045,15 @@ bool ProcessTX(TXSlot* slot, int num) IOPORT(W_TXBusy) &= ~(1<<1); IOPORT(W_TXSlotCmd) &= 0x7FFF; // confirmed - // seems this is set to indicate which clients failed to reply - *(u16*)&RAM[slot->Addr + 0x2] = 0; + if (!MPClientFail) + *(u16*)&RAM[slot->Addr] = 0x0001; + else + *(u16*)&RAM[slot->Addr] = 0x0005; + + // this is set to indicate which clients failed to reply + *(u16*)&RAM[slot->Addr + 0x2] = MPClientFail; IncrementTXCount(slot); - SetIRQ(12); IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; if (IOPORT(W_TXStatCnt) & 0x2000) @@ -774,12 +1063,19 @@ bool ProcessTX(TXSlot* slot, int num) } SetStatus(1); + // TODO: retry the whole cycle if some clients failed to respond + // AND if there is enough time left in CMDCOUNT + // (games seem to always configure CMDCOUNT such that there is no time for retries) + SetIRQ(12); + FireTX(); } return true; case 4: // MP default reply transfer finished { + IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; + IOPORT(W_TXBusy) &= ~0x80; SetStatus(1); FireTX(); @@ -802,98 +1098,403 @@ inline void IncrementRXAddr(u16& addr, u16 inc = 2) } } -bool CheckRX(bool block) +void StartRX() +{ + u16 framelen = *(u16*)&RXBuffer[8]; + RXTime = framelen; + + u16 txrate = *(u16*)&RXBuffer[6]; + if (txrate == 0x14) + { + RXTime *= 4; + RXHalfwordTimeMask = 0x7 & kTimeCheckMask; + } + else + { + RXTime *= 8; + RXHalfwordTimeMask = 0xF & kTimeCheckMask; + } + + u16 addr = IOPORT(W_RXBufWriteCursor) << 1; + IncrementRXAddr(addr, 12); + IOPORT(W_RXTXAddr) = addr >> 1; + + RXBufferPtr = 12; + + SetIRQ(6); + SetStatus(6); + ComStatus |= 1; +} + +void FinishRX() { + ComStatus &= ~0x1; + RXCounter = 0; + + if (!ComStatus) + { + if (IOPORT(W_PowerState) & 0x0300) + SetStatus(9); + else + SetStatus(1); + } + + // TODO: RX stats + + u16 framectl = *(u16*)&RXBuffer[12]; + + // check the frame's destination address + // note: the hardware always checks the first address field, regardless of the frame type/etc + // similarly, the second address field is used to send acks to non-broadcast frames + + u8* dstmac = &RXBuffer[12 + 4]; + if (!(dstmac[0] & 0x01)) + { + if (!MACEqual(dstmac, (u8*)&IOPORT(W_MACAddr0))) + return; + } + + // reject the frame if it's a WEP frame and WEP is off + // TODO: check if sending WEP frames with WEP off works at all? + + if (framectl & (1<<14)) + { + if (!(IOPORT(W_WEPCnt) & (1<<15))) + return; + } + + // apply RX filtering + // TODO: + // * RXFILTER bits 0, 9, 10, 12 not fully understood + // * port 0D8 also affects reception of frames + // * MP CMD frames with a duplicate sequence number are ignored + + u16 rxflags = 0x0010; + + switch ((framectl >> 2) & 0x3) + { + case 0: // management + { + u8* bssid = &RXBuffer[12 + 16]; + if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) + rxflags |= 0x8000; + + u16 subtype = (framectl >> 4) & 0xF; + if (subtype == 0x8) // beacon + { + if (!(rxflags & 0x8000)) + { + if (!(IOPORT(W_RXFilter) & (1<<0))) + return; + } + + rxflags |= 0x0001; + } + else if ((subtype <= 0x5) || + (subtype >= 0xA && subtype <= 0xC)) + { + if (!(rxflags & 0x8000)) + { + // CHECKME! + if (!(IOPORT(W_RXFilter) & (3<<9))) + return; + } + } + } + break; + + case 1: // control + { + if ((framectl & 0xF0) == 0xA0) // PS-poll + { + u8* bssid = &RXBuffer[12 + 4]; + if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) + rxflags |= 0x8000; + + if (!(rxflags & 0x8000)) + { + if (!(IOPORT(W_RXFilter) & (1<<11))) + return; + } + + rxflags |= 0x0005; + } + else + return; + } + break; + + case 2: // data + { + u16 fromto = (framectl >> 8) & 0x3; + if (IOPORT(W_RXFilter2) & (1<<fromto)) + return; + + int bssidoffset[4] = {16, 4, 10, 0}; + if (bssidoffset[fromto]) + { + u8* bssid = &RXBuffer[12 + bssidoffset[fromto]]; + if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) + rxflags |= 0x8000; + } + + u16 rxfilter = IOPORT(W_RXFilter); + + if (!(rxflags & 0x8000)) + { + if (!(rxfilter & (1<<11))) + return; + } + + if (framectl & (1<<11)) // retransmit + { + if (!(rxfilter & (1<<0))) + return; + } + + // check for MP frames + // the hardware simply checks for these specific MAC addresses + // the reply check has priority over the other checks + // TODO: it seems to be impossible to receive a MP reply outside of a CMD transfer's reply timeframe + // if the framectl subtype field is 1 or 5 + // maybe one of the unknown registers controls that? + // maybe it is impossible to receive CF-Ack frames outside of a CF-Poll period? + // TODO: GBAtek says frame type F is for all empty packets? + // my hardware tests say otherwise + + if (MACEqual(&RXBuffer[12 + 16], MPReplyMAC)) + { + if ((framectl & 0xF0) == 0x50) + rxflags |= 0x000F; + else + rxflags |= 0x000E; + } + else if (MACEqual(&RXBuffer[12 + 4], MPCmdMAC)) + { + rxflags |= 0x000C; + } + else if (MACEqual(&RXBuffer[12 + 4], MPAckMAC)) + { + rxflags |= 0x000D; + } + else + { + rxflags |= 0x0008; + } + + switch ((framectl >> 4) & 0xF) + { + case 0x0: break; + + case 0x1: + if ((rxflags & 0xF) == 0xD) + { + if (!(rxfilter & (1<<7))) return; + } + else if ((rxflags & 0xF) != 0xE) + { + if (!(rxfilter & (1<<1))) return; + } + break; + + case 0x2: + if ((rxflags & 0xF) != 0xC) + { + if (!(rxfilter & (1<<2))) return; + } + break; + + case 0x3: + if (!(rxfilter & (1<<3))) return; + break; + + case 0x4: break; + + case 0x5: + if ((rxflags & 0xF) == 0xF) + { + if (!(rxfilter & (1<<8))) return; + } + else + { + if (!(rxfilter & (1<<4))) return; + } + break; + + case 0x6: + if (!(rxfilter & (1<<5))) return; + break; + + case 0x7: + if (!(rxfilter & (1<<6))) return; + break; + + default: + return; + } + } + break; + } + + // build the RX header + + u16 headeraddr = IOPORT(W_RXBufWriteCursor) << 1; + *(u16*)&RAM[headeraddr] = rxflags; + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = 0x0040; // ??? + IncrementRXAddr(headeraddr, 4); + *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[6]; // TX rate + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[8]; // frame length + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = 0x4080; // RSSI + + // signal successful reception + + u16 addr = IOPORT(W_RXTXAddr) << 1; + if (addr & 0x2) IncrementRXAddr(addr); + IOPORT(W_RXBufWriteCursor) = (addr & ~0x3) >> 1; + + SetIRQ(0); + + if ((rxflags & 0x800F) == 0x800C) + { + // reply to CMD frames + + u16 clientmask = *(u16*)&RXBuffer[0xC + 26]; + if (IOPORT(W_AIDLow) && (clientmask & (1 << IOPORT(W_AIDLow)))) + { + SendMPReply(*(u16*)&RXBuffer[0xC + 24], clientmask); + } + else + { + // send a blank + // this is just so the host can have something to receive, instead of hitting a timeout + // 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); + } + } + else if ((rxflags & 0x800F) == 0x8001) + { + // when receiving a beacon with the right BSSID, the beacon's timestamp + // is copied to USCOUNTER + + u32 len = *(u16*)&RXBuffer[8]; + u16 txrate = *(u16*)&RXBuffer[6]; + len *= ((txrate==0x14) ? 4 : 8); + len -= 76; // CHECKME: is this offset fixed? + + u64 timestamp = *(u64*)&RXBuffer[12 + 24]; + timestamp += (u64)len; + + USCounter = timestamp; + } +} + +void MPClientReplyRX(int client) +{ + if (IOPORT(W_PowerState) & 0x0300) + return; + + if (!(IOPORT(W_RXCnt) & 0x8000)) + return; + + if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) + return; + + int framelen; + u8 txrate; + + u8* reply = &MPClientReplies[(client-1)*1024]; + framelen = *(u16*)&reply[10]; + + txrate = reply[8]; + + // TODO: what are the maximum crop values? + u16 framectl = *(u16*)&reply[12]; + if (framectl & (1<<14)) + { + framelen -= (IOPORT(W_RXLenCrop) >> 7) & 0x1FE; + if (framelen > 24) memmove(&RXBuffer[12+24], &RXBuffer[12+28], framelen); + } + else + framelen -= (IOPORT(W_RXLenCrop) << 1) & 0x1FE; + + if (framelen < 0) framelen = 0; + + // TODO rework RX system so we don't need this (by reading directly into MPClientReplies) + memcpy(RXBuffer, reply, 12+framelen); + + *(u16*)&RXBuffer[6] = txrate; + *(u16*)&RXBuffer[8] = framelen; + + RXTimestamp = 0; + StartRX(); +} + +bool CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames +{ + if (IOPORT(W_PowerState) & 0x0300) + return false; + if (!(IOPORT(W_RXCnt) & 0x8000)) return false; if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) return false; - u16 framelen; + int rxlen; + int framelen; u16 framectl; u8 txrate; - bool bssidmatch; - u16 rxflags; + u64 timestamp; for (;;) { - int rxlen = Platform::MP_RecvPacket(RXBuffer, block); - if (rxlen == 0) rxlen = WifiAP::RecvPacket(RXBuffer); - if (rxlen == 0) return false; + timestamp = 0; + + if (type == 0) + { + rxlen = Platform::MP_RecvPacket(RXBuffer, ×tamp); + if (rxlen <= 0) + rxlen = WifiAP::RecvPacket(RXBuffer); + } + else + { + rxlen = Platform::MP_RecvHostPacket(RXBuffer, ×tamp); + if (rxlen < 0) + { + // host is gone + // TODO: make this more resilient + IsMPClient = false; + } + } + + if (rxlen <= 0) return false; if (rxlen < 12+24) continue; framelen = *(u16*)&RXBuffer[10]; if (framelen != rxlen-12) { - printf("bad frame length\n"); + printf("bad frame length %d/%d\n", framelen, rxlen-12); continue; } - framelen -= 4; framectl = *(u16*)&RXBuffer[12+0]; txrate = RXBuffer[8]; - u32 a_src, a_dst, a_bss; - rxflags = 0x0010; - switch (framectl & 0x000C) + // TODO: what are the maximum crop values? + if (framectl & (1<<14)) { - case 0x0000: // management - a_src = 10; - a_dst = 4; - a_bss = 16; - if ((framectl & 0x00F0) == 0x0080) - rxflags |= 0x0001; - break; - - case 0x0004: // control - printf("blarg\n"); - continue; - - case 0x0008: // data - switch (framectl & 0x0300) - { - case 0x0000: // STA to STA - a_src = 10; - a_dst = 4; - a_bss = 16; - break; - case 0x0100: // STA to DS - a_src = 10; - a_dst = 16; - a_bss = 4; - break; - case 0x0200: // DS to STA - a_src = 16; - a_dst = 4; - a_bss = 10; - break; - case 0x0300: // DS to DS - printf("blarg\n"); - continue; - } - // TODO: those also trigger on other framectl values - // like 0208 -> C - framectl &= 0xE7FF; - if (framectl == 0x0228) rxflags |= 0x000C; // MP host frame - else if (framectl == 0x0218) rxflags |= 0x000D; // MP ack frame - else if (framectl == 0x0118) rxflags |= 0x000E; // MP reply frame - else if (framectl == 0x0158) rxflags |= 0x000F; // empty MP reply frame - else rxflags |= 0x0008; - break; + framelen -= (IOPORT(W_RXLenCrop) >> 7) & 0x1FE; + if (framelen > 24) memmove(&RXBuffer[12+24], &RXBuffer[12+28], framelen); } + else + framelen -= (IOPORT(W_RXLenCrop) << 1) & 0x1FE; - if (MACEqual(&RXBuffer[12 + a_src], (u8*)&IOPORT(W_MACAddr0))) - continue; // oops. we received a packet we just sent. - - bssidmatch = MACEqual(&RXBuffer[12 + a_bss], (u8*)&IOPORT(W_BSSID0)); - //if (!(IOPORT(W_BSSID0) & 0x0001) && !(RXBuffer[12 + a_bss] & 0x01) && - if (!MACEqual(&RXBuffer[12 + a_dst], (u8*)&IOPORT(W_MACAddr0)) && - !(RXBuffer[12 + a_dst] & 0x01)) - { - printf("received packet %04X but it didn't pass the MAC check\n", framectl); - continue; - } + if (framelen < 0) framelen = 0; break; } @@ -901,37 +1502,64 @@ bool CheckRX(bool block) WIFI_LOG("wifi: received packet FC:%04X SN:%04X CL:%04X RXT:%d CMT:%d\n", framectl, *(u16*)&RXBuffer[12+4+6+6+6], *(u16*)&RXBuffer[12+4+6+6+6+2+2], framelen*4, IOPORT(W_CmdReplyTime)); - // make RX header - - if (bssidmatch) rxflags |= 0x8000; - - *(u16*)&RXBuffer[0] = rxflags; - *(u16*)&RXBuffer[2] = 0x0040; // ??? *(u16*)&RXBuffer[6] = txrate; *(u16*)&RXBuffer[8] = framelen; - *(u16*)&RXBuffer[10] = 0x4080; // min/max RSSI. dunno - RXTime = framelen; + bool macgood = (RXBuffer[12 + 4] & 0x01) || MACEqual(&RXBuffer[12 + 4], (u8*)&IOPORT(W_MACAddr0)); - if (txrate == 0x14) + if (((framectl & 0x00FF) == 0x0010) && timestamp && macgood) { - RXTime *= 4; - RXHalfwordTimeMask = 0x7; + // if receiving an association response: get the sync value from the host + + u16 aid = *(u16*)&RXBuffer[12+24+4]; + + if (aid) + { + printf("[CLIENT %01X] host sync=%016llX\n", aid&0xF, timestamp); + + IsMPClient = true; + USTimestamp = timestamp; + NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); + } + + RXTimestamp = 0; + StartRX(); } - else + else if (((framectl & 0x00FF) == 0x00C0) && timestamp && macgood && IsMPClient) { - RXTime *= 8; - RXHalfwordTimeMask = 0xF; + IsMPClient = false; + NextSync = 0; + + RXTimestamp = 0; + StartRX(); } + else if (macgood && IsMPClient) + { + // if we are being a MP client, we need to delay this frame until we reach the + // timestamp it came with + // we also need to determine how far we can run after having received this frame - u16 addr = IOPORT(W_RXBufWriteCursor) << 1; - IncrementRXAddr(addr, 12); - IOPORT(W_RXTXAddr) = addr >> 1; + RXTimestamp = timestamp; + if (RXTimestamp < USTimestamp) RXTimestamp = USTimestamp; + NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); - RXBufferPtr = 12; + if (MACEqual(&RXBuffer[12 + 4], MPCmdMAC)) + { + u16 clienttime = *(u16*)&RXBuffer[12+24]; + u16 clientmask = *(u16*)&RXBuffer[12+26]; + + // include the MP reply time window + NextSync += 112 + ((clienttime + 10) * NumClients(clientmask)); + } + } + else + { + // otherwise, just start receiving this frame now + + RXTimestamp = 0; + StartRX(); + } - SetIRQ(6); - SetStatus(6); return true; } @@ -940,7 +1568,7 @@ void MSTimer() { if (IOPORT(W_USCompareCnt)) { - if (USCounter == USCompare) + if ((USCounter & ~0x3FF) == USCompare) { BlockBeaconIRQ14 = false; SetIRQ14(0); @@ -962,55 +1590,108 @@ void MSTimer() void USTimer(u32 param) { - WifiAP::USTimer(); + USTimestamp += kTimerInterval; + + if (IsMPClient && (!ComStatus)) + { + if (RXTimestamp && (USTimestamp >= RXTimestamp)) + { + RXTimestamp = 0; + StartRX(); + } + + if (USTimestamp >= NextSync) + { + // TODO: not do this every tick if it fails to receive a frame! + CheckRX(2); + } + } + + if (!(USTimestamp & 0x3FF & kTimeCheckMask)) + WifiAP::MSTimer(); + + bool switchOffPowerSaving = false; + if (USUntilPowerOn < 0) + { + USUntilPowerOn += kTimerInterval; + + switchOffPowerSaving = (USUntilPowerOn >= 0) && (IOPORT(W_PowerUnk) & 0x0001 || ForcePowerOn); + } + if ((USUntilPowerOn >= 0) && (IOPORT(W_PowerState) & 0x0002 || switchOffPowerSaving)) + { + IOPORT(W_PowerState) = 0; + IOPORT(W_RFPins) = 1; + IOPORT(W_RFPins) = 0x0084; + SetIRQ(11); + } if (IOPORT(W_USCountCnt)) { - USCounter++; + USCounter += kTimerInterval; u32 uspart = (USCounter & 0x3FF); if (IOPORT(W_USCompareCnt)) { u32 beaconus = (IOPORT(W_BeaconCount1) << 10) | (0x3FF - uspart); - if (beaconus == IOPORT(W_PreBeacon)) SetIRQ15(); + if ((beaconus & kTimeCheckMask) == (IOPORT(W_PreBeacon) & kTimeCheckMask)) + SetIRQ15(); } - if (!uspart) MSTimer(); + if (!(uspart & kTimeCheckMask)) + MSTimer(); } if (IOPORT(W_CmdCountCnt) & 0x0001) { if (CmdCounter > 0) { - CmdCounter--; + if (CmdCounter < kTimerInterval) + CmdCounter = 0; + else + CmdCounter -= kTimerInterval; } } if (IOPORT(W_ContentFree) != 0) - IOPORT(W_ContentFree)--; + { + if (IOPORT(W_ContentFree) < kTimerInterval) + IOPORT(W_ContentFree) = 0; + else + IOPORT(W_ContentFree) -= kTimerInterval; + } if (ComStatus == 0) { u16 txbusy = IOPORT(W_TXBusy); if (txbusy) { - ComStatus = 0x2; - if (txbusy & 0x0080) TXCurSlot = 5; - else if (txbusy & 0x0010) TXCurSlot = 4; - else if (txbusy & 0x0008) TXCurSlot = 3; - else if (txbusy & 0x0004) TXCurSlot = 2; - else if (txbusy & 0x0002) TXCurSlot = 1; - else if (txbusy & 0x0001) TXCurSlot = 0; + if (IOPORT(W_PowerState) & 0x0300) + { + ComStatus = 0; + TXCurSlot = -1; + } + else + { + ComStatus = 0x2; + if (txbusy & 0x0080) TXCurSlot = 5; + else if (txbusy & 0x0010) TXCurSlot = 4; + else if (txbusy & 0x0008) TXCurSlot = 3; + else if (txbusy & 0x0004) TXCurSlot = 2; + else if (txbusy & 0x0002) TXCurSlot = 1; + else if (txbusy & 0x0001) TXCurSlot = 0; + } } else { - if ((!(RXCounter & 0x1FF))) + if ((!IsMPClient) || (USTimestamp > NextSync)) { - if (CheckRX(false)) - ComStatus = 0x1; + if ((!(RXCounter & 0x1FF & kTimeCheckMask)) && (!ComStatus)) + { + CheckRX(0); + } } - RXCounter++; + RXCounter += kTimerInterval; } } @@ -1019,6 +1700,12 @@ void USTimer(u32 param) bool finished = ProcessTX(&TXSlots[TXCurSlot], TXCurSlot); if (finished) { + if (IOPORT(W_PowerState) & 0x0300) + { + IOPORT(W_TXBusy) = 0; + SetStatus(9); + } + // transfer finished, see if there's another slot to do // checkme: priority order of beacon/reply // TODO: for CMD, check CMDCOUNT @@ -1039,50 +1726,31 @@ void USTimer(u32 param) } if (ComStatus & 0x1) { - RXTime--; + RXTime -= kTimerInterval; if (!(RXTime & RXHalfwordTimeMask)) { u16 addr = IOPORT(W_RXTXAddr) << 1; if (addr < 0x1FFF) *(u16*)&RAM[addr] = *(u16*)&RXBuffer[RXBufferPtr]; IncrementRXAddr(addr); + IOPORT(W_RXTXAddr) = addr >> 1; RXBufferPtr += 2; - if (RXTime == 0) // finished receiving + if (RXTime <= 0) // finished receiving { - if (addr & 0x2) IncrementRXAddr(addr); - - // copy the RX header - u16 headeraddr = IOPORT(W_RXBufWriteCursor) << 1; - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[0]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[2]; IncrementRXAddr(headeraddr, 4); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[6]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[8]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[10]; - - IOPORT(W_RXBufWriteCursor) = (addr & ~0x3) >> 1; - - SetIRQ(0); - SetStatus(1); - - WIFI_LOG("wifi: finished receiving packet %04X\n", *(u16*)&RXBuffer[12]); - - ComStatus &= ~0x1; - RXCounter = 0; - - if ((RXBuffer[0] & 0x0F) == 0x0C) - { - u16 clientmask = *(u16*)&RXBuffer[0xC + 26]; - if (IOPORT(W_AIDLow) && (RXBuffer[0xC + 4] & 0x01) && (clientmask & (1 << IOPORT(W_AIDLow)))) - { - SendMPReply(*(u16*)&RXBuffer[0xC + 24], *(u16*)&RXBuffer[0xC + 26]); - } - } + FinishRX(); } - - if (addr == (IOPORT(W_RXBufReadCursor) << 1)) + else if (addr == (IOPORT(W_RXBufReadCursor) << 1)) { - printf("wifi: RX buffer full\n"); + // TODO: properly check the crossing of the read cursor + // (for example, if it is outside of the RX buffer) + + printf("wifi: RX buffer full (buf=%04X/%04X rd=%04X wr=%04X rxtx=%04X power=%04X com=%d rxcnt=%04X filter=%04X/%04X frame=%04X/%04X len=%d)\n", + (IOPORT(W_RXBufBegin)>>1)&0xFFF, (IOPORT(W_RXBufEnd)>>1)&0xFFF, + IOPORT(W_RXBufReadCursor), IOPORT(W_RXBufWriteCursor), + IOPORT(W_RXTXAddr), IOPORT(W_PowerState), ComStatus, + IOPORT(W_RXCnt), IOPORT(W_RXFilter), IOPORT(W_RXFilter2), + *(u16*)&RXBuffer[0], *(u16*)&RXBuffer[12], *(u16*)&RXBuffer[8]); RXTime = 0; SetStatus(1); if (TXCurSlot == 0xFFFFFFFF) @@ -1091,15 +1759,15 @@ void USTimer(u32 param) RXCounter = 0; } // TODO: proper error management + if ((!ComStatus) && (IOPORT(W_PowerState) & 0x0300)) + { + SetStatus(9); + } } - - IOPORT(W_RXTXAddr) = addr >> 1; } } - // TODO: make it more accurate, eventually - // in the DS, the wifi system has its own 22MHz clock and doesn't use the system clock - NDS::ScheduleEvent(NDS::Event_Wifi, true, 33, USTimer, 0); + ScheduleTimer(false); } @@ -1137,15 +1805,13 @@ void RFTransfer_Type3() } -// TODO: wifi waitstates - u16 Read(u32 addr) -{//printf("WIFI READ %08X\n", addr); +{ if (addr >= 0x04810000) return 0; addr &= 0x7FFE; - //printf("WIFI: read %08X\n", addr); + if (addr >= 0x4000 && addr < 0x6000) { return *(u16*)&RAM[addr & 0x1FFE]; @@ -1193,7 +1859,6 @@ u16 Read(u32 addr) if (activeread) { u32 rdaddr = IOPORT(W_RXBufReadAddr); - u16 ret = *(u16*)&RAM[rdaddr]; rdaddr += 2; @@ -1223,6 +1888,20 @@ u16 Read(u32 addr) case W_TXBusy: return IOPORT(W_TXBusy) & 0x001F; // no bit for MP replies. odd + + case W_CMDStat0: + case W_CMDStat1: + case W_CMDStat2: + case W_CMDStat3: + case W_CMDStat4: + case W_CMDStat5: + case W_CMDStat6: + case W_CMDStat7: + { + u16 ret = IOPORT(addr&0xFFF); + IOPORT(addr&0xFFF) = 0; + return ret; + } } //printf("WIFI: read %08X\n", addr); @@ -1230,12 +1909,12 @@ u16 Read(u32 addr) } void Write(u32 addr, u16 val) -{//printf("WIFI WRITE %08X %04X\n", addr, val); +{ if (addr >= 0x04810000) return; addr &= 0x7FFE; - //printf("WIFI: write %08X %04X\n", addr, val); + if (addr >= 0x4000 && addr < 0x6000) { *(u16*)&RAM[addr & 0x1FFE] = val; @@ -1252,15 +1931,25 @@ void Write(u32 addr, u16 val) if (!(oldval & 0x0001) && (val & 0x0001)) { - IOPORT(0x034) = 0x0002; - IOPORT(W_RFPins) = 0x0046; - IOPORT(W_RFStatus) = 9; - IOPORT(0x27C) = 0x0005; - // TODO: 02A2?? + if (!(USUntilPowerOn < 0 && ForcePowerOn)) + { + //printf("mode reset power on %08x\n", NDS::ARM7->R[15]); + IOPORT(0x034) = 0x0002; + IOPORT(0x27C) = 0x0005; + // TODO: 02A2?? + + if (IOPORT(W_PowerUnk) & 0x0002) + { + USUntilPowerOn = -2048; + IOPORT(W_PowerState) |= 0x100; + } + } } else if ((oldval & 0x0001) && !(val & 0x0001)) { + //printf("mode reset shutdown %08x\n", NDS::ARM7->R[15]); IOPORT(0x27C) = 0x000A; + PowerDown(); } if (val & 0x2000) @@ -1306,6 +1995,11 @@ void Write(u32 addr, u16 val) case W_ModeWEP: val &= 0x007F; + //printf("writing mode web %x\n", val); + if ((val & 0x7) == 1) + IOPORT(W_PowerUnk) |= 0x0002; + if ((val & 0x7) == 2) + IOPORT(W_PowerUnk) = 0x0003; break; case W_IF: @@ -1316,54 +2010,74 @@ void Write(u32 addr, u16 val) printf("wifi: force-setting IF %04X\n", val); return; + case W_AIDLow: + IOPORT(W_AIDLow) = val & 0x000F; + return; + case W_AIDFull: + IOPORT(W_AIDFull) = val & 0x07FF; + return; + case W_PowerState: - if (val & 0x0002) - { - // TODO: delay for this - SetIRQ(11); - IOPORT(W_PowerState) = 0x0000; + //printf("writing power state %x %08x\n", val, NDS::ARM7->R[15]); + IOPORT(W_PowerState) |= val & 0x0002; - // checkme - IOPORT(W_RFPins) = 0x00C6; - IOPORT(W_RFStatus) = 9; + if (IOPORT(W_ModeReset) & 0x0001 && IOPORT(W_PowerState) & 0x0002) + { + /*if (IOPORT(W_PowerState) & 0x100) + { + AlwaysPowerOn = true; + USUntilPowerOn = -1; + } + else */ + if (IOPORT(W_PowerForce) == 1) + { + //printf("power on\n"); + IOPORT(W_PowerState) |= 0x100; + USUntilPowerOn = -2048; + ForcePowerOn = false; + } } return; case W_PowerForce: - if ((val&0x8001)==0x8000) printf("WIFI: forcing power %04X\n", val); + //if ((val&0x8001)==0x8000) printf("WIFI: forcing power %04X\n", val); + val &= 0x8001; + //printf("writing power force %x %08x\n", val, NDS::ARM7->R[15]); if (val == 0x8001) { + //printf("force power off\n"); IOPORT(0x034) = 0x0002; IOPORT(W_PowerState) = 0x0200; IOPORT(W_TXReqRead) = 0; - IOPORT(W_RFPins) = 0x0046; - IOPORT(W_RFStatus) = 9; + PowerDown(); } - break; - case W_PowerUS: - // schedule timer event when the clock is enabled - // TODO: check whether this resets USCOUNT (and also which other events can reset it) - if ((IOPORT(W_PowerUS) & 0x0001) && !(val & 0x0001)) + if (val == 1 && IOPORT(W_PowerState) & 0x0002) { - printf("WIFI ON\n"); - NDS::ScheduleEvent(NDS::Event_Wifi, false, 33, USTimer, 0); - if (!MPInited) - { - Platform::MP_Init(); - MPInited = true; - } - if (!LANInited) - { - Platform::LAN_Init(); - LANInited = true; - } + //printf("power on\n"); + IOPORT(W_PowerState) |= 0x100; + USUntilPowerOn = -2048; + ForcePowerOn = false; } - else if (!(IOPORT(W_PowerUS) & 0x0001) && (val & 0x0001)) + if (val == 0x8000) { - printf("WIFI OFF\n"); - NDS::CancelEvent(NDS::Event_Wifi); + //printf("force power on\n"); + IOPORT(W_PowerState) |= 0x100; + USUntilPowerOn = -2048; + ForcePowerOn = true; } break; + case W_PowerUS: + IOPORT(W_PowerUS) = val & 0x0003; + UpdatePowerOn(); + return; + case W_PowerUnk: + val &= 0x0003; + //printf("writing power unk %x\n", val); + if ((IOPORT(W_ModeWEP) & 0x7) == 1) + val |= 2; + else if ((IOPORT(W_ModeWEP) & 0x7) == 2) + val = 3; + break; case W_USCountCnt: val &= 0x0001; break; case W_USCompareCnt: @@ -1416,6 +2130,10 @@ void Write(u32 addr, u16 val) IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); IOPORT(W_TXSlotReply1) = 0; } + if (val & 0x8000) + { + FireTX(); + } val &= 0xFF0E; if (val & 0x7FFF) printf("wifi: unknown RXCNT bits set %04X\n", val); break; @@ -1500,6 +2218,7 @@ void Write(u32 addr, u16 val) case W_TXSlotCmd: // checkme: is it possible to cancel a queued transfer that hasn't started yet // by clearing bit15 here? + // TODO: "W_TXBUF_CMD.Bit15 can be set ONLY while W_CMD_COUNT is non-zero." IOPORT(addr&0xFFF) = val; FireTX(); return; @@ -1527,9 +2246,12 @@ void Write(u32 addr, u16 val) case 0x214: case 0x268: return; + + default: + //printf("WIFI unk: write %08X %04X\n", addr, val); + break; } - //printf("WIFI: write %08X %04X\n", addr, val); IOPORT(addr&0xFFF) = val; } @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -51,6 +51,7 @@ enum W_PowerTX = 0x038, W_PowerState = 0x03C, W_PowerForce = 0x040, + W_PowerUnk = 0x48, W_Random = 0x044, @@ -92,6 +93,7 @@ enum W_CmdTotalTime = 0x0C0, W_CmdReplyTime = 0x0C4, W_RXFilter = 0x0D0, + W_RXLenCrop = 0x0DA, W_RXFilter2 = 0x0E0, W_USCountCnt = 0x0E8, @@ -135,12 +137,43 @@ enum W_TXErrorCount = 0x1C0, W_RXCount = 0x1C4, + W_CMDStat0 = 0x1D0, + W_CMDStat1 = 0x1D2, + W_CMDStat2 = 0x1D4, + W_CMDStat3 = 0x1D6, + W_CMDStat4 = 0x1D8, + W_CMDStat5 = 0x1DA, + W_CMDStat6 = 0x1DC, + W_CMDStat7 = 0x1DE, + W_TXSeqNo = 0x210, W_RFStatus = 0x214, W_IFSet = 0x21C, W_RXTXAddr = 0x268, }; +enum +{ + Event_RXCheck = 0, + Event_IRQ15, + Event_MSTimer, + Event_RFWakeup, + Event_RX, + Event_TX, + Event_MPClientSync, + Event_RF, + Event_BB, + + Event_MAX +}; + +struct SchedEvent +{ + void (*Func)(u32 param); + u64 Timestamp; + u32 Param; +}; + extern bool MPInited; @@ -150,7 +183,7 @@ void DeInit(); void Reset(); void DoSavestate(Savestate* file); -void StartTX_Beacon(); +void SetPowerCnt(u32 val); void USTimer(u32 param); diff --git a/src/WifiAP.cpp b/src/WifiAP.cpp index 2f3804b..5351639 100644 --- a/src/WifiAP.cpp +++ b/src/WifiAP.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -91,7 +91,7 @@ void DeInit() void Reset() { // random starting point for the counter - USCounter = 0x428888017ULL; + USCounter = 0x428888000ULL; SeqNo = 0x0120; BeaconDue = false; @@ -115,18 +115,6 @@ bool MACIsBroadcast(u8* a) } -void USTimer() -{ - USCounter++; - - u32 chk = (u32)USCounter; - if (!(chk & 0x1FFFF)) - { - // send beacon every 128ms - BeaconDue = true; - } -} - void MSTimer() { USCounter += 0x400; diff --git a/src/WifiAP.h b/src/WifiAP.h index 67ea424..e5ca1ed 100644 --- a/src/WifiAP.h +++ b/src/WifiAP.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,6 +19,8 @@ #ifndef WIFIAP_H #define WIFIAP_H +#include "types.h" + namespace WifiAP { @@ -31,7 +33,6 @@ bool Init(); void DeInit(); void Reset(); -void USTimer(); void MSTimer(); // packet format: 12-byte TX header + original 802.11 frame diff --git a/src/frontend/FrontendUtil.h b/src/frontend/FrontendUtil.h index f52dced..91f5d88 100644 --- a/src/frontend/FrontendUtil.h +++ b/src/frontend/FrontendUtil.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -21,100 +21,12 @@ #include "types.h" +#include <string> #include <vector> namespace Frontend { -enum -{ - ROMSlot_NDS = 0, - ROMSlot_GBA, - - ROMSlot_MAX -}; - -enum -{ - Load_OK = 0, - - Load_BIOS9Missing, - Load_BIOS9Bad, - - Load_BIOS7Missing, - Load_BIOS7Bad, - - Load_FirmwareMissing, - Load_FirmwareBad, - Load_FirmwareNotBootable, - - Load_DSiBIOS9Missing, - Load_DSiBIOS9Bad, - - Load_DSiBIOS7Missing, - Load_DSiBIOS7Bad, - - Load_DSiNANDMissing, - Load_DSiNANDBad, - - // TODO: more precise errors for ROM loading - Load_ROMLoadError, -}; - -extern char ROMPath [ROMSlot_MAX][1024]; -extern char SRAMPath[ROMSlot_MAX][1024]; -extern bool SavestateLoaded; - -// Stores type of nds rom i.e. nds/srl/dsi. Should be updated everytime an NDS rom is loaded from an archive -extern char NDSROMExtension[4]; - -// initialize the ROM handling utility -void Init_ROM(); - -// deinitialize the ROM handling utility -void DeInit_ROM(); - -// load the BIOS/firmware and boot from it -int LoadBIOS(); - -// load a ROM file to the specified cart slot -// note: loading a ROM to the NDS slot resets emulation -int LoadROM(const char* file, int slot); -int LoadROM(const u8 *romdata, u32 romlength, const char *archivefilename, const char *romfilename, const char *sramfilename, int slot); - -// unload the ROM loaded in the specified cart slot -// simulating ejection of the cartridge -void UnloadROM(int slot); - -void ROMIcon(u8 (&data)[512], u16 (&palette)[16], u32* iconRef); -void AnimatedROMIcon(u8 (&data)[8][512], u16 (&palette)[8][16], u16 (&sequence)[64], u32 (&animatedTexRef)[32 * 32 * 64], std::vector<int> &animatedSequenceRef); - -// reset execution of the current ROM -int Reset(); - -// get the filename associated with the given savestate slot (1-8) -void GetSavestateName(int slot, char* filename, int len); - -// determine whether the given savestate slot does contain a savestate -bool SavestateExists(int slot); - -// load the given savestate file -// if successful, emulation will continue from the savestate's point -bool LoadState(const char* filename); - -// save the current emulator state to the given file -bool SaveState(const char* filename); - -// undo the latest savestate load -void UndoStateLoad(); - -// imports savedata from an external file. Returns the difference between the filesize and the SRAM size -int ImportSRAM(const char* filename); - -// enable or disable cheats -void EnableCheats(bool enable); - - // 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 diff --git a/src/frontend/SharedConfig.h b/src/frontend/SharedConfig.h deleted file mode 100644 index 3598e74..0000000 --- a/src/frontend/SharedConfig.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2016-2021 Arisotura - - This file is part of melonDS. - - melonDS is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - melonDS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#ifndef SHAREDCONFIG_H -#define SHAREDCONFIG_H - -namespace Config -{ - -extern int ConsoleType; -extern int DirectBoot; -extern int SavestateRelocSRAM; - -extern int ExternalBIOSEnable; - -extern char BIOS9Path[1024]; -extern char BIOS7Path[1024]; -extern char FirmwarePath[1024]; - -extern char DSiBIOS9Path[1024]; -extern char DSiBIOS7Path[1024]; -extern char DSiFirmwarePath[1024]; -extern char DSiNANDPath[1024]; - -} - -#endif diff --git a/src/frontend/Util_Audio.cpp b/src/frontend/Util_Audio.cpp index cad2318..fc0edeb 100644 --- a/src/frontend/Util_Audio.cpp +++ b/src/frontend/Util_Audio.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/Util_ROM.cpp b/src/frontend/Util_ROM.cpp deleted file mode 100644 index 4d23c70..0000000 --- a/src/frontend/Util_ROM.cpp +++ /dev/null @@ -1,845 +0,0 @@ -/* - Copyright 2016-2021 Arisotura - - This file is part of melonDS. - - melonDS is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - melonDS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#include <stdio.h> -#include <string.h> - -#include <utility> - -#ifdef ARCHIVE_SUPPORT_ENABLED -#include "ArchiveUtil.h" -#endif -#include "FrontendUtil.h" -#include "SharedConfig.h" -#include "Platform.h" - -#include "NDS.h" -#include "DSi.h" -#include "GBACart.h" - -#include "AREngine.h" - - -namespace Frontend -{ - -char ROMPath [ROMSlot_MAX][1024]; -char SRAMPath [ROMSlot_MAX][1024]; -char PrevSRAMPath[ROMSlot_MAX][1024]; // for savestate 'undo load' - -char NDSROMExtension[4]; - -bool SavestateLoaded; - -ARCodeFile* CheatFile; -bool CheatsOn; - - -void Init_ROM() -{ - SavestateLoaded = false; - - memset(ROMPath[ROMSlot_NDS], 0, 1024); - memset(ROMPath[ROMSlot_GBA], 0, 1024); - memset(SRAMPath[ROMSlot_NDS], 0, 1024); - memset(SRAMPath[ROMSlot_GBA], 0, 1024); - memset(PrevSRAMPath[ROMSlot_NDS], 0, 1024); - memset(PrevSRAMPath[ROMSlot_GBA], 0, 1024); - - CheatFile = nullptr; - CheatsOn = false; -} - -void DeInit_ROM() -{ - if (CheatFile) - { - delete CheatFile; - CheatFile = nullptr; - } -} - -// TODO: currently, when failing to load a ROM for whatever reason, we attempt -// to revert to the previous state and resume execution; this may not be a very -// good thing, depending on what state the core was left in. -// should we do a better state revert (via the savestate system)? completely stop? - -void SetupSRAMPath(int slot) -{ - strncpy(SRAMPath[slot], ROMPath[slot], 1023); - SRAMPath[slot][1023] = '\0'; - strncpy(SRAMPath[slot] + strlen(ROMPath[slot]) - 3, "sav", 3); -} - -int VerifyDSBIOS() -{ - FILE* f; - long len; - - if (!Config::ExternalBIOSEnable) return Load_OK; - - f = Platform::OpenLocalFile(Config::BIOS9Path, "rb"); - if (!f) return Load_BIOS9Missing; - - fseek(f, 0, SEEK_END); - len = ftell(f); - if (len != 0x1000) - { - fclose(f); - return Load_BIOS9Bad; - } - - fclose(f); - - f = Platform::OpenLocalFile(Config::BIOS7Path, "rb"); - if (!f) return Load_BIOS7Missing; - - fseek(f, 0, SEEK_END); - len = ftell(f); - if (len != 0x4000) - { - fclose(f); - return Load_BIOS7Bad; - } - - fclose(f); - - return Load_OK; -} - -int VerifyDSiBIOS() -{ - FILE* f; - long len; - - // TODO: check the first 32 bytes - - f = Platform::OpenLocalFile(Config::DSiBIOS9Path, "rb"); - if (!f) return Load_DSiBIOS9Missing; - - fseek(f, 0, SEEK_END); - len = ftell(f); - if (len != 0x10000) - { - fclose(f); - return Load_DSiBIOS9Bad; - } - - fclose(f); - - f = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb"); - if (!f) return Load_DSiBIOS7Missing; - - fseek(f, 0, SEEK_END); - len = ftell(f); - if (len != 0x10000) - { - fclose(f); - return Load_DSiBIOS7Bad; - } - - fclose(f); - - return Load_OK; -} - -int VerifyDSFirmware() -{ - FILE* f; - long len; - - if (!Config::ExternalBIOSEnable) return Load_FirmwareNotBootable; - - f = Platform::OpenLocalFile(Config::FirmwarePath, "rb"); - if (!f) return Load_FirmwareNotBootable; - - fseek(f, 0, SEEK_END); - len = ftell(f); - if (len == 0x20000) - { - // 128KB firmware, not bootable - fclose(f); - return Load_FirmwareNotBootable; - } - else if (len != 0x40000 && len != 0x80000) - { - fclose(f); - return Load_FirmwareBad; - } - - fclose(f); - - return Load_OK; -} - -int VerifyDSiFirmware() -{ - FILE* f; - long len; - - f = Platform::OpenLocalFile(Config::DSiFirmwarePath, "rb"); - if (!f) return Load_FirmwareMissing; - - fseek(f, 0, SEEK_END); - len = ftell(f); - if (len != 0x20000) - { - // not 128KB - // TODO: check whether those work - fclose(f); - return Load_FirmwareBad; - } - - fclose(f); - - return Load_OK; -} - -int SetupDSiNAND() -{ - FILE* f; - long len; - - f = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b"); - if (!f) return Load_DSiNANDMissing; - - // TODO: some basic checks - // check that it has the nocash footer, and all - - DSi::SDMMCFile = f; - - return Load_OK; -} - -void LoadCheats() -{ - if (CheatFile) - { - delete CheatFile; - CheatFile = nullptr; - } - - char filename[1024]; - if (ROMPath[ROMSlot_NDS][0] != '\0') - { - strncpy(filename, ROMPath[ROMSlot_NDS], 1023); - filename[1023] = '\0'; - strncpy(filename + strlen(ROMPath[ROMSlot_NDS]) - 3, "mch", 3); - } - else - { - strncpy(filename, "firmware.mch", 1023); - } - - // TODO: check for error (malformed cheat file, ...) - CheatFile = new ARCodeFile(filename); - - AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr); -} - -int LoadBIOS() -{ - DSi::CloseDSiNAND(); - - int res; - - res = VerifyDSBIOS(); - if (res != Load_OK) return res; - - if (Config::ConsoleType == 1) - { - res = VerifyDSiBIOS(); - if (res != Load_OK) return res; - - res = VerifyDSiFirmware(); - if (res != Load_OK) return res; - - res = SetupDSiNAND(); - if (res != Load_OK) return res; - } - else - { - res = VerifyDSFirmware(); - if (res != Load_OK) return res; - } - - // TODO: - // original code in the libui frontend called NDS::LoadGBAROM() if needed - // should this be carried over here? - // is that behavior consistent with that of LoadROM() below? - - ROMPath[ROMSlot_NDS][0] = '\0'; - SRAMPath[ROMSlot_NDS][0] = '\0'; - - NDS::SetConsoleType(Config::ConsoleType); - NDS::LoadBIOS(); - - SavestateLoaded = false; - - LoadCheats(); - - return Load_OK; -} - -int LoadROM(const u8 *romdata, u32 romlength, const char *archivefilename, const char *romfilename, const char *sramfilename, int slot) -{ - int res; - bool directboot = Config::DirectBoot != 0; - - if (Config::ConsoleType == 1 && slot == 1) - { - // cannot load a GBA ROM into a DSi - return Load_ROMLoadError; - } - - res = VerifyDSBIOS(); - if (res != Load_OK) return res; - - if (Config::ConsoleType == 1) - { - res = VerifyDSiBIOS(); - if (res != Load_OK) return res; - - res = VerifyDSiFirmware(); - if (res != Load_OK) return res; - - res = SetupDSiNAND(); - if (res != Load_OK) return res; - - GBACart::Eject(); - ROMPath[ROMSlot_GBA][0] = '\0'; - } - else - { - res = VerifyDSFirmware(); - if (res != Load_OK) - { - if (res == Load_FirmwareNotBootable) - directboot = true; - else - return res; - } - } - - char oldpath[1024]; - char oldsram[1024]; - strncpy(oldpath, ROMPath[slot], 1024); - strncpy(oldsram, SRAMPath[slot], 1024); - - strncpy(SRAMPath[slot], sramfilename, 1024); - strncpy(ROMPath[slot], archivefilename, 1024); - - NDS::SetConsoleType(Config::ConsoleType); - - if (slot == ROMSlot_NDS && NDS::LoadROM(romdata, romlength, SRAMPath[slot], directboot)) - { - SavestateLoaded = false; - - LoadCheats(); - - // Reload the inserted GBA cartridge (if any) - // TODO: report failure there?? - //if (ROMPath[ROMSlot_GBA][0] != '\0') NDS::LoadGBAROM(ROMPath[ROMSlot_GBA], SRAMPath[ROMSlot_GBA]); - - strncpy(PrevSRAMPath[slot], SRAMPath[slot], 1024); // safety - return Load_OK; - } - else if (slot == ROMSlot_GBA && NDS::LoadGBAROM(romdata, romlength, romfilename, SRAMPath[slot])) - { - SavestateLoaded = false; // checkme?? - - strncpy(PrevSRAMPath[slot], SRAMPath[slot], 1024); // safety - return Load_OK; - } - else - { - strncpy(ROMPath[slot], oldpath, 1024); - strncpy(SRAMPath[slot], oldsram, 1024); - return Load_ROMLoadError; - } -} - -int LoadROM(const char* file, int slot) -{ - DSi::CloseDSiNAND(); - - int res; - bool directboot = Config::DirectBoot != 0; - - if (Config::ConsoleType == 1 && slot == 1) - { - // cannot load a GBA ROM into a DSi - return Load_ROMLoadError; - } - - res = VerifyDSBIOS(); - if (res != Load_OK) return res; - - if (Config::ConsoleType == 1) - { - res = VerifyDSiBIOS(); - if (res != Load_OK) return res; - - res = VerifyDSiFirmware(); - if (res != Load_OK) return res; - - res = SetupDSiNAND(); - if (res != Load_OK) return res; - - GBACart::Eject(); - ROMPath[ROMSlot_GBA][0] = '\0'; - } - else - { - res = VerifyDSFirmware(); - if (res != Load_OK) - { - if (res == Load_FirmwareNotBootable) - directboot = true; - else - return res; - } - } - - char oldpath[1024]; - char oldsram[1024]; - strncpy(oldpath, ROMPath[slot], 1024); - strncpy(oldsram, SRAMPath[slot], 1024); - - strncpy(ROMPath[slot], file, 1023); - ROMPath[slot][1023] = '\0'; - - SetupSRAMPath(0); - SetupSRAMPath(1); - - NDS::SetConsoleType(Config::ConsoleType); - - if (slot == ROMSlot_NDS && NDS::LoadROM(ROMPath[slot], SRAMPath[slot], directboot)) - { - SavestateLoaded = false; - - LoadCheats(); - - // Reload the inserted GBA cartridge (if any) - // TODO: report failure there?? - if (ROMPath[ROMSlot_GBA][0] != '\0') NDS::LoadGBAROM(ROMPath[ROMSlot_GBA], SRAMPath[ROMSlot_GBA]); - - strncpy(PrevSRAMPath[slot], SRAMPath[slot], 1024); // safety - return Load_OK; - } - else if (slot == ROMSlot_GBA && NDS::LoadGBAROM(ROMPath[slot], SRAMPath[slot])) - { - SavestateLoaded = false; // checkme?? - - strncpy(PrevSRAMPath[slot], SRAMPath[slot], 1024); // safety - return Load_OK; - } - else - { - strncpy(ROMPath[slot], oldpath, 1024); - strncpy(SRAMPath[slot], oldsram, 1024); - return Load_ROMLoadError; - } -} - -void ROMIcon(u8 (&data)[512], u16 (&palette)[16], u32* iconRef) -{ - int index = 0; - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - for (int k = 0; k < 8; k++) - { - for (int l = 0; l < 8; l++) - { - u8 pal_index = index % 2 ? data[index/2] >> 4 : data[index/2] & 0x0F; - u8 r = ((palette[pal_index] >> 0) & 0x1F) * 255 / 31; - u8 g = ((palette[pal_index] >> 5) & 0x1F) * 255 / 31; - u8 b = ((palette[pal_index] >> 10) & 0x1F) * 255 / 31; - u8 a = pal_index ? 255: 0; - u32* row = &iconRef[256 * i + 32 * k + 8 * j]; - row[l] = (a << 24) | (r << 16) | (g << 8) | b; - index++; - } - } - } - } -} - -#define SEQ_FLIPV(i) ((i & 0b1000000000000000) >> 15) -#define SEQ_FLIPH(i) ((i & 0b0100000000000000) >> 14) -#define SEQ_PAL(i) ((i & 0b0011100000000000) >> 11) -#define SEQ_BMP(i) ((i & 0b0000011100000000) >> 8) -#define SEQ_DUR(i) ((i & 0b0000000011111111) >> 0) - -void AnimatedROMIcon(u8 (&data)[8][512], u16 (&palette)[8][16], u16 (&sequence)[64], u32 (&animatedTexRef)[32 * 32 * 64], std::vector<int> &animatedSequenceRef) -{ - for (int i = 0; i < 64; i++) - { - if (!sequence[i]) - break; - u32* frame = &animatedTexRef[32 * 32 * i]; - ROMIcon(data[SEQ_BMP(sequence[i])], palette[SEQ_PAL(sequence[i])], frame); - - if (SEQ_FLIPH(sequence[i])) - { - for (int x = 0; x < 32; x++) - { - for (int y = 0; y < 32/2; y++) - { - std::swap(frame[x * 32 + y], frame[x * 32 + (32 - 1 - y)]); - } - } - } - if (SEQ_FLIPV(sequence[i])) - { - for (int x = 0; x < 32/2; x++) - { - for (int y = 0; y < 32; y++) - { - std::swap(frame[x * 32 + y], frame[(32 - 1 - x) * 32 + y]); - } - } - } - - for (int j = 0; j < SEQ_DUR(sequence[i]); j++) - animatedSequenceRef.push_back(i); - } -} - -void UnloadROM(int slot) -{ - if (slot == ROMSlot_NDS) - { - // TODO! - } - else if (slot == ROMSlot_GBA) - { - GBACart::Eject(); - } - - ROMPath[slot][0] = '\0'; - - DSi::CloseDSiNAND(); -} - -int Reset() -{ - DSi::CloseDSiNAND(); - - int res; - bool directboot = Config::DirectBoot != 0; - - res = VerifyDSBIOS(); - if (res != Load_OK) return res; - - if (Config::ConsoleType == 1) - { - res = VerifyDSiBIOS(); - if (res != Load_OK) return res; - - res = VerifyDSiFirmware(); - if (res != Load_OK) return res; - - res = SetupDSiNAND(); - if (res != Load_OK) return res; - - GBACart::Eject(); - ROMPath[ROMSlot_GBA][0] = '\0'; - } - else - { - res = VerifyDSFirmware(); - if (res != Load_OK) - { - if (res == Load_FirmwareNotBootable) - directboot = true; - else - return res; - } - } - - SavestateLoaded = false; - - NDS::SetConsoleType(Config::ConsoleType); - - if (ROMPath[ROMSlot_NDS][0] == '\0') - { - NDS::LoadBIOS(); - } - else - { - char ext[5] = {0}; int _len = strlen(ROMPath[ROMSlot_NDS]); - strncpy(ext, ROMPath[ROMSlot_NDS] + _len - 4, 4); - - if(!strncasecmp(ext, ".nds", 4) || !strncasecmp(ext, ".srl", 4) || !strncasecmp(ext, ".dsi", 4)) - { - SetupSRAMPath(0); - if (!NDS::LoadROM(ROMPath[ROMSlot_NDS], SRAMPath[ROMSlot_NDS], directboot)) - return Load_ROMLoadError; - } -#ifdef ARCHIVE_SUPPORT_ENABLED - else - { - u8 *romdata = nullptr; u32 romlen; - char romfilename[1024] = {0}, sramfilename[1024]; - strncpy(sramfilename, SRAMPath[ROMSlot_NDS], 1024); // Use existing SRAMPath - - int pos = strlen(sramfilename) - 1; - while(pos > 0 && sramfilename[pos] != '/' && sramfilename[pos] != '\\') - --pos; - - strncpy(romfilename, &sramfilename[pos + 1], 1024); - strncpy(&romfilename[strlen(romfilename) - 3], NDSROMExtension, 3); // extension could be nds, srl or dsi - printf("RESET loading from archive : %s\n", romfilename); - romlen = Archive::ExtractFileFromArchive(ROMPath[ROMSlot_NDS], romfilename, &romdata); - if(!romdata) - return Load_ROMLoadError; - - bool ok = NDS::LoadROM(romdata, romlen, sramfilename, directboot); - delete romdata; - if(!ok) - return Load_ROMLoadError; - } -#endif - } - - if (ROMPath[ROMSlot_GBA][0] != '\0') - { - char ext[5] = {0}; int _len = strlen(ROMPath[ROMSlot_GBA]); - strncpy(ext, ROMPath[ROMSlot_GBA] + _len - 4, 4); - - if(!strncasecmp(ext, ".gba", 4)) - { - SetupSRAMPath(1); - if (!NDS::LoadGBAROM(ROMPath[ROMSlot_GBA], SRAMPath[ROMSlot_GBA])) - return Load_ROMLoadError; - } -#ifdef ARCHIVE_SUPPORT_ENABLED - else - { - u8 *romdata = nullptr; u32 romlen; - char romfilename[1024] = {0}, sramfilename[1024]; - strncpy(sramfilename, SRAMPath[ROMSlot_GBA], 1024); // Use existing SRAMPath - - int pos = strlen(sramfilename) - 1; - while(pos > 0 && sramfilename[pos] != '/' && sramfilename[pos] != '\\') - --pos; - - strncpy(romfilename, &sramfilename[pos + 1], 1024); - strncpy(&romfilename[strlen(romfilename) - 3], "gba", 3); - printf("RESET loading from archive : %s\n", romfilename); - romlen = Archive::ExtractFileFromArchive(ROMPath[ROMSlot_GBA], romfilename, &romdata); - if(!romdata) - return Load_ROMLoadError; - - bool ok = NDS::LoadGBAROM(romdata, romlen, romfilename, SRAMPath[ROMSlot_GBA]); - delete romdata; - if(!ok) - return Load_ROMLoadError; - } -#endif - } - - LoadCheats(); - - return Load_OK; -} - - -// SAVESTATE TODO -// * configurable paths. not everyone wants their ROM directory to be polluted, I guess. - -void GetSavestateName(int slot, char* filename, int len) -{ - int pos; - - if (ROMPath[ROMSlot_NDS][0] == '\0') // running firmware, no ROM - { - strcpy(filename, "firmware"); - pos = 8; - } - else - { - char *rompath; - char ext[5] = {0}; int _len = strlen(ROMPath[ROMSlot_NDS]); - strncpy(ext, ROMPath[ROMSlot_NDS] + _len - 4, 4); - - if(!strncasecmp(ext, ".nds", 4) || !strncasecmp(ext, ".srl", 4) || !strncasecmp(ext, ".dsi", 4)) - rompath = ROMPath[ROMSlot_NDS]; - else - rompath = SRAMPath[ROMSlot_NDS]; // If archive, construct ssname from sram file - - int l = strlen(rompath); - pos = l; - while (rompath[pos] != '.' && pos > 0) pos--; - if (pos == 0) pos = l; - - // avoid buffer overflow. shoddy - if (pos > len-5) pos = len-5; - - strncpy(&filename[0], rompath, pos); - } - strcpy(&filename[pos], ".ml"); - filename[pos+3] = '0'+slot; - filename[pos+4] = '\0'; -} - -bool SavestateExists(int slot) -{ - char ssfile[1024]; - GetSavestateName(slot, ssfile, 1024); - return Platform::FileExists(ssfile); -} - -bool LoadState(const char* filename) -{ - u32 oldGBACartCRC = GBACart::CartCRC; - - // backup - Savestate* backup = new Savestate("timewarp.mln", true); - NDS::DoSavestate(backup); - delete backup; - - bool failed = false; - - Savestate* state = new Savestate(filename, false); - if (state->Error) - { - delete state; - - //uiMsgBoxError(MainWindow, "Error", "Could not load savestate file."); - - // current state might be crapoed, so restore from sane backup - state = new Savestate("timewarp.mln", false); - failed = true; - } - - NDS::DoSavestate(state); - delete state; - - if (!failed) - { - if (Config::SavestateRelocSRAM && ROMPath[ROMSlot_NDS][0]!='\0') - { - strncpy(PrevSRAMPath[ROMSlot_NDS], SRAMPath[0], 1024); - - strncpy(SRAMPath[ROMSlot_NDS], filename, 1019); - int len = strlen(SRAMPath[ROMSlot_NDS]); - strcpy(&SRAMPath[ROMSlot_NDS][len], ".sav"); - SRAMPath[ROMSlot_NDS][len+4] = '\0'; - - NDS::RelocateSave(SRAMPath[ROMSlot_NDS], false); - } - - bool loadedPartialGBAROM = false; - - // in case we have a GBA cart inserted, and the GBA ROM changes - // due to having loaded a save state, we do not want to reload - // the previous cartridge on reset, or commit writes to any - // loaded save file. therefore, their paths are "nulled". - if (GBACart::CartInserted && GBACart::CartCRC != oldGBACartCRC) - { - ROMPath[ROMSlot_GBA][0] = '\0'; - SRAMPath[ROMSlot_GBA][0] = '\0'; - loadedPartialGBAROM = true; - } - - // TODO forward this to user in a meaningful way!! - /*char msg[64]; - if (slot > 0) sprintf(msg, "State loaded from slot %d%s", - slot, loadedPartialGBAROM ? " (GBA ROM header only)" : ""); - else sprintf(msg, "State loaded from file%s", - loadedPartialGBAROM ? " (GBA ROM header only)" : ""); - OSD::AddMessage(0, msg);*/ - - SavestateLoaded = true; - } - - return !failed; -} - -bool SaveState(const char* filename) -{ - Savestate* state = new Savestate(filename, true); - if (state->Error) - { - delete state; - return false; - } - else - { - NDS::DoSavestate(state); - delete state; - - if (Config::SavestateRelocSRAM && ROMPath[ROMSlot_NDS][0]!='\0') - { - strncpy(SRAMPath[ROMSlot_NDS], filename, 1019); - int len = strlen(SRAMPath[ROMSlot_NDS]); - strcpy(&SRAMPath[ROMSlot_NDS][len], ".sav"); - SRAMPath[ROMSlot_NDS][len+4] = '\0'; - - NDS::RelocateSave(SRAMPath[ROMSlot_NDS], true); - } - } - - return true; -} - -void UndoStateLoad() -{ - if (!SavestateLoaded) return; - - // pray that this works - // what do we do if it doesn't??? - // but it should work. - Savestate* backup = new Savestate("timewarp.mln", false); - NDS::DoSavestate(backup); - delete backup; - - if (ROMPath[ROMSlot_NDS][0]!='\0') - { - strncpy(SRAMPath[ROMSlot_NDS], PrevSRAMPath[ROMSlot_NDS], 1024); - NDS::RelocateSave(SRAMPath[ROMSlot_NDS], false); - } -} - -int ImportSRAM(const char* filename) -{ - FILE* file = fopen(filename, "rb"); - fseek(file, 0, SEEK_END); - u32 size = ftell(file); - u8* importData = new u8[size]; - rewind(file); - fread(importData, size, 1, file); - fclose(file); - - int diff = NDS::ImportSRAM(importData, size); - delete[] importData; - return diff; -} - -void EnableCheats(bool enable) -{ - CheatsOn = enable; - if (CheatFile) - AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr); -} - -} diff --git a/src/frontend/Util_Video.cpp b/src/frontend/Util_Video.cpp index 33f0183..778c299 100644 --- a/src/frontend/Util_Video.cpp +++ b/src/frontend/Util_Video.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/mic_blow.h b/src/frontend/mic_blow.h index fcce5aa..0e1069b 100644 --- a/src/frontend/mic_blow.h +++ b/src/frontend/mic_blow.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/ArchiveUtil.cpp b/src/frontend/qt_sdl/ArchiveUtil.cpp index 6919d48..fff1a94 100644 --- a/src/frontend/qt_sdl/ArchiveUtil.cpp +++ b/src/frontend/qt_sdl/ArchiveUtil.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, WaluigiWare64 + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -17,43 +17,66 @@ */ #include "ArchiveUtil.h" +#include "Platform.h" namespace Archive { -QVector<QString> ListArchive(const char* path) +#ifdef __WIN32__ +#define melon_archive_open(a, f, b) archive_read_open_filename_w(a, (const wchar_t*)f.utf16(), b) +#else +#define melon_archive_open(a, f, b) archive_read_open_filename(a, f.toUtf8().constData(), b) +#endif // __WIN32__ + +bool compareCI(const QString& s1, const QString& s2) +{ + return s1.toLower() < s2.toLower(); +} + +QVector<QString> ListArchive(QString path) { struct archive *a; struct archive_entry *entry; int r; - QVector<QString> fileList = {"OK"}; - + QVector<QString> fileList; + a = archive_read_new(); + archive_read_support_filter_all(a); archive_read_support_format_all(a); - r = archive_read_open_filename(a, path, 10240); + + //r = archive_read_open_filename(a, path, 10240); + r = melon_archive_open(a, path, 10240); if (r != ARCHIVE_OK) { return QVector<QString> {"Err"}; } - - while (archive_read_next_header(a, &entry) == ARCHIVE_OK) + + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { - fileList.push_back(archive_entry_pathname(entry)); - archive_read_data_skip(a); + if (archive_entry_filetype(entry) != AE_IFREG) + continue; + + fileList.push_back(archive_entry_pathname_utf8(entry)); + archive_read_data_skip(a); } + archive_read_close(a); - archive_read_free(a); + archive_read_free(a); + if (r != ARCHIVE_OK) { return QVector<QString> {"Err"}; } - + + std::stable_sort(fileList.begin(), fileList.end(), compareCI); + fileList.prepend("OK"); + return fileList; } -QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile, QByteArray *romBuffer) +QVector<QString> ExtractFileFromArchive(QString path, QString wantedFile, QByteArray *romBuffer) { struct archive *a = archive_read_new(); struct archive_entry *entry; @@ -61,8 +84,9 @@ QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile archive_read_support_format_all(a); archive_read_support_filter_all(a); - - r = archive_read_open_filename(a, path, 10240); + + //r = archive_read_open_filename(a, path, 10240); + r = melon_archive_open(a, path, 10240); if (r != ARCHIVE_OK) { return QVector<QString> {"Err"}; @@ -70,7 +94,7 @@ QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { - if (strcmp(wantedFile, archive_entry_pathname(entry)) == 0) + if (strcmp(wantedFile.toUtf8().constData(), archive_entry_pathname_utf8(entry)) == 0) { break; } @@ -92,7 +116,45 @@ QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile } -u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata) +u32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize) +{ + struct archive *a = archive_read_new(); + struct archive_entry *entry; + int r; + + if (!filedata) return -1; + + archive_read_support_format_all(a); + archive_read_support_filter_all(a); + + //r = archive_read_open_filename(a, path, 10240); + r = melon_archive_open(a, path, 10240); + if (r != ARCHIVE_OK) + { + return -1; + } + + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) + { + if (strcmp(wantedFile.toUtf8().constData(), archive_entry_pathname_utf8(entry)) == 0) + { + break; + } + } + + size_t bytesToRead = archive_entry_size(entry); + if (filesize) *filesize = bytesToRead; + *filedata = new u8[bytesToRead]; + ssize_t bytesRead = archive_read_data(a, *filedata, bytesToRead); + + archive_read_close(a); + archive_read_free(a); + + return (u32)bytesRead; + +} + +/*u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata) { QByteArray romBuffer; QVector<QString> extractResult = ExtractFileFromArchive(path, wantedFile, &romBuffer); @@ -107,6 +169,6 @@ u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdat memcpy(*romdata, romBuffer.data(), len); return len; -} +}*/ } diff --git a/src/frontend/qt_sdl/ArchiveUtil.h b/src/frontend/qt_sdl/ArchiveUtil.h index a8a4a14..761f542 100644 --- a/src/frontend/qt_sdl/ArchiveUtil.h +++ b/src/frontend/qt_sdl/ArchiveUtil.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, WaluigiWare64 + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -34,10 +34,11 @@ namespace Archive { - -QVector<QString> ListArchive(const char* path); -QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile, QByteArray *romBuffer); -u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata); + +QVector<QString> ListArchive(QString path); +u32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize); +//QVector<QString> ExtractFileFromArchive(QString path, QString wantedFile, QByteArray *romBuffer); +//u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata); } diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp index d4ce678..4beefaf 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp +++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -62,11 +62,25 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui( connect(grpMicMode, SIGNAL(buttonClicked(int)), this, SLOT(onChangeMicMode(int))); grpMicMode->button(Config::MicInputType)->setChecked(true); - ui->txtMicWavPath->setText(Config::MicWavPath); + ui->txtMicWavPath->setText(QString::fromStdString(Config::MicWavPath)); bool iswav = (Config::MicInputType == 3); ui->txtMicWavPath->setEnabled(iswav); ui->btnMicWavBrowse->setEnabled(iswav); + + int inst = Platform::InstanceID(); + if (inst > 0) + { + ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); + ui->cbInterpolation->setEnabled(false); + ui->cbBitrate->setEnabled(false); + for (QAbstractButton* btn : grpMicMode->buttons()) + btn->setEnabled(false); + ui->txtMicWavPath->setEnabled(false); + ui->btnMicWavBrowse->setEnabled(false); + } + else + ui->lblInstanceNum->hide(); } AudioSettingsDialog::~AudioSettingsDialog() @@ -77,7 +91,7 @@ AudioSettingsDialog::~AudioSettingsDialog() void AudioSettingsDialog::on_AudioSettingsDialog_accepted() { Config::MicInputType = grpMicMode->checkedId(); - strncpy(Config::MicWavPath, ui->txtMicWavPath->text().toStdString().c_str(), 1023); Config::MicWavPath[1023] = '\0'; + Config::MicWavPath = ui->txtMicWavPath->text().toStdString(); Config::Save(); closeDlg(); diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.h b/src/frontend/qt_sdl/AudioSettingsDialog.h index 0bb32c6..498c152 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.h +++ b/src/frontend/qt_sdl/AudioSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.ui b/src/frontend/qt_sdl/AudioSettingsDialog.ui index d7cfadd..8fc38d9 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.ui +++ b/src/frontend/qt_sdl/AudioSettingsDialog.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>482</width> - <height>256</height> + <height>301</height> </rect> </property> <property name="sizePolicy"> @@ -24,6 +24,13 @@ <enum>QLayout::SetFixedSize</enum> </property> <item> + <widget class="QLabel" name="lblInstanceNum"> + <property name="text"> + <string>Configuring settings for instance X</string> + </property> + </widget> + </item> + <item> <widget class="QGroupBox" name="groupBox"> <property name="title"> <string>Audio output</string> @@ -76,7 +83,7 @@ <item row="1" column="1"> <widget class="QComboBox" name="cbBitrate"> <property name="whatsThis"> - <string><html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html></string> + <string><html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html></string> </property> </widget> </item> diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index ad38e37..a8d6e4b 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -1,139 +1,123 @@ -project(qt_sdl) +include(CMakeDependentOption) -SET(SOURCES_QT_SDL +include(FixInterfaceIncludes) + +set(SOURCES_QT_SDL main.cpp main_shaders.h CheatsDialog.cpp - Config.cpp + Config.cpp EmuSettingsDialog.cpp + PowerManagement/PowerManagementDialog.cpp + PowerManagement/resources/battery.qrc InputConfig/InputConfigDialog.cpp InputConfig/MapButton.h InputConfig/resources/ds.qrc VideoSettingsDialog.cpp + CameraSettingsDialog.cpp AudioSettingsDialog.cpp FirmwareSettingsDialog.cpp + PathSettingsDialog.cpp + MPSettingsDialog.cpp WifiSettingsDialog.cpp InterfaceSettingsDialog.cpp ROMInfoDialog.cpp + RAMInfoDialog.cpp TitleManagerDialog.cpp Input.cpp LAN_PCap.cpp LAN_Socket.cpp + LocalMP.cpp OSD.cpp OSD_shaders.h font.h Platform.cpp QPathInput.h + ROMManager.cpp + SaveManager.cpp + CameraManager.cpp ArchiveUtil.h ArchiveUtil.cpp - ../Util_ROM.cpp ../Util_Video.cpp ../Util_Audio.cpp ../FrontendUtil.h ../mic_blow.h - ../SharedConfig.h ${CMAKE_SOURCE_DIR}/res/melon.qrc -) + ) + +if (APPLE) + option(USE_QT6 "Build using Qt 6 instead of 5" ON) +else() + option(USE_QT6 "Build using Qt 6 instead of 5" OFF) +endif() -option(USE_QT6 "Build using Qt 6 instead of 5" OFF) if (WIN32) set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -i <SOURCE> -o <OBJECT>") endif() if (USE_QT6) - if (BUILD_STATIC AND QT6_STATIC_DIR) - set(QT6_STATIC_BASE ${QT6_STATIC_DIR}/lib/cmake/Qt6) - set(Qt6_DIR ${QT6_STATIC_BASE}) - set(Qt6Core_DIR ${QT6_STATIC_BASE}Core) - set(Qt6Gui_DIR ${QT6_STATIC_BASE}Gui) - set(Qt6Widgets_DIR ${QT6_STATIC_BASE}Widgets) - set(Qt6Network_DIR ${QT6_STATIC_BASE}Network) - set(Qt6OpenGL_DIR ${QT6_STATIC_BASE}OpenGL) - set(Qt6OpenGLWidgets_DIR ${QT6_STATIC_BASE}OpenGLWidgets) - endif() - find_package(Qt6 COMPONENTS Core Gui Widgets Network OpenGL OpenGLWidgets REQUIRED) - set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::OpenGL Qt6::OpenGLWidgets) + find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED) + set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Multimedia Qt6::OpenGL Qt6::OpenGLWidgets) else() - if (BUILD_STATIC AND QT5_STATIC_DIR) - set(QT5_STATIC_BASE ${QT5_STATIC_DIR}/lib/cmake/Qt5) - set(Qt5_DIR ${QT5_STATIC_BASE}) - set(Qt5Core_DIR ${QT5_STATIC_BASE}Core) - set(Qt5Gui_DIR ${QT5_STATIC_BASE}Gui) - set(Qt5Widgets_DIR ${QT5_STATIC_BASE}Widgets) - set(Qt5Network_DIR ${QT5_STATIC_BASE}Network) - endif() - find_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED) - set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network) + find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia REQUIRED) + set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Multimedia) endif() set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) +if (BUILD_STATIC) + list(APPEND PKG_CONFIG_EXECUTABLE "--static") +endif() + find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) -find_package(Iconv REQUIRED) -pkg_check_modules(SDL2 REQUIRED sdl2) -pkg_check_modules(SLIRP REQUIRED slirp) -pkg_check_modules(LIBARCHIVE REQUIRED libarchive) -add_compile_definitions(ARCHIVE_SUPPORT_ENABLED) +pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2) +pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp) +pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive) -if (WIN32 AND (CMAKE_BUILD_TYPE STREQUAL Release)) - add_executable(melonDS WIN32 ${SOURCES_QT_SDL}) -else() - add_executable(melonDS ${SOURCES_QT_SDL}) -endif() +fix_interface_includes(PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive) -target_link_libraries(melonDS ${CMAKE_THREAD_LIBS_INIT}) +add_compile_definitions(ARCHIVE_SUPPORT_ENABLED) -target_include_directories(melonDS PRIVATE ${SDL2_INCLUDE_DIRS} ${SDL2_PREFIX}/include ${SLIRP_INCLUDE_DIRS} ${LIBARCHIVE_INCLUDE_DIRS}) -target_link_directories(melonDS PRIVATE ${SDL2_LIBRARY_DIRS} ${SLIRP_LIBRARY_DIRS}) -target_link_directories(melonDS PRIVATE ${LIBARCHIVE_LIBRARY_DIRS}) - -target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") -target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") -target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..") -target_link_libraries(melonDS core) +add_executable(melonDS ${SOURCES_QT_SDL}) if (BUILD_STATIC) - target_link_libraries(melonDS -static ${SDL2_STATIC_LIBRARIES} ${SLIRP_STATIC_LIBRARIES} ${LIBARCHIVE_STATIC_LIBRARIES}) qt_import_plugins(melonDS INCLUDE Qt::QSvgPlugin) -else() - target_link_libraries(melonDS ${SDL2_LIBRARIES} ${SLIRP_LIBRARIES} ${LIBARCHIVE_LIBRARIES}) + target_link_options(melonDS PRIVATE -static) endif() -if (NOT Iconv_IS_BUILT_IN) - target_link_libraries(melonDS ${Iconv_LIBRARIES}) -endif() +target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") +target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") +target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..") +target_link_libraries(melonDS PRIVATE core) +target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive) +target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS}) if (UNIX) option(PORTABLE "Make a portable build that looks for its configuration in the current directory" OFF) - target_link_libraries(melonDS ${QT_LINK_LIBS}) - if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - target_link_libraries(melonDS dl) - endif() elseif (WIN32) option(PORTABLE "Make a portable build that looks for its configuration in the current directory" ON) + configure_file("${CMAKE_SOURCE_DIR}/res/melon.rc.in" "${CMAKE_SOURCE_DIR}/melon.rc") target_sources(melonDS PUBLIC "${CMAKE_SOURCE_DIR}/melon.rc") - target_link_libraries(melonDS comctl32 d2d1 dwrite uxtheme ws2_32 iphlpapi gdi32) - if (BUILD_STATIC) - target_link_libraries(melonDS imm32 winmm version setupapi -static z zstd ${QT_LINK_LIBS}) - else() - target_link_libraries(melonDS ${QT_LINK_LIBS}) - endif() + target_link_libraries(melonDS PRIVATE ws2_32 iphlpapi) + set_target_properties(melonDS PROPERTIES LINK_FLAGS_DEBUG "-mconsole") endif() if (PORTABLE) - add_definitions(-DPORTABLE) + target_compile_definitions(melonDS PRIVATE PORTABLE) endif() if (APPLE) + target_sources(melonDS PRIVATE sem_timedwait.cpp) + # Copy icon into the bundle set(RESOURCE_FILES "${CMAKE_SOURCE_DIR}/res/melon.icns") target_sources(melonDS PUBLIC "${RESOURCE_FILES}") @@ -143,14 +127,7 @@ if (APPLE) MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/melon.plist.in OUTPUT_NAME melonDS RESOURCE "${RESOURCE_FILES}") - - # Qt 6 requires macOS 10.15 if building on 10.15 or greater - if(CMAKE_SYSTEM_VERSION VERSION_GREATER_EQUAL 19.0.0) - if (USE_QT6) - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version" FORCE) - endif() - endif() option(MACOS_BUNDLE_LIBS "Bundle libraries with the app on macOS" OFF) option(MACOS_BUILD_DMG "Build DMG image of the macOS application bundle" OFF) @@ -168,8 +145,8 @@ endif() if (UNIX AND NOT APPLE) foreach(SIZE 16 32 48 64 128 256) install(FILES ${CMAKE_SOURCE_DIR}/res/icon/melon_${SIZE}x${SIZE}.png - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/${SIZE}x${SIZE}/apps - RENAME net.kuribo64.melonDS.png) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/${SIZE}x${SIZE}/apps + RENAME net.kuribo64.melonDS.png) endforeach() install(FILES ${CMAKE_SOURCE_DIR}/res/net.kuribo64.melonDS.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) diff --git a/src/frontend/qt_sdl/CameraManager.cpp b/src/frontend/qt_sdl/CameraManager.cpp new file mode 100644 index 0000000..19cf8d4 --- /dev/null +++ b/src/frontend/qt_sdl/CameraManager.cpp @@ -0,0 +1,612 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "CameraManager.h" +#include "Config.h" + + +#if QT_VERSION >= 0x060000 + +CameraFrameDumper::CameraFrameDumper(QObject* parent) : QVideoSink(parent) +{ + cam = (CameraManager*)parent; + + connect(this, &CameraFrameDumper::videoFrameChanged, this, &CameraFrameDumper::present); +} + +void CameraFrameDumper::present(const QVideoFrame& _frame) +{ + QVideoFrame frame(_frame); + if (!frame.map(QVideoFrame::ReadOnly)) + return; + if (!frame.isReadable()) + { + frame.unmap(); + return; + } + + switch (frame.pixelFormat()) + { + case QVideoFrameFormat::Format_XRGB8888: + case QVideoFrameFormat::Format_YUYV: + cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrameFormat::Format_YUYV); + break; + + case QVideoFrameFormat::Format_UYVY: + cam->feedFrame_UYVY((u32*)frame.bits(0), frame.width(), frame.height()); + break; + + case QVideoFrameFormat::Format_NV12: + cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height()); + break; + } + + frame.unmap(); +} + +#else + +CameraFrameDumper::CameraFrameDumper(QObject* parent) : QAbstractVideoSurface(parent) +{ + cam = (CameraManager*)parent; +} + +bool CameraFrameDumper::present(const QVideoFrame& _frame) +{ + QVideoFrame frame(_frame); + if (!frame.map(QAbstractVideoBuffer::ReadOnly)) + return false; + if (!frame.isReadable()) + { + frame.unmap(); + return false; + } + + switch (frame.pixelFormat()) + { + case QVideoFrame::Format_RGB32: + case QVideoFrame::Format_YUYV: + cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrame::Format_YUYV); + break; + + case QVideoFrame::Format_UYVY: + cam->feedFrame_UYVY((u32*)frame.bits(0), frame.width(), frame.height()); + break; + + case QVideoFrame::Format_NV12: + cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height()); + break; + } + + frame.unmap(); + + return true; +} + +QList<QVideoFrame::PixelFormat> CameraFrameDumper::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const +{ + QList<QVideoFrame::PixelFormat> ret; + + ret.append(QVideoFrame::Format_RGB32); + ret.append(QVideoFrame::Format_YUYV); + ret.append(QVideoFrame::Format_UYVY); + ret.append(QVideoFrame::Format_NV12); + + return ret; +} + +#endif + + +CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject() +{ + this->num = num; + + startNum = 0; + + // QCamera needs to be controlled from the UI thread, hence this + connect(this, SIGNAL(camStartSignal()), this, SLOT(camStart())); + connect(this, SIGNAL(camStopSignal()), this, SLOT(camStop())); + + frameWidth = width; + frameHeight = height; + frameFormatYUV = yuv; + + int fbsize = frameWidth * frameHeight; + if (yuv) fbsize /= 2; + frameBuffer = new u32[fbsize]; + tempFrameBuffer = new u32[fbsize]; + + inputType = -1; + xFlip = false; + init(); +} + +CameraManager::~CameraManager() +{ + deInit(); + + // save settings here? + + delete[] frameBuffer; +} + +void CameraManager::init() +{ + if (inputType != -1) + deInit(); + + startNum = 0; + + inputType = Config::Camera[num].InputType; + imagePath = QString::fromStdString(Config::Camera[num].ImagePath); + camDeviceName = QString::fromStdString(Config::Camera[num].CamDeviceName); + + camDevice = nullptr; + + { + // fill the framebuffer with black + + int total = frameWidth * frameHeight; + u32 fill = 0; + if (frameFormatYUV) + { + total /= 2; + fill = 0x80008000; + } + + for (int i = 0; i < total; i++) + frameBuffer[i] = fill; + } + + if (inputType == 1) + { + // still image + + QImage img(imagePath); + if (!img.isNull()) + { + QImage imgconv = img.convertToFormat(QImage::Format_RGB32); + if (frameFormatYUV) + { + copyFrame_RGBtoYUV((u32*)img.bits(), img.width(), img.height(), + frameBuffer, frameWidth, frameHeight, + false); + } + else + { + copyFrame_Straight((u32*)img.bits(), img.width(), img.height(), + frameBuffer, frameWidth, frameHeight, + false, false); + } + } + } + else if (inputType == 2) + { + // physical camera + +#if QT_VERSION >= 0x060000 + const QList<QCameraDevice> cameras = QMediaDevices::videoInputs(); + for (const QCameraDevice& cam : cameras) + { + if (QString(cam.id()) == camDeviceName) + { + camDevice = new QCamera(cam); + break; + } + } + + if (camDevice) + { + const QList<QCameraFormat> supported = camDevice->cameraDevice().videoFormats(); + bool good = false; + for (const QCameraFormat& item : supported) + { + if (item.pixelFormat() != QVideoFrameFormat::Format_YUYV && + item.pixelFormat() != QVideoFrameFormat::Format_UYVY && + item.pixelFormat() != QVideoFrameFormat::Format_NV12 && + item.pixelFormat() != QVideoFrameFormat::Format_XRGB8888) + continue; + + if (item.resolution().width() != 640 && item.resolution().height() != 480) + continue; + + camDevice->setCameraFormat(item); + good = true; + break; + } + + if (!good) + { + delete camDevice; + camDevice = nullptr; + } + else + { + camDumper = new CameraFrameDumper(this); + + camSession = new QMediaCaptureSession(this); + camSession->setCamera(camDevice); + camSession->setVideoOutput(camDumper); + } + } +#else + camDevice = new QCamera(camDeviceName.toUtf8()); + if (camDevice->error() != QCamera::NoError) + { + delete camDevice; + camDevice = nullptr; + } + + if (camDevice) + { + camDevice->load(); + + const QList<QCameraViewfinderSettings> supported = camDevice->supportedViewfinderSettings(); + bool good = false; + for (const QCameraViewfinderSettings& item : supported) + { + if (item.pixelFormat() != QVideoFrame::Format_YUYV && + item.pixelFormat() != QVideoFrame::Format_UYVY && + item.pixelFormat() != QVideoFrame::Format_NV12 && + item.pixelFormat() != QVideoFrame::Format_RGB32) + continue; + + if (item.resolution().width() != 640 && item.resolution().height() != 480) + continue; + + camDevice->setViewfinderSettings(item); + good = true; + break; + } + + camDevice->unload(); + + if (!good) + { + delete camDevice; + camDevice = nullptr; + } + else + { + camDumper = new CameraFrameDumper(this); + camDevice->setViewfinder(camDumper); + } + } +#endif + } +} + +void CameraManager::deInit() +{ + if (inputType == 2) + { + if (camDevice) + { + camDevice->stop(); + delete camDevice; + delete camDumper; +#if QT_VERSION >= 0x060000 + delete camSession; +#endif + } + } + + camDevice = nullptr; + inputType = -1; +} + +void CameraManager::start() +{ + if (startNum == 1) return; + startNum = 1; + + if (inputType == 2) + { + emit camStartSignal(); + } +} + +void CameraManager::stop() +{ + if (startNum == 0) return; + startNum = 0; + + if (inputType == 2) + { + emit camStopSignal(); + } +} + +bool CameraManager::isStarted() +{ + return startNum != 0; +} + +void CameraManager::camStart() +{ + if (camDevice) + camDevice->start(); +} + +void CameraManager::camStop() +{ + if (camDevice) + camDevice->stop(); +} + +void CameraManager::setXFlip(bool flip) +{ + xFlip = flip; +} + +void CameraManager::captureFrame(u32* frame, int width, int height, bool yuv) +{ + frameMutex.lock(); + + if ((width == frameWidth) && + (height == frameHeight) && + (yuv == frameFormatYUV) && + (!xFlip)) + { + int len = width * height; + if (yuv) len /= 2; + memcpy(frame, frameBuffer, len * sizeof(u32)); + } + else + { + if (yuv == frameFormatYUV) + { + copyFrame_Straight(frameBuffer, frameWidth, frameHeight, + frame, width, height, + xFlip, yuv); + } + else if (yuv) + { + copyFrame_RGBtoYUV(frameBuffer, frameWidth, frameHeight, + frame, width, height, + xFlip); + } + else + { + copyFrame_YUVtoRGB(frameBuffer, frameWidth, frameHeight, + frame, width, height, + xFlip); + } + } + + frameMutex.unlock(); +} + +void CameraManager::feedFrame(u32* frame, int width, int height, bool yuv) +{ + frameMutex.lock(); + + if (width == frameWidth && height == frameHeight && yuv == frameFormatYUV) + { + int len = width * height; + if (yuv) len /= 2; + memcpy(frameBuffer, frame, len * sizeof(u32)); + } + else + { + if (yuv == frameFormatYUV) + { + copyFrame_Straight(frame, width, height, + frameBuffer, frameWidth, frameHeight, + false, yuv); + } + else if (yuv) + { + copyFrame_RGBtoYUV(frame, width, height, + frameBuffer, frameWidth, frameHeight, + false); + } + else + { + copyFrame_YUVtoRGB(frame, width, height, + frameBuffer, frameWidth, frameHeight, + false); + } + } + + frameMutex.unlock(); +} + +void CameraManager::feedFrame_UYVY(u32* frame, int width, int height) +{ + for (int y = 0; y < frameHeight; y++) + { + int sy = (y * height) / frameHeight; + + for (int x = 0; x < frameWidth; x+=2) + { + int sx = (x * width) / frameWidth; + + u32 val = frame[((sy*width) + sx) >> 1]; + + val = ((val & 0xFF00FF00) >> 8) | ((val & 0x00FF00FF) << 8); + + tempFrameBuffer[((y*frameWidth) + x) >> 1] = val; + } + } + + feedFrame(tempFrameBuffer, frameWidth, frameHeight, true); +} + +void CameraManager::feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height) +{ + for (int y = 0; y < frameHeight; y++) + { + int sy = (y * height) / frameHeight; + + for (int x = 0; x < frameWidth; x+=2) + { + int sx1 = (x * width) / frameWidth; + int sx2 = ((x+1) * width) / frameWidth; + + u32 val; + + u8 y1 = planeY[(sy*width) + sx1]; + u8 y2 = planeY[(sy*width) + sx2]; + + int uvpos = (((sy>>1)*(width>>1)) + (sx1>>1)); + u8 u = planeUV[uvpos << 1]; + u8 v = planeUV[(uvpos << 1) + 1]; + + val = y1 | (u << 8) | (y2 << 16) | (v << 24); + tempFrameBuffer[((y*frameWidth) + x) >> 1] = val; + } + } + + feedFrame(tempFrameBuffer, frameWidth, frameHeight, true); +} + +void CameraManager::copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv) +{ + if (yuv) + { + swidth /= 2; + dwidth /= 2; + } + + for (int dy = 0; dy < dheight; dy++) + { + int sy = (dy * sheight) / dheight; + + for (int dx = 0; dx < dwidth; dx++) + { + int sx = (dx * swidth) / dwidth; + if (xflip) sx = swidth-1 - sx; + + u32 val = src[(sy * swidth) + sx]; + + if (yuv) + { + if (xflip) + val = (val & 0xFF00FF00) | + ((val >> 16) & 0xFF) | + ((val & 0xFF) << 16); + } + else + val |= 0xFF000000; + + dst[(dy * dwidth) + dx] = val; + } + } +} + +void CameraManager::copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip) +{ + for (int dy = 0; dy < dheight; dy++) + { + int sy = (dy * sheight) / dheight; + + for (int dx = 0; dx < dwidth; dx+=2) + { + int sx; + + sx = (dx * swidth) / dwidth; + if (xflip) sx = swidth-1 - sx; + + u32 pixel1 = src[sy*swidth + sx]; + + sx = ((dx+1) * swidth) / dwidth; + if (xflip) sx = swidth-1 - sx; + + u32 pixel2 = src[sy*swidth + sx]; + + int r1 = (pixel1 >> 16) & 0xFF; + int g1 = (pixel1 >> 8) & 0xFF; + int b1 = pixel1 & 0xFF; + + int r2 = (pixel2 >> 16) & 0xFF; + int g2 = (pixel2 >> 8) & 0xFF; + int b2 = pixel2 & 0xFF; + + int y1 = ((r1 * 19595) + (g1 * 38470) + (b1 * 7471)) >> 16; + int u1 = ((b1 - y1) * 32244) >> 16; + int v1 = ((r1 - y1) * 57475) >> 16; + + int y2 = ((r2 * 19595) + (g2 * 38470) + (b2 * 7471)) >> 16; + int u2 = ((b2 - y2) * 32244) >> 16; + int v2 = ((r2 - y2) * 57475) >> 16; + + u1 += 128; v1 += 128; + u2 += 128; v2 += 128; + + y1 = std::clamp(y1, 0, 255); u1 = std::clamp(u1, 0, 255); v1 = std::clamp(v1, 0, 255); + y2 = std::clamp(y2, 0, 255); u2 = std::clamp(u2, 0, 255); v2 = std::clamp(v2, 0, 255); + + // huh + u1 = (u1 + u2) >> 1; + v1 = (v1 + v2) >> 1; + + dst[(dy*dwidth + dx) / 2] = y1 | (u1 << 8) | (y2 << 16) | (v1 << 24); + } + } +} + +void CameraManager::copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip) +{ + for (int dy = 0; dy < dheight; dy++) + { + int sy = (dy * sheight) / dheight; + + for (int dx = 0; dx < dwidth; dx+=2) + { + int sx = (dx * swidth) / dwidth; + if (xflip) sx = swidth-2 - sx; + + u32 val = src[(sy*swidth + sx) / 2]; + + int y1, y2; + if (xflip) + { + y1 = (val >> 16) & 0xFF; + y2 = val & 0xFF; + } + else + { + y1 = val & 0xFF; + y2 = (val >> 16) & 0xFF; + } + int u = (val >> 8) & 0xFF; + int v = (val >> 24) & 0xFF; + + u -= 128; v -= 128; + + int r1 = y1 + ((v * 91881) >> 16); + int g1 = y1 - ((v * 46793) >> 16) - ((u * 22544) >> 16); + int b1 = y1 + ((u * 116129) >> 16); + + int r2 = y2 + ((v * 91881) >> 16); + int g2 = y2 - ((v * 46793) >> 16) - ((u * 22544) >> 16); + int b2 = y2 + ((u * 116129) >> 16); + + r1 = std::clamp(r1, 0, 255); g1 = std::clamp(g1, 0, 255); b1 = std::clamp(b1, 0, 255); + r2 = std::clamp(r2, 0, 255); g2 = std::clamp(g2, 0, 255); b2 = std::clamp(b2, 0, 255); + + u32 col1 = 0xFF000000 | (r1 << 16) | (g1 << 8) | b1; + u32 col2 = 0xFF000000 | (r2 << 16) | (g2 << 8) | b2; + + dst[dy*dwidth + dx ] = col1; + dst[dy*dwidth + dx+1] = col2; + } + } +} diff --git a/src/frontend/qt_sdl/CameraManager.h b/src/frontend/qt_sdl/CameraManager.h new file mode 100644 index 0000000..6743d19 --- /dev/null +++ b/src/frontend/qt_sdl/CameraManager.h @@ -0,0 +1,134 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef CAMERAMANAGER_H +#define CAMERAMANAGER_H + +#include <QCamera> +#if QT_VERSION >= 0x060000 + #include <QMediaDevices> + #include <QCameraDevice> + #include <QMediaCaptureSession> + #include <QVideoSink> +#else + #include <QCameraInfo> + #include <QAbstractVideoSurface> + #include <QVideoSurfaceFormat> +#endif +#include <QMutex> + +#include "types.h" + +class CameraManager; + + +#if QT_VERSION >= 0x060000 + +class CameraFrameDumper : public QVideoSink +{ + Q_OBJECT + +public: + CameraFrameDumper(QObject* parent = nullptr); + +public slots: + void present(const QVideoFrame& frame); + +private: + CameraManager* cam; +}; + +#else + +class CameraFrameDumper : public QAbstractVideoSurface +{ + Q_OBJECT + +public: + CameraFrameDumper(QObject* parent = nullptr); + + bool present(const QVideoFrame& frame) override; + QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const override; + +private: + CameraManager* cam; +}; + +#endif + + +class CameraManager : public QObject +{ + Q_OBJECT + +public: + CameraManager(int num, int width, int height, bool yuv); + ~CameraManager(); + + void init(); + void deInit(); + + void start(); + void stop(); + bool isStarted(); + + void setXFlip(bool flip); + + void captureFrame(u32* frame, int width, int height, bool yuv); + + void feedFrame(u32* frame, int width, int height, bool yuv); + void feedFrame_UYVY(u32* frame, int width, int height); + void feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height); + +signals: + void camStartSignal(); + void camStopSignal(); + +private slots: + void camStart(); + void camStop(); + +private: + int num; + + int startNum; + + int inputType; + QString imagePath; + QString camDeviceName; + + QCamera* camDevice; + CameraFrameDumper* camDumper; +#if QT_VERSION >= 0x060000 + QMediaCaptureSession* camSession; +#endif + + int frameWidth, frameHeight; + bool frameFormatYUV; + u32* frameBuffer; + u32* tempFrameBuffer; + QMutex frameMutex; + + bool xFlip; + + void copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv); + void copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip); + void copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip); +}; + +#endif // CAMERAMANAGER_H diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.cpp b/src/frontend/qt_sdl/CameraSettingsDialog.cpp new file mode 100644 index 0000000..1844e0f --- /dev/null +++ b/src/frontend/qt_sdl/CameraSettingsDialog.cpp @@ -0,0 +1,304 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> +#include <QFileDialog> +#include <QPaintEvent> +#include <QPainter> + +#include "types.h" + +#include "CameraSettingsDialog.h" +#include "ui_CameraSettingsDialog.h" + + +CameraSettingsDialog* CameraSettingsDialog::currentDlg = nullptr; + +extern std::string EmuDirectory; + +extern CameraManager* camManager[2]; + + +CameraPreviewPanel::CameraPreviewPanel(QWidget* parent) : QWidget(parent) +{ + currentCam = nullptr; + updateTimer = startTimer(50); +} + +CameraPreviewPanel::~CameraPreviewPanel() +{ + killTimer(updateTimer); +} + +void CameraPreviewPanel::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + + if (!currentCam) + { + painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); + return; + } + + QImage picture(256, 192, QImage::Format_RGB32); + currentCam->captureFrame((u32*)picture.bits(), 256, 192, false); + painter.drawImage(0, 0, picture); +} + + +CameraSettingsDialog::CameraSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CameraSettingsDialog) +{ + previewPanel = nullptr; + currentCfg = nullptr; + currentCam = nullptr; + + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + for (int i = 0; i < 2; i++) + { + oldCamSettings[i] = Config::Camera[i]; + } + + ui->cbCameraSel->addItem("DSi outer camera"); + ui->cbCameraSel->addItem("DSi inner camera"); + +#if QT_VERSION >= 0x060000 + const QList<QCameraDevice> cameras = QMediaDevices::videoInputs(); + for (const QCameraDevice &cameraInfo : cameras) + { + QString name = cameraInfo.description(); + QCameraDevice::Position pos = cameraInfo.position(); + if (pos != QCameraDevice::UnspecifiedPosition) + { + name += " ("; + if (pos == QCameraDevice::FrontFace) + name += "inner camera"; + else if (pos == QCameraDevice::BackFace) + name += "outer camera"; + name += ")"; + } + + ui->cbPhysicalCamera->addItem(name, QString(cameraInfo.id())); + } +#else + const QList<QCameraInfo> cameras = QCameraInfo::availableCameras(); + for (const QCameraInfo &cameraInfo : cameras) + { + QString name = cameraInfo.description(); + QCamera::Position pos = cameraInfo.position(); + if (pos != QCamera::UnspecifiedPosition) + { + name += " ("; + if (pos == QCamera::FrontFace) + name += "inner camera"; + else if (pos == QCamera::BackFace) + name += "outer camera"; + name += ")"; + } + + ui->cbPhysicalCamera->addItem(name, cameraInfo.deviceName()); + } +#endif + ui->rbPictureCamera->setEnabled(ui->cbPhysicalCamera->count() > 0); + + grpInputType = new QButtonGroup(this); + grpInputType->addButton(ui->rbPictureNone, 0); + grpInputType->addButton(ui->rbPictureImg, 1); + grpInputType->addButton(ui->rbPictureCamera, 2); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + connect(grpInputType, SIGNAL(buttonClicked(int)), this, SLOT(onChangeInputType(int))); +#else + connect(grpInputType, SIGNAL(idClicked(int)), this, SLOT(onChangeInputType(int))); +#endif + + previewPanel = new CameraPreviewPanel(this); + QVBoxLayout* previewLayout = new QVBoxLayout(); + previewLayout->addWidget(previewPanel); + ui->grpPreview->setLayout(previewLayout); + previewPanel->setMinimumSize(256, 192); + previewPanel->setMaximumSize(256, 192); + + on_cbCameraSel_currentIndexChanged(ui->cbCameraSel->currentIndex()); +} + +CameraSettingsDialog::~CameraSettingsDialog() +{ + delete ui; +} + +void CameraSettingsDialog::on_CameraSettingsDialog_accepted() +{ + for (int i = 0; i < 2; i++) + { + camManager[i]->stop(); + } + + Config::Save(); + + closeDlg(); +} + +void CameraSettingsDialog::on_CameraSettingsDialog_rejected() +{ + for (int i = 0; i < 2; i++) + { + camManager[i]->stop(); + camManager[i]->deInit(); + Config::Camera[i] = oldCamSettings[i]; + camManager[i]->init(); + } + + closeDlg(); +} + +void CameraSettingsDialog::on_cbCameraSel_currentIndexChanged(int id) +{ + if (!previewPanel) return; + + if (currentCam) + { + currentCam->stop(); + } + + currentId = id; + currentCfg = &Config::Camera[id]; + //currentCam = camManager[id]; + currentCam = nullptr; + populateCamControls(id); + currentCam = camManager[id]; + previewPanel->setCurrentCam(currentCam); + + currentCam->start(); +} + +void CameraSettingsDialog::onChangeInputType(int type) +{ + if (!currentCfg) return; + + if (currentCam) + { + currentCam->stop(); + currentCam->deInit(); + } + + currentCfg->InputType = type; + + ui->txtSrcImagePath->setEnabled(type == 1); + ui->btnSrcImageBrowse->setEnabled(type == 1); + ui->cbPhysicalCamera->setEnabled((type == 2) && (ui->cbPhysicalCamera->count()>0)); + + currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString(); + + if (ui->cbPhysicalCamera->count() > 0) + currentCfg->CamDeviceName = ui->cbPhysicalCamera->currentData().toString().toStdString(); + + if (currentCam) + { + currentCam->init(); + currentCam->start(); + } +} + +void CameraSettingsDialog::on_txtSrcImagePath_textChanged() +{ + if (!currentCfg) return; + + if (currentCam) + { + currentCam->stop(); + currentCam->deInit(); + } + + currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString(); + + if (currentCam) + { + currentCam->init(); + currentCam->start(); + } +} + +void CameraSettingsDialog::on_btnSrcImageBrowse_clicked() +{ + QString file = QFileDialog::getOpenFileName(this, + "Select image file...", + QString::fromStdString(EmuDirectory), + "Image files (*.png *.jpg *.jpeg *.bmp);;Any file (*.*)"); + + if (file.isEmpty()) return; + + ui->txtSrcImagePath->setText(file); +} + +void CameraSettingsDialog::on_cbPhysicalCamera_currentIndexChanged(int id) +{ + if (!currentCfg) return; + + if (currentCam) + { + currentCam->stop(); + currentCam->deInit(); + } + + currentCfg->CamDeviceName = ui->cbPhysicalCamera->itemData(id).toString().toStdString(); + + if (currentCam) + { + currentCam->init(); + currentCam->start(); + } +} + +void CameraSettingsDialog::populateCamControls(int id) +{ + Config::CameraConfig& cfg = Config::Camera[id]; + + int type = cfg.InputType; + if (type < 0 || type >= grpInputType->buttons().count()) type = 0; + grpInputType->button(type)->setChecked(true); + + ui->txtSrcImagePath->setText(QString::fromStdString(cfg.ImagePath)); + + bool deviceset = false; + QString device = QString::fromStdString(cfg.CamDeviceName); + for (int i = 0; i < ui->cbPhysicalCamera->count(); i++) + { + QString itemdev = ui->cbPhysicalCamera->itemData(i).toString(); + if (itemdev == device) + { + ui->cbPhysicalCamera->setCurrentIndex(i); + deviceset = true; + break; + } + } + if (!deviceset) + ui->cbPhysicalCamera->setCurrentIndex(0); + + onChangeInputType(type); + + ui->chkFlipPicture->setChecked(cfg.XFlip); +} + +void CameraSettingsDialog::on_chkFlipPicture_clicked() +{ + if (!currentCfg) return; + + currentCfg->XFlip = ui->chkFlipPicture->isChecked(); + if (currentCam) currentCam->setXFlip(currentCfg->XFlip); +} diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.h b/src/frontend/qt_sdl/CameraSettingsDialog.h new file mode 100644 index 0000000..8572ac4 --- /dev/null +++ b/src/frontend/qt_sdl/CameraSettingsDialog.h @@ -0,0 +1,108 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef CAMERASETTINGSDIALOG_H +#define CAMERASETTINGSDIALOG_H + +#include <QDialog> +#include <QButtonGroup> + +#include "Config.h" +#include "CameraManager.h" + +namespace Ui { class CameraSettingsDialog; } +class CameraSettingsDialog; + +class CameraPreviewPanel : public QWidget +{ + Q_OBJECT + +public: + CameraPreviewPanel(QWidget* parent); + ~CameraPreviewPanel(); + + void setCurrentCam(CameraManager* cam) + { + currentCam = cam; + } + +protected: + void paintEvent(QPaintEvent* event) override; + void timerEvent(QTimerEvent* event) override + { + repaint(); + } + +private: + int updateTimer; + CameraManager* currentCam; +}; + +class CameraSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CameraSettingsDialog(QWidget* parent); + ~CameraSettingsDialog(); + + static CameraSettingsDialog* currentDlg; + static CameraSettingsDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + currentDlg = new CameraSettingsDialog(parent); + currentDlg->open(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + } + +private slots: + void on_CameraSettingsDialog_accepted(); + void on_CameraSettingsDialog_rejected(); + + void on_cbCameraSel_currentIndexChanged(int id); + void onChangeInputType(int type); + void on_txtSrcImagePath_textChanged(); + void on_btnSrcImageBrowse_clicked(); + void on_cbPhysicalCamera_currentIndexChanged(int id); + void on_chkFlipPicture_clicked(); + +private: + Ui::CameraSettingsDialog* ui; + + QButtonGroup* grpInputType; + CameraPreviewPanel* previewPanel; + + int currentId; + Config::CameraConfig* currentCfg; + CameraManager* currentCam; + + Config::CameraConfig oldCamSettings[2]; + + void populateCamControls(int id); +}; + +#endif // CAMERASETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.ui b/src/frontend/qt_sdl/CameraSettingsDialog.ui new file mode 100644 index 0000000..bbaf45b --- /dev/null +++ b/src/frontend/qt_sdl/CameraSettingsDialog.ui @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CameraSettingsDialog</class> + <widget class="QDialog" name="CameraSettingsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>605</width> + <height>341</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Camera settings - melonDS</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetFixedSize</enum> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Configure emulated camera:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cbCameraSel"/> + </item> + </layout> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Picture source</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="2"> + <widget class="QLineEdit" name="txtSrcImagePath"/> + </item> + <item row="0" column="0" colspan="4"> + <widget class="QRadioButton" name="rbPictureNone"> + <property name="text"> + <string>None (blank)</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QPushButton" name="btnSrcImageBrowse"> + <property name="text"> + <string>Browse...</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QRadioButton" name="rbPictureCamera"> + <property name="text"> + <string>Physical camera:</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QRadioButton" name="rbPictureImg"> + <property name="text"> + <string>Image file:</string> + </property> + </widget> + </item> + <item row="2" column="2" colspan="2"> + <widget class="QComboBox" name="cbPhysicalCamera"/> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Picture settings</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QCheckBox" name="chkFlipPicture"> + <property name="text"> + <string>Flip horizontally</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="2" rowspan="3"> + <widget class="QGroupBox" name="grpPreview"> + <property name="title"> + <string>Preview</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CameraSettingsDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>CameraSettingsDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/frontend/qt_sdl/CheatsDialog.cpp b/src/frontend/qt_sdl/CheatsDialog.cpp index afa0805..4537df3 100644 --- a/src/frontend/qt_sdl/CheatsDialog.cpp +++ b/src/frontend/qt_sdl/CheatsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -24,6 +24,7 @@ #include "types.h" #include "Platform.h" #include "Config.h" +#include "ROMManager.h" #include "CheatsDialog.h" #include "ui_CheatsDialog.h" @@ -33,15 +34,13 @@ CheatsDialog* CheatsDialog::currentDlg = nullptr; extern std::string EmuDirectory; -namespace Frontend { extern ARCodeFile* CheatFile; } - CheatsDialog::CheatsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CheatsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - codeFile = Frontend::CheatFile; + codeFile = ROMManager::GetCheatFile(); QStandardItemModel* model = new QStandardItemModel(); ui->tvCodeList->setModel(model); @@ -55,7 +54,7 @@ CheatsDialog::CheatsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Cheats { ARCodeCat& cat = *i; - QStandardItem* catitem = new QStandardItem(cat.Name); + QStandardItem* catitem = new QStandardItem(QString::fromStdString(cat.Name)); catitem->setEditable(true); catitem->setData(QVariant::fromValue(i)); root->appendRow(catitem); @@ -64,7 +63,7 @@ CheatsDialog::CheatsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Cheats { ARCode& code = *j; - QStandardItem* codeitem = new QStandardItem(code.Name); + QStandardItem* codeitem = new QStandardItem(QString::fromStdString(code.Name)); codeitem->setEditable(true); codeitem->setCheckable(true); codeitem->setCheckState(code.Enabled ? Qt::Checked : Qt::Unchecked); @@ -113,13 +112,12 @@ void CheatsDialog::on_btnNewCat_clicked() ARCodeCat cat; cat.Codes.clear(); - memset(cat.Name, 0, 128); - strncpy(cat.Name, "(new category)", 127); + cat.Name = "(new category)"; codeFile->Categories.push_back(cat); ARCodeCatList::iterator id = codeFile->Categories.end(); id--; - QStandardItem* catitem = new QStandardItem(cat.Name); + QStandardItem* catitem = new QStandardItem(QString::fromStdString(cat.Name)); catitem->setEditable(true); catitem->setData(QVariant::fromValue(id)); root->appendRow(catitem); @@ -160,8 +158,7 @@ void CheatsDialog::on_btnNewARCode_clicked() ARCodeCat& cat = *it_cat; ARCode code; - memset(code.Name, 0, 128); - strncpy(code.Name, "(new AR code)", 127); + code.Name = "(new AR code)"; code.Enabled = true; code.CodeLen = 0; memset(code.Code, 0, sizeof(code.Code)); @@ -169,7 +166,7 @@ void CheatsDialog::on_btnNewARCode_clicked() cat.Codes.push_back(code); ARCodeList::iterator id = cat.Codes.end(); id--; - QStandardItem* codeitem = new QStandardItem(code.Name); + QStandardItem* codeitem = new QStandardItem(QString::fromStdString(code.Name)); codeitem->setEditable(true); codeitem->setCheckable(true); codeitem->setCheckState(code.Enabled ? Qt::Checked : Qt::Unchecked); @@ -275,13 +272,12 @@ void CheatsDialog::onCheatEntryModified(QStandardItem* item) if (item->text().isEmpty()) { - QString oldname = QString(cat.Name); + QString oldname = QString::fromStdString(cat.Name); item->setText(oldname.isEmpty() ? "(blank category name?)" : oldname); } else { - strncpy(cat.Name, item->text().toStdString().c_str(), 127); - cat.Name[127] = '\0'; + cat.Name = item->text().toStdString(); } } else if (data.canConvert<ARCodeList::iterator>()) @@ -290,13 +286,12 @@ void CheatsDialog::onCheatEntryModified(QStandardItem* item) if (item->text().isEmpty()) { - QString oldname = QString(code.Name); + QString oldname = QString::fromStdString(code.Name); item->setText(oldname.isEmpty() ? "(blank code name?)" : oldname); } else { - strncpy(code.Name, item->text().toStdString().c_str(), 127); - code.Name[127] = '\0'; + code.Name = item->text().toStdString(); } code.Enabled = (item->checkState() == Qt::Checked); diff --git a/src/frontend/qt_sdl/CheatsDialog.h b/src/frontend/qt_sdl/CheatsDialog.h index 926657c..c5a7e13 100644 --- a/src/frontend/qt_sdl/CheatsDialog.h +++ b/src/frontend/qt_sdl/CheatsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 30babaf..8b2f3d4 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -36,290 +36,315 @@ int JoystickID; int WindowWidth; int WindowHeight; -int WindowMaximized; +bool WindowMaximized; int ScreenRotation; int ScreenGap; int ScreenLayout; -int ScreenSwap; +bool ScreenSwap; int ScreenSizing; -int IntegerScaling; +bool IntegerScaling; int ScreenAspectTop; int ScreenAspectBot; -int ScreenFilter; +bool ScreenFilter; -int ScreenUseGL; -int ScreenVSync; +bool ScreenUseGL; +bool ScreenVSync; int ScreenVSyncInterval; int _3DRenderer; -int Threaded3D; +bool Threaded3D; int GL_ScaleFactor; -int GL_BetterPolygons; +bool GL_BetterPolygons; -int LimitFPS; -int AudioSync; -int ShowOSD; +bool LimitFPS; +bool AudioSync; +bool ShowOSD; int ConsoleType; -int DirectBoot; +bool DirectBoot; #ifdef JIT_ENABLED -int JIT_Enable = false; +bool JIT_Enable = false; int JIT_MaxBlockSize = 32; -int JIT_BranchOptimisations = true; -int JIT_LiteralOptimisations = true; -int JIT_FastMemory = true; +bool JIT_BranchOptimisations = true; +bool JIT_LiteralOptimisations = true; +bool JIT_FastMemory = true; #endif -int ExternalBIOSEnable; +bool ExternalBIOSEnable; -char BIOS9Path[1024]; -char BIOS7Path[1024]; -char FirmwarePath[1024]; +std::string BIOS9Path; +std::string BIOS7Path; +std::string FirmwarePath; -char DSiBIOS9Path[1024]; -char DSiBIOS7Path[1024]; -char DSiFirmwarePath[1024]; -char DSiNANDPath[1024]; +std::string DSiBIOS9Path; +std::string DSiBIOS7Path; +std::string DSiFirmwarePath; +std::string DSiNANDPath; -int DLDIEnable; -char DLDISDPath[1024]; +bool DLDIEnable; +std::string DLDISDPath; int DLDISize; -int DLDIReadOnly; -int DLDIFolderSync; -char DLDIFolderPath[1024]; +bool DLDIReadOnly; +bool DLDIFolderSync; +std::string DLDIFolderPath; -int DSiSDEnable; -char DSiSDPath[1024]; +bool DSiSDEnable; +std::string DSiSDPath; int DSiSDSize; -int DSiSDReadOnly; -int DSiSDFolderSync; -char DSiSDFolderPath[1024]; +bool DSiSDReadOnly; +bool DSiSDFolderSync; +std::string DSiSDFolderPath; -int FirmwareOverrideSettings; -char FirmwareUsername[64]; +bool FirmwareOverrideSettings; +std::string FirmwareUsername; int FirmwareLanguage; int FirmwareBirthdayMonth; int FirmwareBirthdayDay; int FirmwareFavouriteColour; -char FirmwareMessage[1024]; -char FirmwareMAC[18]; -int RandomizeMAC; +std::string FirmwareMessage; +std::string FirmwareMAC; -int SocketBindAnyAddr; -char LANDevice[128]; -int DirectLAN; +int MPAudioMode; +int MPRecvTimeout; -int SavestateRelocSRAM; +std::string LANDevice; +bool DirectLAN; + +bool SavestateRelocSRAM; int AudioInterp; int AudioBitrate; int AudioVolume; int MicInputType; -char MicWavPath[1024]; +std::string MicWavPath; + +std::string LastROMFolder; -char LastROMFolder[1024]; +std::string RecentROMList[10]; -char RecentROMList[10][1024]; +std::string SaveFilePath; +std::string SavestatePath; +std::string CheatFilePath; -int EnableCheats; +bool EnableCheats; -int MouseHide; +bool MouseHide; int MouseHideSeconds; -int PauseLostFocus; +bool PauseLostFocus; + +bool DSBatteryLevelOkay; +int DSiBatteryLevel; +bool DSiBatteryCharging; + +CameraConfig Camera[2]; const char* kConfigFile = "melonDS.ini"; +const char* kUniqueConfigFile = "melonDS.%d.ini"; ConfigEntry ConfigFile[] = { - {"Key_A", 0, &KeyMapping[0], -1, NULL, 0}, - {"Key_B", 0, &KeyMapping[1], -1, NULL, 0}, - {"Key_Select", 0, &KeyMapping[2], -1, NULL, 0}, - {"Key_Start", 0, &KeyMapping[3], -1, NULL, 0}, - {"Key_Right", 0, &KeyMapping[4], -1, NULL, 0}, - {"Key_Left", 0, &KeyMapping[5], -1, NULL, 0}, - {"Key_Up", 0, &KeyMapping[6], -1, NULL, 0}, - {"Key_Down", 0, &KeyMapping[7], -1, NULL, 0}, - {"Key_R", 0, &KeyMapping[8], -1, NULL, 0}, - {"Key_L", 0, &KeyMapping[9], -1, NULL, 0}, - {"Key_X", 0, &KeyMapping[10], -1, NULL, 0}, - {"Key_Y", 0, &KeyMapping[11], -1, NULL, 0}, - - {"Joy_A", 0, &JoyMapping[0], -1, NULL, 0}, - {"Joy_B", 0, &JoyMapping[1], -1, NULL, 0}, - {"Joy_Select", 0, &JoyMapping[2], -1, NULL, 0}, - {"Joy_Start", 0, &JoyMapping[3], -1, NULL, 0}, - {"Joy_Right", 0, &JoyMapping[4], -1, NULL, 0}, - {"Joy_Left", 0, &JoyMapping[5], -1, NULL, 0}, - {"Joy_Up", 0, &JoyMapping[6], -1, NULL, 0}, - {"Joy_Down", 0, &JoyMapping[7], -1, NULL, 0}, - {"Joy_R", 0, &JoyMapping[8], -1, NULL, 0}, - {"Joy_L", 0, &JoyMapping[9], -1, NULL, 0}, - {"Joy_X", 0, &JoyMapping[10], -1, NULL, 0}, - {"Joy_Y", 0, &JoyMapping[11], -1, NULL, 0}, - - {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1, NULL, 0}, - {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1, NULL, 0}, - {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, NULL, 0}, - {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, NULL, 0}, - {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1, NULL, 0}, - {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, NULL, 0}, - {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1, NULL, 0}, - {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1, NULL, 0}, - {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1, NULL, 0}, - {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1, NULL, 0}, - {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1, NULL, 0}, - - {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, NULL, 0}, - {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, NULL, 0}, - {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, NULL, 0}, - {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, NULL, 0}, - {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, NULL, 0}, - {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, NULL, 0}, - {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1, NULL, 0}, - {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1, NULL, 0}, - {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1, NULL, 0}, - {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1, NULL, 0}, - {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1, NULL, 0}, - - {"JoystickID", 0, &JoystickID, 0, NULL, 0}, - - {"WindowWidth", 0, &WindowWidth, 256, NULL, 0}, - {"WindowHeight", 0, &WindowHeight, 384, NULL, 0}, - {"WindowMax", 0, &WindowMaximized, 0, NULL, 0}, - - {"ScreenRotation", 0, &ScreenRotation, 0, NULL, 0}, - {"ScreenGap", 0, &ScreenGap, 0, NULL, 0}, - {"ScreenLayout", 0, &ScreenLayout, 0, NULL, 0}, - {"ScreenSwap", 0, &ScreenSwap, 0, NULL, 0}, - {"ScreenSizing", 0, &ScreenSizing, 0, NULL, 0}, - {"IntegerScaling", 0, &IntegerScaling, 0, NULL, 0}, - {"ScreenAspectTop",0, &ScreenAspectTop,0, NULL, 0}, - {"ScreenAspectBot",0, &ScreenAspectBot,0, NULL, 0}, - {"ScreenFilter", 0, &ScreenFilter, 1, NULL, 0}, - - {"ScreenUseGL", 0, &ScreenUseGL, 0, NULL, 0}, - {"ScreenVSync", 0, &ScreenVSync, 0, NULL, 0}, - {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1, NULL, 0}, - - {"3DRenderer", 0, &_3DRenderer, 0, NULL, 0}, - {"Threaded3D", 0, &Threaded3D, 1, NULL, 0}, - - {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, NULL, 0}, - {"GL_BetterPolygons", 0, &GL_BetterPolygons, 0, NULL, 0}, - - {"LimitFPS", 0, &LimitFPS, 1, NULL, 0}, - {"AudioSync", 0, &AudioSync, 0, NULL, 0}, - {"ShowOSD", 0, &ShowOSD, 1, NULL, 0}, - - {"ConsoleType", 0, &ConsoleType, 0, NULL, 0}, - {"DirectBoot", 0, &DirectBoot, 1, NULL, 0}, + {"Key_A", 0, &KeyMapping[0], -1, true}, + {"Key_B", 0, &KeyMapping[1], -1, true}, + {"Key_Select", 0, &KeyMapping[2], -1, true}, + {"Key_Start", 0, &KeyMapping[3], -1, true}, + {"Key_Right", 0, &KeyMapping[4], -1, true}, + {"Key_Left", 0, &KeyMapping[5], -1, true}, + {"Key_Up", 0, &KeyMapping[6], -1, true}, + {"Key_Down", 0, &KeyMapping[7], -1, true}, + {"Key_R", 0, &KeyMapping[8], -1, true}, + {"Key_L", 0, &KeyMapping[9], -1, true}, + {"Key_X", 0, &KeyMapping[10], -1, true}, + {"Key_Y", 0, &KeyMapping[11], -1, true}, + + {"Joy_A", 0, &JoyMapping[0], -1, true}, + {"Joy_B", 0, &JoyMapping[1], -1, true}, + {"Joy_Select", 0, &JoyMapping[2], -1, true}, + {"Joy_Start", 0, &JoyMapping[3], -1, true}, + {"Joy_Right", 0, &JoyMapping[4], -1, true}, + {"Joy_Left", 0, &JoyMapping[5], -1, true}, + {"Joy_Up", 0, &JoyMapping[6], -1, true}, + {"Joy_Down", 0, &JoyMapping[7], -1, true}, + {"Joy_R", 0, &JoyMapping[8], -1, true}, + {"Joy_L", 0, &JoyMapping[9], -1, true}, + {"Joy_X", 0, &JoyMapping[10], -1, true}, + {"Joy_Y", 0, &JoyMapping[11], -1, true}, + + {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1, true}, + {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1, true}, + {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, true}, + {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, true}, + {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1, true}, + {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, true}, + {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1, true}, + {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1, true}, + {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1, true}, + {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1, true}, + {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1, true}, + + {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, true}, + {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, true}, + {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, true}, + {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, true}, + {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, true}, + {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, true}, + {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1, true}, + {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1, true}, + {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1, true}, + {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1, true}, + {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1, true}, + + {"JoystickID", 0, &JoystickID, 0, true}, + + {"WindowWidth", 0, &WindowWidth, 256, true}, + {"WindowHeight", 0, &WindowHeight, 384, true}, + {"WindowMax", 1, &WindowMaximized, false, true}, + + {"ScreenRotation", 0, &ScreenRotation, 0, true}, + {"ScreenGap", 0, &ScreenGap, 0, true}, + {"ScreenLayout", 0, &ScreenLayout, 0, true}, + {"ScreenSwap", 1, &ScreenSwap, false, true}, + {"ScreenSizing", 0, &ScreenSizing, 0, true}, + {"IntegerScaling", 1, &IntegerScaling, false, true}, + {"ScreenAspectTop",0, &ScreenAspectTop,0, true}, + {"ScreenAspectBot",0, &ScreenAspectBot,0, true}, + {"ScreenFilter", 1, &ScreenFilter, true, true}, + + {"ScreenUseGL", 1, &ScreenUseGL, false, false}, + {"ScreenVSync", 1, &ScreenVSync, false, false}, + {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1, false}, + + {"3DRenderer", 0, &_3DRenderer, 0, false}, + {"Threaded3D", 1, &Threaded3D, true, false}, + + {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, false}, + {"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false}, + + {"LimitFPS", 1, &LimitFPS, true, false}, + {"AudioSync", 1, &AudioSync, false}, + {"ShowOSD", 1, &ShowOSD, true, false}, + + {"ConsoleType", 0, &ConsoleType, 0, false}, + {"DirectBoot", 1, &DirectBoot, true, false}, #ifdef JIT_ENABLED - {"JIT_Enable", 0, &JIT_Enable, 0, NULL, 0}, - {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32, NULL, 0}, - {"JIT_BranchOptimisations", 0, &JIT_BranchOptimisations, 1, NULL, 0}, - {"JIT_LiteralOptimisations", 0, &JIT_LiteralOptimisations, 1, NULL, 0}, + {"JIT_Enable", 1, &JIT_Enable, false, false}, + {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32, false}, + {"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true, false}, + {"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true, false}, #ifdef __APPLE__ - {"JIT_FastMemory", 0, &JIT_FastMemory, 0, NULL, 0}, + {"JIT_FastMemory", 1, &JIT_FastMemory, false, false}, #else - {"JIT_FastMemory", 0, &JIT_FastMemory, 1, NULL, 0}, + {"JIT_FastMemory", 1, &JIT_FastMemory, true, false}, #endif #endif - {"ExternalBIOSEnable", 0, &ExternalBIOSEnable, 0, NULL, 0}, - - {"BIOS9Path", 1, BIOS9Path, 0, "", 1023}, - {"BIOS7Path", 1, BIOS7Path, 0, "", 1023}, - {"FirmwarePath", 1, FirmwarePath, 0, "", 1023}, - - {"DSiBIOS9Path", 1, DSiBIOS9Path, 0, "", 1023}, - {"DSiBIOS7Path", 1, DSiBIOS7Path, 0, "", 1023}, - {"DSiFirmwarePath", 1, DSiFirmwarePath, 0, "", 1023}, - {"DSiNANDPath", 1, DSiNANDPath, 0, "", 1023}, - - {"DLDIEnable", 0, &DLDIEnable, 0, NULL, 0}, - {"DLDISDPath", 1, DLDISDPath, 0, "dldi.bin", 1023}, - {"DLDISize", 0, &DLDISize, 0, NULL, 0}, - {"DLDIReadOnly", 0, &DLDIReadOnly, 0, NULL, 0}, - {"DLDIFolderSync", 0, &DLDIFolderSync, 0, NULL, 0}, - {"DLDIFolderPath", 1, DLDIFolderPath, 0, "", 1023}, - - {"DSiSDEnable", 0, &DSiSDEnable, 0, NULL, 0}, - {"DSiSDPath", 1, DSiSDPath, 0, "dsisd.bin", 1023}, - {"DSiSDSize", 0, &DSiSDSize, 0, NULL, 0}, - {"DSiSDReadOnly", 0, &DSiSDReadOnly, 0, NULL, 0}, - {"DSiSDFolderSync", 0, &DSiSDFolderSync, 0, NULL, 0}, - {"DSiSDFolderPath", 1, DSiSDFolderPath, 0, "", 1023}, - - {"FirmwareOverrideSettings", 0, &FirmwareOverrideSettings, false, NULL, 0}, - {"FirmwareUsername", 1, FirmwareUsername, 0, "melonDS", 63}, - {"FirmwareLanguage", 0, &FirmwareLanguage, 1, NULL, 0}, - {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 0, NULL, 0}, - {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 0, NULL, 0}, - {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0, NULL, 0}, - {"FirmwareMessage", 1, FirmwareMessage, 0, "", 1023}, - {"FirmwareMAC", 1, FirmwareMAC, 0, "", 17}, - {"RandomizeMAC", 0, &RandomizeMAC, 0, NULL, 0}, - - {"SockBindAnyAddr", 0, &SocketBindAnyAddr, 0, NULL, 0}, - {"LANDevice", 1, LANDevice, 0, "", 127}, - {"DirectLAN", 0, &DirectLAN, 0, NULL, 0}, - - {"SavStaRelocSRAM", 0, &SavestateRelocSRAM, 0, NULL, 0}, - - {"AudioInterp", 0, &AudioInterp, 0, NULL, 0}, - {"AudioBitrate", 0, &AudioBitrate, 0, NULL, 0}, - {"AudioVolume", 0, &AudioVolume, 256, NULL, 0}, - {"MicInputType", 0, &MicInputType, 1, NULL, 0}, - {"MicWavPath", 1, MicWavPath, 0, "", 1023}, - - {"LastROMFolder", 1, LastROMFolder, 0, "", 1023}, - - {"RecentROM_0", 1, RecentROMList[0], 0, "", 1023}, - {"RecentROM_1", 1, RecentROMList[1], 0, "", 1023}, - {"RecentROM_2", 1, RecentROMList[2], 0, "", 1023}, - {"RecentROM_3", 1, RecentROMList[3], 0, "", 1023}, - {"RecentROM_4", 1, RecentROMList[4], 0, "", 1023}, - {"RecentROM_5", 1, RecentROMList[5], 0, "", 1023}, - {"RecentROM_6", 1, RecentROMList[6], 0, "", 1023}, - {"RecentROM_7", 1, RecentROMList[7], 0, "", 1023}, - {"RecentROM_8", 1, RecentROMList[8], 0, "", 1023}, - {"RecentROM_9", 1, RecentROMList[9], 0, "", 1023}, - - {"EnableCheats", 0, &EnableCheats, 0, NULL, 0}, - - {"MouseHide", 0, &MouseHide, 0, NULL, 0}, - {"MouseHideSeconds", 0, &MouseHideSeconds, 5, NULL, 0}, - {"PauseLostFocus", 0, &PauseLostFocus, 0, NULL, 0}, - - {"", -1, NULL, 0, NULL, 0} + {"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false, false}, + + {"BIOS9Path", 2, &BIOS9Path, (std::string)"", false}, + {"BIOS7Path", 2, &BIOS7Path, (std::string)"", false}, + {"FirmwarePath", 2, &FirmwarePath, (std::string)"", false}, + + {"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)"", false}, + {"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)"", false}, + {"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)"", false}, + {"DSiNANDPath", 2, &DSiNANDPath, (std::string)"", false}, + + {"DLDIEnable", 1, &DLDIEnable, false, false}, + {"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin", false}, + {"DLDISize", 0, &DLDISize, 0, false}, + {"DLDIReadOnly", 1, &DLDIReadOnly, false, false}, + {"DLDIFolderSync", 1, &DLDIFolderSync, false, false}, + {"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)"", false}, + + {"DSiSDEnable", 1, &DSiSDEnable, false, false}, + {"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin", false}, + {"DSiSDSize", 0, &DSiSDSize, 0, false}, + {"DSiSDReadOnly", 1, &DSiSDReadOnly, false, false}, + {"DSiSDFolderSync", 1, &DSiSDFolderSync, false, false}, + {"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)"", false}, + + {"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false, true}, + {"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS", true}, + {"FirmwareLanguage", 0, &FirmwareLanguage, 1, true}, + {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1, true}, + {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1, true}, + {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0, true}, + {"FirmwareMessage", 2, &FirmwareMessage, (std::string)"", true}, + {"FirmwareMAC", 2, &FirmwareMAC, (std::string)"", true}, + + {"MPAudioMode", 0, &MPAudioMode, 1, false}, + {"MPRecvTimeout", 0, &MPRecvTimeout, 25, false}, + + {"LANDevice", 2, &LANDevice, (std::string)"", false}, + {"DirectLAN", 1, &DirectLAN, false, false}, + + {"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false, false}, + + {"AudioInterp", 0, &AudioInterp, 0, false}, + {"AudioBitrate", 0, &AudioBitrate, 0, false}, + {"AudioVolume", 0, &AudioVolume, 256, true}, + {"MicInputType", 0, &MicInputType, 1, false}, + {"MicWavPath", 2, &MicWavPath, (std::string)"", false}, + + {"LastROMFolder", 2, &LastROMFolder, (std::string)"", true}, + + {"RecentROM_0", 2, &RecentROMList[0], (std::string)"", true}, + {"RecentROM_1", 2, &RecentROMList[1], (std::string)"", true}, + {"RecentROM_2", 2, &RecentROMList[2], (std::string)"", true}, + {"RecentROM_3", 2, &RecentROMList[3], (std::string)"", true}, + {"RecentROM_4", 2, &RecentROMList[4], (std::string)"", true}, + {"RecentROM_5", 2, &RecentROMList[5], (std::string)"", true}, + {"RecentROM_6", 2, &RecentROMList[6], (std::string)"", true}, + {"RecentROM_7", 2, &RecentROMList[7], (std::string)"", true}, + {"RecentROM_8", 2, &RecentROMList[8], (std::string)"", true}, + {"RecentROM_9", 2, &RecentROMList[9], (std::string)"", true}, + + {"SaveFilePath", 2, &SaveFilePath, (std::string)"", true}, + {"SavestatePath", 2, &SavestatePath, (std::string)"", true}, + {"CheatFilePath", 2, &CheatFilePath, (std::string)"", true}, + + {"EnableCheats", 1, &EnableCheats, false, true}, + + {"MouseHide", 1, &MouseHide, false, false}, + {"MouseHideSeconds", 0, &MouseHideSeconds, 5, false}, + {"PauseLostFocus", 1, &PauseLostFocus, false, false}, + + {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true, true}, + {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true}, + {"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true}, + + // TODO!! + // we need a more elegant way to deal with this + {"Camera0_InputType", 0, &Camera[0].InputType, 0, false}, + {"Camera0_ImagePath", 2, &Camera[0].ImagePath, (std::string)"", false}, + {"Camera0_CamDeviceName", 2, &Camera[0].CamDeviceName, (std::string)"", false}, + {"Camera0_XFlip", 1, &Camera[0].XFlip, false, false}, + {"Camera1_InputType", 0, &Camera[1].InputType, 0, false}, + {"Camera1_ImagePath", 2, &Camera[1].ImagePath, (std::string)"", false}, + {"Camera1_CamDeviceName", 2, &Camera[1].CamDeviceName, (std::string)"", false}, + {"Camera1_XFlip", 1, &Camera[1].XFlip, false, false}, + + {"", -1, nullptr, 0, false} }; -void Load() +void LoadFile(int inst) { - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + FILE* f; + if (inst > 0) { - if (!entry->Value) break; - - if (entry->Type == 0) - *(int*)entry->Value = entry->DefaultInt; - else - { - strncpy((char*)entry->Value, entry->DefaultStr, entry->StrLength); - ((char*)entry->Value)[entry->StrLength] = '\0'; - } - - entry++; + char name[100] = {0}; + snprintf(name, 99, kUniqueConfigFile, inst+1); + f = Platform::OpenLocalFile(name, "r"); } + else + f = Platform::OpenLocalFile(kConfigFile, "r"); - FILE* f = Platform::OpenLocalFile(kConfigFile, "r"); if (!f) return; char linebuf[1024]; @@ -334,44 +359,75 @@ void Load() entryname[31] = '\0'; if (ret < 2) continue; - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) { - if (!entry->Value) break; - if (!strncmp(entry->Name, entryname, 32)) { - if (entry->Type == 0) - *(int*)entry->Value = strtol(entryval, NULL, 10); - else - strncpy((char*)entry->Value, entryval, entry->StrLength); + if ((inst > 0) && (!entry->InstanceUnique)) + break; + + switch (entry->Type) + { + case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break; + case 1: *(bool*)entry->Value = strtol(entryval, NULL, 10) ? true:false; break; + case 2: *(std::string*)entry->Value = entryval; break; + } break; } - - entry++; } } fclose(f); } +void Load() +{ + + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) + { + switch (entry->Type) + { + case 0: *(int*)entry->Value = std::get<int>(entry->Default); break; + case 1: *(bool*)entry->Value = std::get<bool>(entry->Default); break; + case 2: *(std::string*)entry->Value = std::get<std::string>(entry->Default); break; + } + } + + LoadFile(0); + + int inst = Platform::InstanceID(); + if (inst > 0) + LoadFile(inst); +} + void Save() { - FILE* f = Platform::OpenLocalFile(kConfigFile, "w"); - if (!f) return; + int inst = Platform::InstanceID(); - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + FILE* f; + if (inst > 0) { - if (!entry->Value) break; + char name[100] = {0}; + snprintf(name, 99, kUniqueConfigFile, inst+1); + f = Platform::OpenLocalFile(name, "w"); + } + else + f = Platform::OpenLocalFile(kConfigFile, "w"); - if (entry->Type == 0) - fprintf(f, "%s=%d\r\n", entry->Name, *(int*)entry->Value); - else - fprintf(f, "%s=%s\r\n", entry->Name, (char*)entry->Value); + if (!f) return; - entry++; + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) + { + if ((inst > 0) && (!entry->InstanceUnique)) + continue; + + switch (entry->Type) + { + case 0: fprintf(f, "%s=%d\r\n", entry->Name, *(int*)entry->Value); break; + case 1: fprintf(f, "%s=%d\r\n", entry->Name, *(bool*)entry->Value ? 1:0); break; + case 2: fprintf(f, "%s=%s\r\n", entry->Name, (*(std::string*)entry->Value).c_str()); break; + } } fclose(f); diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index ad9b4c6..6ccae5f 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,6 +19,9 @@ #ifndef PLATFORMCONFIG_H #define PLATFORMCONFIG_H +#include <variant> +#include <string> + enum { HK_Lid = 0, @@ -35,17 +38,35 @@ enum HK_MAX }; +enum +{ + screenSizing_Even, + screenSizing_EmphTop, + screenSizing_EmphBot, + screenSizing_Auto, + screenSizing_TopOnly, + screenSizing_BotOnly, + screenSizing_MAX, +}; + namespace Config { struct ConfigEntry { char Name[32]; - int Type; - void* Value; - int DefaultInt; - const char* DefaultStr; - int StrLength; // should be set to actual array length minus one + int Type; // 0=int 1=bool 2=string + void* Value; // pointer to the value variable + std::variant<int, bool, std::string> Default; + bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer +}; + +struct CameraConfig +{ + int InputType; // 0=blank 1=image 2=camera + std::string ImagePath; + std::string CamDeviceName; + bool XFlip; }; @@ -59,99 +80,110 @@ extern int JoystickID; extern int WindowWidth; extern int WindowHeight; -extern int WindowMaximized; +extern bool WindowMaximized; extern int ScreenRotation; extern int ScreenGap; extern int ScreenLayout; -extern int ScreenSwap; +extern bool ScreenSwap; extern int ScreenSizing; extern int ScreenAspectTop; extern int ScreenAspectBot; -extern int IntegerScaling; -extern int ScreenFilter; +extern bool IntegerScaling; +extern bool ScreenFilter; -extern int ScreenUseGL; -extern int ScreenVSync; +extern bool ScreenUseGL; +extern bool ScreenVSync; extern int ScreenVSyncInterval; extern int _3DRenderer; -extern int Threaded3D; +extern bool Threaded3D; extern int GL_ScaleFactor; -extern int GL_BetterPolygons; +extern bool GL_BetterPolygons; -extern int LimitFPS; -extern int AudioSync; -extern int ShowOSD; +extern bool LimitFPS; +extern bool AudioSync; +extern bool ShowOSD; extern int ConsoleType; -extern int DirectBoot; +extern bool DirectBoot; #ifdef JIT_ENABLED -extern int JIT_Enable; +extern bool JIT_Enable; extern int JIT_MaxBlockSize; -extern int JIT_BranchOptimisations; -extern int JIT_LiteralOptimisations; -extern int JIT_FastMemory; +extern bool JIT_BranchOptimisations; +extern bool JIT_LiteralOptimisations; +extern bool JIT_FastMemory; #endif -extern int ExternalBIOSEnable; +extern bool ExternalBIOSEnable; -extern char BIOS9Path[1024]; -extern char BIOS7Path[1024]; -extern char FirmwarePath[1024]; +extern std::string BIOS9Path; +extern std::string BIOS7Path; +extern std::string FirmwarePath; -extern char DSiBIOS9Path[1024]; -extern char DSiBIOS7Path[1024]; -extern char DSiFirmwarePath[1024]; -extern char DSiNANDPath[1024]; +extern std::string DSiBIOS9Path; +extern std::string DSiBIOS7Path; +extern std::string DSiFirmwarePath; +extern std::string DSiNANDPath; -extern int DLDIEnable; -extern char DLDISDPath[1024]; +extern bool DLDIEnable; +extern std::string DLDISDPath; extern int DLDISize; -extern int DLDIReadOnly; -extern int DLDIFolderSync; -extern char DLDIFolderPath[1024]; +extern bool DLDIReadOnly; +extern bool DLDIFolderSync; +extern std::string DLDIFolderPath; -extern int DSiSDEnable; -extern char DSiSDPath[1024]; +extern bool DSiSDEnable; +extern std::string DSiSDPath; extern int DSiSDSize; -extern int DSiSDReadOnly; -extern int DSiSDFolderSync; -extern char DSiSDFolderPath[1024]; +extern bool DSiSDReadOnly; +extern bool DSiSDFolderSync; +extern std::string DSiSDFolderPath; -extern int FirmwareOverrideSettings; -extern char FirmwareUsername[64]; +extern bool FirmwareOverrideSettings; +extern std::string FirmwareUsername; extern int FirmwareLanguage; extern int FirmwareBirthdayMonth; extern int FirmwareBirthdayDay; extern int FirmwareFavouriteColour; -extern char FirmwareMessage[1024]; -extern char FirmwareMAC[18]; -extern int RandomizeMAC; +extern std::string FirmwareMessage; +extern std::string FirmwareMAC; + +extern int MPAudioMode; +extern int MPRecvTimeout; -extern int SocketBindAnyAddr; -extern char LANDevice[128]; -extern int DirectLAN; +extern std::string LANDevice; +extern bool DirectLAN; -extern int SavestateRelocSRAM; +extern bool SavestateRelocSRAM; extern int AudioInterp; extern int AudioBitrate; extern int AudioVolume; extern int MicInputType; -extern char MicWavPath[1024]; +extern std::string MicWavPath; -extern char LastROMFolder[1024]; +extern std::string LastROMFolder; -extern char RecentROMList[10][1024]; +extern std::string RecentROMList[10]; -extern int EnableCheats; +extern std::string SaveFilePath; +extern std::string SavestatePath; +extern std::string CheatFilePath; -extern int MouseHide; +extern bool EnableCheats; + +extern bool MouseHide; extern int MouseHideSeconds; -extern int PauseLostFocus; +extern bool PauseLostFocus; + +extern bool DSBatteryLevelOkay; +extern int DSiBatteryLevel; +extern bool DSiBatteryCharging; + +extern CameraConfig Camera[2]; void Load(); diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp index fd2ca85..bd40568 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp +++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -42,27 +42,27 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->chkExternalBIOS->setChecked(Config::ExternalBIOSEnable != 0); - ui->txtBIOS9Path->setText(Config::BIOS9Path); - ui->txtBIOS7Path->setText(Config::BIOS7Path); - ui->txtFirmwarePath->setText(Config::FirmwarePath); + ui->chkExternalBIOS->setChecked(Config::ExternalBIOSEnable); + ui->txtBIOS9Path->setText(QString::fromStdString(Config::BIOS9Path)); + ui->txtBIOS7Path->setText(QString::fromStdString(Config::BIOS7Path)); + ui->txtFirmwarePath->setText(QString::fromStdString(Config::FirmwarePath)); - ui->txtDSiBIOS9Path->setText(Config::DSiBIOS9Path); - ui->txtDSiBIOS7Path->setText(Config::DSiBIOS7Path); - ui->txtDSiFirmwarePath->setText(Config::DSiFirmwarePath); - ui->txtDSiNANDPath->setText(Config::DSiNANDPath); + ui->txtDSiBIOS9Path->setText(QString::fromStdString(Config::DSiBIOS9Path)); + ui->txtDSiBIOS7Path->setText(QString::fromStdString(Config::DSiBIOS7Path)); + ui->txtDSiFirmwarePath->setText(QString::fromStdString(Config::DSiFirmwarePath)); + ui->txtDSiNANDPath->setText(QString::fromStdString(Config::DSiNANDPath)); ui->cbxConsoleType->addItem("DS"); ui->cbxConsoleType->addItem("DSi (experimental)"); ui->cbxConsoleType->setCurrentIndex(Config::ConsoleType); - ui->chkDirectBoot->setChecked(Config::DirectBoot != 0); + ui->chkDirectBoot->setChecked(Config::DirectBoot); #ifdef JIT_ENABLED - ui->chkEnableJIT->setChecked(Config::JIT_Enable != 0); - ui->chkJITBranchOptimisations->setChecked(Config::JIT_BranchOptimisations != 0); - ui->chkJITLiteralOptimisations->setChecked(Config::JIT_LiteralOptimisations != 0); - ui->chkJITFastMemory->setChecked(Config::JIT_FastMemory != 0); + ui->chkEnableJIT->setChecked(Config::JIT_Enable); + ui->chkJITBranchOptimisations->setChecked(Config::JIT_BranchOptimisations); + ui->chkJITLiteralOptimisations->setChecked(Config::JIT_LiteralOptimisations); + ui->chkJITFastMemory->setChecked(Config::JIT_FastMemory); #ifdef __APPLE__ ui->chkJITFastMemory->setDisabled(true); #endif @@ -101,20 +101,20 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new ui->cbxDSiSDSize->addItem(sizelbl); } - ui->cbDLDIEnable->setChecked(Config::DLDIEnable != 0); - ui->txtDLDISDPath->setText(Config::DLDISDPath); + ui->cbDLDIEnable->setChecked(Config::DLDIEnable); + ui->txtDLDISDPath->setText(QString::fromStdString(Config::DLDISDPath)); ui->cbxDLDISize->setCurrentIndex(Config::DLDISize); - ui->cbDLDIReadOnly->setChecked(Config::DLDIReadOnly != 0); - ui->cbDLDIFolder->setChecked(Config::DLDIFolderSync != 0); - ui->txtDLDIFolder->setText(Config::DLDIFolderPath); + ui->cbDLDIReadOnly->setChecked(Config::DLDIReadOnly); + ui->cbDLDIFolder->setChecked(Config::DLDIFolderSync); + ui->txtDLDIFolder->setText(QString::fromStdString(Config::DLDIFolderPath)); on_cbDLDIEnable_toggled(); - ui->cbDSiSDEnable->setChecked(Config::DSiSDEnable != 0); - ui->txtDSiSDPath->setText(Config::DSiSDPath); + ui->cbDSiSDEnable->setChecked(Config::DSiSDEnable); + ui->txtDSiSDPath->setText(QString::fromStdString(Config::DSiSDPath)); ui->cbxDSiSDSize->setCurrentIndex(Config::DSiSDSize); - ui->cbDSiSDReadOnly->setChecked(Config::DSiSDReadOnly != 0); - ui->cbDSiSDFolder->setChecked(Config::DSiSDFolderSync != 0); - ui->txtDSiSDFolder->setText(Config::DSiSDFolderPath); + ui->cbDSiSDReadOnly->setChecked(Config::DSiSDReadOnly); + ui->cbDSiSDFolder->setChecked(Config::DSiSDFolderSync); + ui->txtDSiSDFolder->setText(QString::fromStdString(Config::DSiSDFolderPath)); on_cbDSiSDEnable_toggled(); } @@ -140,8 +140,7 @@ void EmuSettingsDialog::verifyFirmware() // looked at has 0x180 bytes from the header repeated at 0x3FC80, but // bytes 0x0C-0x14 are different. - char filename[1024]; - strncpy(filename, ui->txtFirmwarePath->text().toStdString().c_str(), 1023); filename[1023] = '\0'; + std::string filename = ui->txtFirmwarePath->text().toStdString(); FILE* f = Platform::OpenLocalFile(filename, "rb"); if (!f) return; u8 chk1[0x180], chk2[0x180]; @@ -175,24 +174,24 @@ void EmuSettingsDialog::done(int r) verifyFirmware(); int consoleType = ui->cbxConsoleType->currentIndex(); - int directBoot = ui->chkDirectBoot->isChecked() ? 1:0; + bool directBoot = ui->chkDirectBoot->isChecked(); - int jitEnable = ui->chkEnableJIT->isChecked() ? 1:0; + bool jitEnable = ui->chkEnableJIT->isChecked(); int jitMaxBlockSize = ui->spnJITMaximumBlockSize->value(); - int jitBranchOptimisations = ui->chkJITBranchOptimisations->isChecked() ? 1:0; - int jitLiteralOptimisations = ui->chkJITLiteralOptimisations->isChecked() ? 1:0; - int jitFastMemory = ui->chkJITFastMemory->isChecked() ? 1:0; + bool jitBranchOptimisations = ui->chkJITBranchOptimisations->isChecked(); + bool jitLiteralOptimisations = ui->chkJITLiteralOptimisations->isChecked(); + bool jitFastMemory = ui->chkJITFastMemory->isChecked(); - int externalBiosEnable = ui->chkExternalBIOS->isChecked() ? 1:0; + bool externalBiosEnable = ui->chkExternalBIOS->isChecked(); std::string bios9Path = ui->txtBIOS9Path->text().toStdString(); std::string bios7Path = ui->txtBIOS7Path->text().toStdString(); std::string firmwarePath = ui->txtFirmwarePath->text().toStdString(); - int dldiEnable = ui->cbDLDIEnable->isChecked() ? 1:0; + bool dldiEnable = ui->cbDLDIEnable->isChecked(); std::string dldiSDPath = ui->txtDLDISDPath->text().toStdString(); int dldiSize = ui->cbxDLDISize->currentIndex(); - int dldiReadOnly = ui->cbDLDIReadOnly->isChecked() ? 1:0; - int dldiFolderSync = ui->cbDLDIFolder->isChecked() ? 1:0; + bool dldiReadOnly = ui->cbDLDIReadOnly->isChecked(); + bool dldiFolderSync = ui->cbDLDIFolder->isChecked(); std::string dldiFolderPath = ui->txtDLDIFolder->text().toStdString(); std::string dsiBios9Path = ui->txtDSiBIOS9Path->text().toStdString(); @@ -200,11 +199,11 @@ void EmuSettingsDialog::done(int r) std::string dsiFirmwarePath = ui->txtDSiFirmwarePath->text().toStdString(); std::string dsiNANDPath = ui->txtDSiNANDPath->text().toStdString(); - int dsiSDEnable = ui->cbDSiSDEnable->isChecked() ? 1:0; + bool dsiSDEnable = ui->cbDSiSDEnable->isChecked(); std::string dsiSDPath = ui->txtDSiSDPath->text().toStdString(); int dsiSDSize = ui->cbxDSiSDSize->currentIndex(); - int dsiSDReadOnly = ui->cbDSiSDReadOnly->isChecked() ? 1:0; - int dsiSDFolderSync = ui->cbDSiSDFolder->isChecked() ? 1:0; + bool dsiSDReadOnly = ui->cbDSiSDReadOnly->isChecked(); + bool dsiSDFolderSync = ui->cbDSiSDFolder->isChecked(); std::string dsiSDFolderPath = ui->txtDSiSDFolder->text().toStdString(); if (consoleType != Config::ConsoleType @@ -217,25 +216,25 @@ void EmuSettingsDialog::done(int r) || jitFastMemory != Config::JIT_FastMemory #endif || externalBiosEnable != Config::ExternalBIOSEnable - || strcmp(Config::BIOS9Path, bios9Path.c_str()) != 0 - || strcmp(Config::BIOS7Path, bios7Path.c_str()) != 0 - || strcmp(Config::FirmwarePath, firmwarePath.c_str()) != 0 + || bios9Path != Config::BIOS9Path + || bios7Path != Config::BIOS7Path + || firmwarePath != Config::FirmwarePath || dldiEnable != Config::DLDIEnable - || strcmp(Config::DLDISDPath, dldiSDPath.c_str()) != 0 + || dldiSDPath != Config::DLDISDPath || dldiSize != Config::DLDISize || dldiReadOnly != Config::DLDIReadOnly || dldiFolderSync != Config::DLDIFolderSync - || strcmp(Config::DLDIFolderPath, dldiFolderPath.c_str()) != 0 - || strcmp(Config::DSiBIOS9Path, dsiBios9Path.c_str()) != 0 - || strcmp(Config::DSiBIOS7Path, dsiBios7Path.c_str()) != 0 - || strcmp(Config::DSiFirmwarePath, dsiFirmwarePath.c_str()) != 0 - || strcmp(Config::DSiNANDPath, dsiNANDPath.c_str()) != 0 + || dldiFolderPath != Config::DLDIFolderPath + || dsiBios9Path != Config::DSiBIOS9Path + || dsiBios7Path != Config::DSiBIOS7Path + || dsiFirmwarePath != Config::DSiFirmwarePath + || dsiNANDPath != Config::DSiNANDPath || dsiSDEnable != Config::DSiSDEnable - || strcmp(Config::DSiSDPath, dsiSDPath.c_str()) != 0 + || dsiSDPath != Config::DSiSDPath || dsiSDSize != Config::DSiSDSize || dsiSDReadOnly != Config::DSiSDReadOnly || dsiSDFolderSync != Config::DSiSDFolderSync - || strcmp(Config::DSiSDFolderPath, dsiSDFolderPath.c_str()) != 0) + || dsiSDFolderPath != Config::DSiSDFolderPath) { if (RunningSomething && QMessageBox::warning(this, "Reset necessary to apply changes", @@ -244,28 +243,28 @@ void EmuSettingsDialog::done(int r) return; Config::ExternalBIOSEnable = externalBiosEnable; - strncpy(Config::BIOS9Path, bios9Path.c_str(), 1023); Config::BIOS9Path[1023] = '\0'; - strncpy(Config::BIOS7Path, bios7Path.c_str(), 1023); Config::BIOS7Path[1023] = '\0'; - strncpy(Config::FirmwarePath, firmwarePath.c_str(), 1023); Config::FirmwarePath[1023] = '\0'; + Config::BIOS9Path = bios9Path; + Config::BIOS7Path = bios7Path; + Config::FirmwarePath = firmwarePath; Config::DLDIEnable = dldiEnable; - strncpy(Config::DLDISDPath, dldiSDPath.c_str(), 1023); Config::DLDISDPath[1023] = '\0'; + Config::DLDISDPath = dldiSDPath; Config::DLDISize = dldiSize; Config::DLDIReadOnly = dldiReadOnly; Config::DLDIFolderSync = dldiFolderSync; - strncpy(Config::DLDIFolderPath, dldiFolderPath.c_str(), 1023); Config::DLDIFolderPath[1023] = '\0'; + Config::DLDIFolderPath = dldiFolderPath; - strncpy(Config::DSiBIOS9Path, dsiBios9Path.c_str(), 1023); Config::DSiBIOS9Path[1023] = '\0'; - strncpy(Config::DSiBIOS7Path, dsiBios7Path.c_str(), 1023); Config::DSiBIOS7Path[1023] = '\0'; - strncpy(Config::DSiFirmwarePath, dsiFirmwarePath.c_str(), 1023); Config::DSiFirmwarePath[1023] = '\0'; - strncpy(Config::DSiNANDPath, dsiNANDPath.c_str(), 1023); Config::DSiNANDPath[1023] = '\0'; + Config::DSiBIOS9Path = dsiBios9Path; + Config::DSiBIOS7Path = dsiBios7Path; + Config::DSiFirmwarePath = dsiFirmwarePath; + Config::DSiNANDPath = dsiNANDPath; Config::DSiSDEnable = dsiSDEnable; - strncpy(Config::DSiSDPath, dsiSDPath.c_str(), 1023); Config::DSiSDPath[1023] = '\0'; + Config::DSiSDPath = dsiSDPath; Config::DSiSDSize = dsiSDSize; Config::DSiSDReadOnly = dsiSDReadOnly; Config::DSiSDFolderSync = dsiSDFolderSync; - strncpy(Config::DSiSDFolderPath, dsiSDFolderPath.c_str(), 1023); Config::DSiSDFolderPath[1023] = '\0'; + Config::DSiSDFolderPath = dsiSDFolderPath; #ifdef JIT_ENABLED Config::JIT_Enable = jitEnable; diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.h b/src/frontend/qt_sdl/EmuSettingsDialog.h index 60e2160..6a79626 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.h +++ b/src/frontend/qt_sdl/EmuSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp index 0b2cad6..ffca567 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2020 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -18,6 +18,7 @@ #include <QMessageBox> +#include "Platform.h" #include "Config.h" #include "FirmwareSettingsDialog.h" @@ -35,7 +36,7 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->usernameEdit->setText(Config::FirmwareUsername); + ui->usernameEdit->setText(QString::fromStdString(Config::FirmwareUsername)); ui->languageBox->addItems(languages); ui->languageBox->setCurrentIndex(Config::FirmwareLanguage); @@ -59,13 +60,19 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent } ui->colorsEdit->setCurrentIndex(Config::FirmwareFavouriteColour); - ui->messageEdit->setText(Config::FirmwareMessage); + ui->messageEdit->setText(QString::fromStdString(Config::FirmwareMessage)); ui->overrideFirmwareBox->setChecked(Config::FirmwareOverrideSettings); - ui->txtMAC->setText(Config::FirmwareMAC); - ui->cbRandomizeMAC->setChecked(Config::RandomizeMAC != 0); - on_cbRandomizeMAC_toggled(); + ui->txtMAC->setText(QString::fromStdString(Config::FirmwareMAC)); + + on_overrideFirmwareBox_toggled(); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } FirmwareSettingsDialog::~FirmwareSettingsDialog() @@ -123,7 +130,7 @@ void FirmwareSettingsDialog::done(int r) return; } - int newOverride = ui->overrideFirmwareBox->isChecked(); + bool newOverride = ui->overrideFirmwareBox->isChecked(); std::string newName = ui->usernameEdit->text().toStdString(); int newLanguage = ui->languageBox->currentIndex(); @@ -133,17 +140,15 @@ void FirmwareSettingsDialog::done(int r) std::string newMessage = ui->messageEdit->text().toStdString(); std::string newMAC = ui->txtMAC->text().toStdString(); - int newRandomizeMAC = ui->cbRandomizeMAC->isChecked() ? 1:0; if ( newOverride != Config::FirmwareOverrideSettings - || strcmp(newName.c_str(), Config::FirmwareUsername) != 0 + || newName != Config::FirmwareUsername || newLanguage != Config::FirmwareLanguage || newFavColor != Config::FirmwareFavouriteColour || newBirthdayDay != Config::FirmwareBirthdayDay || newBirthdayMonth != Config::FirmwareBirthdayMonth - || strcmp(newMessage.c_str(), Config::FirmwareMessage) != 0 - || strcmp(newMAC.c_str(), Config::FirmwareMAC) != 0 - || newRandomizeMAC != Config::RandomizeMAC) + || newMessage != Config::FirmwareMessage + || newMAC != Config::FirmwareMAC) { if (RunningSomething && QMessageBox::warning(this, "Reset necessary to apply changes", @@ -153,15 +158,14 @@ void FirmwareSettingsDialog::done(int r) Config::FirmwareOverrideSettings = newOverride; - strncpy(Config::FirmwareUsername, newName.c_str(), 63); Config::FirmwareUsername[63] = '\0'; + Config::FirmwareUsername = newName; Config::FirmwareLanguage = newLanguage; Config::FirmwareFavouriteColour = newFavColor; Config::FirmwareBirthdayDay = newBirthdayDay; Config::FirmwareBirthdayMonth = newBirthdayMonth; - strncpy(Config::FirmwareMessage, newMessage.c_str(), 1023); Config::FirmwareMessage[1023] = '\0'; + Config::FirmwareMessage = newMessage; - strncpy(Config::FirmwareMAC, newMAC.c_str(), 17); Config::FirmwareMAC[17] = '\0'; - Config::RandomizeMAC = newRandomizeMAC; + Config::FirmwareMAC = newMAC; Config::Save(); @@ -202,8 +206,9 @@ void FirmwareSettingsDialog::on_cbxBirthdayMonth_currentIndexChanged(int idx) } } -void FirmwareSettingsDialog::on_cbRandomizeMAC_toggled() +void FirmwareSettingsDialog::on_overrideFirmwareBox_toggled() { - bool disable = ui->cbRandomizeMAC->isChecked(); - ui->txtMAC->setDisabled(disable); + bool disable = !ui->overrideFirmwareBox->isChecked(); + ui->grpUserSettings->setDisabled(disable); + ui->grpWifiSettings->setDisabled(disable); } diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.h b/src/frontend/qt_sdl/FirmwareSettingsDialog.h index 1ae409f..b3695e2 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.h +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2020 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -109,7 +109,7 @@ public: } currentDlg = new FirmwareSettingsDialog(parent); - currentDlg->show(); + currentDlg->open(); return currentDlg; } static void closeDlg() @@ -123,7 +123,7 @@ private slots: void done(int r); void on_cbxBirthdayMonth_currentIndexChanged(int idx); - void on_cbRandomizeMAC_toggled(); + void on_overrideFirmwareBox_toggled(); private: bool verifyMAC(); diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.ui b/src/frontend/qt_sdl/FirmwareSettingsDialog.ui index a97689c..3714629 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.ui +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>511</width> - <height>342</height> + <height>357</height> </rect> </property> <property name="sizePolicy"> @@ -24,6 +24,13 @@ <enum>QLayout::SetFixedSize</enum> </property> <item> + <widget class="QLabel" name="lblInstanceNum"> + <property name="text"> + <string>Configuring settings for instance X</string> + </property> + </widget> + </item> + <item> <widget class="QGroupBox" name="grpGeneral"> <property name="title"> <string>General</string> @@ -144,9 +151,9 @@ </widget> </item> <item row="1" column="1"> - <widget class="QCheckBox" name="cbRandomizeMAC"> + <widget class="QLabel" name="label_6"> <property name="text"> - <string>Randomize</string> + <string>(leave empty to use default MAC)</string> </property> </widget> </item> diff --git a/src/frontend/qt_sdl/Input.cpp b/src/frontend/qt_sdl/Input.cpp index cca11e7..c1ef87c 100644 --- a/src/frontend/qt_sdl/Input.cpp +++ b/src/frontend/qt_sdl/Input.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/Input.h b/src/frontend/qt_sdl/Input.h index d349f19..0d2292d 100644 --- a/src/frontend/qt_sdl/Input.h +++ b/src/frontend/qt_sdl/Input.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp index 2a50539..92a0186 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -24,6 +24,7 @@ #include <SDL2/SDL.h> #include "types.h" +#include "Platform.h" #include "Config.h" #include "MapButton.h" @@ -123,6 +124,12 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new } setupKeypadPage(); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring mappings for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } InputConfigDialog::~InputConfigDialog() diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h index 069dfcb..f1ea059 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui index 6f4bb5d..0db61b1 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>710</width> - <height>709</height> + <width>770</width> + <height>678</height> </rect> </property> <property name="windowTitle"> @@ -20,7 +20,7 @@ <property name="sizeConstraint"> <enum>QLayout::SetFixedSize</enum> </property> - <item row="7" column="1"> + <item row="8" column="1"> <widget class="QDialogButtonBox" name="buttonBox"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -30,49 +30,7 @@ </property> </widget> </item> - <item row="6" column="0" colspan="2"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="label"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Joystick:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="cbxJoystick"> - <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="whatsThis"> - <string><html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html></string> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="0" colspan="2"> + <item row="1" column="0" colspan="2"> <widget class="QTabWidget" name="tabWidget"> <property name="currentIndex"> <number>0</number> @@ -82,13 +40,128 @@ <string>DS keypad</string> </attribute> <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="0"> + <item row="0" column="0"> <widget class="QStackedWidget" name="stackMapping"> <property name="currentIndex"> <number>0</number> </property> <widget class="QWidget" name="keyPage"> <layout class="QGridLayout" name="gridLayout_2"> + <item row="4" column="1" colspan="3"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="btnJoyMapSwitch"> + <property name="text"> + <string>Switch to Joystick mappings</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="3"> + <widget class="QWidget" name="widget_4" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_15"> + <item> + <widget class="QGroupBox" name="grp_R"> + <property name="title"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">R</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnKeyR"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>R</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item row="1" column="1"> <widget class="QWidget" name="widget_3" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_14"> @@ -108,12 +181,12 @@ <item> <widget class="QGroupBox" name="grp_L"> <property name="title"> - <string>L</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QHBoxLayout" name="horizontalLayout_5"> + <layout class="QVBoxLayout" name="verticalLayout_4"> <property name="spacing"> <number>3</number> </property> @@ -130,10 +203,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">L</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnKeyL"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -157,32 +240,6 @@ </layout> </widget> </item> - <item row="1" column="2"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string/> - </property> - <property name="pixmap"> - <pixmap resource="resources/ds.qrc">:/ds/ds_back.svg</pixmap> - </property> - <property name="scaledContents"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="2" column="2"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string/> - </property> - <property name="pixmap"> - <pixmap resource="resources/ds.qrc">:/ds/ds_open.svg</pixmap> - </property> - <property name="scaledContents"> - <bool>false</bool> - </property> - </widget> - </item> <item row="2" column="3"> <widget class="QGroupBox" name="grp_ABXY"> <property name="sizePolicy"> @@ -250,7 +307,7 @@ <item alignment="Qt::AlignHCenter"> <widget class="QGroupBox" name="grp_X"> <property name="title"> - <string>X</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -272,10 +329,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_13"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">X</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnKeyX"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -320,7 +387,7 @@ <item alignment="Qt::AlignHCenter"> <widget class="QGroupBox" name="grp_Y"> <property name="title"> - <string>Y</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -342,10 +409,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Y</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnKeyY"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -369,7 +446,7 @@ <item alignment="Qt::AlignHCenter"> <widget class="QGroupBox" name="grp_A"> <property name="title"> - <string>A</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -391,10 +468,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">A</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnKeyA"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -451,7 +538,7 @@ <item alignment="Qt::AlignHCenter"> <widget class="QGroupBox" name="grp_B"> <property name="title"> - <string>B</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -473,10 +560,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">B</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnKeyB"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -516,6 +613,131 @@ </layout> </widget> </item> + <item row="3" column="2"> + <layout class="QHBoxLayout" name="layout_SelectStart"> + <property name="spacing"> + <number>3</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="grp_Select"> + <property name="title"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QLabel" name="label_18"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Select</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnKeySelect"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Select</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="grp_Start"> + <property name="title"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QLabel" name="label_19"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Start</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnKeyStart"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Start</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> <item row="2" column="1"> <widget class="QGroupBox" name="grp_ControlPad"> <property name="sizePolicy"> @@ -583,7 +805,7 @@ <item alignment="Qt::AlignHCenter"> <widget class="QGroupBox" name="grp_Up"> <property name="title"> - <string>Up</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -605,10 +827,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Up</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnKeyUp"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -653,7 +885,7 @@ <item alignment="Qt::AlignHCenter"> <widget class="QGroupBox" name="grp_Left"> <property name="title"> - <string>Left</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -675,10 +907,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Left</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnKeyLeft"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -702,7 +944,7 @@ <item alignment="Qt::AlignHCenter"> <widget class="QGroupBox" name="grp_Right"> <property name="title"> - <string>Right</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -724,10 +966,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Right</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnKeyRight"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -784,7 +1036,7 @@ <item alignment="Qt::AlignHCenter"> <widget class="QGroupBox" name="grp_Down"> <property name="title"> - <string>Down</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -806,10 +1058,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Down</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnKeyDown"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -849,6 +1111,32 @@ </layout> </widget> </item> + <item row="2" column="2"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="resources/ds.qrc">:/ds/ds_open.svg</pixmap> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="resources/ds.qrc">:/ds/ds_back.svg</pixmap> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + </widget> + </item> <item row="0" column="2"> <widget class="QLabel" name="label_7"> <property name="font"> @@ -864,10 +1152,14 @@ </property> </widget> </item> + </layout> + </widget> + <widget class="QWidget" name="joyPage"> + <layout class="QGridLayout" name="gridLayout_3"> <item row="4" column="1" colspan="3"> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QHBoxLayout" name="horizontalLayout_12"> <item> - <spacer name="horizontalSpacer"> + <spacer name="horizontalSpacer_3"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -880,14 +1172,14 @@ </spacer> </item> <item> - <widget class="QPushButton" name="btnJoyMapSwitch"> + <widget class="QPushButton" name="btnKeyMapSwitch"> <property name="text"> - <string>Switch to Joystick mappings</string> + <string>Switch to Keyboard mappings</string> </property> </widget> </item> <item> - <spacer name="horizontalSpacer_2"> + <spacer name="horizontalSpacer_4"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -901,183 +1193,6 @@ </item> </layout> </item> - <item row="3" column="2"> - <layout class="QHBoxLayout" name="layout_SelectStart"> - <property name="spacing"> - <number>3</number> - </property> - <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="grp_Select"> - <property name="title"> - <string>Select</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2"> - <property name="spacing"> - <number>3</number> - </property> - <property name="leftMargin"> - <number>3</number> - </property> - <property name="topMargin"> - <number>3</number> - </property> - <property name="rightMargin"> - <number>3</number> - </property> - <property name="bottomMargin"> - <number>3</number> - </property> - <item> - <widget class="QPushButton" name="btnKeySelect"> - <property name="minimumSize"> - <size> - <width>72</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>68</width> - <height>16777215</height> - </size> - </property> - <property name="styleSheet"> - <string notr="true">min-width: 68px;</string> - </property> - <property name="text"> - <string>Select</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="grp_Start"> - <property name="title"> - <string>Start</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2"> - <property name="spacing"> - <number>3</number> - </property> - <property name="leftMargin"> - <number>3</number> - </property> - <property name="topMargin"> - <number>3</number> - </property> - <property name="rightMargin"> - <number>3</number> - </property> - <property name="bottomMargin"> - <number>3</number> - </property> - <item> - <widget class="QPushButton" name="btnKeyStart"> - <property name="minimumSize"> - <size> - <width>72</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>68</width> - <height>16777215</height> - </size> - </property> - <property name="styleSheet"> - <string notr="true">min-width: 68px;</string> - </property> - <property name="text"> - <string>Start</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </item> - <item row="1" column="3"> - <widget class="QWidget" name="widget_4" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout_15"> - <item> - <widget class="QGroupBox" name="grp_R"> - <property name="title"> - <string>R</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_4"> - <property name="spacing"> - <number>3</number> - </property> - <property name="leftMargin"> - <number>3</number> - </property> - <property name="topMargin"> - <number>3</number> - </property> - <property name="rightMargin"> - <number>3</number> - </property> - <property name="bottomMargin"> - <number>3</number> - </property> - <item> - <widget class="QPushButton" name="btnKeyR"> - <property name="minimumSize"> - <size> - <width>72</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>68</width> - <height>16777215</height> - </size> - </property> - <property name="styleSheet"> - <string notr="true">min-width: 68px;</string> - </property> - <property name="text"> - <string>R</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_8"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="joyPage"> - <layout class="QGridLayout" name="gridLayout_3"> <item row="1" column="1"> <widget class="QWidget" name="widget" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_2"> @@ -1097,12 +1212,12 @@ <item> <widget class="QGroupBox" name="grp_L_2"> <property name="title"> - <string>L</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QHBoxLayout" name="horizontalLayout_9"> + <layout class="QVBoxLayout" name="verticalLayout_7"> <property name="spacing"> <number>3</number> </property> @@ -1119,10 +1234,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_20"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">L</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnJoyL"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -1146,8 +1271,34 @@ </layout> </widget> </item> - <item row="2" column="3"> - <widget class="QGroupBox" name="grp_ABXY_2"> + <item row="1" column="2"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="resources/ds.qrc">:/ds/ds_back.svg</pixmap> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="resources/ds.qrc">:/ds/ds_open.svg</pixmap> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QGroupBox" name="grp_ControlPad_2"> <property name="sizePolicy"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <horstretch>0</horstretch> @@ -1163,7 +1314,7 @@ <property name="checkable"> <bool>false</bool> </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> + <layout class="QVBoxLayout" name="verticalLayout_6"> <property name="spacing"> <number>0</number> </property> @@ -1180,8 +1331,8 @@ <number>3</number> </property> <item> - <widget class="QWidget" name="widget_X_2" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout_7"> + <widget class="QWidget" name="widget_Up_2" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_25"> <property name="spacing"> <number>0</number> </property> @@ -1198,7 +1349,7 @@ <number>0</number> </property> <item> - <spacer name="spacer_9"> + <spacer name="spacer_13"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -1211,14 +1362,14 @@ </spacer> </item> <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="grp_X_2"> + <widget class="QGroupBox" name="grp_Up_2"> <property name="title"> - <string>X</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout_2"> + <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout_2"> <property name="spacing"> <number>3</number> </property> @@ -1235,10 +1386,20 @@ <number>3</number> </property> <item> - <widget class="QPushButton" name="btnJoyX"> + <widget class="QLabel" name="label_23"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Up</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnJoyUp"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -1252,7 +1413,7 @@ <string notr="true">min-width: 68px;</string> </property> <property name="text"> - <string>X</string> + <string>Up</string> </property> </widget> </item> @@ -1260,7 +1421,7 @@ </widget> </item> <item> - <spacer name="spacer_10"> + <spacer name="spacer_14"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -1276,19 +1437,19 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="layout_YA_2"> + <layout class="QHBoxLayout" name="layout_LeftRight_2"> <property name="spacing"> <number>3</number> </property> <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="grp_Y_2"> + <widget class="QGroupBox" name="grp_Left_2"> <property name="title"> - <string>Y</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout_2"> + <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_4"> <property name="spacing"> <number>3</number> </property> @@ -1305,10 +1466,20 @@ <number>3</number> </property> <item> - <widget class="QPushButton" name="btnJoyY"> + <widget class="QLabel" name="label_22"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Left</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnJoyLeft"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -1322,7 +1493,7 @@ <string notr="true">min-width: 68px;</string> </property> <property name="text"> - <string>Y</string> + <string>Left</string> </property> </widget> </item> @@ -1330,14 +1501,14 @@ </widget> </item> <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="grp_A_2"> + <widget class="QGroupBox" name="grp_Right_2"> <property name="title"> - <string>A</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout_2"> + <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_4"> <property name="spacing"> <number>3</number> </property> @@ -1354,10 +1525,20 @@ <number>3</number> </property> <item> - <widget class="QPushButton" name="btnJoyA"> + <widget class="QLabel" name="label_30"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Right</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnJoyRight"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -1371,7 +1552,7 @@ <string notr="true">min-width: 68px;</string> </property> <property name="text"> - <string>A</string> + <string>Right</string> </property> </widget> </item> @@ -1381,8 +1562,8 @@ </layout> </item> <item> - <widget class="QWidget" name="widget_B_2" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout_11"> + <widget class="QWidget" name="widget_Down_2" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_26"> <property name="spacing"> <number>0</number> </property> @@ -1399,7 +1580,7 @@ <number>0</number> </property> <item> - <spacer name="spacer_11"> + <spacer name="spacer_15"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -1412,14 +1593,14 @@ </spacer> </item> <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="grp_B_2"> + <widget class="QGroupBox" name="grp_Down_2"> <property name="title"> - <string>B</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout_2"> + <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout_2"> <property name="spacing"> <number>3</number> </property> @@ -1436,10 +1617,20 @@ <number>3</number> </property> <item> - <widget class="QPushButton" name="btnJoyB"> + <widget class="QLabel" name="label_31"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Down</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnJoyDown"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -1453,7 +1644,7 @@ <string notr="true">min-width: 68px;</string> </property> <property name="text"> - <string>B</string> + <string>Down</string> </property> </widget> </item> @@ -1461,7 +1652,7 @@ </widget> </item> <item> - <spacer name="spacer_12"> + <spacer name="spacer_16"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -1479,8 +1670,8 @@ </layout> </widget> </item> - <item row="2" column="1"> - <widget class="QGroupBox" name="grp_ControlPad_2"> + <item row="2" column="3"> + <widget class="QGroupBox" name="grp_ABXY_2"> <property name="sizePolicy"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <horstretch>0</horstretch> @@ -1496,7 +1687,7 @@ <property name="checkable"> <bool>false</bool> </property> - <layout class="QVBoxLayout" name="verticalLayout_6"> + <layout class="QVBoxLayout" name="verticalLayout_3"> <property name="spacing"> <number>0</number> </property> @@ -1513,8 +1704,8 @@ <number>3</number> </property> <item> - <widget class="QWidget" name="widget_Up_2" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout_25"> + <widget class="QWidget" name="widget_X_2" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_7"> <property name="spacing"> <number>0</number> </property> @@ -1531,7 +1722,7 @@ <number>0</number> </property> <item> - <spacer name="spacer_13"> + <spacer name="spacer_9"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -1544,14 +1735,14 @@ </spacer> </item> <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="grp_Up_2"> + <widget class="QGroupBox" name="grp_X_2"> <property name="title"> - <string>Up</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout_2"> + <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout_2"> <property name="spacing"> <number>3</number> </property> @@ -1568,10 +1759,20 @@ <number>3</number> </property> <item> - <widget class="QPushButton" name="btnJoyUp"> + <widget class="QLabel" name="label_24"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">X</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnJoyX"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -1585,7 +1786,7 @@ <string notr="true">min-width: 68px;</string> </property> <property name="text"> - <string>Up</string> + <string>X</string> </property> </widget> </item> @@ -1593,7 +1794,7 @@ </widget> </item> <item> - <spacer name="spacer_14"> + <spacer name="spacer_10"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -1609,19 +1810,19 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="layout_LeftRight_2"> + <layout class="QHBoxLayout" name="layout_YA_2"> <property name="spacing"> <number>3</number> </property> <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="grp_Left_2"> + <widget class="QGroupBox" name="grp_Y_2"> <property name="title"> - <string>Left</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_4"> + <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout_2"> <property name="spacing"> <number>3</number> </property> @@ -1638,10 +1839,20 @@ <number>3</number> </property> <item> - <widget class="QPushButton" name="btnJoyLeft"> + <widget class="QLabel" name="label_25"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Y</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnJoyY"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -1655,7 +1866,7 @@ <string notr="true">min-width: 68px;</string> </property> <property name="text"> - <string>Left</string> + <string>Y</string> </property> </widget> </item> @@ -1663,14 +1874,14 @@ </widget> </item> <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="grp_Right_2"> + <widget class="QGroupBox" name="grp_A_2"> <property name="title"> - <string>Right</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_4"> + <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout_2"> <property name="spacing"> <number>3</number> </property> @@ -1687,10 +1898,20 @@ <number>3</number> </property> <item> - <widget class="QPushButton" name="btnJoyRight"> + <widget class="QLabel" name="label_26"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">A</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnJoyA"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -1704,7 +1925,7 @@ <string notr="true">min-width: 68px;</string> </property> <property name="text"> - <string>Right</string> + <string>A</string> </property> </widget> </item> @@ -1714,8 +1935,8 @@ </layout> </item> <item> - <widget class="QWidget" name="widget_Down_2" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout_26"> + <widget class="QWidget" name="widget_B_2" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_11"> <property name="spacing"> <number>0</number> </property> @@ -1732,7 +1953,7 @@ <number>0</number> </property> <item> - <spacer name="spacer_15"> + <spacer name="spacer_11"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -1745,14 +1966,14 @@ </spacer> </item> <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="grp_Down_2"> + <widget class="QGroupBox" name="grp_B_2"> <property name="title"> - <string>Down</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout_2"> + <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout_2"> <property name="spacing"> <number>3</number> </property> @@ -1769,10 +1990,20 @@ <number>3</number> </property> <item> - <widget class="QPushButton" name="btnJoyDown"> + <widget class="QLabel" name="label_27"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">B</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnJoyB"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -1786,7 +2017,7 @@ <string notr="true">min-width: 68px;</string> </property> <property name="text"> - <string>Down</string> + <string>B</string> </property> </widget> </item> @@ -1794,7 +2025,7 @@ </widget> </item> <item> - <spacer name="spacer_16"> + <spacer name="spacer_12"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -1812,84 +2043,6 @@ </layout> </widget> </item> - <item row="2" column="2"> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string/> - </property> - <property name="pixmap"> - <pixmap resource="resources/ds.qrc">:/ds/ds_open.svg</pixmap> - </property> - <property name="scaledContents"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QLabel" name="label_6"> - <property name="font"> - <font> - <pointsize>15</pointsize> - </font> - </property> - <property name="text"> - <string>Joystick mappings</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item row="1" column="2"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string/> - </property> - <property name="pixmap"> - <pixmap resource="resources/ds.qrc">:/ds/ds_back.svg</pixmap> - </property> - <property name="scaledContents"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="4" column="1" colspan="3"> - <layout class="QHBoxLayout" name="horizontalLayout_12"> - <item> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="btnKeyMapSwitch"> - <property name="text"> - <string>Switch to Keyboard mappings</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_4"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> <item row="3" column="2"> <layout class="QHBoxLayout" name="layout_SelectStart_2"> <property name="spacing"> @@ -1898,7 +2051,7 @@ <item alignment="Qt::AlignHCenter"> <widget class="QGroupBox" name="grp_Select_2"> <property name="title"> - <string>Select</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -1920,10 +2073,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_28"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Select</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnJoySelect"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -1947,7 +2110,7 @@ <item alignment="Qt::AlignHCenter"> <widget class="QGroupBox" name="grp_Start_2"> <property name="title"> - <string>Start</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -1969,10 +2132,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_29"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Start</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnJoyStart"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -2001,12 +2174,12 @@ <item> <widget class="QGroupBox" name="grp_R_2"> <property name="title"> - <string>R</string> + <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <layout class="QHBoxLayout" name="horizontalLayout_8"> + <layout class="QVBoxLayout" name="verticalLayout_8"> <property name="spacing"> <number>3</number> </property> @@ -2023,10 +2196,20 @@ <number>3</number> </property> <item> + <widget class="QLabel" name="label_21"> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">R</span></p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="btnJoyR"> <property name="minimumSize"> <size> - <width>72</width> + <width>70</width> <height>0</height> </size> </property> @@ -2063,6 +2246,21 @@ </layout> </widget> </item> + <item row="0" column="2"> + <widget class="QLabel" name="label_6"> + <property name="font"> + <font> + <pointsize>15</pointsize> + </font> + </property> + <property name="text"> + <string>Joystick mappings</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> </layout> </widget> </widget> @@ -2081,6 +2279,55 @@ </widget> </widget> </item> + <item row="7" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Joystick:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cbxJoystick"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis"> + <string><html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html></string> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="lblInstanceNum"> + <property name="text"> + <string>Configuring mappings for instance X</string> + </property> + </widget> + </item> </layout> </widget> <resources> diff --git a/src/frontend/qt_sdl/InputConfig/MapButton.h b/src/frontend/qt_sdl/InputConfig/MapButton.h index a1b1a16..afefed7 100644 --- a/src/frontend/qt_sdl/InputConfig/MapButton.h +++ b/src/frontend/qt_sdl/InputConfig/MapButton.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -163,6 +163,7 @@ public: setCheckable(true); setText(mappingText()); + setFocusPolicy(Qt::StrongFocus); //Fixes binding keys in macOS connect(this, &JoyMapButton::clicked, this, &JoyMapButton::onClick); @@ -245,19 +246,21 @@ protected: Sint16 axisval = SDL_JoystickGetAxis(joy, i); int diff = abs(axisval - axesRest[i]); - if (axesRest[i] < -16384 && axisval >= 0) - { - *mapping = (oldmap & 0xFFFF) | 0x10000 | (2 << 20) | (i << 24); - click(); - return; - } - else if (diff > 16384) + if (diff >= 16384) { - int axistype; - if (axisval > 0) axistype = 0; - else axistype = 1; + if (axesRest[i] < -16384) // Trigger + { + *mapping = (oldmap & 0xFFFF) | 0x10000 | (2 << 20) | (i << 24); + } + else // Analog stick + { + int axistype; + if (axisval > 0) axistype = 0; + else axistype = 1; + + *mapping = (oldmap & 0xFFFF) | 0x10000 | (axistype << 20) | (i << 24); + } - *mapping = (oldmap & 0xFFFF) | 0x10000 | (axistype << 20) | (i << 24); click(); return; } @@ -352,4 +355,4 @@ private: int axesRest[16]; }; -#endif // MAPBUTTON_H
\ No newline at end of file +#endif // MAPBUTTON_H diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp index 788e596..7c5eae6 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, WaluigiWare64 + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.h b/src/frontend/qt_sdl/InterfaceSettingsDialog.h index ae21e3f..114aa04 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.h +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura, WaluigiWare64 + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/LAN_PCap.cpp b/src/frontend/qt_sdl/LAN_PCap.cpp index ed3eee9..86c218f 100644 --- a/src/frontend/qt_sdl/LAN_PCap.cpp +++ b/src/frontend/qt_sdl/LAN_PCap.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -114,6 +114,12 @@ bool TryLoadPCap(void* lib) bool Init(bool open_adapter) { + PCapAdapter = NULL; + PacketLen = 0; + RXNum = 0; + + NumAdapters = 0; + // TODO: how to deal with cases where an adapter is unplugged or changes config?? if (!PCapLib) { @@ -142,12 +148,6 @@ bool Init(bool open_adapter) } } - PCapAdapter = NULL; - PacketLen = 0; - RXNum = 0; - - NumAdapters = 0; - char errbuf[PCAP_ERRBUF_SIZE]; int ret; @@ -318,7 +318,7 @@ bool Init(bool open_adapter) PCapAdapterData = &Adapters[0]; for (int i = 0; i < NumAdapters; i++) { - if (!strncmp(Adapters[i].DeviceName, Config::LANDevice, 128)) + if (!strncmp(Adapters[i].DeviceName, Config::LANDevice.c_str(), 128)) PCapAdapterData = &Adapters[i]; } diff --git a/src/frontend/qt_sdl/LAN_PCap.h b/src/frontend/qt_sdl/LAN_PCap.h index 8e9ad9f..610c0ae 100644 --- a/src/frontend/qt_sdl/LAN_PCap.h +++ b/src/frontend/qt_sdl/LAN_PCap.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/LAN_Socket.cpp b/src/frontend/qt_sdl/LAN_Socket.cpp index 83ddd99..6c00b58 100644 --- a/src/frontend/qt_sdl/LAN_Socket.cpp +++ b/src/frontend/qt_sdl/LAN_Socket.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/LAN_Socket.h b/src/frontend/qt_sdl/LAN_Socket.h index c6afc08..2073d1b 100644 --- a/src/frontend/qt_sdl/LAN_Socket.h +++ b/src/frontend/qt_sdl/LAN_Socket.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/LocalMP.cpp b/src/frontend/qt_sdl/LocalMP.cpp new file mode 100644 index 0000000..fb7ef7a --- /dev/null +++ b/src/frontend/qt_sdl/LocalMP.cpp @@ -0,0 +1,634 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef __WIN32__ + #include <windows.h> +#else + #include <fcntl.h> + #include <semaphore.h> + #include <time.h> + #ifdef __APPLE__ + #include "sem_timedwait.h" + #endif +#endif + +#include <string> +#include <QSharedMemory> + +#include "Config.h" +#include "LocalMP.h" + + +namespace LocalMP +{ + +u32 MPUniqueID; +u8 PacketBuffer[2048]; + +struct MPQueueHeader +{ + u16 NumInstances; + u16 InstanceBitmask; // bitmask of all instances present + u16 ConnectedBitmask; // bitmask of which instances are ready to send/receive packets + u32 PacketWriteOffset; + u32 ReplyWriteOffset; + u16 MPHostInstanceID; // instance ID from which the last CMD frame was sent + u16 MPReplyBitmask; // bitmask of which clients replied in time +}; + +struct MPPacketHeader +{ + u32 Magic; + u32 SenderID; + u32 Type; // 0=regular 1=CMD 2=reply 3=ack + u32 Length; + u64 Timestamp; +}; + +struct MPSync +{ + u32 Magic; + u32 SenderID; + u16 ClientMask; + u16 Type; + u64 Timestamp; +}; + +QSharedMemory* MPQueue; +int InstanceID; +u32 PacketReadOffset; +u32 ReplyReadOffset; + +const u32 kQueueSize = 0x20000; +const u32 kMaxFrameSize = 0x800; +const u32 kPacketStart = sizeof(MPQueueHeader); +const u32 kReplyStart = kQueueSize / 2; +const u32 kPacketEnd = kReplyStart; +const u32 kReplyEnd = kQueueSize; + +int RecvTimeout; + +int LastHostID; + + +// we need to come up with our own abstraction layer for named semaphores +// because QSystemSemaphore doesn't support waiting with a timeout +// and, as such, is unsuitable to our needs + +#ifdef __WIN32__ + +bool SemInited[32]; +HANDLE SemPool[32]; + +void SemPoolInit() +{ + for (int i = 0; i < 32; i++) + { + SemPool[i] = INVALID_HANDLE_VALUE; + SemInited[i] = false; + } +} + +void SemDeinit(int num); + +void SemPoolDeinit() +{ + for (int i = 0; i < 32; i++) + SemDeinit(i); +} + +bool SemInit(int num) +{ + if (SemInited[num]) + return true; + + char semname[64]; + sprintf(semname, "Local\\melonNIFI_Sem%02d", num); + + HANDLE sem = CreateSemaphore(nullptr, 0, 64, semname); + SemPool[num] = sem; + SemInited[num] = true; + return sem != INVALID_HANDLE_VALUE; +} + +void SemDeinit(int num) +{ + if (SemPool[num] != INVALID_HANDLE_VALUE) + { + CloseHandle(SemPool[num]); + SemPool[num] = INVALID_HANDLE_VALUE; + } + + SemInited[num] = false; +} + +bool SemPost(int num) +{ + SemInit(num); + return ReleaseSemaphore(SemPool[num], 1, nullptr) != 0; +} + +bool SemWait(int num, int timeout) +{ + return WaitForSingleObject(SemPool[num], timeout) == WAIT_OBJECT_0; +} + +void SemReset(int num) +{ + while (WaitForSingleObject(SemPool[num], 0) == WAIT_OBJECT_0); +} + +#else + +bool SemInited[32]; +sem_t* SemPool[32]; + +void SemPoolInit() +{ + for (int i = 0; i < 32; i++) + { + SemPool[i] = SEM_FAILED; + SemInited[i] = false; + } +} + +void SemDeinit(int num); + +void SemPoolDeinit() +{ + for (int i = 0; i < 32; i++) + SemDeinit(i); +} + +bool SemInit(int num) +{ + if (SemInited[num]) + return true; + + char semname[64]; + sprintf(semname, "/melonNIFI_Sem%02d", num); + + sem_t* sem = sem_open(semname, O_CREAT, 0644, 0); + SemPool[num] = sem; + SemInited[num] = true; + return sem != SEM_FAILED; +} + +void SemDeinit(int num) +{ + if (SemPool[num] != SEM_FAILED) + { + sem_close(SemPool[num]); + SemPool[num] = SEM_FAILED; + } + + SemInited[num] = false; +} + +bool SemPost(int num) +{ + SemInit(num); + return sem_post(SemPool[num]) == 0; +} + +bool SemWait(int num, int timeout) +{ + if (!timeout) + return sem_trywait(SemPool[num]) == 0; + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += timeout * 1000000; + long sec = ts.tv_nsec / 1000000000; + ts.tv_nsec -= sec * 1000000000; + ts.tv_sec += sec; + + return sem_timedwait(SemPool[num], &ts) == 0; +} + +void SemReset(int num) +{ + while (sem_trywait(SemPool[num]) == 0); +} + +#endif + + +bool Init() +{ + MPQueue = new QSharedMemory("melonNIFI"); + + if (!MPQueue->attach()) + { + printf("MP sharedmem doesn't exist. creating\n"); + if (!MPQueue->create(kQueueSize)) + { + printf("MP sharedmem create failed :(\n"); + return false; + } + + MPQueue->lock(); + memset(MPQueue->data(), 0, MPQueue->size()); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + header->PacketWriteOffset = kPacketStart; + header->ReplyWriteOffset = kReplyStart; + MPQueue->unlock(); + } + + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + + u16 mask = header->InstanceBitmask; + for (int i = 0; i < 16; i++) + { + if (!(mask & (1<<i))) + { + InstanceID = i; + header->InstanceBitmask |= (1<<i); + //header->ConnectedBitmask |= (1 << i); + break; + } + } + header->NumInstances++; + + PacketReadOffset = header->PacketWriteOffset; + ReplyReadOffset = header->ReplyWriteOffset; + + MPQueue->unlock(); + + // prepare semaphores + // semaphores 0-15: regular frames; semaphore I is posted when instance I needs to process a new frame + // semaphores 16-31: MP replies; semaphore I is posted when instance I needs to process a new MP reply + + SemPoolInit(); + SemInit(InstanceID); + SemInit(16+InstanceID); + + LastHostID = -1; + + printf("MP comm init OK, instance ID %d\n", InstanceID); + + RecvTimeout = 25; + + return true; +} + +void DeInit() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + header->ConnectedBitmask &= ~(1 << InstanceID); + header->InstanceBitmask &= ~(1 << InstanceID); + header->NumInstances--; + MPQueue->unlock(); + + SemPoolDeinit(); + + MPQueue->detach(); + delete MPQueue; +} + +void SetRecvTimeout(int timeout) +{ + RecvTimeout = timeout; +} + +void Begin() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + PacketReadOffset = header->PacketWriteOffset; + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(InstanceID); + SemReset(16+InstanceID); + header->ConnectedBitmask |= (1 << InstanceID); + MPQueue->unlock(); +} + +void End() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + //SemReset(InstanceID); + //SemReset(16+InstanceID); + header->ConnectedBitmask &= ~(1 << InstanceID); + MPQueue->unlock(); +} + +void FIFORead(int fifo, void* buf, int len) +{ + u8* data = (u8*)MPQueue->data(); + + u32 offset, start, end; + if (fifo == 0) + { + offset = PacketReadOffset; + start = kPacketStart; + end = kPacketEnd; + } + else + { + offset = ReplyReadOffset; + start = kReplyStart; + end = kReplyEnd; + } + + if ((offset + len) >= end) + { + u32 part1 = end - offset; + memcpy(buf, &data[offset], part1); + memcpy(&((u8*)buf)[part1], &data[start], len - part1); + offset = start + len - part1; + } + else + { + memcpy(buf, &data[offset], len); + offset += len; + } + + if (fifo == 0) PacketReadOffset = offset; + else ReplyReadOffset = offset; +} + +void FIFOWrite(int fifo, void* buf, int len) +{ + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + u32 offset, start, end; + if (fifo == 0) + { + offset = header->PacketWriteOffset; + start = kPacketStart; + end = kPacketEnd; + } + else + { + offset = header->ReplyWriteOffset; + start = kReplyStart; + end = kReplyEnd; + } + + if ((offset + len) >= end) + { + u32 part1 = end - offset; + memcpy(&data[offset], buf, part1); + memcpy(&data[start], &((u8*)buf)[part1], len - part1); + offset = start + len - part1; + } + else + { + memcpy(&data[offset], buf, len); + offset += len; + } + + if (fifo == 0) header->PacketWriteOffset = offset; + else header->ReplyWriteOffset = offset; +} + +int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) +{ + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + u16 mask = header->ConnectedBitmask; + + // TODO: check if the FIFO is full! + + MPPacketHeader pktheader; + pktheader.Magic = 0x4946494E; + pktheader.SenderID = InstanceID; + pktheader.Type = type; + pktheader.Length = len; + pktheader.Timestamp = timestamp; + + type &= 0xFFFF; + int nfifo = (type == 2) ? 1 : 0; + FIFOWrite(nfifo, &pktheader, sizeof(pktheader)); + if (len) + FIFOWrite(nfifo, packet, len); + + if (type == 1) + { + // NOTE: this is not guarded against, say, multiple multiplay games happening on the same machine + // we would need to pass the packet's SenderID through the wifi module for that + header->MPHostInstanceID = InstanceID; + header->MPReplyBitmask = 0; + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(16 + InstanceID); + } + else if (type == 2) + { + header->MPReplyBitmask |= (1 << InstanceID); + } + + MPQueue->unlock(); + + if (type == 2) + { + SemPost(16 + header->MPHostInstanceID); + } + else + { + for (int i = 0; i < 16; i++) + { + if (mask & (1<<i)) + SemPost(i); + } + } + + return len; +} + +int RecvPacketGeneric(u8* packet, bool block, u64* timestamp) +{ + for (;;) + { + if (!SemWait(InstanceID, block ? RecvTimeout : 0)) + { + return 0; + } + + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + MPPacketHeader pktheader; + FIFORead(0, &pktheader, sizeof(pktheader)); + + if (pktheader.Magic != 0x4946494E) + { + printf("PACKET FIFO OVERFLOW\n"); + PacketReadOffset = header->PacketWriteOffset; + SemReset(InstanceID); + MPQueue->unlock(); + return 0; + } + + if (pktheader.SenderID == InstanceID) + { + // skip this packet + PacketReadOffset += pktheader.Length; + if (PacketReadOffset >= kPacketEnd) + PacketReadOffset += kPacketStart - kPacketEnd; + + MPQueue->unlock(); + continue; + } + + if (pktheader.Length) + { + FIFORead(0, packet, pktheader.Length); + + if (pktheader.Type == 1) + LastHostID = pktheader.SenderID; + } + + if (timestamp) *timestamp = pktheader.Timestamp; + MPQueue->unlock(); + return pktheader.Length; + } +} + +int SendPacket(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(0, packet, len, timestamp); +} + +int RecvPacket(u8* packet, u64* timestamp) +{ + return RecvPacketGeneric(packet, false, timestamp); +} + + +int SendCmd(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(1, packet, len, timestamp); +} + +int SendReply(u8* packet, int len, u64 timestamp, u16 aid) +{ + return SendPacketGeneric(2 | (aid<<16), packet, len, timestamp); +} + +int SendAck(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(3, packet, len, timestamp); +} + +int RecvHostPacket(u8* packet, u64* timestamp) +{ + if (LastHostID != -1) + { + // check if the host is still connected + + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + u16 curinstmask = header->ConnectedBitmask; + MPQueue->unlock(); + + if (!(curinstmask & (1 << LastHostID))) + return -1; + } + + return RecvPacketGeneric(packet, true, timestamp); +} + +u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask) +{ + u16 ret = 0; + u16 myinstmask = (1 << InstanceID); + u16 curinstmask; + + { + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + curinstmask = header->ConnectedBitmask; + MPQueue->unlock(); + } + + // if all clients have left: return early + if ((myinstmask & curinstmask) == curinstmask) + return 0; + + for (;;) + { + if (!SemWait(16+InstanceID, RecvTimeout)) + { + // no more replies available + return ret; + } + + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + MPPacketHeader pktheader; + FIFORead(1, &pktheader, sizeof(pktheader)); + + if (pktheader.Magic != 0x4946494E) + { + printf("REPLY FIFO OVERFLOW\n"); + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(16+InstanceID); + MPQueue->unlock(); + return 0; + } + + if ((pktheader.SenderID == InstanceID) || // packet we sent out (shouldn't happen, but hey) + (pktheader.Timestamp < (timestamp - 32))) // stale packet + { + // skip this packet + ReplyReadOffset += pktheader.Length; + if (ReplyReadOffset >= kReplyEnd) + ReplyReadOffset += kReplyStart - kReplyEnd; + + MPQueue->unlock(); + continue; + } + + if (pktheader.Length) + { + u32 aid = (pktheader.Type >> 16); + FIFORead(1, &packets[(aid-1)*1024], pktheader.Length); + ret |= (1 << aid); + } + + myinstmask |= (1 << pktheader.SenderID); + if (((myinstmask & curinstmask) == curinstmask) || + ((ret & aidmask) == aidmask)) + { + // all the clients have sent their reply + + MPQueue->unlock(); + return ret; + } + + MPQueue->unlock(); + } +} + +} + diff --git a/src/NDSCart_SRAMManager.h b/src/frontend/qt_sdl/LocalMP.h index 89b65ce..51dfcb9 100644 --- a/src/NDSCart_SRAMManager.h +++ b/src/frontend/qt_sdl/LocalMP.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -16,24 +16,30 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#ifndef NDSCART_SRAMMANAGER_H -#define NDSCART_SRAMMANAGER_H +#ifndef LOCALMP_H +#define LOCALMP_H #include "types.h" -namespace NDSCart_SRAMManager +namespace LocalMP { - extern u32 SecondaryBufferLength; - bool Init(); - void DeInit(); +bool Init(); +void DeInit(); - void Setup(const char* path, u8* buffer, u32 length); - void RequestFlush(); +void SetRecvTimeout(int timeout); + +void Begin(); +void End(); + +int SendPacket(u8* data, int len, u64 timestamp); +int RecvPacket(u8* data, u64* timestamp); +int SendCmd(u8* data, int len, u64 timestamp); +int SendReply(u8* data, int len, u64 timestamp, u16 aid); +int SendAck(u8* data, int len, u64 timestamp); +int RecvHostPacket(u8* data, u64* timestamp); +u16 RecvReplies(u8* data, u64 timestamp, u16 aidmask); - bool NeedsFlush(); - void FlushSecondaryBuffer(u8* dst = NULL, s32 dstLength = 0); - void UpdateBuffer(u8* src, s32 srcLength); } -#endif // NDSCART_SRAMMANAGER_H
\ No newline at end of file +#endif // LOCALMP_H diff --git a/src/frontend/qt_sdl/MPSettingsDialog.cpp b/src/frontend/qt_sdl/MPSettingsDialog.cpp new file mode 100644 index 0000000..e311422 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.cpp @@ -0,0 +1,73 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> +#include <QMessageBox> + +#include "types.h" +#include "Platform.h" +#include "Config.h" + +#include "LAN_Socket.h" +#include "LAN_PCap.h" +#include "Wifi.h" + +#include "MPSettingsDialog.h" +#include "ui_MPSettingsDialog.h" + + +MPSettingsDialog* MPSettingsDialog::currentDlg = nullptr; + +extern bool RunningSomething; + + +MPSettingsDialog::MPSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MPSettingsDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + grpAudioMode = new QButtonGroup(this); + grpAudioMode->addButton(ui->rbAudioAll, 0); + grpAudioMode->addButton(ui->rbAudioOneOnly, 1); + grpAudioMode->addButton(ui->rbAudioActiveOnly, 2); + grpAudioMode->button(Config::MPAudioMode)->setChecked(true); + + ui->sbReceiveTimeout->setValue(Config::MPRecvTimeout); +} + +MPSettingsDialog::~MPSettingsDialog() +{ + delete ui; +} + +void MPSettingsDialog::done(int r) +{ + if (r == QDialog::Accepted) + { + Config::MPAudioMode = grpAudioMode->checkedId(); + Config::MPRecvTimeout = ui->sbReceiveTimeout->value(); + + Config::Save(); + } + + QDialog::done(r); + + closeDlg(); +} + +// diff --git a/src/frontend/qt_sdl/MPSettingsDialog.h b/src/frontend/qt_sdl/MPSettingsDialog.h new file mode 100644 index 0000000..fe917e8 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.h @@ -0,0 +1,65 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef MPSETTINGSDIALOG_H +#define MPSETTINGSDIALOG_H + +#include <QDialog> +#include <QButtonGroup> + +namespace Ui { class MPSettingsDialog; } +class MPSettingsDialog; + +class MPSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MPSettingsDialog(QWidget* parent); + ~MPSettingsDialog(); + + static MPSettingsDialog* currentDlg; + static MPSettingsDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + currentDlg = new MPSettingsDialog(parent); + currentDlg->open(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + } + +private slots: + void done(int r); + + // + +private: + Ui::MPSettingsDialog* ui; + + QButtonGroup* grpAudioMode; +}; + +#endif // MPSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/MPSettingsDialog.ui b/src/frontend/qt_sdl/MPSettingsDialog.ui new file mode 100644 index 0000000..bce0fc9 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.ui @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MPSettingsDialog</class> + <widget class="QDialog" name="MPSettingsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>466</width> + <height>202</height> + </rect> + </property> + <property name="windowTitle"> + <string>Multiplayer settings - melonDS</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Audio output</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="0"> + <widget class="QRadioButton" name="rbAudioOneOnly"> + <property name="text"> + <string>Instance 1 only</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QRadioButton" name="rbAudioAll"> + <property name="text"> + <string>All instances</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QRadioButton" name="rbAudioActiveOnly"> + <property name="text"> + <string>Active instance only</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Network</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1"> + <widget class="QSpinBox" name="sbReceiveTimeout"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="maximum"> + <number>1000</number> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Data reception timeout: </string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>milliseconds</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>MPSettingsDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>MPSettingsDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/frontend/qt_sdl/OSD.cpp b/src/frontend/qt_sdl/OSD.cpp index d9f75fd..6f060a9 100644 --- a/src/frontend/qt_sdl/OSD.cpp +++ b/src/frontend/qt_sdl/OSD.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -146,7 +146,7 @@ void LayoutText(const char* text, u32* width, u32* height, int* breaks) u32 w = 0; u32 h = 14; u32 totalw = 0; - u32 maxw = mainWindow->panel->width() - (kOSDMargin*2); + u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2); int lastbreak = -1; int numbrk = 0; u16* ptr; @@ -236,7 +236,7 @@ void RenderText(u32 color, const char* text, Item* item) memset(item->Bitmap, 0, w*h*sizeof(u32)); u32 x = 0, y = 1; - u32 maxw = mainWindow->panel->width() - (kOSDMargin*2); + u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2); int curline = 0; u16* ptr; diff --git a/src/frontend/qt_sdl/OSD.h b/src/frontend/qt_sdl/OSD.h index 8fbfc7b..d624fc6 100644 --- a/src/frontend/qt_sdl/OSD.h +++ b/src/frontend/qt_sdl/OSD.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/OSD_shaders.h b/src/frontend/qt_sdl/OSD_shaders.h index 3ff51a9..e224fd1 100644 --- a/src/frontend/qt_sdl/OSD_shaders.h +++ b/src/frontend/qt_sdl/OSD_shaders.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp new file mode 100644 index 0000000..286032e --- /dev/null +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -0,0 +1,126 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> +#include <QFileDialog> +#include <QMessageBox> + +#include "types.h" +#include "Config.h" +#include "Platform.h" + +#include "PathSettingsDialog.h" +#include "ui_PathSettingsDialog.h" + + +PathSettingsDialog* PathSettingsDialog::currentDlg = nullptr; + +extern std::string EmuDirectory; +extern bool RunningSomething; + +bool PathSettingsDialog::needsReset = false; + + +PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PathSettingsDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + ui->txtSaveFilePath->setText(QString::fromStdString(Config::SaveFilePath)); + ui->txtSavestatePath->setText(QString::fromStdString(Config::SavestatePath)); + ui->txtCheatFilePath->setText(QString::fromStdString(Config::CheatFilePath)); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring paths for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); +} + +PathSettingsDialog::~PathSettingsDialog() +{ + delete ui; +} + +void PathSettingsDialog::done(int r) +{ + needsReset = false; + + if (r == QDialog::Accepted) + { + std::string saveFilePath = ui->txtSaveFilePath->text().toStdString(); + std::string savestatePath = ui->txtSavestatePath->text().toStdString(); + std::string cheatFilePath = ui->txtCheatFilePath->text().toStdString(); + + if ( saveFilePath != Config::SaveFilePath + || savestatePath != Config::SavestatePath + || cheatFilePath != Config::CheatFilePath) + { + if (RunningSomething + && QMessageBox::warning(this, "Reset necessary to apply changes", + "The emulation will be reset for the changes to take place.", + QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) + return; + + Config::SaveFilePath = saveFilePath; + Config::SavestatePath = savestatePath; + Config::CheatFilePath = cheatFilePath; + + Config::Save(); + + needsReset = true; + } + } + + QDialog::done(r); + + closeDlg(); +} + +void PathSettingsDialog::on_btnSaveFileBrowse_clicked() +{ + QString dir = QFileDialog::getExistingDirectory(this, + "Select save files path...", + QString::fromStdString(EmuDirectory)); + + if (dir.isEmpty()) return; + + ui->txtSaveFilePath->setText(dir); +} + +void PathSettingsDialog::on_btnSavestateBrowse_clicked() +{ + QString dir = QFileDialog::getExistingDirectory(this, + "Select savestates path...", + QString::fromStdString(EmuDirectory)); + + if (dir.isEmpty()) return; + + ui->txtSavestatePath->setText(dir); +} + +void PathSettingsDialog::on_btnCheatFileBrowse_clicked() +{ + QString dir = QFileDialog::getExistingDirectory(this, + "Select cheat files path...", + QString::fromStdString(EmuDirectory)); + + if (dir.isEmpty()) return; + + ui->txtCheatFilePath->setText(dir); +} diff --git a/src/frontend/qt_sdl/PathSettingsDialog.h b/src/frontend/qt_sdl/PathSettingsDialog.h new file mode 100644 index 0000000..ef4fd2d --- /dev/null +++ b/src/frontend/qt_sdl/PathSettingsDialog.h @@ -0,0 +1,67 @@ + +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef PATHSETTINGSDIALOG_H +#define PATHSETTINGSDIALOG_H + +#include <QDialog> + +namespace Ui { class PathSettingsDialog; } +class PathSettingsDialog; + +class PathSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PathSettingsDialog(QWidget* parent); + ~PathSettingsDialog(); + + static PathSettingsDialog* currentDlg; + static PathSettingsDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + currentDlg = new PathSettingsDialog(parent); + currentDlg->open(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + } + + static bool needsReset; + +private slots: + void done(int r); + + void on_btnSaveFileBrowse_clicked(); + void on_btnSavestateBrowse_clicked(); + void on_btnCheatFileBrowse_clicked(); + +private: + Ui::PathSettingsDialog* ui; +}; + +#endif // PATHSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/PathSettingsDialog.ui b/src/frontend/qt_sdl/PathSettingsDialog.ui new file mode 100644 index 0000000..295b1c4 --- /dev/null +++ b/src/frontend/qt_sdl/PathSettingsDialog.ui @@ -0,0 +1,156 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PathSettingsDialog</class> + <widget class="QDialog" name="PathSettingsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>439</width> + <height>185</height> + </rect> + </property> + <property name="windowTitle"> + <string>Path settings - melonDS</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Cheat files path:</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QPushButton" name="btnCheatFileBrowse"> + <property name="text"> + <string>Browse...</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Savestates path:</string> + </property> + </widget> + </item> + <item row="5" column="0" colspan="3"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="txtSavestatePath"> + <property name="clearButtonEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="btnSaveFileBrowse"> + <property name="text"> + <string>Browse...</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="txtCheatFilePath"> + <property name="clearButtonEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="6" column="0" colspan="3"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QPushButton" name="btnSavestateBrowse"> + <property name="text"> + <string>Browse...</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Save files path:</string> + </property> + </widget> + </item> + <item row="4" column="0" colspan="3"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Leave a path blank to use the current ROM's path.</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="txtSaveFilePath"> + <property name="clearButtonEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0" colspan="3"> + <widget class="QLabel" name="lblInstanceNum"> + <property name="text"> + <string>Configuring paths for instance X</string> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>txtSaveFilePath</tabstop> + <tabstop>btnSaveFileBrowse</tabstop> + <tabstop>txtSavestatePath</tabstop> + <tabstop>btnSavestateBrowse</tabstop> + <tabstop>txtCheatFilePath</tabstop> + <tabstop>btnCheatFileBrowse</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>PathSettingsDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>PathSettingsDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 812c953..f9eaf42 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -20,28 +20,7 @@ #include <stdlib.h> #include <string.h> -#ifdef __WIN32__ - #define NTDDI_VERSION 0x06000000 // GROSS FUCKING HACK - #include <winsock2.h> - #include <windows.h> - //#include <knownfolders.h> // FUCK THAT SHIT - #include <shlobj.h> - #include <ws2tcpip.h> - #include <io.h> - #define dup _dup - #define socket_t SOCKET - #define sockaddr_t SOCKADDR -#else - #include <unistd.h> - #include <netinet/in.h> - #include <sys/select.h> - #include <sys/socket.h> - - #define socket_t int - #define sockaddr_t struct sockaddr - #define closesocket close -#endif - +#include <string> #include <QStandardPaths> #include <QString> #include <QDir> @@ -49,31 +28,83 @@ #include <QSemaphore> #include <QMutex> #include <QOpenGLContext> +#include <QSharedMemory> #include "Platform.h" #include "Config.h" +#include "ROMManager.h" +#include "CameraManager.h" #include "LAN_Socket.h" #include "LAN_PCap.h" -#include <string> - -#ifndef INVALID_SOCKET - #define INVALID_SOCKET (socket_t)-1 -#endif +#include "LocalMP.h" std::string EmuDirectory; +extern CameraManager* camManager[2]; + void emuStop(); namespace Platform { -socket_t MPSocket; -sockaddr_t MPSendAddr; -u8 PacketBuffer[2048]; +QSharedMemory* IPCBuffer = nullptr; +int IPCInstanceID; + +void IPCInit() +{ + IPCInstanceID = 0; + + IPCBuffer = new QSharedMemory("melonIPC"); + + if (!IPCBuffer->attach()) + { + printf("IPC sharedmem doesn't exist. creating\n"); + if (!IPCBuffer->create(1024)) + { + printf("IPC sharedmem create failed :(\n"); + delete IPCBuffer; + IPCBuffer = nullptr; + return; + } + + IPCBuffer->lock(); + memset(IPCBuffer->data(), 0, IPCBuffer->size()); + IPCBuffer->unlock(); + } + + IPCBuffer->lock(); + u8* data = (u8*)IPCBuffer->data(); + u16 mask = *(u16*)&data[0]; + for (int i = 0; i < 16; i++) + { + if (!(mask & (1<<i))) + { + IPCInstanceID = i; + *(u16*)&data[0] |= (1<<i); + break; + } + } + IPCBuffer->unlock(); + + printf("IPC: instance ID %d\n", IPCInstanceID); +} + +void IPCDeInit() +{ + if (IPCBuffer) + { + IPCBuffer->lock(); + u8* data = (u8*)IPCBuffer->data(); + *(u16*)&data[0] &= ~(1<<IPCInstanceID); + IPCBuffer->unlock(); -#define NIFI_VER 1 + IPCBuffer->detach(); + delete IPCBuffer; + } + IPCBuffer = nullptr; +} void Init(int argc, char** argv) @@ -109,10 +140,13 @@ void Init(int argc, char** argv) confdir = config.absolutePath() + "/melonDS/"; EmuDirectory = confdir.toStdString(); #endif + + IPCInit(); } void DeInit() { + IPCDeInit(); } @@ -122,6 +156,22 @@ void StopEmu() } +int InstanceID() +{ + return IPCInstanceID; +} + +std::string InstanceFileSuffix() +{ + int inst = IPCInstanceID; + if (inst == 0) return ""; + + char suffix[16] = {0}; + snprintf(suffix, 15, ".%d", inst+1); + return suffix; +} + + int GetConfigInt(ConfigEntry entry) { const int imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; @@ -168,7 +218,6 @@ bool GetConfigBool(ConfigEntry entry) case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0; case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0; - case Firm_RandomizeMAC: return Config::RandomizeMAC != 0; case Firm_OverrideSettings: return Config::FirmwareOverrideSettings != 0; } @@ -207,7 +256,7 @@ bool GetConfigArray(ConfigEntry entry, void* data) { case Firm_MAC: { - char* mac_in = Config::FirmwareMAC; + std::string& mac_in = Config::FirmwareMAC; u8* mac_out = (u8*)data; int o = 0; @@ -371,140 +420,80 @@ bool Mutex_TryLock(Mutex* mutex) return ((QMutex*) mutex)->try_lock(); } - -bool MP_Init() +void Sleep(u64 usecs) { - int opt_true = 1; - int res; + QThread::usleep(usecs); +} -#ifdef __WIN32__ - WSADATA wsadata; - if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) - { - return false; - } -#endif // __WIN32__ - MPSocket = socket(AF_INET, SOCK_DGRAM, 0); - if (MPSocket < 0) - { - return false; - } - - res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } +void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) +{ + if (ROMManager::NDSSave) + ROMManager::NDSSave->RequestFlush(savedata, savelen, writeoffset, writelen); +} - sockaddr_t saddr; - saddr.sa_family = AF_INET; - *(u32*)&saddr.sa_data[2] = htonl(Config::SocketBindAnyAddr ? INADDR_ANY : INADDR_LOOPBACK); - *(u16*)&saddr.sa_data[0] = htons(7064); - res = bind(MPSocket, &saddr, sizeof(sockaddr_t)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } +void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) +{ + if (ROMManager::GBASave) + ROMManager::GBASave->RequestFlush(savedata, savelen, writeoffset, writelen); +} - res = setsockopt(MPSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } - MPSendAddr.sa_family = AF_INET; - *(u32*)&MPSendAddr.sa_data[2] = htonl(INADDR_BROADCAST); - *(u16*)&MPSendAddr.sa_data[0] = htons(7064); - return true; +bool MP_Init() +{ + return LocalMP::Init(); } void MP_DeInit() { - if (MPSocket >= 0) - closesocket(MPSocket); - -#ifdef __WIN32__ - WSACleanup(); -#endif // __WIN32__ + return LocalMP::DeInit(); } -int MP_SendPacket(u8* data, int len) +void MP_Begin() { - if (MPSocket < 0) - return 0; - - if (len > 2048-8) - { - printf("MP_SendPacket: error: packet too long (%d)\n", len); - return 0; - } - - *(u32*)&PacketBuffer[0] = htonl(0x4946494E); // NIFI - PacketBuffer[4] = NIFI_VER; - PacketBuffer[5] = 0; - *(u16*)&PacketBuffer[6] = htons(len); - memcpy(&PacketBuffer[8], data, len); - - int slen = sendto(MPSocket, (const char*)PacketBuffer, len+8, 0, &MPSendAddr, sizeof(sockaddr_t)); - if (slen < 8) return 0; - return slen - 8; + return LocalMP::Begin(); } -int MP_RecvPacket(u8* data, bool block) +void MP_End() { - if (MPSocket < 0) - return 0; - - fd_set fd; - struct timeval tv; - - FD_ZERO(&fd); - FD_SET(MPSocket, &fd); - tv.tv_sec = 0; - tv.tv_usec = block ? 5000 : 0; - - if (!select(MPSocket+1, &fd, 0, 0, &tv)) - { - return 0; - } + return LocalMP::End(); +} - sockaddr_t fromAddr; - socklen_t fromLen = sizeof(sockaddr_t); - int rlen = recvfrom(MPSocket, (char*)PacketBuffer, 2048, 0, &fromAddr, &fromLen); - if (rlen < 8+24) - { - return 0; - } - rlen -= 8; +int MP_SendPacket(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendPacket(data, len, timestamp); +} - if (ntohl(*(u32*)&PacketBuffer[0]) != 0x4946494E) - { - return 0; - } +int MP_RecvPacket(u8* data, u64* timestamp) +{ + return LocalMP::RecvPacket(data, timestamp); +} - if (PacketBuffer[4] != NIFI_VER) - { - return 0; - } +int MP_SendCmd(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendCmd(data, len, timestamp); +} - if (ntohs(*(u16*)&PacketBuffer[6]) != rlen) - { - return 0; - } +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid) +{ + return LocalMP::SendReply(data, len, timestamp, aid); +} - memcpy(data, &PacketBuffer[8], rlen); - return rlen; +int MP_SendAck(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendAck(data, len, timestamp); } +int MP_RecvHostPacket(u8* data, u64* timestamp) +{ + return LocalMP::RecvHostPacket(data, timestamp); +} +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask) +{ + return LocalMP::RecvReplies(data, timestamp, aidmask); +} bool LAN_Init() { @@ -549,9 +538,20 @@ int LAN_RecvPacket(u8* data) return LAN_Socket::RecvPacket(data); } -void Sleep(u64 usecs) + +void Camera_Start(int num) { - QThread::usleep(usecs); + return camManager[num]->start(); +} + +void Camera_Stop(int num) +{ + return camManager[num]->stop(); +} + +void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv) +{ + return camManager[num]->captureFrame(frame, width, height, yuv); } } diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp new file mode 100644 index 0000000..89f74e5 --- /dev/null +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp @@ -0,0 +1,154 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "PowerManagementDialog.h" +#include "ui_PowerManagementDialog.h" + +#include "SPI.h" +#include "DSi_I2C.h" +#include "NDS.h" +#include "Config.h" +#include "Platform.h" + +#include "types.h" + +#include <QtDebug> + +PowerManagementDialog* PowerManagementDialog::currentDlg = nullptr; + +PowerManagementDialog::PowerManagementDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PowerManagementDialog) +{ + inited = false; + + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + if (NDS::ConsoleType == 1) + { + ui->grpDSBattery->setEnabled(false); + + oldDSiBatteryLevel = DSi_BPTWL::GetBatteryLevel(); + oldDSiBatteryCharging = DSi_BPTWL::GetBatteryCharging(); + } + else + { + ui->grpDSiBattery->setEnabled(false); + + oldDSBatteryLevel = SPI_Powerman::GetBatteryLevelOkay(); + } + + updateDSBatteryLevelControls(); + + ui->cbDSiBatteryCharging->setChecked(DSi_BPTWL::GetBatteryCharging()); + int dsiBatterySliderPos; + switch (DSi_BPTWL::GetBatteryLevel()) + { + case DSi_BPTWL::batteryLevel_AlmostEmpty: dsiBatterySliderPos = 0; break; + case DSi_BPTWL::batteryLevel_Low: dsiBatterySliderPos = 1; break; + case DSi_BPTWL::batteryLevel_Half: dsiBatterySliderPos = 2; break; + case DSi_BPTWL::batteryLevel_ThreeQuarters: dsiBatterySliderPos = 3; break; + case DSi_BPTWL::batteryLevel_Full: dsiBatterySliderPos = 4; break; + } + ui->sliderDSiBatteryLevel->setValue(dsiBatterySliderPos); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Setting battery levels for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); + + inited = true; +} + +PowerManagementDialog::~PowerManagementDialog() +{ + delete ui; +} + +void PowerManagementDialog::done(int r) +{ + if (r == QDialog::Accepted) + { + if (NDS::ConsoleType == 1) + { + Config::DSiBatteryLevel = DSi_BPTWL::GetBatteryLevel(); + Config::DSiBatteryCharging = DSi_BPTWL::GetBatteryCharging(); + } + else + { + Config::DSBatteryLevelOkay = SPI_Powerman::GetBatteryLevelOkay(); + } + } + else + { + if (NDS::ConsoleType == 1) + { + DSi_BPTWL::SetBatteryLevel(oldDSiBatteryLevel); + DSi_BPTWL::SetBatteryCharging(oldDSiBatteryCharging); + } + else + { + SPI_Powerman::SetBatteryLevelOkay(oldDSBatteryLevel); + } + } + + QDialog::done(r); + + closeDlg(); +} + +void PowerManagementDialog::on_rbDSBatteryLow_clicked() +{ + SPI_Powerman::SetBatteryLevelOkay(false); +} + +void PowerManagementDialog::on_rbDSBatteryOkay_clicked() +{ + SPI_Powerman::SetBatteryLevelOkay(true); +} + +void PowerManagementDialog::updateDSBatteryLevelControls() +{ + if (SPI_Powerman::GetBatteryLevelOkay()) + ui->rbDSBatteryOkay->setChecked(true); + else + ui->rbDSBatteryLow->setChecked(true); +} + +void PowerManagementDialog::on_cbDSiBatteryCharging_toggled() +{ + DSi_BPTWL::SetBatteryCharging(ui->cbDSiBatteryCharging->isChecked()); +} + +void PowerManagementDialog::on_sliderDSiBatteryLevel_valueChanged(int value) +{ + if (!inited) return; + + u8 newBatteryLevel; + switch (value) + { + case 0: newBatteryLevel = DSi_BPTWL::batteryLevel_AlmostEmpty; break; + case 1: newBatteryLevel = DSi_BPTWL::batteryLevel_Low; break; + case 2: newBatteryLevel = DSi_BPTWL::batteryLevel_Half; break; + case 3: newBatteryLevel = DSi_BPTWL::batteryLevel_ThreeQuarters; break; + case 4: newBatteryLevel = DSi_BPTWL::batteryLevel_Full; break; + } + DSi_BPTWL::SetBatteryLevel(newBatteryLevel); + updateDSBatteryLevelControls(); +} + diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h new file mode 100644 index 0000000..f335a5e --- /dev/null +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h @@ -0,0 +1,77 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef POWERMANAGEMENTDIALOG_H +#define POWERMANAGEMENTDIALOG_H + +#include <QDialog> +#include <QAbstractButton> + +#include "types.h" + +namespace Ui { class PowerManagementDialog; } +class PowerManagementDialog; + +class PowerManagementDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PowerManagementDialog(QWidget* parent); + ~PowerManagementDialog(); + + static PowerManagementDialog* currentDlg; + static PowerManagementDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + currentDlg = new PowerManagementDialog(parent); + currentDlg->open(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + } + +private slots: + void done(int r); + + void on_rbDSBatteryLow_clicked(); + void on_rbDSBatteryOkay_clicked(); + + void on_cbDSiBatteryCharging_toggled(); + void on_sliderDSiBatteryLevel_valueChanged(int value); + +private: + Ui::PowerManagementDialog* ui; + + bool inited; + bool oldDSBatteryLevel; + u8 oldDSiBatteryLevel; + bool oldDSiBatteryCharging; + + void updateDSBatteryLevelControls(); +}; + +#endif // POWERMANAGEMENTDIALOG_H + diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui new file mode 100644 index 0000000..77af225 --- /dev/null +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui @@ -0,0 +1,274 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PowerManagementDialog</class> + <widget class="QDialog" name="PowerManagementDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>562</width> + <height>288</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Power management - melonDS</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetFixedSize</enum> + </property> + <item row="4" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QGroupBox" name="grpDSiBattery"> + <property name="title"> + <string>DSi Battery</string> + </property> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="1" column="2"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Almost Empty</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" colspan="5"> + <widget class="QCheckBox" name="cbDSiBatteryCharging"> + <property name="text"> + <string>Charging</string> + </property> + </widget> + </item> + <item row="1" column="8"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Full</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_1"> + <property name="text"> + <string>Battery Level</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="resources/battery.qrc">:/dsibattery/dsi_batteryalmostempty.svg</pixmap> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="2" column="4"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="resources/battery.qrc">:/dsibattery/dsi_batterylow.svg</pixmap> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="5"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="resources/battery.qrc">:/dsibattery/dsi_battery2.svg</pixmap> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="2" column="6"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="resources/battery.qrc">:/dsibattery/dsi_battery3.svg</pixmap> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="7"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="resources/battery.qrc">:/dsibattery/dsi_batteryfull.svg</pixmap> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="3" colspan="5"> + <widget class="QSlider" name="sliderDSiBatteryLevel"> + <property name="maximum"> + <number>4</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksBothSides</enum> + </property> + <property name="tickInterval"> + <number>1</number> + </property> + </widget> + </item> + <item row="2" column="0" colspan="3"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="grpDSBattery"> + <property name="title"> + <string>DS Battery</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="1"> + <widget class="QRadioButton" name="rbDSBatteryLow"> + <property name="text"> + <string>Low</string> + </property> + </widget> + </item> + <item row="0" column="0" rowspan="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Battery Level</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QRadioButton" name="rbDSBatteryOkay"> + <property name="text"> + <string>Okay</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="lblInstanceNum"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Configuring settings for instance X</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="resources/battery.qrc"/> + </resources> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>PowerManagementDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>PowerManagementDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/frontend/qt_sdl/PowerManagement/resources/battery.qrc b/src/frontend/qt_sdl/PowerManagement/resources/battery.qrc new file mode 100644 index 0000000..7f9c95b --- /dev/null +++ b/src/frontend/qt_sdl/PowerManagement/resources/battery.qrc @@ -0,0 +1,9 @@ +<RCC> + <qresource prefix="dsibattery"> + <file>dsi_batteryalmostempty.svg</file> + <file>dsi_batterylow.svg</file> + <file>dsi_battery2.svg</file> + <file>dsi_battery3.svg</file> + <file>dsi_batteryfull.svg</file> + </qresource> +</RCC> diff --git a/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery2.svg b/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery2.svg new file mode 100644 index 0000000..e9c4b75 --- /dev/null +++ b/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery2.svg @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="12.236976mm" + height="6.2838535mm" + viewBox="0 0 12.236976 6.283854" + version="1.1" + id="svg5" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + sodipodi:docname="battery2.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview7" + pagecolor="#ff8100" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="1" + inkscape:pagecheckerboard="false" + inkscape:document-units="mm" + showgrid="true" + height="200mm" + showborder="true" + inkscape:showpageshadow="false" + borderlayer="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:zoom="10.24" + inkscape:cx="2.7832031" + inkscape:cy="2.0507813" + inkscape:window-width="1920" + inkscape:window-height="1011" + inkscape:window-x="0" + inkscape:window-y="32" + inkscape:window-maximized="1" + inkscape:current-layer="layer1"> + <inkscape:grid + type="xygrid" + id="grid11" + spacingx="0.26458333" + spacingy="0.26458333" + originx="-91.777345" + originy="-47.128916" /> + </sodipodi:namedview> + <defs + id="defs2" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-91.777344,-47.12891)"> + <rect + style="fill:#000000;fill-rule:evenodd;stroke-width:0.0211535" + id="rect153" + width="1.3229166" + height="2.9765623" + x="91.777344" + y="48.782555" /> + <rect + style="fill:#000000;stroke-width:0.0162915" + id="rect177" + width="10.914062" + height="0.66145831" + x="93.100258" + y="47.12891" /> + <rect + style="fill:#000000;stroke-width:0.0165364" + id="rect179" + width="0.66145831" + height="4.9609375" + x="93.100258" + y="47.790367" /> + <rect + style="fill:#000000;stroke-width:0.0162915" + id="rect181" + width="10.914062" + height="0.66145873" + x="93.100258" + y="52.751305" /> + <rect + style="fill:#000000;stroke-width:0.0165364" + id="rect183" + width="0.66145831" + height="4.9609375" + x="103.35286" + y="47.790367" /> + <rect + style="fill:#ffffff;stroke-width:0.0162585" + id="rect318" + width="9.5911455" + height="0.49609375" + x="93.761719" + y="47.790367" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect408" + width="0.49609372" + height="3.96875" + x="93.761719" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.015706" + id="rect410" + width="9.5911455" + height="0.49609351" + x="93.761719" + y="52.255211" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect412" + width="0.4960939" + height="3.9687498" + x="102.85677" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0224686" + id="rect414" + width="0.66145849" + height="3.9687498" + x="95.911453" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect414-3" + width="0.66145885" + height="3.9687498" + x="98.226562" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect414-6" + width="0.66145819" + height="3.9687498" + x="100.54166" + y="48.286461" /> + <rect + style="fill:#999999;stroke-width:0.0165364" + id="rect1736" + width="1.6536455" + height="3.9687498" + x="94.257812" + y="48.286461" /> + <rect + style="fill:#999999;stroke-width:0.0165364" + id="rect1738" + width="1.6536458" + height="3.96875" + x="96.572914" + y="48.286461" /> + <rect + style="fill:#00ccff;stroke-width:0.0165364" + id="rect1740" + width="1.6536458" + height="3.96875" + x="98.888016" + y="48.286461" /> + <rect + style="fill:#00ccff;stroke-width:0.0165364" + id="rect1742" + width="1.6536458" + height="3.96875" + x="101.20312" + y="48.286461" /> + </g> +</svg> diff --git a/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery3.svg b/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery3.svg new file mode 100644 index 0000000..d464ef3 --- /dev/null +++ b/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery3.svg @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="12.236976mm" + height="6.2838535mm" + viewBox="0 0 12.236976 6.283854" + version="1.1" + id="svg5" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + sodipodi:docname="battery3.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview7" + pagecolor="#ff8100" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="1" + inkscape:pagecheckerboard="false" + inkscape:document-units="mm" + showgrid="true" + height="200mm" + showborder="true" + inkscape:showpageshadow="false" + borderlayer="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:zoom="10.24" + inkscape:cx="2.7832031" + inkscape:cy="2.0507813" + inkscape:window-width="1920" + inkscape:window-height="1011" + inkscape:window-x="0" + inkscape:window-y="32" + inkscape:window-maximized="1" + inkscape:current-layer="layer1"> + <inkscape:grid + type="xygrid" + id="grid11" + spacingx="0.26458333" + spacingy="0.26458333" + originx="-91.777345" + originy="-47.128916" /> + </sodipodi:namedview> + <defs + id="defs2" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-91.777344,-47.12891)"> + <rect + style="fill:#000000;fill-rule:evenodd;stroke-width:0.0211535" + id="rect153" + width="1.3229166" + height="2.9765623" + x="91.777344" + y="48.782555" /> + <rect + style="fill:#000000;stroke-width:0.0162915" + id="rect177" + width="10.914062" + height="0.66145831" + x="93.100258" + y="47.12891" /> + <rect + style="fill:#000000;stroke-width:0.0165364" + id="rect179" + width="0.66145831" + height="4.9609375" + x="93.100258" + y="47.790367" /> + <rect + style="fill:#000000;stroke-width:0.0162915" + id="rect181" + width="10.914062" + height="0.66145873" + x="93.100258" + y="52.751305" /> + <rect + style="fill:#000000;stroke-width:0.0165364" + id="rect183" + width="0.66145831" + height="4.9609375" + x="103.35286" + y="47.790367" /> + <rect + style="fill:#ffffff;stroke-width:0.0162585" + id="rect318" + width="9.5911455" + height="0.49609375" + x="93.761719" + y="47.790367" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect408" + width="0.49609372" + height="3.96875" + x="93.761719" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.015706" + id="rect410" + width="9.5911455" + height="0.49609351" + x="93.761719" + y="52.255211" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect412" + width="0.4960939" + height="3.9687498" + x="102.85677" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0224686" + id="rect414" + width="0.66145849" + height="3.9687498" + x="95.911453" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect414-3" + width="0.66145885" + height="3.9687498" + x="98.226562" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect414-6" + width="0.66145819" + height="3.9687498" + x="100.54166" + y="48.286461" /> + <rect + style="fill:#999999;stroke-width:0.0165364" + id="rect1736" + width="1.6536455" + height="3.9687498" + x="94.257812" + y="48.286461" /> + <rect + style="fill:#00ccff;stroke-width:0.0165364" + id="rect1738" + width="1.6536458" + height="3.96875" + x="96.572914" + y="48.286461" /> + <rect + style="fill:#00ccff;stroke-width:0.0165364" + id="rect1740" + width="1.6536458" + height="3.96875" + x="98.888016" + y="48.286461" /> + <rect + style="fill:#00ccff;stroke-width:0.0165364" + id="rect1742" + width="1.6536458" + height="3.96875" + x="101.20312" + y="48.286461" /> + </g> +</svg> diff --git a/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryalmostempty.svg b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryalmostempty.svg new file mode 100644 index 0000000..4f598fa --- /dev/null +++ b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryalmostempty.svg @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="12.236976mm" + height="6.2838535mm" + viewBox="0 0 12.236976 6.283854" + version="1.1" + id="svg5" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + sodipodi:docname="dsi_batteryalmostempty.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview7" + pagecolor="#ff8100" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="1" + inkscape:pagecheckerboard="false" + inkscape:document-units="mm" + showgrid="true" + height="200mm" + showborder="true" + inkscape:showpageshadow="false" + borderlayer="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:zoom="14.481547" + inkscape:cx="35.389866" + inkscape:cy="16.503762" + inkscape:window-width="1920" + inkscape:window-height="1011" + inkscape:window-x="0" + inkscape:window-y="32" + inkscape:window-maximized="1" + inkscape:current-layer="layer1"> + <inkscape:grid + type="xygrid" + id="grid11" + spacingx="0.26458333" + spacingy="0.26458333" + originx="-91.77735" + originy="-47.128928" /> + </sodipodi:namedview> + <defs + id="defs2" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-91.777344,-47.12891)"> + <rect + style="fill:#ff2a2a;fill-rule:evenodd;stroke-width:0.0211535" + id="rect153" + width="1.3229166" + height="2.9765623" + x="91.777344" + y="48.782555" /> + <rect + style="fill:#ff2a2a;stroke-width:0.0162915" + id="rect177" + width="10.914062" + height="0.66145831" + x="93.100258" + y="47.12891" /> + <rect + style="fill:#ff2a2a;stroke-width:0.0165364" + id="rect179" + width="0.66145831" + height="4.9609375" + x="93.100258" + y="47.790367" /> + <rect + style="fill:#ff2a2a;stroke-width:0.0162915" + id="rect181" + width="10.914062" + height="0.66145873" + x="93.100258" + y="52.751305" /> + <rect + style="fill:#ff2a2a;stroke-width:0.0165364" + id="rect183" + width="0.66145831" + height="4.9609375" + x="103.35286" + y="47.790367" /> + <rect + style="fill:#ffffff;stroke-width:0.0162585" + id="rect318" + width="9.5911455" + height="0.49609375" + x="93.761719" + y="47.790367" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect408" + width="0.49609372" + height="3.96875" + x="93.761719" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.015706" + id="rect410" + width="9.5911455" + height="0.49609351" + x="93.761719" + y="52.255211" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect412" + width="0.4960939" + height="3.9687498" + x="102.85677" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0224686" + id="rect414" + width="0.66145849" + height="3.9687498" + x="95.911453" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect414-3" + width="0.66145885" + height="3.9687498" + x="98.226562" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect414-6" + width="0.66145819" + height="3.9687498" + x="100.54166" + y="48.286461" /> + <rect + style="fill:#999999;stroke-width:0.0165364" + id="rect1736" + width="1.6536455" + height="3.9687498" + x="94.257812" + y="48.286461" /> + <rect + style="fill:#999999;stroke-width:0.0165364" + id="rect1738" + width="1.6536458" + height="3.96875" + x="96.572914" + y="48.286461" /> + <rect + style="fill:#999999;stroke-width:0.0165364" + id="rect1740" + width="1.6536458" + height="3.96875" + x="98.888016" + y="48.286461" /> + <rect + style="fill:#ff2a2a;stroke-width:0.0165364" + id="rect1742" + width="1.6536458" + height="3.96875" + x="101.20312" + y="48.286461" /> + </g> +</svg> diff --git a/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryfull.svg b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryfull.svg new file mode 100644 index 0000000..dbf8499 --- /dev/null +++ b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryfull.svg @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="12.236976mm" + height="6.2838535mm" + viewBox="0 0 12.236976 6.283854" + version="1.1" + id="svg5" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + sodipodi:docname="batteryfull.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview7" + pagecolor="#ff8100" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="1" + inkscape:pagecheckerboard="false" + inkscape:document-units="mm" + showgrid="true" + height="200mm" + showborder="true" + inkscape:showpageshadow="false" + borderlayer="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:zoom="10.24" + inkscape:cx="2.7832031" + inkscape:cy="2.0507813" + inkscape:window-width="1920" + inkscape:window-height="1011" + inkscape:window-x="0" + inkscape:window-y="32" + inkscape:window-maximized="1" + inkscape:current-layer="layer1"> + <inkscape:grid + type="xygrid" + id="grid11" + spacingx="0.26458333" + spacingy="0.26458333" + originx="-91.777345" + originy="-47.128916" /> + </sodipodi:namedview> + <defs + id="defs2" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-91.777344,-47.12891)"> + <rect + style="fill:#000000;fill-rule:evenodd;stroke-width:0.0211535" + id="rect153" + width="1.3229166" + height="2.9765623" + x="91.777344" + y="48.782555" /> + <rect + style="fill:#000000;stroke-width:0.0162915" + id="rect177" + width="10.914062" + height="0.66145831" + x="93.100258" + y="47.12891" /> + <rect + style="fill:#000000;stroke-width:0.0165364" + id="rect179" + width="0.66145831" + height="4.9609375" + x="93.100258" + y="47.790367" /> + <rect + style="fill:#000000;stroke-width:0.0162915" + id="rect181" + width="10.914062" + height="0.66145873" + x="93.100258" + y="52.751305" /> + <rect + style="fill:#000000;stroke-width:0.0165364" + id="rect183" + width="0.66145831" + height="4.9609375" + x="103.35286" + y="47.790367" /> + <rect + style="fill:#ffffff;stroke-width:0.0162585" + id="rect318" + width="9.5911455" + height="0.49609375" + x="93.761719" + y="47.790367" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect408" + width="0.49609372" + height="3.96875" + x="93.761719" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.015706" + id="rect410" + width="9.5911455" + height="0.49609351" + x="93.761719" + y="52.255211" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect412" + width="0.4960939" + height="3.9687498" + x="102.85677" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0224686" + id="rect414" + width="0.66145849" + height="3.9687498" + x="95.911453" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect414-3" + width="0.66145885" + height="3.9687498" + x="98.226562" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect414-6" + width="0.66145819" + height="3.9687498" + x="100.54166" + y="48.286461" /> + <rect + style="fill:#00ccff;stroke-width:0.0165364" + id="rect1736" + width="1.6536455" + height="3.9687498" + x="94.257812" + y="48.286461" /> + <rect + style="fill:#00ccff;stroke-width:0.0165364" + id="rect1738" + width="1.6536458" + height="3.96875" + x="96.572914" + y="48.286461" /> + <rect + style="fill:#00ccff;stroke-width:0.0165364" + id="rect1740" + width="1.6536458" + height="3.96875" + x="98.888016" + y="48.286461" /> + <rect + style="fill:#00ccff;stroke-width:0.0165364" + id="rect1742" + width="1.6536458" + height="3.96875" + x="101.20312" + y="48.286461" /> + </g> +</svg> diff --git a/src/frontend/qt_sdl/PowerManagement/resources/dsi_batterylow.svg b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batterylow.svg new file mode 100644 index 0000000..d337675 --- /dev/null +++ b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batterylow.svg @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="12.236976mm" + height="6.2838535mm" + viewBox="0 0 12.236976 6.283854" + version="1.1" + id="svg5" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + sodipodi:docname="batterylow.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview7" + pagecolor="#ff8100" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="1" + inkscape:pagecheckerboard="false" + inkscape:document-units="mm" + showgrid="true" + height="200mm" + showborder="true" + inkscape:showpageshadow="false" + borderlayer="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:zoom="0.45254834" + inkscape:cx="325.93203" + inkscape:cy="232.01941" + inkscape:window-width="1920" + inkscape:window-height="1011" + inkscape:window-x="0" + inkscape:window-y="32" + inkscape:window-maximized="1" + inkscape:current-layer="layer1"> + <inkscape:grid + type="xygrid" + id="grid11" + spacingx="0.26458333" + spacingy="0.26458333" + originx="-91.777345" + originy="-47.128916" /> + </sodipodi:namedview> + <defs + id="defs2" /> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-91.777344,-47.12891)"> + <rect + style="fill:#000000;fill-rule:evenodd;stroke-width:0.0211535" + id="rect153" + width="1.3229166" + height="2.9765623" + x="91.777344" + y="48.782555" /> + <rect + style="fill:#000000;stroke-width:0.0162915" + id="rect177" + width="10.914062" + height="0.66145831" + x="93.100258" + y="47.12891" /> + <rect + style="fill:#000000;stroke-width:0.0165364" + id="rect179" + width="0.66145831" + height="4.9609375" + x="93.100258" + y="47.790367" /> + <rect + style="fill:#000000;stroke-width:0.0162915" + id="rect181" + width="10.914062" + height="0.66145873" + x="93.100258" + y="52.751305" /> + <rect + style="fill:#000000;stroke-width:0.0165364" + id="rect183" + width="0.66145831" + height="4.9609375" + x="103.35286" + y="47.790367" /> + <rect + style="fill:#ffffff;stroke-width:0.0162585" + id="rect318" + width="9.5911455" + height="0.49609375" + x="93.761719" + y="47.790367" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect408" + width="0.49609372" + height="3.96875" + x="93.761719" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.015706" + id="rect410" + width="9.5911455" + height="0.49609351" + x="93.761719" + y="52.255211" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect412" + width="0.4960939" + height="3.9687498" + x="102.85677" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0224686" + id="rect414" + width="0.66145849" + height="3.9687498" + x="95.911453" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect414-3" + width="0.66145885" + height="3.9687498" + x="98.226562" + y="48.286461" /> + <rect + style="fill:#ffffff;stroke-width:0.0158876" + id="rect414-6" + width="0.66145819" + height="3.9687498" + x="100.54166" + y="48.286461" /> + <rect + style="fill:#999999;stroke-width:0.0165364" + id="rect1736" + width="1.6536455" + height="3.9687498" + x="94.257812" + y="48.286461" /> + <rect + style="fill:#999999;stroke-width:0.0165364" + id="rect1738" + width="1.6536458" + height="3.96875" + x="96.572914" + y="48.286461" /> + <rect + style="fill:#999999;stroke-width:0.0165364" + id="rect1740" + width="1.6536458" + height="3.96875" + x="98.888016" + y="48.286461" /> + <rect + style="fill:#ff2a2a;stroke-width:0.0165364" + id="rect1742" + width="1.6536458" + height="3.96875" + x="101.20312" + y="48.286461" /> + </g> +</svg> diff --git a/src/frontend/qt_sdl/QPathInput.h b/src/frontend/qt_sdl/QPathInput.h index 1cb1f7c..beb618c 100644 --- a/src/frontend/qt_sdl/QPathInput.h +++ b/src/frontend/qt_sdl/QPathInput.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/RAMInfoDialog.cpp b/src/frontend/qt_sdl/RAMInfoDialog.cpp new file mode 100644 index 0000000..b13ff02 --- /dev/null +++ b/src/frontend/qt_sdl/RAMInfoDialog.cpp @@ -0,0 +1,302 @@ +/* + Copyright 2016-2021 Arisotura + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "RAMInfoDialog.h" +#include "ui_RAMInfoDialog.h" + +#include "main.h" + +extern EmuThread* emuThread; + +s32 GetMainRAMValue(const u32& addr, const ramInfo_ByteType& byteType) +{ + switch (byteType) + { + case ramInfo_OneByte: + return *(s8*)(NDS::MainRAM + (addr&NDS::MainRAMMask)); + case ramInfo_TwoBytes: + return *(s16*)(NDS::MainRAM + (addr&NDS::MainRAMMask)); + case ramInfo_FourBytes: + return *(s32*)(NDS::MainRAM + (addr&NDS::MainRAMMask)); + default: + return 0; + } +} + +RAMInfoDialog* RAMInfoDialog::currentDlg = nullptr; + +RAMInfoDialog::RAMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::RAMInfoDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + qRegisterMetaType<QVector<int>>("QVector<int>"); + qRegisterMetaType<u32>("u32"); + qRegisterMetaType<s32>("s32"); + qRegisterMetaType<s16>("s16"); + qRegisterMetaType<s8>("s8"); + + SearchThread = new RAMSearchThread(this); + connect(SearchThread, &RAMSearchThread::SetProgressbarValue, this, &RAMInfoDialog::SetProgressbarValue); + connect(SearchThread, &RAMSearchThread::finished, this, &RAMInfoDialog::OnSearchFinished); + // First search (Show everything in main ram) + SearchThread->Start(ramInfoSTh_SearchAll); + + TableUpdater = new QTimer(this); + TableUpdater->setInterval(100); + connect(TableUpdater, &QTimer::timeout, this, &RAMInfoDialog::ShowRowsInTable); + TableUpdater->start(); +} + +RAMInfoDialog::~RAMInfoDialog() +{ + delete SearchThread; + if (TableUpdater->isActive()) + TableUpdater->stop(); + delete TableUpdater; + delete ui; +} + +void RAMInfoDialog::OnSearchFinished() +{ + SearchThread->wait(); + ui->btnSearch->setEnabled(true); + ui->ramTable->clearContents(); + ui->ramTable->setRowCount(SearchThread->GetResults()->size()); + ui->ramTable->verticalScrollBar()->setSliderPosition(0); + ui->txtFound->setText(QString("Found: %1").arg(SearchThread->GetResults()->size())); +} + +void RAMInfoDialog::ShowRowsInTable() +{ + const u32& scrollValue = ui->ramTable->verticalScrollBar()->sliderPosition(); + std::vector<ramInfo_RowData>* RowDataVector = SearchThread->GetResults(); + + for (u32 row = scrollValue; row < std::min<u32>(scrollValue+25, RowDataVector->size()); row++) + { + ramInfo_RowData& rowData = RowDataVector->at(row); + rowData.Update(SearchThread->GetSearchByteType()); + + if (ui->ramTable->item(row, ramInfo_Address) == nullptr) + { + // A new row + QTableWidgetItem* addressItem = new QTableWidgetItem(QString("%1").arg(rowData.Address, 8, 16)); + QTableWidgetItem* valueItem = new QTableWidgetItem(QString("%1").arg(rowData.Value)); + QTableWidgetItem* previousItem = new QTableWidgetItem(QString("%1").arg(rowData.Previous)); + + addressItem->setFlags(addressItem->flags() & ~Qt::ItemIsEditable); + valueItem->setFlags(valueItem->flags() | Qt::ItemIsEditable); + previousItem->setFlags(previousItem->flags() & ~Qt::ItemIsEditable); + + ui->ramTable->setItem(row, ramInfo_Address, addressItem); + ui->ramTable->setItem(row, ramInfo_Value, valueItem); + ui->ramTable->setItem(row, ramInfo_Previous, previousItem); + } + else + { + // A row that exists + ui->ramTable->item(row, ramInfo_Address)->setText(QString("%1").arg(rowData.Address, 8, 16)); + ui->ramTable->item(row, ramInfo_Value)->setText(QString("%1").arg(rowData.Value)); + ui->ramTable->item(row, ramInfo_Previous)->setText(QString("%1").arg(rowData.Previous)); + if (rowData.Value != rowData.Previous) + ui->ramTable->item(row, ramInfo_Previous)->setForeground(Qt::red); + } + } +} + +void RAMInfoDialog::ClearTableContents() +{ + ui->ramTable->clearContents(); + ui->ramTable->setRowCount(0); +} + +void RAMInfoDialog::SetProgressbarValue(const u32& value) +{ + ui->progressBar->setValue(value); +} + +void RAMInfoDialog::done(int r) +{ + QDialog::done(r); + closeDlg(); +} + +void RAMInfoDialog::on_btnSearch_clicked() +{ + ui->btnSearch->setEnabled(false); + ui->radiobtn1byte->setEnabled(false); + ui->radiobtn2bytes->setEnabled(false); + ui->radiobtn4bytes->setEnabled(false); + + if (ui->txtSearch->text().isEmpty()) + SearchThread->Start(ramInfoSTh_SearchAll); + else + SearchThread->Start(ui->txtSearch->text().toInt()); + + if (!TableUpdater->isActive()) + TableUpdater->start(); +} + +void RAMInfoDialog::on_btnClear_clicked() +{ + SearchThread->Stop(); + TableUpdater->stop(); + + ui->radiobtn1byte->setEnabled(true); + ui->radiobtn2bytes->setEnabled(true); + ui->radiobtn4bytes->setEnabled(true); + + OnSearchFinished(); +} + +void RAMInfoDialog::on_radiobtn1byte_clicked() +{ + SearchThread->SetSearchByteType(ramInfo_OneByte); +} + +void RAMInfoDialog::on_radiobtn2bytes_clicked() +{ + SearchThread->SetSearchByteType(ramInfo_TwoBytes); +} + +void RAMInfoDialog::on_radiobtn4bytes_clicked() +{ + SearchThread->SetSearchByteType(ramInfo_FourBytes); +} + +void RAMInfoDialog::on_ramTable_itemChanged(QTableWidgetItem *item) +{ + ramInfo_RowData& rowData = SearchThread->GetResults()->at(item->row()); + s32 itemValue = item->text().toInt(); + + if (rowData.Value != itemValue) + rowData.SetValue(itemValue); +} + +/** + * RAMSearchThread + */ + +RAMSearchThread::RAMSearchThread(RAMInfoDialog* dialog) : Dialog(dialog) +{ + RowDataVector = new std::vector<ramInfo_RowData>(); +} + +RAMSearchThread::~RAMSearchThread() +{ + Stop(); + if (RowDataVector) + { + delete RowDataVector; + RowDataVector = nullptr; + } +} + +void RAMSearchThread::Start(const s32& searchValue, const ramInfoSTh_SearchMode& searchMode) +{ + SearchValue = searchValue; + SearchMode = searchMode; + start(); +} + +void RAMSearchThread::Start(const ramInfoSTh_SearchMode& searchMode) +{ + SearchMode = searchMode; + start(); +} + +void RAMSearchThread::Stop() +{ + SearchRunning = false; + RowDataVector->clear(); + quit(); + wait(); +} + +void RAMSearchThread::run() +{ + SearchRunning = true; + u32 progress = 0; + + // Pause game running + emuThread->emuPause(); + + // For following search modes below, RowDataVector must be filled. + if (SearchMode == ramInfoSTh_SearchAll || RowDataVector->size() == 0) + { + // First search mode + for (u32 addr = 0x02000000; SearchRunning && addr < 0x02000000+NDS::MainRAMMaxSize; addr += SearchByteType) + { + const s32& value = GetMainRAMValue(addr, SearchByteType); + + RowDataVector->push_back({ addr, value, value }); + + // A solution to prevent to call too many slot. + u32 newProgress = (int)((addr-0x02000000) / (NDS::MainRAMMaxSize-1.0f) * 100); + if (progress < newProgress) + { + progress = newProgress; + emit SetProgressbarValue(progress); + } + } + } + + if (SearchMode == ramInfoSTh_Default) + { + // Next search mode + std::vector<ramInfo_RowData>* newRowDataVector = new std::vector<ramInfo_RowData>(); + for (u32 row = 0; SearchRunning && row < RowDataVector->size(); row++) + { + const u32& addr = RowDataVector->at(row).Address; + const s32& value = GetMainRAMValue(addr, SearchByteType); + + if (SearchValue == value) + newRowDataVector->push_back({ addr, value, value }); + + // A solution to prevent to call too many slot. + u32 newProgress = (int)(row / (RowDataVector->size()-1.0f) * 100); + if (progress < newProgress) + { + progress = newProgress; + emit SetProgressbarValue(progress); + } + } + delete RowDataVector; + RowDataVector = newRowDataVector; + } + + // Unpause game running + emuThread->emuUnpause(); + + SearchRunning = false; +} + +void RAMSearchThread::SetSearchByteType(const ramInfo_ByteType& bytetype) +{ + SearchByteType = bytetype; +} + +ramInfo_ByteType RAMSearchThread::GetSearchByteType() const +{ + return SearchByteType; +} + +std::vector<ramInfo_RowData>* RAMSearchThread::GetResults() +{ + return RowDataVector; +} diff --git a/src/frontend/qt_sdl/RAMInfoDialog.h b/src/frontend/qt_sdl/RAMInfoDialog.h new file mode 100644 index 0000000..f44ae93 --- /dev/null +++ b/src/frontend/qt_sdl/RAMInfoDialog.h @@ -0,0 +1,161 @@ +/* + Copyright 2016-2021 Arisotura + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef RAMINFODIALOG_H +#define RAMINFODIALOG_H + +#include <QDialog> +#include <QTableWidget> +#include <QScrollBar> +#include <QThread> +#include <QTimer> + +#include "types.h" +#include "NDS.h" + +namespace Ui { class RAMInfoDialog; } +class RAMInfoDialog; +class RAMSearchThread; +class RAMUpdateThread; + +enum ramInfo_ByteType +{ + ramInfo_OneByte = 1, + ramInfo_TwoBytes = 2, + ramInfo_FourBytes = 4 +}; + +enum ramInfoSTh_SearchMode +{ + ramInfoSTh_Default, + ramInfoSTh_SearchAll +}; + +enum +{ + ramInfo_Address, + ramInfo_Value, + ramInfo_Previous +}; + +s32 GetMainRAMValue(const u32& addr, const ramInfo_ByteType& byteType); + +struct ramInfo_RowData +{ + u32 Address; + s32 Value; + s32 Previous; + + void Update(const ramInfo_ByteType& byteType) + { + Value = GetMainRAMValue(Address, byteType); + } + + void SetValue(const s32& value) + { + NDS::MainRAM[Address&NDS::MainRAMMask] = (u32)value; + Value = value; + } +}; + +class RAMInfoDialog : public QDialog +{ + Q_OBJECT + +public: + explicit RAMInfoDialog(QWidget* parent); + ~RAMInfoDialog(); + + static RAMInfoDialog* currentDlg; + static RAMInfoDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + currentDlg = new RAMInfoDialog(parent); + currentDlg->show(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + } + + s32 SearchValue = 0; + + void ClearTableContents(); + +private slots: + void done(int r); + + void on_btnSearch_clicked(); + void on_btnClear_clicked(); + void on_radiobtn1byte_clicked(); + void on_radiobtn2bytes_clicked(); + void on_radiobtn4bytes_clicked(); + void on_ramTable_itemChanged(QTableWidgetItem *item); + + void OnSearchFinished(); + void ShowRowsInTable(); + void SetProgressbarValue(const u32& value); + +private: + Ui::RAMInfoDialog* ui; + + RAMSearchThread* SearchThread; + QTimer* TableUpdater; +}; + +class RAMSearchThread : public QThread +{ + Q_OBJECT + +public: + explicit RAMSearchThread(RAMInfoDialog* dialog); + ~RAMSearchThread() override; + + void Start(const s32& searchValue, const ramInfoSTh_SearchMode& searchMode = ramInfoSTh_Default); + void Start(const ramInfoSTh_SearchMode& searchMode); + + void SetSearchByteType(const ramInfo_ByteType& bytetype); + ramInfo_ByteType GetSearchByteType() const; + std::vector<ramInfo_RowData>* GetResults(); + + void Stop(); + +private: + void run(); + + RAMInfoDialog* Dialog; + bool SearchRunning = false; + + ramInfoSTh_SearchMode SearchMode; + s32 SearchValue; + ramInfo_ByteType SearchByteType = ramInfo_OneByte; + std::vector<ramInfo_RowData>* RowDataVector = nullptr; + + void ClearTableContents(); + +signals: + void SetProgressbarValue(const u32& value); +}; + +#endif // RAMINFODIALOG_H diff --git a/src/frontend/qt_sdl/RAMInfoDialog.ui b/src/frontend/qt_sdl/RAMInfoDialog.ui new file mode 100644 index 0000000..46beaa5 --- /dev/null +++ b/src/frontend/qt_sdl/RAMInfoDialog.ui @@ -0,0 +1,237 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>RAMInfoDialog</class> + <widget class="QDialog" name="RAMInfoDialog"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>550</width> + <height>411</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>550</width> + <height>411</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>550</width> + <height>411</height> + </size> + </property> + <property name="windowTitle"> + <string>RAM info - melonDS</string> + </property> + <property name="modal"> + <bool>false</bool> + </property> + <widget class="QGroupBox" name="groupBox"> + <property name="geometry"> + <rect> + <x>340</x> + <y>10</y> + <width>201</width> + <height>111</height> + </rect> + </property> + <property name="title"> + <string>Search</string> + </property> + <widget class="QPushButton" name="btnSearch"> + <property name="geometry"> + <rect> + <x>130</x> + <y>20</y> + <width>61</width> + <height>23</height> + </rect> + </property> + <property name="text"> + <string>Search</string> + </property> + </widget> + <widget class="QLineEdit" name="txtSearch"> + <property name="geometry"> + <rect> + <x>50</x> + <y>20</y> + <width>71</width> + <height>21</height> + </rect> + </property> + <property name="maxLength"> + <number>5</number> + </property> + </widget> + <widget class="QPushButton" name="btnClear"> + <property name="geometry"> + <rect> + <x>120</x> + <y>80</y> + <width>71</width> + <height>23</height> + </rect> + </property> + <property name="text"> + <string>Clear</string> + </property> + </widget> + <widget class="QLabel" name="labelValue"> + <property name="geometry"> + <rect> + <x>10</x> + <y>20</y> + <width>41</width> + <height>21</height> + </rect> + </property> + <property name="text"> + <string>Value:</string> + </property> + </widget> + <widget class="QRadioButton" name="radiobtn1byte"> + <property name="geometry"> + <rect> + <x>10</x> + <y>50</y> + <width>90</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>1byte</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QRadioButton" name="radiobtn2bytes"> + <property name="geometry"> + <rect> + <x>10</x> + <y>70</y> + <width>90</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>2bytes</string> + </property> + </widget> + <widget class="QRadioButton" name="radiobtn4bytes"> + <property name="geometry"> + <rect> + <x>10</x> + <y>90</y> + <width>90</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>4bytes</string> + </property> + </widget> + </widget> + <widget class="QProgressBar" name="progressBar"> + <property name="geometry"> + <rect> + <x>10</x> + <y>380</y> + <width>321</width> + <height>23</height> + </rect> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="value"> + <number>0</number> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="textVisible"> + <bool>true</bool> + </property> + <property name="format"> + <string>%p%</string> + </property> + </widget> + <widget class="QTableWidget" name="ramTable"> + <property name="geometry"> + <rect> + <x>10</x> + <y>30</y> + <width>321</width> + <height>341</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>321</width> + <height>341</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>321</width> + <height>341</height> + </size> + </property> + <property name="editTriggers"> + <set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked</set> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + <attribute name="verticalHeaderMinimumSectionSize"> + <number>16</number> + </attribute> + <attribute name="verticalHeaderDefaultSectionSize"> + <number>16</number> + </attribute> + <column> + <property name="text"> + <string>Address</string> + </property> + </column> + <column> + <property name="text"> + <string>Value</string> + </property> + </column> + <column> + <property name="text"> + <string>Previous</string> + </property> + </column> + </widget> + <widget class="QLabel" name="txtFound"> + <property name="geometry"> + <rect> + <x>10</x> + <y>10</y> + <width>101</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>Found:</string> + </property> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/frontend/qt_sdl/ROMInfoDialog.cpp b/src/frontend/qt_sdl/ROMInfoDialog.cpp index 9166efe..e82ec4b 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.cpp +++ b/src/frontend/qt_sdl/ROMInfoDialog.cpp @@ -1,5 +1,5 @@ /*
- Copyright 2016-2021 Arisotura, WaluigiWare64
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -45,14 +45,14 @@ ROMInfoDialog::ROMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ROMI u32 iconData[32 * 32];
- Frontend::ROMIcon(NDSCart::Banner.Icon, NDSCart::Banner.Palette, iconData);
+ ROMManager::ROMIcon(NDSCart::Banner.Icon, NDSCart::Banner.Palette, iconData);
iconImage = QImage(reinterpret_cast<unsigned char*>(iconData), 32, 32, QImage::Format_ARGB32).copy();
ui->iconImage->setPixmap(QPixmap::fromImage(iconImage));
if (NDSCart::Banner.Version == 0x103)
{
u32 animatedIconData[32 * 32 * 64] = {0};
- Frontend::AnimatedROMIcon(NDSCart::Banner.DSiIcon, NDSCart::Banner.DSiPalette, NDSCart::Banner.DSiSequence, animatedIconData, animatedSequence);
+ ROMManager::AnimatedROMIcon(NDSCart::Banner.DSiIcon, NDSCart::Banner.DSiPalette, NDSCart::Banner.DSiSequence, animatedIconData, animatedSequence);
for (int i = 0; i < 64; i++)
{
@@ -75,12 +75,12 @@ ROMInfoDialog::ROMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ROMI ui->iconTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle));
- ui->japaneseTitle->setText(QString::fromUtf16(NDSCart::Banner.JapaneseTitle, 128));
- ui->englishTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle, 128));
- ui->frenchTitle->setText(QString::fromUtf16(NDSCart::Banner.FrenchTitle, 128));
- ui->germanTitle->setText(QString::fromUtf16(NDSCart::Banner.GermanTitle, 128));
- ui->italianTitle->setText(QString::fromUtf16(NDSCart::Banner.ItalianTitle, 128));
- ui->spanishTitle->setText(QString::fromUtf16(NDSCart::Banner.SpanishTitle, 128));
+ ui->japaneseTitle->setText(QString::fromUtf16(NDSCart::Banner.JapaneseTitle));
+ ui->englishTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle));
+ ui->frenchTitle->setText(QString::fromUtf16(NDSCart::Banner.FrenchTitle));
+ ui->germanTitle->setText(QString::fromUtf16(NDSCart::Banner.GermanTitle));
+ ui->italianTitle->setText(QString::fromUtf16(NDSCart::Banner.ItalianTitle));
+ ui->spanishTitle->setText(QString::fromUtf16(NDSCart::Banner.SpanishTitle));
if (NDSCart::Banner.Version > 1)
ui->chineseTitle->setText(QString::fromUtf16(NDSCart::Banner.ChineseTitle));
@@ -130,7 +130,7 @@ void ROMInfoDialog::on_saveIconButton_clicked() {
QString filename = QFileDialog::getSaveFileName(this,
"Save Icon",
- Config::LastROMFolder,
+ QString::fromStdString(Config::LastROMFolder),
"PNG Images (*.png)");
if (filename.isEmpty())
return;
diff --git a/src/frontend/qt_sdl/ROMInfoDialog.h b/src/frontend/qt_sdl/ROMInfoDialog.h index 5193554..fd036a0 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.h +++ b/src/frontend/qt_sdl/ROMInfoDialog.h @@ -1,5 +1,5 @@ /*
- Copyright 2016-2021 Arisotura, WaluigiWare64
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -25,7 +25,7 @@ #include <QImage>
#include "types.h"
-#include "FrontendUtil.h"
+#include "ROMManager.h"
namespace Ui { class ROMInfoDialog; }
class ROMInfoDialog;
@@ -58,7 +58,7 @@ public: private slots:
void done(int r);
-
+
void on_saveIconButton_clicked();
void iconSetFrame(int frame);
diff --git a/src/frontend/qt_sdl/ROMInfoDialog.ui b/src/frontend/qt_sdl/ROMInfoDialog.ui index 0c65cab..1c9d844 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.ui +++ b/src/frontend/qt_sdl/ROMInfoDialog.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>427</width> - <height>434</height> + <width>559</width> + <height>532</height> </rect> </property> <property name="sizePolicy"> @@ -22,12 +22,6 @@ <layout class="QGridLayout" name="gridLayout"> <item row="2" column="0"> <widget class="QGroupBox" name="titles"> - <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> <property name="title"> <string>Titles</string> </property> @@ -350,12 +344,6 @@ </item> <item row="3" column="1"> <widget class="QGroupBox" name="filesystem"> - <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> <property name="title"> <string>Filesystem</string> </property> @@ -441,12 +429,6 @@ </item> <item row="3" column="0"> <widget class="QGroupBox" name="generalInfo"> - <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> <property name="title"> <string>General info</string> </property> @@ -668,7 +650,7 @@ </layout> </widget> </item> - <item row="0" column="3"> + <item row="0" column="2"> <widget class="QGroupBox" name="dsiIconBox"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> @@ -742,43 +724,11 @@ </layout> </widget> </item> - <item row="0" column="0"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>55</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="4"> + <item row="0" column="3"> <spacer name="horizontalSpacer_3"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="2"> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> </spacer> </item> <item row="1" column="1"> @@ -788,6 +738,13 @@ </property> </widget> </item> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> </layout> </widget> </item> diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp new file mode 100644 index 0000000..716a454 --- /dev/null +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -0,0 +1,875 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> +#include <string.h> + +#include <string> +#include <utility> + +#ifdef ARCHIVE_SUPPORT_ENABLED +#include "ArchiveUtil.h" +#endif +#include "ROMManager.h" +#include "Config.h" +#include "Platform.h" + +#include "NDS.h" +#include "DSi.h" +#include "SPI.h" +#include "DSi_I2C.h" + + +namespace ROMManager +{ + +int CartType = -1; +std::string BaseROMDir = ""; +std::string BaseROMName = ""; +std::string BaseAssetName = ""; + +int GBACartType = -1; +std::string BaseGBAROMDir = ""; +std::string BaseGBAROMName = ""; +std::string BaseGBAAssetName = ""; + +SaveManager* NDSSave = nullptr; +SaveManager* GBASave = nullptr; + +bool SavestateLoaded = false; +std::string PreviousSaveFile = ""; + +ARCodeFile* CheatFile = nullptr; +bool CheatsOn = false; + + +int LastSep(std::string path) +{ + int i = path.length() - 1; + while (i >= 0) + { + if (path[i] == '/' || path[i] == '\\') + return i; + + i--; + } + + return -1; +} + +std::string GetAssetPath(bool gba, std::string configpath, std::string ext, std::string file="") +{ + if (configpath.empty()) + configpath = gba ? BaseGBAROMDir : BaseROMDir; + + if (file.empty()) + { + file = gba ? BaseGBAAssetName : BaseAssetName; + if (file.empty()) + file = "firmware"; + } + + for (;;) + { + int i = configpath.length() - 1; + if (i < 0) break; + if (configpath[i] == '/' || configpath[i] == '\\') + configpath = configpath.substr(0, i); + else + break; + } + + if (!configpath.empty()) + configpath += "/"; + + return configpath + file + ext; +} + + +QString VerifyDSBIOS() +{ + FILE* f; + long len; + + f = Platform::OpenLocalFile(Config::BIOS9Path, "rb"); + if (!f) return "DS ARM9 BIOS was not found or could not be accessed. Check your emu settings."; + + fseek(f, 0, SEEK_END); + len = ftell(f); + if (len != 0x1000) + { + fclose(f); + return "DS ARM9 BIOS is not a valid BIOS dump."; + } + + fclose(f); + + f = Platform::OpenLocalFile(Config::BIOS7Path, "rb"); + if (!f) return "DS ARM7 BIOS was not found or could not be accessed. Check your emu settings."; + + fseek(f, 0, SEEK_END); + len = ftell(f); + if (len != 0x4000) + { + fclose(f); + return "DS ARM7 BIOS is not a valid BIOS dump."; + } + + fclose(f); + + return ""; +} + +QString VerifyDSiBIOS() +{ + FILE* f; + long len; + + // TODO: check the first 32 bytes + + f = Platform::OpenLocalFile(Config::DSiBIOS9Path, "rb"); + if (!f) return "DSi ARM9 BIOS was not found or could not be accessed. Check your emu settings."; + + fseek(f, 0, SEEK_END); + len = ftell(f); + if (len != 0x10000) + { + fclose(f); + return "DSi ARM9 BIOS is not a valid BIOS dump."; + } + + fclose(f); + + f = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb"); + if (!f) return "DSi ARM7 BIOS was not found or could not be accessed. Check your emu settings."; + + fseek(f, 0, SEEK_END); + len = ftell(f); + if (len != 0x10000) + { + fclose(f); + return "DSi ARM7 BIOS is not a valid BIOS dump."; + } + + fclose(f); + + return ""; +} + +QString VerifyDSFirmware() +{ + FILE* f; + long len; + + f = Platform::OpenLocalFile(Config::FirmwarePath, "rb"); + if (!f) return "DS firmware was not found or could not be accessed. Check your emu settings."; + + fseek(f, 0, SEEK_END); + len = ftell(f); + if (len == 0x20000) + { + // 128KB firmware, not bootable + fclose(f); + // TODO report it somehow? detect in core? + return ""; + } + else if (len != 0x40000 && len != 0x80000) + { + fclose(f); + return "DS firmware is not a valid firmware dump."; + } + + fclose(f); + + return ""; +} + +QString VerifyDSiFirmware() +{ + FILE* f; + long len; + + f = Platform::OpenLocalFile(Config::DSiFirmwarePath, "rb"); + if (!f) return "DSi firmware was not found or could not be accessed. Check your emu settings."; + + fseek(f, 0, SEEK_END); + len = ftell(f); + if (len != 0x20000) + { + // not 128KB + // TODO: check whether those work + fclose(f); + return "DSi firmware is not a valid firmware dump."; + } + + fclose(f); + + return ""; +} + +QString VerifyDSiNAND() +{ + FILE* f; + long len; + + f = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b"); + if (!f) return "DSi NAND was not found or could not be accessed. Check your emu settings."; + + // TODO: some basic checks + // check that it has the nocash footer, and all + + fclose(f); + + return ""; +} + +QString VerifySetup() +{ + QString res; + + if (Config::ExternalBIOSEnable) + { + res = VerifyDSBIOS(); + if (!res.isEmpty()) return res; + } + + if (Config::ConsoleType == 1) + { + res = VerifyDSiBIOS(); + if (!res.isEmpty()) return res; + + if (Config::ExternalBIOSEnable) + { + res = VerifyDSiFirmware(); + if (!res.isEmpty()) return res; + } + + res = VerifyDSiNAND(); + if (!res.isEmpty()) return res; + } + else + { + if (Config::ExternalBIOSEnable) + { + res = VerifyDSFirmware(); + if (!res.isEmpty()) return res; + } + } + + return ""; +} + + +std::string GetSavestateName(int slot) +{ + std::string ext = ".ml"; + ext += (char)('0'+slot); + return GetAssetPath(false, Config::SavestatePath, ext); +} + +bool SavestateExists(int slot) +{ + std::string ssfile = GetSavestateName(slot); + return Platform::FileExists(ssfile); +} + +bool LoadState(std::string filename) +{ + // backup + Savestate* backup = new Savestate("timewarp.mln", true); + NDS::DoSavestate(backup); + delete backup; + + bool failed = false; + + Savestate* state = new Savestate(filename, false); + if (state->Error) + { + delete state; + + // current state might be crapoed, so restore from sane backup + state = new Savestate("timewarp.mln", false); + failed = true; + } + + bool res = NDS::DoSavestate(state); + delete state; + + if (!res) + { + failed = true; + state = new Savestate("timewarp.mln", false); + NDS::DoSavestate(state); + delete state; + } + + if (failed) return false; + + if (Config::SavestateRelocSRAM && NDSSave) + { + PreviousSaveFile = NDSSave->GetPath(); + + std::string savefile = filename.substr(LastSep(filename)+1); + savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); + savefile += Platform::InstanceFileSuffix(); + NDSSave->SetPath(savefile, true); + } + + SavestateLoaded = true; + + return true; +} + +bool SaveState(std::string filename) +{ + Savestate* state = new Savestate(filename, true); + if (state->Error) + { + delete state; + return false; + } + + NDS::DoSavestate(state); + delete state; + + if (Config::SavestateRelocSRAM && NDSSave) + { + std::string savefile = filename.substr(LastSep(filename)+1); + savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); + savefile += Platform::InstanceFileSuffix(); + NDSSave->SetPath(savefile, false); + } + + return true; +} + +void UndoStateLoad() +{ + if (!SavestateLoaded) return; + + // pray that this works + // what do we do if it doesn't??? + // but it should work. + Savestate* backup = new Savestate("timewarp.mln", false); + NDS::DoSavestate(backup); + delete backup; + + if (NDSSave && (!PreviousSaveFile.empty())) + { + NDSSave->SetPath(PreviousSaveFile, true); + } +} + + +void UnloadCheats() +{ + if (CheatFile) + { + delete CheatFile; + CheatFile = nullptr; + } +} + +void LoadCheats() +{ + UnloadCheats(); + + std::string filename = GetAssetPath(false, Config::CheatFilePath, ".mch"); + + // TODO: check for error (malformed cheat file, ...) + CheatFile = new ARCodeFile(filename); + + AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr); +} + +void EnableCheats(bool enable) +{ + CheatsOn = enable; + if (CheatFile) + AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr); +} + +ARCodeFile* GetCheatFile() +{ + return CheatFile; +} + + +void SetBatteryLevels() +{ + if (NDS::ConsoleType == 1) + { + DSi_BPTWL::SetBatteryLevel(Config::DSiBatteryLevel); + DSi_BPTWL::SetBatteryCharging(Config::DSiBatteryCharging); + } + else + { + SPI_Powerman::SetBatteryLevelOkay(Config::DSBatteryLevelOkay); + } +} + +void Reset() +{ + NDS::SetConsoleType(Config::ConsoleType); + if (Config::ConsoleType == 1) EjectGBACart(); + NDS::Reset(); + SetBatteryLevels(); + + if ((CartType != -1) && NDSSave) + { + std::string oldsave = NDSSave->GetPath(); + std::string newsave = GetAssetPath(false, Config::SaveFilePath, ".sav"); + newsave += Platform::InstanceFileSuffix(); + if (oldsave != newsave) + NDSSave->SetPath(newsave, false); + } + + if ((GBACartType != -1) && GBASave) + { + std::string oldsave = GBASave->GetPath(); + std::string newsave = GetAssetPath(true, Config::SaveFilePath, ".sav"); + newsave += Platform::InstanceFileSuffix(); + if (oldsave != newsave) + GBASave->SetPath(newsave, false); + } + + if (!BaseROMName.empty()) + { + if (Config::DirectBoot || NDS::NeedsDirectBoot()) + { + NDS::SetupDirectBoot(BaseROMName); + } + } +} + + +bool LoadBIOS() +{ + NDS::SetConsoleType(Config::ConsoleType); + + if (NDS::NeedsDirectBoot()) + return false; + + /*if (NDSSave) delete NDSSave; + NDSSave = nullptr; + + CartType = -1; + BaseROMDir = ""; + BaseROMName = ""; + BaseAssetName = "";*/ + + NDS::Reset(); + SetBatteryLevels(); + return true; +} + + +bool LoadROM(QStringList filepath, bool reset) +{ + if (filepath.empty()) return false; + + u8* filedata; + u32 filelen; + + std::string basepath; + std::string romname; + + int num = filepath.count(); + if (num == 1) + { + // regular file + + std::string filename = filepath.at(0).toStdString(); + FILE* f = Platform::OpenFile(filename, "rb", true); + if (!f) return false; + + fseek(f, 0, SEEK_END); + long len = ftell(f); + if (len > 0x40000000) + { + fclose(f); + return false; + } + + fseek(f, 0, SEEK_SET); + filedata = new u8[len]; + size_t nread = fread(filedata, (size_t)len, 1, f); + if (nread != 1) + { + fclose(f); + delete[] filedata; + return false; + } + + fclose(f); + filelen = (u32)len; + + int pos = LastSep(filename); + basepath = filename.substr(0, pos); + romname = filename.substr(pos+1); + } +#ifdef ARCHIVE_SUPPORT_ENABLED + else if (num == 2) + { + // file inside archive + + u32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen); + if (lenread < 0) return false; + if (!filedata) return false; + if (lenread != filelen) + { + delete[] filedata; + return false; + } + + std::string std_archivepath = filepath.at(0).toStdString(); + basepath = std_archivepath.substr(0, LastSep(std_archivepath)); + + std::string std_romname = filepath.at(1).toStdString(); + romname = std_romname.substr(LastSep(std_romname)+1); + } +#endif + else + return false; + + if (NDSSave) delete NDSSave; + NDSSave = nullptr; + + BaseROMDir = basepath; + BaseROMName = romname; + BaseAssetName = romname.substr(0, romname.rfind('.')); + + if (reset) + { + NDS::SetConsoleType(Config::ConsoleType); + NDS::EjectCart(); + NDS::Reset(); + SetBatteryLevels(); + } + + u32 savelen = 0; + u8* savedata = nullptr; + + std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav"); + std::string origsav = savname; + savname += Platform::InstanceFileSuffix(); + + FILE* sav = Platform::OpenFile(savname, "rb", true); + if (!sav) sav = Platform::OpenFile(origsav, "rb", true); + if (sav) + { + fseek(sav, 0, SEEK_END); + savelen = (u32)ftell(sav); + + fseek(sav, 0, SEEK_SET); + savedata = new u8[savelen]; + fread(savedata, savelen, 1, sav); + fclose(sav); + } + + bool res = NDS::LoadCart(filedata, filelen, savedata, savelen); + if (res && reset) + { + if (Config::DirectBoot || NDS::NeedsDirectBoot()) + { + NDS::SetupDirectBoot(romname); + } + } + + if (res) + { + CartType = 0; + NDSSave = new SaveManager(savname); + + LoadCheats(); + } + + if (savedata) delete[] savedata; + delete[] filedata; + return res; +} + +void EjectCart() +{ + if (NDSSave) delete NDSSave; + NDSSave = nullptr; + + UnloadCheats(); + + NDS::EjectCart(); + + CartType = -1; + BaseROMDir = ""; + BaseROMName = ""; + BaseAssetName = ""; +} + +bool CartInserted() +{ + return CartType != -1; +} + +QString CartLabel() +{ + if (CartType == -1) + return "(none)"; + + QString ret = QString::fromStdString(BaseROMName); + + int maxlen = 32; + if (ret.length() > maxlen) + ret = ret.left(maxlen-6) + "..." + ret.right(3); + + return ret; +} + + +bool LoadGBAROM(QStringList filepath) +{ + if (Config::ConsoleType == 1) return false; + if (filepath.empty()) return false; + + u8* filedata; + u32 filelen; + + std::string basepath; + std::string romname; + + int num = filepath.count(); + if (num == 1) + { + // regular file + + std::string filename = filepath.at(0).toStdString(); + FILE* f = Platform::OpenFile(filename, "rb", true); + if (!f) return false; + + fseek(f, 0, SEEK_END); + long len = ftell(f); + if (len > 0x40000000) + { + fclose(f); + return false; + } + + fseek(f, 0, SEEK_SET); + filedata = new u8[len]; + size_t nread = fread(filedata, (size_t)len, 1, f); + if (nread != 1) + { + fclose(f); + delete[] filedata; + return false; + } + + fclose(f); + filelen = (u32)len; + + int pos = LastSep(filename); + basepath = filename.substr(0, pos); + romname = filename.substr(pos+1); + } +#ifdef ARCHIVE_SUPPORT_ENABLED + else if (num == 2) + { + // file inside archive + + u32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen); + if (lenread < 0) return false; + if (!filedata) return false; + if (lenread != filelen) + { + delete[] filedata; + return false; + } + + std::string std_archivepath = filepath.at(0).toStdString(); + basepath = std_archivepath.substr(0, LastSep(std_archivepath)); + + std::string std_romname = filepath.at(1).toStdString(); + romname = std_romname.substr(LastSep(std_romname)+1); + } +#endif + else + return false; + + if (GBASave) delete GBASave; + GBASave = nullptr; + + BaseGBAROMDir = basepath; + BaseGBAROMName = romname; + BaseGBAAssetName = romname.substr(0, romname.rfind('.')); + + u32 savelen = 0; + u8* savedata = nullptr; + + std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav"); + std::string origsav = savname; + savname += Platform::InstanceFileSuffix(); + + FILE* sav = Platform::OpenFile(savname, "rb", true); + if (!sav) sav = Platform::OpenFile(origsav, "rb", true); + if (sav) + { + fseek(sav, 0, SEEK_END); + savelen = (u32)ftell(sav); + + fseek(sav, 0, SEEK_SET); + savedata = new u8[savelen]; + fread(savedata, savelen, 1, sav); + fclose(sav); + } + + bool res = NDS::LoadGBACart(filedata, filelen, savedata, savelen); + + if (res) + { + GBACartType = 0; + GBASave = new SaveManager(savname); + } + + if (savedata) delete[] savedata; + delete[] filedata; + return res; +} + +void LoadGBAAddon(int type) +{ + if (Config::ConsoleType == 1) return; + + if (GBASave) delete GBASave; + GBASave = nullptr; + + NDS::LoadGBAAddon(type); + + GBACartType = type; + BaseGBAROMDir = ""; + BaseGBAROMName = ""; + BaseGBAAssetName = ""; +} + +void EjectGBACart() +{ + if (GBASave) delete GBASave; + GBASave = nullptr; + + NDS::EjectGBACart(); + + GBACartType = -1; + BaseGBAROMDir = ""; + BaseGBAROMName = ""; + BaseGBAAssetName = ""; +} + +bool GBACartInserted() +{ + return GBACartType != -1; +} + +QString GBACartLabel() +{ + if (Config::ConsoleType == 1) return "none (DSi)"; + + switch (GBACartType) + { + case 0: + { + QString ret = QString::fromStdString(BaseGBAROMName); + + int maxlen = 32; + if (ret.length() > maxlen) + ret = ret.left(maxlen-6) + "..." + ret.right(3); + + return ret; + } + + case NDS::GBAAddon_RAMExpansion: + return "Memory expansion"; + } + + return "(none)"; +} + + +void ROMIcon(u8 (&data)[512], u16 (&palette)[16], u32* iconRef) +{ + int index = 0; + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + for (int k = 0; k < 8; k++) + { + for (int l = 0; l < 8; l++) + { + u8 pal_index = index % 2 ? data[index/2] >> 4 : data[index/2] & 0x0F; + u8 r = ((palette[pal_index] >> 0) & 0x1F) * 255 / 31; + u8 g = ((palette[pal_index] >> 5) & 0x1F) * 255 / 31; + u8 b = ((palette[pal_index] >> 10) & 0x1F) * 255 / 31; + u8 a = pal_index ? 255: 0; + u32* row = &iconRef[256 * i + 32 * k + 8 * j]; + row[l] = (a << 24) | (r << 16) | (g << 8) | b; + index++; + } + } + } + } +} + +#define SEQ_FLIPV(i) ((i & 0b1000000000000000) >> 15) +#define SEQ_FLIPH(i) ((i & 0b0100000000000000) >> 14) +#define SEQ_PAL(i) ((i & 0b0011100000000000) >> 11) +#define SEQ_BMP(i) ((i & 0b0000011100000000) >> 8) +#define SEQ_DUR(i) ((i & 0b0000000011111111) >> 0) + +void AnimatedROMIcon(u8 (&data)[8][512], u16 (&palette)[8][16], u16 (&sequence)[64], u32 (&animatedTexRef)[32 * 32 * 64], std::vector<int> &animatedSequenceRef) +{ + for (int i = 0; i < 64; i++) + { + if (!sequence[i]) + break; + u32* frame = &animatedTexRef[32 * 32 * i]; + ROMIcon(data[SEQ_BMP(sequence[i])], palette[SEQ_PAL(sequence[i])], frame); + + if (SEQ_FLIPH(sequence[i])) + { + for (int x = 0; x < 32; x++) + { + for (int y = 0; y < 32/2; y++) + { + std::swap(frame[x * 32 + y], frame[x * 32 + (32 - 1 - y)]); + } + } + } + if (SEQ_FLIPV(sequence[i])) + { + for (int x = 0; x < 32/2; x++) + { + for (int y = 0; y < 32; y++) + { + std::swap(frame[x * 32 + y], frame[(32 - 1 - x) * 32 + y]); + } + } + } + + for (int j = 0; j < SEQ_DUR(sequence[i]); j++) + animatedSequenceRef.push_back(i); + } +} + +} diff --git a/src/frontend/qt_sdl/ROMManager.h b/src/frontend/qt_sdl/ROMManager.h new file mode 100644 index 0000000..9e82a5b --- /dev/null +++ b/src/frontend/qt_sdl/ROMManager.h @@ -0,0 +1,66 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef ROMMANAGER_H +#define ROMMANAGER_H + +#include "types.h" +#include "SaveManager.h" +#include "AREngine.h" + +#include <string> +#include <vector> + +namespace ROMManager +{ + +extern SaveManager* NDSSave; +extern SaveManager* GBASave; + +QString VerifySetup(); +void Reset(); +bool LoadBIOS(); + +bool LoadROM(QStringList filepath, bool reset); +void EjectCart(); +bool CartInserted(); +QString CartLabel(); + +bool LoadGBAROM(QStringList filepath); +void LoadGBAAddon(int type); +void EjectGBACart(); +bool GBACartInserted(); +QString GBACartLabel(); + +std::string GetSavestateName(int slot); +bool SavestateExists(int slot); +bool LoadState(std::string filename); +bool SaveState(std::string filename); +void UndoStateLoad(); + +void EnableCheats(bool enable); +ARCodeFile* GetCheatFile(); + +void ROMIcon(u8 (&data)[512], u16 (&palette)[16], u32* iconRef); +void AnimatedROMIcon(u8 (&data)[8][512], u16 (&palette)[8][16], + u16 (&sequence)[64], u32 (&animatedTexRef)[32 * 32 * 64], + std::vector<int> &animatedSequenceRef); + +} + +#endif // ROMMANAGER_H diff --git a/src/frontend/qt_sdl/SaveManager.cpp b/src/frontend/qt_sdl/SaveManager.cpp new file mode 100644 index 0000000..d9e2138 --- /dev/null +++ b/src/frontend/qt_sdl/SaveManager.cpp @@ -0,0 +1,194 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> +#include <string.h> + +#include "SaveManager.h" +#include "Platform.h" + + +SaveManager::SaveManager(std::string path) : QThread() +{ + SecondaryBuffer = nullptr; + SecondaryBufferLength = 0; + SecondaryBufferLock = new QMutex(); + + Running = false; + + Path = path; + + Buffer = nullptr; + Length = 0; + FlushRequested = false; + + FlushVersion = 0; + PreviousFlushVersion = 0; + TimeAtLastFlushRequest = 0; + + if (!path.empty()) + { + Running = true; + start(); + } +} + +SaveManager::~SaveManager() +{ + if (Running) + { + Running = false; + wait(); + FlushSecondaryBuffer(); + } + + if (SecondaryBuffer) delete[] SecondaryBuffer; + + delete SecondaryBufferLock; + + if (Buffer) delete[] Buffer; +} + +std::string SaveManager::GetPath() +{ + return Path; +} + +void SaveManager::SetPath(std::string path, bool reload) +{ + Path = path; + + if (reload) + { + FILE* f = Platform::OpenFile(Path, "rb", true); + if (f) + { + fread(Buffer, 1, Length, f); + fclose(f); + } + } + else + FlushRequested = true; +} + +void SaveManager::RequestFlush(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) +{ + if (Length != savelen) + { + if (Buffer) delete[] Buffer; + + Length = savelen; + Buffer = new u8[Length]; + + memcpy(Buffer, savedata, Length); + } + else + { + if ((writeoffset+writelen) > savelen) + { + u32 len = savelen - writeoffset; + memcpy(&Buffer[writeoffset], &savedata[writeoffset], len); + len = writelen - len; + if (len > savelen) len = savelen; + memcpy(&Buffer[0], &savedata[0], len); + } + else + { + memcpy(&Buffer[writeoffset], &savedata[writeoffset], writelen); + } + } + + FlushRequested = true; +} + +void SaveManager::CheckFlush() +{ + if (!FlushRequested) return; + + SecondaryBufferLock->lock(); + + printf("SaveManager: Flush requested\n"); + + if (SecondaryBufferLength != Length) + { + if (SecondaryBuffer) delete[] SecondaryBuffer; + + SecondaryBufferLength = Length; + SecondaryBuffer = new u8[SecondaryBufferLength]; + } + + memcpy(SecondaryBuffer, Buffer, Length); + + FlushRequested = false; + FlushVersion++; + TimeAtLastFlushRequest = time(nullptr); + + SecondaryBufferLock->unlock(); +} + +void SaveManager::run() +{ + for (;;) + { + QThread::msleep(100); + + if (!Running) return; + + // We debounce for two seconds after last flush request to ensure that writing has finished. + if (TimeAtLastFlushRequest == 0 || difftime(time(nullptr), TimeAtLastFlushRequest) < 2) + { + continue; + } + + FlushSecondaryBuffer(); + } +} + +void SaveManager::FlushSecondaryBuffer(u8* dst, u32 dstLength) +{ + if (!SecondaryBuffer) return; + + // When flushing to a file, there's no point in re-writing the exact same data. + if (!dst && !NeedsFlush()) return; + // When flushing to memory, we don't know if dst already has any data so we only check that we CAN flush. + if (dst && dstLength < SecondaryBufferLength) return; + + SecondaryBufferLock->lock(); + if (dst) + { + memcpy(dst, SecondaryBuffer, SecondaryBufferLength); + } + else + { + FILE* f = Platform::OpenFile(Path, "wb"); + if (f) + { + printf("SaveManager: Written\n"); + fwrite(SecondaryBuffer, SecondaryBufferLength, 1, f); + fclose(f); + } + } + PreviousFlushVersion = FlushVersion; + TimeAtLastFlushRequest = 0; + SecondaryBufferLock->unlock(); +} + +bool SaveManager::NeedsFlush() +{ + return FlushVersion != PreviousFlushVersion; +} diff --git a/src/frontend/qt_sdl/SaveManager.h b/src/frontend/qt_sdl/SaveManager.h new file mode 100644 index 0000000..0d38f4b --- /dev/null +++ b/src/frontend/qt_sdl/SaveManager.h @@ -0,0 +1,70 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef SAVEMANAGER_H +#define SAVEMANAGER_H + +#include <string> +#include <unistd.h> +#include <time.h> +#include <atomic> +#include <QThread> +#include <QMutex> + +#include "types.h" + +class SaveManager : public QThread +{ + Q_OBJECT + void run() override; + +public: + SaveManager(std::string path); + ~SaveManager(); + + std::string GetPath(); + void SetPath(std::string path, bool reload); + + void RequestFlush(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen); + void CheckFlush(); + + bool NeedsFlush(); + void FlushSecondaryBuffer(u8* dst = nullptr, u32 dstLength = 0); + +private: + std::string Path; + + std::atomic_bool Running; + + u8* Buffer; + u32 Length; + bool FlushRequested; + + QMutex* SecondaryBufferLock; + u8* SecondaryBuffer; + u32 SecondaryBufferLength; + + time_t TimeAtLastFlushRequest; + + // We keep versions in case the user closes the application before + // a flush cycle is finished. + u32 PreviousFlushVersion; + u32 FlushVersion; +}; + +#endif // SAVEMANAGER_H diff --git a/src/frontend/qt_sdl/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp index 0a5e65d..8087ee6 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.cpp +++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -23,7 +23,7 @@ #include "types.h" #include "Platform.h" #include "Config.h" -#include "FrontendUtil.h" +#include "ROMManager.h" #include "DSi_NAND.h" #include "TitleManagerDialog.h" @@ -31,7 +31,7 @@ #include "ui_TitleImportDialog.h" -FILE* TitleManagerDialog::curNAND = nullptr; +bool TitleManagerDialog::NANDInited = false; TitleManagerDialog* TitleManagerDialog::currentDlg = nullptr; extern std::string EmuDirectory; @@ -111,7 +111,7 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid) DSi_NAND::GetTitleInfo(category, titleid, version, &header, &banner); u32 icondata[32*32]; - Frontend::ROMIcon(banner.Icon, banner.Palette, icondata); + ROMManager::ROMIcon(banner.Icon, banner.Palette, icondata); QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_ARGB32); QIcon icon(QPixmap::fromImage(iconimg.copy())); @@ -136,6 +136,8 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid) bool TitleManagerDialog::openNAND() { + NANDInited = false; + FILE* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb"); if (!bios7i) return false; @@ -145,28 +147,21 @@ bool TitleManagerDialog::openNAND() fread(es_keyY, 16, 1, bios7i); fclose(bios7i); - curNAND = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b"); - if (!curNAND) - return false; - - if (!DSi_NAND::Init(curNAND, es_keyY)) + if (!DSi_NAND::Init(es_keyY)) { - fclose(curNAND); - curNAND = nullptr; return false; } + NANDInited = true; return true; } void TitleManagerDialog::closeNAND() { - if (curNAND) + if (NANDInited) { DSi_NAND::DeInit(); - - fclose(curNAND); - curNAND = nullptr; + NANDInited = false; } } diff --git a/src/frontend/qt_sdl/TitleManagerDialog.h b/src/frontend/qt_sdl/TitleManagerDialog.h index 682362a..cba7047 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.h +++ b/src/frontend/qt_sdl/TitleManagerDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -45,7 +45,7 @@ public: explicit TitleManagerDialog(QWidget* parent); ~TitleManagerDialog(); - static FILE* curNAND; + static bool NANDInited; static bool openNAND(); static void closeNAND(); diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.cpp b/src/frontend/qt_sdl/VideoSettingsDialog.cpp index 0c3f13e..87a796d 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.cpp +++ b/src/frontend/qt_sdl/VideoSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.h b/src/frontend/qt_sdl/VideoSettingsDialog.h index 4503012..527cc93 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.h +++ b/src/frontend/qt_sdl/VideoSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp index d438179..9bf265e 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp +++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -50,12 +50,12 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - LAN_Socket::Init(); haspcap = LAN_PCap::Init(false); ui->rbDirectMode->setText("Direct mode (requires " PCAP_NAME " and ethernet connection)"); - ui->cbBindAnyAddr->setChecked(Config::SocketBindAnyAddr != 0); + ui->lblAdapterMAC->setText("(none)"); + ui->lblAdapterIP->setText("(none)"); int sel = 0; for (int i = 0; i < LAN_PCap::NumAdapters; i++) @@ -64,13 +64,14 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->cbxDirectAdapter->addItem(QString(adapter->FriendlyName)); - if (!strncmp(adapter->DeviceName, Config::LANDevice, 128)) + if (!strncmp(adapter->DeviceName, Config::LANDevice.c_str(), 128)) sel = i; } ui->cbxDirectAdapter->setCurrentIndex(sel); - ui->rbDirectMode->setChecked(Config::DirectLAN != 0); - ui->rbIndirectMode->setChecked(Config::DirectLAN == 0); + // errrr??? + ui->rbDirectMode->setChecked(Config::DirectLAN); + ui->rbIndirectMode->setChecked(!Config::DirectLAN); if (!haspcap) ui->rbDirectMode->setEnabled(false); updateAdapterControls(); @@ -87,19 +88,17 @@ void WifiSettingsDialog::done(int r) if (r == QDialog::Accepted) { - Config::SocketBindAnyAddr = ui->cbBindAnyAddr->isChecked() ? 1:0; - Config::DirectLAN = ui->rbDirectMode->isChecked() ? 1:0; + Config::DirectLAN = ui->rbDirectMode->isChecked(); int sel = ui->cbxDirectAdapter->currentIndex(); if (sel < 0 || sel >= LAN_PCap::NumAdapters) sel = 0; if (LAN_PCap::NumAdapters < 1) { - Config::LANDevice[0] = '\0'; + Config::LANDevice = ""; } else { - strncpy(Config::LANDevice, LAN_PCap::Adapters[sel].DeviceName, 127); - Config::LANDevice[127] = '\0'; + Config::LANDevice = LAN_PCap::Adapters[sel].DeviceName; } Config::Save(); diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.h b/src/frontend/qt_sdl/WifiSettingsDialog.h index a7cf538..da94924 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.h +++ b/src/frontend/qt_sdl/WifiSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.ui b/src/frontend/qt_sdl/WifiSettingsDialog.ui index 0897059..444e1d5 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.ui +++ b/src/frontend/qt_sdl/WifiSettingsDialog.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>572</width> - <height>273</height> + <height>217</height> </rect> </property> <property name="sizePolicy"> @@ -26,16 +26,26 @@ <item> <widget class="QGroupBox" name="groupBox"> <property name="title"> - <string>Local</string> + <string>Network mode</string> </property> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> - <widget class="QCheckBox" name="cbBindAnyAddr"> + <widget class="QRadioButton" name="rbIndirectMode"> + <property name="whatsThis"> + <string><html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html></string> + </property> + <property name="text"> + <string>Indirect mode (uses libslirp, recommended)</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QRadioButton" name="rbDirectMode"> <property name="whatsThis"> - <string><html><head/><body><p>Enabling this allows (theoretically) playing local multiplayer games over a local network. It may or may not help make for a better connection in general.</p></body></html></string> + <string><html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html></string> </property> <property name="text"> - <string>Bind socket to any address</string> + <string>Direct mode [TEXT PLACEHOLDER]</string> </property> </widget> </item> @@ -43,91 +53,62 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_2"> + <widget class="QGroupBox" name="groupBox_3"> <property name="title"> - <string>Online</string> + <string>Direct mode settings</string> </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="3" column="0" rowspan="3" colspan="2"> - <widget class="QGroupBox" name="groupBox_3"> - <property name="title"> - <string>Direct mode settings</string> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Network adapter:</string> </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Network adapter:</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="cbxDirectAdapter"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>300</width> - <height>0</height> - </size> - </property> - <property name="whatsThis"> - <string><html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html></string> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>MAC address:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="lblAdapterMAC"> - <property name="text"> - <string>[PLACEHOLDER]</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>IP address:</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLabel" name="lblAdapterIP"> - <property name="text"> - <string>[PLACEHOLDER]</string> - </property> - </widget> - </item> - </layout> </widget> </item> - <item row="1" column="0"> - <widget class="QRadioButton" name="rbIndirectMode"> + <item row="0" column="1"> + <widget class="QComboBox" name="cbxDirectAdapter"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>0</height> + </size> + </property> <property name="whatsThis"> - <string><html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html></string> + <string><html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html></string> </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> <property name="text"> - <string>Indirect mode (uses libslirp, recommended)</string> + <string>MAC address:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="lblAdapterMAC"> + <property name="text"> + <string>[PLACEHOLDER]</string> </property> </widget> </item> <item row="2" column="0"> - <widget class="QRadioButton" name="rbDirectMode"> - <property name="whatsThis"> - <string><html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html></string> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>IP address:</string> </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="lblAdapterIP"> <property name="text"> - <string>Direct mode [TEXT PLACEHOLDER]</string> + <string>[PLACEHOLDER]</string> </property> </widget> </item> diff --git a/src/frontend/qt_sdl/font.h b/src/frontend/qt_sdl/font.h index 59720ab..01e3bd2 100644 --- a/src/frontend/qt_sdl/font.h +++ b/src/frontend/qt_sdl/font.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 31bb3c0..88704b6 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -25,6 +25,7 @@ #include <string> #include <algorithm> +#include <QProcess> #include <QApplication> #include <QMessageBox> #include <QMenuBar> @@ -54,12 +55,17 @@ #include "EmuSettingsDialog.h" #include "InputConfig/InputConfigDialog.h" #include "VideoSettingsDialog.h" +#include "CameraSettingsDialog.h" #include "AudioSettingsDialog.h" #include "FirmwareSettingsDialog.h" +#include "PathSettingsDialog.h" +#include "MPSettingsDialog.h" #include "WifiSettingsDialog.h" #include "InterfaceSettingsDialog.h" #include "ROMInfoDialog.h" +#include "RAMInfoDialog.h" #include "TitleManagerDialog.h" +#include "PowerManagement/PowerManagementDialog.h" #include "types.h" #include "version.h" @@ -74,13 +80,16 @@ #include "SPU.h" #include "Wifi.h" #include "Platform.h" +#include "LocalMP.h" #include "Config.h" #include "Savestate.h" #include "main_shaders.h" +#include "ROMManager.h" #include "ArchiveUtil.h" +#include "CameraManager.h" // TODO: uniform variable spelling @@ -97,6 +106,7 @@ bool videoSettingsDirty; SDL_AudioDeviceID audioDevice; int audioFreq; +bool audioMuted; SDL_cond* audioSync; SDL_mutex* audioSyncLock; @@ -107,9 +117,22 @@ u32 micExtBufferWritePos; u32 micWavLength; s16* micWavBuffer; +CameraManager* camManager[2]; +bool camStarted[2]; + +const struct { int id; float ratio; const char* label; } aspectRatios[] = +{ + { 0, 1, "4:3 (native)" }, + { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, + { 1, (16.f / 9) / (4.f / 3), "16:9" }, + { 2, (21.f / 9) / (4.f / 3), "21:9" }, + { 3, 0, "window" } +}; + void micCallback(void* data, Uint8* stream, int len); + void audioCallback(void* data, Uint8* stream, int len) { len /= (sizeof(s16) * 2); @@ -125,7 +148,7 @@ void audioCallback(void* data, Uint8* stream, int len) SDL_CondSignal(audioSync); SDL_UnlockMutex(audioSyncLock); - if (num_in < 1) + if ((num_in < 1) || audioMuted) { memset(stream, 0, len*sizeof(s16)*2); return; @@ -145,6 +168,23 @@ void audioCallback(void* data, Uint8* stream, int len) Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume); } +void audioMute() +{ + int inst = Platform::InstanceID(); + audioMuted = false; + + switch (Config::MPAudioMode) + { + case 1: // only instance 1 + if (inst > 0) audioMuted = true; + break; + + case 2: // only currently focused instance + if (!mainWindow->isActiveWindow()) audioMuted = true; + break; + } +} + void micOpen() { @@ -180,7 +220,7 @@ void micClose() micDevice = 0; } -void micLoadWav(const char* name) +void micLoadWav(std::string name) { SDL_AudioSpec format; memset(&format, 0, sizeof(SDL_AudioSpec)); @@ -191,7 +231,7 @@ void micLoadWav(const char* name) u8* buf; u32 len; - if (!SDL_LoadWAV(name, &format, &buf, &len)) + if (!SDL_LoadWAV(name.c_str(), &format, &buf, &len)) return; const u64 dstfreq = 44100; @@ -317,7 +357,7 @@ EmuThread::EmuThread(QObject* parent) : QThread(parent) EmuPause = 0; RunningSomething = false; - connect(this, SIGNAL(windowUpdate()), mainWindow->panel, SLOT(repaint())); + connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint())); connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString))); connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart())); connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop())); @@ -325,7 +365,7 @@ EmuThread::EmuThread(QObject* parent) : QThread(parent) connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger())); connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger())); connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger())); - connect(this, SIGNAL(screenLayoutChange()), mainWindow->panel, SLOT(onScreenLayoutChanged())); + connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged())); connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); @@ -413,6 +453,8 @@ void EmuThread::run() double frameLimitError = 0.0; double lastMeasureTime = lastTime; + u32 winUpdateCount = 0, winUpdateFreq = 1; + char melontitle[100]; while (EmuRunning != 0) @@ -499,7 +541,7 @@ void EmuThread::run() micProcess(); // auto screen layout - if (Config::ScreenSizing == 3) + if (Config::ScreenSizing == screenSizing_Auto) { mainScreenPos[2] = mainScreenPos[1]; mainScreenPos[1] = mainScreenPos[0]; @@ -511,14 +553,14 @@ void EmuThread::run() { // constant flickering, likely displaying 3D on both screens // TODO: when both screens are used for 2D only...??? - guess = 0; + guess = screenSizing_Even; } else { if (mainScreenPos[0] == 1) - guess = 1; + guess = screenSizing_EmphTop; else - guess = 2; + guess = screenSizing_EmphBot; } if (guess != autoScreenSizing) @@ -541,6 +583,12 @@ void EmuThread::run() // emulate u32 nlines = NDS::RunFrame(); + if (ROMManager::NDSSave) + ROMManager::NDSSave->CheckFlush(); + + if (ROMManager::GBASave) + ROMManager::GBASave->CheckFlush(); + FrontBufferLock.lock(); FrontBuffer = GPU::FrontBuffer; #ifdef OGLRENDERER_ENABLED @@ -563,11 +611,16 @@ void EmuThread::run() if (EmuRunning == 0) break; - emit windowUpdate(); + winUpdateCount++; + if (winUpdateCount >= winUpdateFreq) + { + emit windowUpdate(); + winUpdateCount = 0; + } bool fastforward = Input::HotkeyDown(HK_FastForward); - if (Config::AudioSync && (!fastforward) && audioDevice) + if (Config::AudioSync && !fastforward && audioDevice) { SDL_LockMutex(audioSyncLock); while (SPU::GetOutputSize() > 1024) @@ -616,7 +669,15 @@ void EmuThread::run() float fpstarget = 1.0/frametimeStep; - sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + winUpdateFreq = fps / (u32)round(fpstarget); + if (winUpdateFreq < 1) + winUpdateFreq = 1; + + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + else + sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); changeWindowTitle(melontitle); } } @@ -631,7 +692,11 @@ void EmuThread::run() EmuStatus = EmuRunning; - sprintf(melontitle, "melonDS " MELONDS_VERSION); + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "melonDS " MELONDS_VERSION); + else + sprintf(melontitle, "melonDS (%d)", inst+1); changeWindowTitle(melontitle); SDL_Delay(75); @@ -719,19 +784,39 @@ bool EmuThread::emuIsActive() return (RunningSomething == 1); } +ScreenHandler::ScreenHandler(QWidget* widget) +{ + widget->setMouseTracking(true); + widget->setAttribute(Qt::WA_AcceptTouchEvents); + QTimer* mouseTimer = setupMouseTimer(); + widget->connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) widget->setCursor(Qt::BlankCursor);}); +} + +ScreenHandler::~ScreenHandler() +{ + mouseTimer->stop(); +} void ScreenHandler::screenSetupLayout(int w, int h) { int sizing = Config::ScreenSizing; if (sizing == 3) sizing = autoScreenSizing; - float aspectRatios[] = + float aspectTop, aspectBot; + + for (auto ratio : aspectRatios) { - 1.f, - (16.f/9)/(4.f/3), - (21.f/9)/(4.f/3), - ((float)w/h)/(4.f/3) - }; + if (ratio.id == Config::ScreenAspectTop) + aspectTop = ratio.ratio; + if (ratio.id == Config::ScreenAspectBot) + aspectBot = ratio.ratio; + } + + if (aspectTop == 0) + aspectTop = (float) w / h; + + if (aspectBot == 0) + aspectBot = (float) w / h; Frontend::SetupScreenLayout(w, h, Config::ScreenLayout, @@ -740,8 +825,8 @@ void ScreenHandler::screenSetupLayout(int w, int h) Config::ScreenGap, Config::IntegerScaling != 0, Config::ScreenSwap != 0, - aspectRatios[Config::ScreenAspectTop], - aspectRatios[Config::ScreenAspectBot]); + aspectTop, + aspectBot); numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); } @@ -754,6 +839,11 @@ QSize ScreenHandler::screenGetMinSize(int factor = 1) int w = 256 * factor; int h = 192 * factor; + if (Config::ScreenSizing == 4 || Config::ScreenSizing == 5) + { + return QSize(w, h); + } + if (Config::ScreenLayout == 0) // natural { if (isHori) @@ -889,7 +979,7 @@ void ScreenHandler::screenHandleTouch(QTouchEvent* event) void ScreenHandler::showCursor() { - mainWindow->panel->setCursor(Qt::ArrowCursor); + mainWindow->panelWidget->setCursor(Qt::ArrowCursor); mouseTimer->start(); } @@ -903,7 +993,7 @@ QTimer* ScreenHandler::setupMouseTimer() return mouseTimer; } -ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent) +ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent), ScreenHandler(this) { screen[0] = QImage(256, 192, QImage::Format_RGB32); screen[1] = QImage(256, 192, QImage::Format_RGB32); @@ -911,17 +1001,12 @@ ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent) screenTrans[0].reset(); screenTrans[1].reset(); - touching = false; - - setAttribute(Qt::WA_AcceptTouchEvents); - OSD::Init(nullptr); } ScreenPanelNative::~ScreenPanelNative() { OSD::DeInit(nullptr); - mouseTimer->stop(); } void ScreenPanelNative::setupScreenLayout() @@ -1020,17 +1105,11 @@ void ScreenPanelNative::onScreenLayoutChanged() } -ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QOpenGLWidget(parent) -{ - touching = false; - - setAttribute(Qt::WA_AcceptTouchEvents); -} +ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QOpenGLWidget(parent), ScreenHandler(this) +{} ScreenPanelGL::~ScreenPanelGL() { - mouseTimer->stop(); - makeCurrent(); OSD::DeInit(this); @@ -1281,11 +1360,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) oldW = Config::WindowWidth; oldH = Config::WindowHeight; - oldMax = Config::WindowMaximized!=0; + oldMax = Config::WindowMaximized; setWindowTitle("melonDS " MELONDS_VERSION); setAttribute(Qt::WA_DeleteOnClose); setAcceptDrops(true); + setFocusPolicy(Qt::ClickFocus); + + int inst = Platform::InstanceID(); QMenuBar* menubar = new QMenuBar(); { @@ -1295,16 +1377,16 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); - actOpenROMArchive = menu->addAction("Open ROM inside archive..."); + /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); - actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT)); + actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ recentMenu = menu->addMenu("Open recent"); for (int i = 0; i < 10; ++i) { - char* item = Config::RecentROMList[i]; - if (strlen(item) > 0) - recentFileList.push_back(item); + std::string item = Config::RecentROMList[i]; + if (!item.empty()) + recentFileList.push_back(QString::fromStdString(item)); } updateRecentFilesMenu(); @@ -1314,6 +1396,41 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) menu->addSeparator(); + actCurrentCart = menu->addAction("DS slot: " + ROMManager::CartLabel()); + actCurrentCart->setEnabled(false); + + actInsertCart = menu->addAction("Insert cart..."); + connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); + + actEjectCart = menu->addAction("Eject cart"); + connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); + + menu->addSeparator(); + + actCurrentGBACart = menu->addAction("GBA slot: " + ROMManager::GBACartLabel()); + actCurrentGBACart->setEnabled(false); + + actInsertGBACart = menu->addAction("Insert ROM cart..."); + connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); + + { + QMenu* submenu = menu->addMenu("Insert add-on cart"); + + actInsertGBAAddon[0] = submenu->addAction("Memory expansion"); + actInsertGBAAddon[0]->setData(QVariant(NDS::GBAAddon_RAMExpansion)); + connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); + } + + actEjectGBACart = menu->addAction("Eject cart"); + connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); + + menu->addSeparator(); + + actImportSavefile = menu->addAction("Import savefile"); + connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); + + menu->addSeparator(); + { QMenu* submenu = menu->addMenu("Save state"); @@ -1351,9 +1468,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12)); connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad); - actImportSavefile = menu->addAction("Import savefile"); - connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); - menu->addSeparator(); actQuit = menu->addAction("Quit"); @@ -1377,20 +1491,39 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) menu->addSeparator(); + actPowerManagement = menu->addAction("Power management"); + connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); + + menu->addSeparator(); + actEnableCheats = menu->addAction("Enable cheats"); actEnableCheats->setCheckable(true); connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); - actSetupCheats = menu->addAction("Setup cheat codes"); - actSetupCheats->setMenuRole(QAction::NoRole); - connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); + //if (inst == 0) + { + actSetupCheats = menu->addAction("Setup cheat codes"); + actSetupCheats->setMenuRole(QAction::NoRole); + connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); + + menu->addSeparator(); + actROMInfo = menu->addAction("ROM info"); + connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); - menu->addSeparator(); - actROMInfo = menu->addAction("ROM info"); - connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); + actRAMInfo = menu->addAction("RAM search"); + connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); + + actTitleManager = menu->addAction("Manage DSi titles"); + connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + } - actTitleManager = menu->addAction("Manage DSi titles"); - connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + { + menu->addSeparator(); + QMenu* submenu = menu->addMenu("Multiplayer"); + + actMPNewInstance = submenu->addAction("Launch new instance"); + connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); + } } { QMenu* menu = menubar->addMenu("Config"); @@ -1399,7 +1532,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); #ifdef __APPLE__ - QAction* actPreferences = menu->addAction("Preferences..."); + actPreferences = menu->addAction("Preferences..."); connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); actPreferences->setMenuRole(QAction::PreferencesRole); #endif @@ -1410,17 +1543,26 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actVideoSettings = menu->addAction("Video settings"); connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings); + actCameraSettings = menu->addAction("Camera settings"); + connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); + actAudioSettings = menu->addAction("Audio settings"); connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); + actMPSettings = menu->addAction("Multiplayer settings"); + connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); + actWifiSettings = menu->addAction("Wifi settings"); connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); + actFirmwareSettings = menu->addAction("Firmware settings"); + connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); + actInterfaceSettings = menu->addAction("Interface settings"); connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); - actFirmwareSettings = menu->addAction("Firmware settings"); - connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); + actPathSettings = menu->addAction("Path settings"); + connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); { QMenu* submenu = menu->addMenu("Savestate settings"); @@ -1503,7 +1645,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", "Bottom only"}; - for (int i = 0; i < 6; i++) + for (int i = 0; i < screenSizing_MAX; i++) { actScreenSizing[i] = submenu->addAction(QString(screensizing[i])); actScreenSizing[i]->setActionGroup(grpScreenSizing); @@ -1522,34 +1664,34 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { QMenu* submenu = menu->addMenu("Aspect ratio"); grpScreenAspectTop = new QActionGroup(submenu); + grpScreenAspectBot = new QActionGroup(submenu); + actScreenAspectTop = new QAction*[sizeof(aspectRatios) / sizeof(aspectRatios[0])]; + actScreenAspectBot = new QAction*[sizeof(aspectRatios) / sizeof(aspectRatios[0])]; - const char* aspectRatiosTop[] = {"Top 4:3 (native)", "Top 16:9", "Top 21:9", "Top window"}; - - for (int i = 0; i < 4; i++) + for (int i = 0; i < 2; i++) { - actScreenAspectTop[i] = submenu->addAction(QString(aspectRatiosTop[i])); - actScreenAspectTop[i]->setActionGroup(grpScreenAspectTop); - actScreenAspectTop[i]->setData(QVariant(i)); - actScreenAspectTop[i]->setCheckable(true); - } - - connect(grpScreenAspectTop, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspectTop); + QActionGroup* group = grpScreenAspectTop; + QAction** actions = actScreenAspectTop; - submenu->addSeparator(); - - grpScreenAspectBot = new QActionGroup(submenu); + if (i == 1) + { + group = grpScreenAspectBot; + submenu->addSeparator(); + actions = actScreenAspectBot; + } - const char* aspectRatiosBot[] = {"Bottom 4:3 (native)", "Bottom 16:9", "Bottom 21:9", "Bottom window"}; + for (int j = 0; j < sizeof(aspectRatios) / sizeof(aspectRatios[0]); j++) + { + auto ratio = aspectRatios[j]; + QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label); + actions[j] = submenu->addAction(label); + actions[j]->setActionGroup(group); + actions[j]->setData(QVariant(ratio.id)); + actions[j]->setCheckable(true); + } - for (int i = 0; i < 4; i++) - { - actScreenAspectBot[i] = submenu->addAction(QString(aspectRatiosBot[i])); - actScreenAspectBot[i]->setActionGroup(grpScreenAspectBot); - actScreenAspectBot[i]->setData(QVariant(i)); - actScreenAspectBot[i]->setCheckable(true); + connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); } - - connect(grpScreenAspectBot, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspectBot); } actScreenFiltering = menu->addAction("Screen filtering"); @@ -1574,6 +1716,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) resize(Config::WindowWidth, Config::WindowHeight); + if (Config::FirmwareUsername == "Arisotura") + actMPNewInstance->setText("Fart"); + #ifdef Q_OS_MAC QPoint screenCenter = screen()->availableGeometry().center(); QRect frameGeo = frameGeometry(); @@ -1588,6 +1733,16 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) createScreenPanel(); + actEjectCart->setEnabled(false); + actEjectGBACart->setEnabled(false); + + if (Config::ConsoleType == 1) + { + actInsertGBACart->setEnabled(false); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(false); + } + for (int i = 0; i < 9; i++) { actSaveState[i]->setEnabled(false); @@ -1601,14 +1756,17 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actStop->setEnabled(false); actFrameStep->setEnabled(false); + actPowerManagement->setEnabled(false); + actSetupCheats->setEnabled(false); - actTitleManager->setEnabled(strlen(Config::DSiNANDPath) > 0); + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); - actEnableCheats->setChecked(Config::EnableCheats != 0); + actEnableCheats->setChecked(Config::EnableCheats); actROMInfo->setEnabled(false); + actRAMInfo->setEnabled(false); - actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM != 0); + actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM); actScreenRotation[Config::ScreenRotation]->setChecked(true); @@ -1623,18 +1781,36 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actScreenLayout[Config::ScreenLayout]->setChecked(true); actScreenSizing[Config::ScreenSizing]->setChecked(true); - actIntegerScaling->setChecked(Config::IntegerScaling != 0); + actIntegerScaling->setChecked(Config::IntegerScaling); - actScreenSwap->setChecked(Config::ScreenSwap != 0); + actScreenSwap->setChecked(Config::ScreenSwap); - actScreenAspectTop[Config::ScreenAspectTop]->setChecked(true); - actScreenAspectBot[Config::ScreenAspectBot]->setChecked(true); + for (int i = 0; i < sizeof(aspectRatios) / sizeof(aspectRatios[0]); i++) + { + if (Config::ScreenAspectTop == aspectRatios[i].id) + actScreenAspectTop[i]->setChecked(true); + if (Config::ScreenAspectBot == aspectRatios[i].id) + actScreenAspectBot[i]->setChecked(true); + } - actScreenFiltering->setChecked(Config::ScreenFilter != 0); - actShowOSD->setChecked(Config::ShowOSD != 0); + actScreenFiltering->setChecked(Config::ScreenFilter); + actShowOSD->setChecked(Config::ShowOSD); - actLimitFramerate->setChecked(Config::LimitFPS != 0); - actAudioSync->setChecked(Config::AudioSync != 0); + actLimitFramerate->setChecked(Config::LimitFPS); + actAudioSync->setChecked(Config::AudioSync); + + if (inst > 0) + { + actEmuSettings->setEnabled(false); + actVideoSettings->setEnabled(false); + actMPSettings->setEnabled(false); + actWifiSettings->setEnabled(false); + actInterfaceSettings->setEnabled(false); + +#ifdef __APPLE__ + actPreferences->setEnabled(false); +#endif // __APPLE__ + } } MainWindow::~MainWindow() @@ -1645,17 +1821,13 @@ void MainWindow::createScreenPanel() { hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); - QTimer* mouseTimer; - if (hasOGL) { - panelGL = new ScreenPanelGL(this); + ScreenPanelGL* panelGL = new ScreenPanelGL(this); panelGL->show(); panel = panelGL; - panelGL->setMouseTracking(true); - mouseTimer = panelGL->setupMouseTimer(); - connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) panelGL->setCursor(Qt::BlankCursor);}); + panelWidget = panelGL; if (!panelGL->isValid()) hasOGL = false; @@ -1672,17 +1844,14 @@ void MainWindow::createScreenPanel() if (!hasOGL) { - panelNative = new ScreenPanelNative(this); + ScreenPanelNative* panelNative = new ScreenPanelNative(this); panel = panelNative; - panel->show(); - - panelNative->setMouseTracking(true); - mouseTimer = panelNative->setupMouseTimer(); - connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) panelNative->setCursor(Qt::BlankCursor);}); + panelWidget = panelNative; + panelWidget->show(); } - setCentralWidget(panel); + setCentralWidget(panelWidget); - connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged())); + connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged())); emit screenLayoutChange(); } @@ -1690,7 +1859,7 @@ QOpenGLContext* MainWindow::getOGLContext() { if (!hasOGL) return nullptr; - QOpenGLWidget* glpanel = (QOpenGLWidget*)panel; + QOpenGLWidget* glpanel = dynamic_cast<QOpenGLWidget*>(panel); return glpanel->context(); } @@ -1755,9 +1924,9 @@ void MainWindow::dragEnterEvent(QDragEnterEvent* event) QStringList acceptedExts{".nds", ".srl", ".dsi", ".gba", ".rar", ".zip", ".7z", ".tar", ".tar.gz", ".tar.xz", ".tar.bz2"}; - for(const QString &ext : acceptedExts) + for (const QString &ext : acceptedExts) { - if(filename.endsWith(ext, Qt::CaseInsensitive)) + if (filename.endsWith(ext, Qt::CaseInsensitive)) event->acceptProposedAction(); } } @@ -1769,69 +1938,79 @@ void MainWindow::dropEvent(QDropEvent* event) QList<QUrl> urls = event->mimeData()->urls(); if (urls.count() > 1) return; // not handling more than one file at once - emuThread->emuPause(); - QString filename = urls.at(0).toLocalFile(); - QString ext = filename.right(3).toLower(); + QStringList arcexts{".zip", ".7z", ".rar", ".tar", ".tar.gz", ".tar.xz", ".tar.bz2"}; - recentFileList.removeAll(filename); - recentFileList.prepend(filename); - updateRecentFilesMenu(); - - char _filename[1024]; - strncpy(_filename, filename.toStdString().c_str(), 1023); _filename[1023] = '\0'; + emuThread->emuPause(); - int slot; int res; - if (ext == "gba") - { - slot = 1; - res = Frontend::LoadROM(_filename, Frontend::ROMSlot_GBA); - } - else if(ext == "nds" || ext == "srl" || ext == "dsi") + if (!verifySetup()) { - slot = 0; - res = Frontend::LoadROM(_filename, Frontend::ROMSlot_NDS); + emuThread->emuUnpause(); + return; } - else + + for (const QString &ext : arcexts) { - QByteArray romBuffer; - QString romFileName = pickAndExtractFileFromArchive(_filename, &romBuffer); - if(romFileName.isEmpty()) - { - res = Frontend::Load_ROMLoadError; - } - else + if (filename.endsWith(ext, Qt::CaseInsensitive)) { - slot = (romFileName.endsWith(".gba", Qt::CaseInsensitive) ? 1 : 0); - QString sramFileName = QFileInfo(_filename).absolutePath() + QDir::separator() + QFileInfo(romFileName).completeBaseName() + ".sav"; - - if(slot == 0) - strncpy(Frontend::NDSROMExtension, QFileInfo(romFileName).suffix().toStdString().c_str(), 4); + QString arcfile = pickFileFromArchive(filename); + if (arcfile.isEmpty()) + { + emuThread->emuUnpause(); + return; + } - res = Frontend::LoadROM((const u8*)romBuffer.constData(), romBuffer.size(), - _filename, romFileName.toStdString().c_str(), sramFileName.toStdString().c_str(), - slot); + filename += "|" + arcfile; } } - if (res != Frontend::Load_OK) - { - QMessageBox::critical(this, - "melonDS", - loadErrorStr(res)); - emuThread->emuUnpause(); - } - else if (slot == 1) + QStringList file = filename.split('|'); + + if (filename.endsWith(".gba", Qt::CaseInsensitive)) { - // checkme + if (!ROMManager::LoadGBAROM(file)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + emuThread->emuUnpause(); + + updateCartInserted(true); } else { + if (!ROMManager::LoadROM(file, true)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + + recentFileList.removeAll(filename); + recentFileList.prepend(filename); + updateRecentFilesMenu(); + + NDS::Start(); emuThread->emuRun(); + + updateCartInserted(false); } } +void MainWindow::focusInEvent(QFocusEvent* event) +{ + audioMute(); +} + +void MainWindow::focusOutEvent(QFocusEvent* event) +{ + audioMute(); +} + void MainWindow::onAppStateChanged(Qt::ApplicationState state) { if (state == Qt::ApplicationInactive) @@ -1846,145 +2025,179 @@ void MainWindow::onAppStateChanged(Qt::ApplicationState state) } } -QString MainWindow::loadErrorStr(int error) +bool MainWindow::verifySetup() { - switch (error) + QString res = ROMManager::VerifySetup(); + if (!res.isEmpty()) { - case Frontend::Load_BIOS9Missing: - return "DS ARM9 BIOS was not found or could not be accessed. Check your emu settings."; - case Frontend::Load_BIOS9Bad: - return "DS ARM9 BIOS is not a valid BIOS dump."; + QMessageBox::critical(this, "melonDS", res); + return false; + } - case Frontend::Load_BIOS7Missing: - return "DS ARM7 BIOS was not found or could not be accessed. Check your emu settings."; - case Frontend::Load_BIOS7Bad: - return "DS ARM7 BIOS is not a valid BIOS dump."; + return true; +} - case Frontend::Load_FirmwareMissing: - return "DS firmware was not found or could not be accessed. Check your emu settings."; - case Frontend::Load_FirmwareBad: - return "DS firmware is not a valid firmware dump."; - case Frontend::Load_FirmwareNotBootable: - return "DS firmware is not bootable."; +bool MainWindow::preloadROMs(QString filename, QString gbafilename) +{ + if (!verifySetup()) + { + return false; + } - case Frontend::Load_DSiBIOS9Missing: - return "DSi ARM9 BIOS was not found or could not be accessed. Check your emu settings."; - case Frontend::Load_DSiBIOS9Bad: - return "DSi ARM9 BIOS is not a valid BIOS dump."; + bool gbaloaded = false; + if (!gbafilename.isEmpty()) + { + QStringList gbafile = gbafilename.split('|'); + if (!ROMManager::LoadGBAROM(gbafile)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); + return false; + } - case Frontend::Load_DSiBIOS7Missing: - return "DSi ARM7 BIOS was not found or could not be accessed. Check your emu settings."; - case Frontend::Load_DSiBIOS7Bad: - return "DSi ARM7 BIOS is not a valid BIOS dump."; + gbaloaded = true; + } - case Frontend::Load_DSiNANDMissing: - return "DSi NAND was not found or could not be accessed. Check your emu settings."; - case Frontend::Load_DSiNANDBad: - return "DSi NAND is not a valid NAND dump."; + QStringList file = filename.split('|'); + if (!ROMManager::LoadROM(file, true)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + return false; + } - case Frontend::Load_ROMLoadError: - return "Failed to load the ROM. Make sure the file is accessible and isn't used by another application."; + recentFileList.removeAll(filename); + recentFileList.prepend(filename); + updateRecentFilesMenu(); + + NDS::Start(); + emuThread->emuRun(); - default: return "Unknown error during launch; smack Arisotura."; + updateCartInserted(false); + + if (gbaloaded) + { + updateCartInserted(true); } + + return true; } -void MainWindow::loadROM(QByteArray *romData, QString archiveFileName, QString romFileName) +QString MainWindow::pickFileFromArchive(QString archiveFileName) { - recentFileList.removeAll(archiveFileName); - recentFileList.prepend(archiveFileName); - updateRecentFilesMenu(); + QVector<QString> archiveROMList = Archive::ListArchive(archiveFileName); - // Strip entire archive name and get folder path - strncpy(Config::LastROMFolder, QFileInfo(archiveFileName).absolutePath().toStdString().c_str(), 1024); + QString romFileName = ""; // file name inside archive - QString sramFileName = QFileInfo(archiveFileName).absolutePath() + QDir::separator() + QFileInfo(romFileName).completeBaseName() + ".sav"; + if (archiveROMList.size() > 2) + { + archiveROMList.removeFirst(); - int slot; int res; - if (romFileName.endsWith("gba")) + bool ok; + QString toLoad = QInputDialog::getItem(this, "melonDS", + "This archive contains multiple files. Select which ROM you want to load.", archiveROMList.toList(), 0, false, &ok); + if (!ok) // User clicked on cancel + return QString(); + + romFileName = toLoad; + } + else if (archiveROMList.size() == 2) { - slot = 1; - res = Frontend::LoadROM((const u8*)romData->constData(), romData->size(), - archiveFileName.toStdString().c_str(), - romFileName.toStdString().c_str(), sramFileName.toStdString().c_str(), - Frontend::ROMSlot_GBA); + romFileName = archiveROMList.at(1); } - else + else if ((archiveROMList.size() == 1) && (archiveROMList[0] == QString("OK"))) { - strncpy(Frontend::NDSROMExtension, QFileInfo(romFileName).suffix().toStdString().c_str(), 4); - slot = 0; - res = Frontend::LoadROM((const u8*)romData->constData(), romData->size(), - archiveFileName.toStdString().c_str(), - romFileName.toStdString().c_str(), sramFileName.toStdString().c_str(), - Frontend::ROMSlot_NDS); + QMessageBox::warning(this, "melonDS", "This archive is empty."); } - - if (res != Frontend::Load_OK) + else { - QMessageBox::critical(this, - "melonDS", - loadErrorStr(res)); - emuThread->emuUnpause(); + QMessageBox::critical(this, "melonDS", "This archive could not be read. It may be corrupt or you don't have the permissions."); } - else if (slot == 1) + + return romFileName; +} + +QStringList MainWindow::pickROM(bool gba) +{ + QString console; + QStringList romexts; + QStringList arcexts{"*.zip", "*.7z", "*.rar", "*.tar", "*.tar.gz", "*.tar.xz", "*.tar.bz2"}; + QStringList ret; + + if (gba) { - // checkme - emuThread->emuUnpause(); + console = "GBA"; + romexts.append("*.gba"); } else { - emuThread->emuRun(); + console = "DS"; + romexts.append({"*.nds", "*.dsi", "*.ids", "*.srl"}); } -} -void MainWindow::loadROM(QString filename) -{ - recentFileList.removeAll(filename); - recentFileList.prepend(filename); - updateRecentFilesMenu(); + QString filter = romexts.join(' ') + " " + arcexts.join(' '); + filter = console + " ROMs (" + filter + ");;Any file (*.*)"; - // TODO: validate the input file!! - // * check that it is a proper ROM - // * ensure the binary offsets are sane - // * etc + QString filename = QFileDialog::getOpenFileName(this, + "Open "+console+" ROM", + QString::fromStdString(Config::LastROMFolder), + filter); + if (filename.isEmpty()) + return ret; - // this shit is stupid - char file[1024]; - strncpy(file, filename.toStdString().c_str(), 1023); file[1023] = '\0'; + int pos = filename.length() - 1; + while (filename[pos] != '/' && filename[pos] != '\\' && pos > 0) pos--; + QString path_dir = filename.left(pos); + QString path_file = filename.mid(pos+1); - int pos = strlen(file)-1; - while (file[pos] != '/' && file[pos] != '\\' && pos > 0) pos--; - strncpy(Config::LastROMFolder, file, pos); - Config::LastROMFolder[pos] = '\0'; - char* ext = &file[strlen(file)-3]; + Config::LastROMFolder = path_dir.toStdString(); - int slot; int res; - if (!strcasecmp(ext, "gba")) + bool isarc = false; + for (const auto& ext : arcexts) { - slot = 1; - res = Frontend::LoadROM(file, Frontend::ROMSlot_GBA); + int l = ext.length() - 1; + if (path_file.right(l).toLower() == ext.right(l)) + { + isarc = true; + break; + } } - else + + if (isarc) { - slot = 0; - res = Frontend::LoadROM(file, Frontend::ROMSlot_NDS); - } + path_file = pickFileFromArchive(filename); + if (path_file.isEmpty()) + return ret; - if (res != Frontend::Load_OK) + ret.append(filename); + ret.append(path_file); + } + else { - QMessageBox::critical(this, - "melonDS", - loadErrorStr(res)); - emuThread->emuUnpause(); + ret.append(filename); } - else if (slot == 1) + + return ret; +} + +void MainWindow::updateCartInserted(bool gba) +{ + bool inserted; + if (gba) { - // checkme - emuThread->emuUnpause(); + inserted = ROMManager::GBACartInserted() && (Config::ConsoleType == 0); + actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + actEjectGBACart->setEnabled(inserted); } else { - emuThread->emuRun(); + inserted = ROMManager::CartInserted(); + actCurrentCart->setText("DS slot: " + ROMManager::CartLabel()); + actEjectCart->setEnabled(inserted); + actImportSavefile->setEnabled(inserted); + actSetupCheats->setEnabled(inserted); + actROMInfo->setEnabled(inserted); + actRAMInfo->setEnabled(inserted); } } @@ -1992,99 +2205,43 @@ void MainWindow::onOpenFile() { emuThread->emuPause(); - QString filename = QFileDialog::getOpenFileName(this, - "Open ROM", - Config::LastROMFolder, - "DS ROMs (*.nds *.dsi *.srl);;GBA ROMs (*.gba *.zip);;Any file (*.*)"); - if (filename.isEmpty()) + if (!verifySetup()) { emuThread->emuUnpause(); return; } - loadROM(filename); -} - -void MainWindow::onOpenFileArchive() -{ - emuThread->emuPause(); - - QString archiveFileName = QFileDialog::getOpenFileName(this, - "Open ROM Archive", - Config::LastROMFolder, - "Archived ROMs (*.zip *.7z *.rar *.tar *.tar.gz *.tar.xz *.tar.bz2);;Any file (*.*)"); - if (archiveFileName.isEmpty()) + QStringList file = pickROM(false); + if (file.isEmpty()) { emuThread->emuUnpause(); return; } - QByteArray romBuffer; - QString romFileName = pickAndExtractFileFromArchive(archiveFileName, &romBuffer); - if(!romFileName.isEmpty()) + if (!ROMManager::LoadROM(file, true)) { - loadROM(&romBuffer, archiveFileName, romFileName); + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; } -} -QString MainWindow::pickAndExtractFileFromArchive(QString archiveFileName, QByteArray *romBuffer) -{ - printf("Finding list of ROMs...\n"); - QVector<QString> archiveROMList = Archive::ListArchive(archiveFileName.toUtf8().constData()); - - - QString romFileName; // file name inside archive - - if (archiveROMList.size() > 2) - { - archiveROMList.removeFirst(); - - bool ok; - QString toLoad = QInputDialog::getItem(this, "melonDS", - "The archive was found to have multiple files. Select which ROM you want to load.", archiveROMList.toList(), 0, false, &ok); - if(!ok) // User clicked on cancel - return QString(); + QString filename = file.join('|'); + recentFileList.removeAll(filename); + recentFileList.prepend(filename); + updateRecentFilesMenu(); - printf("Extracting '%s'\n", toLoad.toUtf8().constData()); - QVector<QString> extractResult = Archive::ExtractFileFromArchive(archiveFileName.toUtf8().constData(), toLoad.toUtf8().constData(), romBuffer); - if (extractResult[0] != QString("Err")) - { - romFileName = extractResult[0]; - } - else - { - QMessageBox::critical(this, "melonDS", QString("There was an error while trying to extract the ROM from the archive: ") + extractResult[1]); - } - } - else if (archiveROMList.size() == 2) - { - printf("Extracting the only ROM in archive\n"); - QVector<QString> extractResult = Archive::ExtractFileFromArchive(archiveFileName.toUtf8().constData(), archiveROMList.at(1).toUtf8().constData(), romBuffer); - if (extractResult[0] != QString("Err")) - { - romFileName = extractResult[0]; - } - else - { - QMessageBox::critical(this, "melonDS", QString("There was an error while trying to extract the ROM from the archive: ") + extractResult[1]); - } - } - else if ((archiveROMList.size() == 1) && (archiveROMList[0] == QString("OK"))) - { - QMessageBox::warning(this, "melonDS", "The archive is intact, but there are no files inside."); - } - else - { - QMessageBox::critical(this, "melonDS", "The archive could not be read. It may be corrupt or you don't have the permissions."); - } + NDS::Start(); + emuThread->emuRun(); - return romFileName; + updateCartInserted(false); } void MainWindow::onClearRecentFiles() { recentFileList.clear(); - memset(Config::RecentROMList, 0, 10 * 1024); + for (int i = 0; i < 10; i++) + Config::RecentROMList[i] = ""; updateRecentFilesMenu(); } @@ -2092,8 +2249,10 @@ void MainWindow::updateRecentFilesMenu() { recentMenu->clear(); - for(int i = 0; i < recentFileList.size(); ++i) + for (int i = 0; i < recentFileList.size(); ++i) { + if (i >= 10) break; + QString item_full = recentFileList.at(i); QString item_display = item_full; int itemlen = item_full.length(); @@ -2120,16 +2279,18 @@ void MainWindow::updateRecentFilesMenu() actRecentFile_i->setData(item_full); connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile); - if(i < 10) - strncpy(Config::RecentROMList[i], recentFileList.at(i).toStdString().c_str(), 1024); + Config::RecentROMList[i] = recentFileList.at(i).toStdString(); } + while (recentFileList.size() > 10) + recentFileList.removeLast(); + recentMenu->addSeparator(); QAction *actClearRecentList = recentMenu->addAction("Clear"); connect(actClearRecentList, &QAction::triggered, this, &MainWindow::onClearRecentFiles); - if(recentFileList.empty()) + if (recentFileList.empty()) actClearRecentList->setEnabled(false); Config::Save(); @@ -2138,48 +2299,139 @@ void MainWindow::updateRecentFilesMenu() void MainWindow::onClickRecentFile() { QAction *act = (QAction *)sender(); - QString fileName = act->data().toString(); + QString filename = act->data().toString(); + QStringList file = filename.split('|'); + + emuThread->emuPause(); - if (fileName.endsWith(".gba", Qt::CaseInsensitive) || - fileName.endsWith(".nds", Qt::CaseInsensitive) || - fileName.endsWith(".srl", Qt::CaseInsensitive) || - fileName.endsWith(".dsi", Qt::CaseInsensitive)) + if (!verifySetup()) { - emuThread->emuPause(); - loadROM(fileName); + emuThread->emuUnpause(); + return; } - else + + if (!ROMManager::LoadROM(file, true)) { - // Archives - QString archiveFileName = fileName; - QByteArray romBuffer; - QString romFileName = MainWindow::pickAndExtractFileFromArchive(archiveFileName, &romBuffer); - if(!romFileName.isEmpty()) - { - emuThread->emuPause(); - loadROM(&romBuffer, archiveFileName, romFileName); - } + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; } + + recentFileList.removeAll(filename); + recentFileList.prepend(filename); + updateRecentFilesMenu(); + + NDS::Start(); + emuThread->emuRun(); + + updateCartInserted(false); } void MainWindow::onBootFirmware() { - // TODO: check the whole GBA cart shito + emuThread->emuPause(); + if (!verifySetup()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadBIOS()) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "This firmware is not bootable."); + emuThread->emuUnpause(); + return; + } + + NDS::Start(); + emuThread->emuRun(); +} + +void MainWindow::onInsertCart() +{ emuThread->emuPause(); - int res = Frontend::LoadBIOS(); - if (res != Frontend::Load_OK) + QStringList file = pickROM(false); + if (file.isEmpty()) { - QMessageBox::critical(this, - "melonDS", - loadErrorStr(res)); emuThread->emuUnpause(); + return; } - else + + if (!ROMManager::LoadROM(file, false)) { - emuThread->emuRun(); + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; } + + emuThread->emuUnpause(); + + updateCartInserted(false); +} + +void MainWindow::onEjectCart() +{ + emuThread->emuPause(); + + ROMManager::EjectCart(); + + emuThread->emuUnpause(); + + updateCartInserted(false); +} + +void MainWindow::onInsertGBACart() +{ + emuThread->emuPause(); + + QStringList file = pickROM(true); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadGBAROM(file)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onInsertGBAAddon() +{ + QAction* act = (QAction*)sender(); + int type = act->data().toInt(); + + emuThread->emuPause(); + + ROMManager::LoadGBAAddon(type); + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onEjectGBACart() +{ + emuThread->emuPause(); + + ROMManager::EjectGBACart(); + + emuThread->emuUnpause(); + + updateCartInserted(true); } void MainWindow::onSaveState() @@ -2188,17 +2440,17 @@ void MainWindow::onSaveState() emuThread->emuPause(); - char filename[1024]; + std::string filename; if (slot > 0) { - Frontend::GetSavestateName(slot, filename, 1024); + filename = ROMManager::GetSavestateName(slot); } else { // TODO: specific 'last directory' for savestate files? QString qfilename = QFileDialog::getSaveFileName(this, "Save state", - Config::LastROMFolder, + QString::fromStdString(Config::LastROMFolder), "melonDS savestates (*.mln);;Any file (*.*)"); if (qfilename.isEmpty()) { @@ -2206,10 +2458,10 @@ void MainWindow::onSaveState() return; } - strncpy(filename, qfilename.toStdString().c_str(), 1023); filename[1023] = '\0'; + filename = qfilename.toStdString(); } - if (Frontend::SaveState(filename)) + if (ROMManager::SaveState(filename)) { char msg[64]; if (slot > 0) sprintf(msg, "State saved to slot %d", slot); @@ -2232,17 +2484,17 @@ void MainWindow::onLoadState() emuThread->emuPause(); - char filename[1024]; + std::string filename; if (slot > 0) { - Frontend::GetSavestateName(slot, filename, 1024); + filename = ROMManager::GetSavestateName(slot); } else { // TODO: specific 'last directory' for savestate files? QString qfilename = QFileDialog::getOpenFileName(this, "Load state", - Config::LastROMFolder, + QString::fromStdString(Config::LastROMFolder), "melonDS savestates (*.ml*);;Any file (*.*)"); if (qfilename.isEmpty()) { @@ -2250,7 +2502,7 @@ void MainWindow::onLoadState() return; } - strncpy(filename, qfilename.toStdString().c_str(), 1023); filename[1023] = '\0'; + filename = qfilename.toStdString(); } if (!Platform::FileExists(filename)) @@ -2264,7 +2516,7 @@ void MainWindow::onLoadState() return; } - if (Frontend::LoadState(filename)) + if (ROMManager::LoadState(filename)) { char msg[64]; if (slot > 0) sprintf(msg, "State loaded from slot %d", slot); @@ -2284,7 +2536,7 @@ void MainWindow::onLoadState() void MainWindow::onUndoStateLoad() { emuThread->emuPause(); - Frontend::UndoStateLoad(); + ROMManager::UndoStateLoad(); emuThread->emuUnpause(); OSD::AddMessage(0, "State load undone"); @@ -2292,36 +2544,52 @@ void MainWindow::onUndoStateLoad() void MainWindow::onImportSavefile() { - if (!RunningSomething) return; - emuThread->emuPause(); QString path = QFileDialog::getOpenFileName(this, "Select savefile", - Config::LastROMFolder, + QString::fromStdString(Config::LastROMFolder), "Savefiles (*.sav *.bin *.dsv);;Any file (*.*)"); - if (!path.isEmpty()) + if (path.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + FILE* f = Platform::OpenFile(path.toStdString(), "rb", true); + if (!f) + { + QMessageBox::critical(this, "melonDS", "Could not open the given savefile."); + emuThread->emuUnpause(); + return; + } + + if (RunningSomething) { if (QMessageBox::warning(this, - "Emulation will be reset and data overwritten", + "melonDS", "The emulation will be reset and the current savefile overwritten.", - QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok) + QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) { - int res = Frontend::Reset(); - if (res != Frontend::Load_OK) - { - QMessageBox::critical(this, "melonDS", "Reset failed\n" + loadErrorStr(res)); - } - else - { - int diff = Frontend::ImportSRAM(path.toStdString().c_str()); - if (diff > 0) - OSD::AddMessage(0, "Trimmed savefile"); - else if (diff < 0) - OSD::AddMessage(0, "Savefile shorter than SRAM"); - } + emuThread->emuUnpause(); + return; } + + ROMManager::Reset(); } + + u32 len; + fseek(f, 0, SEEK_END); + len = (u32)ftell(f); + + u8* data = new u8[len]; + fseek(f, 0, SEEK_SET); + fread(data, len, 1, f); + + NDS::LoadSave(data, len); + delete[] data; + + fclose(f); emuThread->emuUnpause(); } @@ -2360,19 +2628,10 @@ void MainWindow::onReset() actUndoStateLoad->setEnabled(false); - int res = Frontend::Reset(); - if (res != Frontend::Load_OK) - { - QMessageBox::critical(this, - "melonDS", - loadErrorStr(res)); - emuThread->emuUnpause(); - } - else - { - OSD::AddMessage(0, "Reset"); - emuThread->emuRun(); - } + ROMManager::Reset(); + + OSD::AddMessage(0, "Reset"); + emuThread->emuRun(); } void MainWindow::onStop() @@ -2393,7 +2652,7 @@ void MainWindow::onFrameStep() void MainWindow::onEnableCheats(bool checked) { Config::EnableCheats = checked?1:0; - Frontend::EnableCheats(Config::EnableCheats != 0); + ROMManager::EnableCheats(Config::EnableCheats != 0); } void MainWindow::onSetupCheats() @@ -2414,11 +2673,33 @@ void MainWindow::onROMInfo() ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this); } +void MainWindow::onRAMInfo() +{ + RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this); +} + void MainWindow::onOpenTitleManager() { TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); } +void MainWindow::onMPNewInstance() +{ + //QProcess::startDetached(QApplication::applicationFilePath()); + QProcess newinst; + newinst.setProgram(QApplication::applicationFilePath()); + newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); + +#ifdef __WIN32__ + newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) + { + args->flags |= CREATE_NEW_CONSOLE; + }); +#endif + + newinst.startDetached(); +} + void MainWindow::onOpenEmuSettings() { emuThread->emuPause(); @@ -2431,11 +2712,33 @@ void MainWindow::onEmuSettingsDialogFinished(int res) { emuThread->emuUnpause(); + if (Config::ConsoleType == 1) + { + actInsertGBACart->setEnabled(false); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(false); + actEjectGBACart->setEnabled(false); + } + else + { + actInsertGBACart->setEnabled(true); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(true); + actEjectGBACart->setEnabled(ROMManager::GBACartInserted()); + } + if (EmuSettingsDialog::needsReset) onReset(); + actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + if (!RunningSomething) - actTitleManager->setEnabled(strlen(Config::DSiNANDPath) > 0); + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); +} + +void MainWindow::onOpenPowerManagement() +{ + PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this); } void MainWindow::onOpenInputConfig() @@ -2457,6 +2760,27 @@ void MainWindow::onOpenVideoSettings() connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings); } +void MainWindow::onOpenCameraSettings() +{ + emuThread->emuPause(); + + camStarted[0] = camManager[0]->isStarted(); + camStarted[1] = camManager[1]->isStarted(); + if (camStarted[0]) camManager[0]->stop(); + if (camStarted[1]) camManager[1]->stop(); + + CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this); + connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished); +} + +void MainWindow::onCameraSettingsFinished(int res) +{ + if (camStarted[0]) camManager[0]->start(); + if (camStarted[1]) camManager[1]->start(); + + emuThread->emuUnpause(); +} + void MainWindow::onOpenAudioSettings() { AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this); @@ -2480,6 +2804,22 @@ void MainWindow::onFirmwareSettingsFinished(int res) emuThread->emuUnpause(); } +void MainWindow::onOpenPathSettings() +{ + emuThread->emuPause(); + + PathSettingsDialog* dlg = PathSettingsDialog::openDlg(this); + connect(dlg, &PathSettingsDialog::finished, this, &MainWindow::onPathSettingsFinished); +} + +void MainWindow::onPathSettingsFinished(int res) +{ + if (PathSettingsDialog::needsReset) + onReset(); + + emuThread->emuUnpause(); +} + void MainWindow::onUpdateAudioSettings() { SPU::SetInterpolation(Config::AudioInterp); @@ -2515,6 +2855,22 @@ void MainWindow::onAudioSettingsFinished(int res) micOpen(); } +void MainWindow::onOpenMPSettings() +{ + emuThread->emuPause(); + + MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this); + connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished); +} + +void MainWindow::onMPSettingsFinished(int res) +{ + audioMute(); + LocalMP::SetRecvTimeout(Config::MPRecvTimeout); + + emuThread->emuUnpause(); +} + void MainWindow::onOpenWifiSettings() { emuThread->emuPause(); @@ -2525,12 +2881,6 @@ void MainWindow::onOpenWifiSettings() void MainWindow::onWifiSettingsFinished(int res) { - if (Wifi::MPInited) - { - Platform::MP_DeInit(); - Platform::MP_Init(); - } - Platform::LAN_DeInit(); Platform::LAN_Init(); @@ -2550,10 +2900,7 @@ void MainWindow::onOpenInterfaceSettings() void MainWindow::onUpdateMouseTimer() { - if (hasOGL) - panelGL->mouseTimer->setInterval(Config::MouseHideSeconds*1000); - else - panelNative->mouseTimer->setInterval(Config::MouseHideSeconds*1000); + panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000); } void MainWindow::onInterfaceSettingsFinished(int res) @@ -2569,8 +2916,8 @@ void MainWindow::onChangeSavestateSRAMReloc(bool checked) void MainWindow::onChangeScreenSize() { int factor = ((QAction*)sender())->data().toInt(); - QSize diff = size() - panel->size(); - resize(dynamic_cast<ScreenHandler*>(panel)->screenGetMinSize(factor) + diff); + QSize diff = size() - panelWidget->size(); + resize(panel->screenGetMinSize(factor) + diff); } void MainWindow::onChangeScreenRotation(QAction* act) @@ -2601,6 +2948,22 @@ void MainWindow::onChangeScreenSwap(bool checked) { Config::ScreenSwap = checked?1:0; + // Swap between top and bottom screen when displaying one screen. + if (Config::ScreenSizing == screenSizing_TopOnly) + { + // Bottom Screen. + Config::ScreenSizing = screenSizing_BotOnly; + actScreenSizing[screenSizing_TopOnly]->setChecked(false); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + } + else if (Config::ScreenSizing == screenSizing_BotOnly) + { + // Top Screen. + Config::ScreenSizing = screenSizing_TopOnly; + actScreenSizing[screenSizing_BotOnly]->setChecked(false); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + } + emit screenLayoutChange(); } @@ -2612,18 +2975,19 @@ void MainWindow::onChangeScreenSizing(QAction* act) emit screenLayoutChange(); } -void MainWindow::onChangeScreenAspectTop(QAction* act) +void MainWindow::onChangeScreenAspect(QAction* act) { int aspect = act->data().toInt(); - Config::ScreenAspectTop = aspect; - - emit screenLayoutChange(); -} + QActionGroup* group = act->actionGroup(); -void MainWindow::onChangeScreenAspectBot(QAction* act) -{ - int aspect = act->data().toInt(); - Config::ScreenAspectBot = aspect; + if (group == grpScreenAspectTop) + { + Config::ScreenAspectTop = aspect; + } + else + { + Config::ScreenAspectBot = aspect; + } emit screenLayoutChange(); } @@ -2678,39 +3042,24 @@ void MainWindow::onFullscreenToggled() void MainWindow::onEmuStart() { - // TODO: make savestates work in DSi mode!! - if (Config::ConsoleType == 1) + for (int i = 1; i < 9; i++) { - for (int i = 0; i < 9; i++) - { - actSaveState[i]->setEnabled(false); - actLoadState[i]->setEnabled(false); - } - actUndoStateLoad->setEnabled(false); - } - else - { - for (int i = 1; i < 9; i++) - { - actSaveState[i]->setEnabled(true); - actLoadState[i]->setEnabled(Frontend::SavestateExists(i)); - } - actSaveState[0]->setEnabled(true); - actLoadState[0]->setEnabled(true); - actUndoStateLoad->setEnabled(false); + actSaveState[i]->setEnabled(true); + actLoadState[i]->setEnabled(ROMManager::SavestateExists(i)); } + actSaveState[0]->setEnabled(true); + actLoadState[0]->setEnabled(true); + actUndoStateLoad->setEnabled(false); actPause->setEnabled(true); actPause->setChecked(false); actReset->setEnabled(true); actStop->setEnabled(true); actFrameStep->setEnabled(true); - actImportSavefile->setEnabled(true); - actSetupCheats->setEnabled(true); - actTitleManager->setEnabled(false); + actPowerManagement->setEnabled(true); - actROMInfo->setEnabled(true); + actTitleManager->setEnabled(false); } void MainWindow::onEmuStop() @@ -2723,17 +3072,15 @@ void MainWindow::onEmuStop() actLoadState[i]->setEnabled(false); } actUndoStateLoad->setEnabled(false); - actImportSavefile->setEnabled(false); actPause->setEnabled(false); actReset->setEnabled(false); actStop->setEnabled(false); actFrameStep->setEnabled(false); - actSetupCheats->setEnabled(false); - actTitleManager->setEnabled(strlen(Config::DSiNANDPath) > 0); + actPowerManagement->setEnabled(false); - actROMInfo->setEnabled(false); + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); } void MainWindow::onUpdateVideoSettings(bool glchange) @@ -2743,16 +3090,10 @@ void MainWindow::onUpdateVideoSettings(bool glchange) emuThread->emuPause(); if (hasOGL) - { emuThread->deinitOpenGL(); - delete panelGL; - } - else - { - delete panelNative; - } + delete panel; createScreenPanel(); - connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); + connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint())); if (hasOGL) emuThread->initOpenGL(); } @@ -2767,9 +3108,6 @@ void emuStop() { RunningSomething = false; - Frontend::UnloadROM(Frontend::ROMSlot_NDS); - Frontend::UnloadROM(Frontend::ROMSlot_GBA); - emit emuThread->windowEmuStop(); OSD::AddMessage(0xFFC040, "Shutdown"); @@ -2788,7 +3126,8 @@ bool MelonApplication::event(QEvent *event) QFileOpenEvent *openEvent = static_cast<QFileOpenEvent*>(event); emuThread->emuPause(); - mainWindow->loadROM(openEvent->file()); + if (!mainWindow->preloadROMs(openEvent->file(), "")) + emuThread->emuUnpause(); } return QApplication::event(event); @@ -2796,7 +3135,7 @@ bool MelonApplication::event(QEvent *event) int main(int argc, char** argv) { - srand(time(NULL)); + srand(time(nullptr)); printf("melonDS " MELONDS_VERSION "\n"); printf(MELONDS_URL "\n"); @@ -2816,9 +3155,13 @@ int main(int argc, char** argv) { printf("SDL couldn't init joystick\n"); } - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) + if (SDL_Init(SDL_INIT_AUDIO) < 0) { - QMessageBox::critical(NULL, "melonDS", "SDL shat itself :("); + const char* err = SDL_GetError(); + QString errorStr = "Failed to initialize SDL. This could indicate an issue with your audio driver.\n\nThe error was: "; + errorStr += err; + + QMessageBox::critical(NULL, "melonDS", errorStr); return 1; } @@ -2843,7 +3186,7 @@ int main(int argc, char** argv) SANITIZE(Config::ScreenRotation, 0, 3); SANITIZE(Config::ScreenGap, 0, 500); SANITIZE(Config::ScreenLayout, 0, 3); - SANITIZE(Config::ScreenSizing, 0, 5); + SANITIZE(Config::ScreenSizing, 0, (int)screenSizing_MAX); SANITIZE(Config::ScreenAspectTop, 0, 4); SANITIZE(Config::ScreenAspectBot, 0, 4); #undef SANITIZE @@ -2856,6 +3199,7 @@ int main(int argc, char** argv) format.setSwapInterval(0); QSurfaceFormat::setDefaultFormat(format); + audioMuted = false; audioSync = SDL_CreateCond(); audioSyncLock = SDL_CreateMutex(); @@ -2881,13 +3225,18 @@ int main(int argc, char** argv) micDevice = 0; - memset(micExtBuffer, 0, sizeof(micExtBuffer)); micExtBufferWritePos = 0; micWavBuffer = nullptr; - Frontend::Init_ROM(); - Frontend::EnableCheats(Config::EnableCheats != 0); + camStarted[0] = false; + camStarted[1] = false; + camManager[0] = new CameraManager(0, 640, 480, true); + camManager[1] = new CameraManager(1, 640, 480, true); + camManager[0]->setXFlip(Config::Camera[0].XFlip); + camManager[1]->setXFlip(Config::Camera[1].XFlip); + + ROMManager::EnableCheats(Config::EnableCheats != 0); Frontend::Init_Audio(audioFreq); @@ -2910,33 +3259,17 @@ int main(int argc, char** argv) emuThread->start(); emuThread->emuPause(); + audioMute(); + QObject::connect(&melon, &QApplication::applicationStateChanged, mainWindow, &MainWindow::onAppStateChanged); if (argc > 1) { - char* file = argv[1]; - char* ext = &file[strlen(file)-3]; - - if (!strcasecmp(ext, "nds") || !strcasecmp(ext, "srl") || !strcasecmp(ext, "dsi")) - { - int res = Frontend::LoadROM(file, Frontend::ROMSlot_NDS); + QString file = argv[1]; + QString gbafile = ""; + if (argc > 2) gbafile = argv[2]; - if (res == Frontend::Load_OK) - { - if (argc > 2) - { - file = argv[2]; - ext = &file[strlen(file)-3]; - - if (!strcasecmp(ext, "gba")) - { - Frontend::LoadROM(file, Frontend::ROMSlot_GBA); - } - } - - emuThread->emuRun(); - } - } + mainWindow->preloadROMs(file, gbafile); } int ret = melon.exec(); @@ -2947,8 +3280,6 @@ int main(int argc, char** argv) Input::CloseJoystick(); - Frontend::DeInit_ROM(); - if (audioDevice) SDL_CloseAudioDevice(audioDevice); micClose(); @@ -2957,6 +3288,9 @@ int main(int argc, char** argv) if (micWavBuffer) delete[] micWavBuffer; + delete camManager[0]; + delete camManager[1]; + Config::Save(); SDL_Quit(); @@ -2972,7 +3306,7 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho { int argc = 0; wchar_t** argv_w = CommandLineToArgvW(GetCommandLineW(), &argc); - char* nullarg = ""; + char nullarg[] = {'\0'}; char** argv = new char*[argc]; for (int i = 0; i < argc; i++) @@ -2987,7 +3321,8 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho if (argv_w) LocalFree(argv_w); - /*if (AttachConsole(ATTACH_PARENT_PROCESS)) + //if (AttachConsole(ATTACH_PARENT_PROCESS)) + /*if (AllocConsole()) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 0b5e917..1977b7f 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -101,7 +101,8 @@ class ScreenHandler Q_GADGET public: - virtual ~ScreenHandler() {} + ScreenHandler(QWidget* widget); + virtual ~ScreenHandler(); QTimer* setupMouseTimer(); void updateMouseTimer(); QTimer* mouseTimer; @@ -121,7 +122,7 @@ protected: int screenKind[Frontend::MaxScreenTransforms]; int numScreens; - bool touching; + bool touching = false; void showCursor(); }; @@ -133,7 +134,7 @@ class ScreenPanelNative : public QWidget, public ScreenHandler public: explicit ScreenPanelNative(QWidget* parent); - ~ScreenPanelNative(); + virtual ~ScreenPanelNative(); protected: void paintEvent(QPaintEvent* event) override; @@ -163,7 +164,7 @@ class ScreenPanelGL : public QOpenGLWidget, public ScreenHandler, protected QOpe public: explicit ScreenPanelGL(QWidget* parent); - ~ScreenPanelGL(); + virtual ~ScreenPanelGL(); protected: void initializeGL() override; @@ -211,8 +212,7 @@ public: bool hasOGL; QOpenGLContext* getOGLContext(); - void loadROM(QString filename); - void loadROM(QByteArray *romData, QString archiveFileName, QString romFileName); + bool preloadROMs(QString filename, QString gbafilename); void onAppStateChanged(Qt::ApplicationState state); @@ -226,15 +226,22 @@ protected: void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + signals: void screenLayoutChange(); private slots: void onOpenFile(); - void onOpenFileArchive(); void onClickRecentFile(); void onClearRecentFiles(); void onBootFirmware(); + void onInsertCart(); + void onEjectCart(); + void onInsertGBACart(); + void onInsertGBAAddon(); + void onEjectGBACart(); void onSaveState(); void onLoadState(); void onUndoStateLoad(); @@ -249,20 +256,29 @@ private slots: void onSetupCheats(); void onCheatsDialogFinished(int res); void onROMInfo(); + void onRAMInfo(); void onOpenTitleManager(); + void onMPNewInstance(); void onOpenEmuSettings(); void onEmuSettingsDialogFinished(int res); + void onOpenPowerManagement(); void onOpenInputConfig(); void onInputConfigFinished(int res); void onOpenVideoSettings(); + void onOpenCameraSettings(); + void onCameraSettingsFinished(int res); void onOpenAudioSettings(); - void onOpenFirmwareSettings(); void onUpdateAudioSettings(); void onAudioSettingsFinished(int res); + void onOpenMPSettings(); + void onMPSettingsFinished(int res); void onOpenWifiSettings(); void onWifiSettingsFinished(int res); + void onOpenFirmwareSettings(); void onFirmwareSettingsFinished(int res); + void onOpenPathSettings(); + void onPathSettingsFinished(int res); void onOpenInterfaceSettings(); void onInterfaceSettingsFinished(int res); void onUpdateMouseTimer(); @@ -273,8 +289,7 @@ private slots: void onChangeScreenLayout(QAction* act); void onChangeScreenSwap(bool checked); void onChangeScreenSizing(QAction* act); - void onChangeScreenAspectTop(QAction* act); - void onChangeScreenAspectBot(QAction* act); + void onChangeScreenAspect(QAction* act); void onChangeIntegerScaling(bool checked); void onChangeScreenFiltering(bool checked); void onChangeShowOSD(bool checked); @@ -291,33 +306,41 @@ private slots: void onFullscreenToggled(); private: + QStringList currentROM; + QStringList currentGBAROM; QList<QString> recentFileList; QMenu *recentMenu; void updateRecentFilesMenu(); - QString pickAndExtractFileFromArchive(QString archiveFileName, QByteArray *romBuffer); + bool verifySetup(); + QString pickFileFromArchive(QString archiveFileName); + QStringList pickROM(bool gba); + void updateCartInserted(bool gba); void createScreenPanel(); - QString loadErrorStr(int error); - bool pausedManually = false; int oldW, oldH; bool oldMax; public: - QWidget* panel; - ScreenPanelGL* panelGL; - ScreenPanelNative* panelNative; + ScreenHandler* panel; + QWidget* panelWidget; QAction* actOpenROM; - QAction* actOpenROMArchive; QAction* actBootFirmware; + QAction* actCurrentCart; + QAction* actInsertCart; + QAction* actEjectCart; + QAction* actCurrentGBACart; + QAction* actInsertGBACart; + QAction* actInsertGBAAddon[1]; + QAction* actEjectGBACart; + QAction* actImportSavefile; QAction* actSaveState[9]; QAction* actLoadState[9]; QAction* actUndoStateLoad; - QAction* actImportSavefile; QAction* actQuit; QAction* actPause; @@ -327,14 +350,23 @@ public: QAction* actEnableCheats; QAction* actSetupCheats; QAction* actROMInfo; + QAction* actRAMInfo; QAction* actTitleManager; + QAction* actMPNewInstance; QAction* actEmuSettings; +#ifdef __APPLE__ + QAction* actPreferences; +#endif + QAction* actPowerManagement; QAction* actInputConfig; QAction* actVideoSettings; + QAction* actCameraSettings; QAction* actAudioSettings; + QAction* actMPSettings; QAction* actWifiSettings; QAction* actFirmwareSettings; + QAction* actPathSettings; QAction* actInterfaceSettings; QAction* actSavestateSRAMReloc; QAction* actScreenSize[4]; @@ -349,9 +381,9 @@ public: QAction* actScreenSizing[6]; QAction* actIntegerScaling; QActionGroup* grpScreenAspectTop; - QAction* actScreenAspectTop[4]; + QAction** actScreenAspectTop; QActionGroup* grpScreenAspectBot; - QAction* actScreenAspectBot[4]; + QAction** actScreenAspectBot; QAction* actScreenFiltering; QAction* actShowOSD; QAction* actLimitFramerate; diff --git a/src/frontend/qt_sdl/main_shaders.h b/src/frontend/qt_sdl/main_shaders.h index 0aa99ad..ca835c0 100644 --- a/src/frontend/qt_sdl/main_shaders.h +++ b/src/frontend/qt_sdl/main_shaders.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/sem_timedwait.cpp b/src/frontend/qt_sdl/sem_timedwait.cpp new file mode 100644 index 0000000..38b3c16 --- /dev/null +++ b/src/frontend/qt_sdl/sem_timedwait.cpp @@ -0,0 +1,488 @@ +/* + * s e m _ t i m e d w a i t + * + * Function: + * Implements a version of sem_timedwait(). + * + * Description: + * Not all systems implement sem_timedwait(), which is a version of + * sem_wait() with a timeout. Mac OS X is one example, at least up to + * and including version 10.6 (Leopard). If such a function is needed, + * this code provides a reasonable implementation, which I think is + * compatible with the standard version, although possibly less + * efficient. It works by creating a thread that interrupts a normal + * sem_wait() call after the specified timeout. + * + * Call: + * + * The Linux man pages say: + * + * #include <semaphore.h> + * + * int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); + * + * sem_timedwait() is the same as sem_wait(), except that abs_timeout + * specifies a limit on the amount of time that the call should block if + * the decrement cannot be immediately performed. The abs_timeout argument + * points to a structure that specifies an absolute timeout in seconds and + * nanoseconds since the Epoch (00:00:00, 1 January 1970). This structure + * is defined as follows: + * + * struct timespec { + * time_t tv_sec; Seconds + * long tv_nsec; Nanoseconds [0 .. 999999999] + * }; + * + * If the timeout has already expired by the time of the call, and the + * semaphore could not be locked immediately, then sem_timedwait() fails + * with a timeout error (errno set to ETIMEDOUT). + * If the operation can be performed immediately, then sem_timedwait() + * never fails with a timeout error, regardless of the value of abs_timeout. + * Furthermore, the validity of abs_timeout is not checked in this case. + * + * Limitations: + * + * The mechanism used involves sending a SIGUSR2 signal to the thread + * calling sem_timedwait(). The handler for this signal is set to a null + * routine which does nothing, and with any flags for the signal + * (eg SA_RESTART) cleared. Note that this effective disabling of the + * SIGUSR2 signal is a side-effect of using this routine, and means it + * may not be a completely transparent plug-in replacement for a + * 'normal' sig_timedwait() call. Since OS X does not declare the + * sem_timedwait() call in its standard include files, the relevant + * declaration (shown above in the man pages extract) will probably have + * to be added to any code that uses this. + * + * Compiling: + * This compiles and runs cleanly on OS X (10.6) with gcc with the + * -Wall -ansi -pedantic flags. On Linux, using -ansi causes a sweep of + * compiler complaints about the timespec structure, but it compiles + * and works fine with just -Wall -pedantic. (Since Linux provides + * sem_timedwait() anyway, this really isn't needed on Linux.) However, + * since Linux provides sem_timedwait anyway, the sem_timedwait() + * code in this file is only compiled on OS X, and is a null on other + * systems. + * + * Testing: + * This file contains a test program that exercises the sem_timedwait + * code. It is compiled if the pre-processor variable TEST is defined. + * For more details, see the comments for the test routine at the end + * of the file. + * + * Author: Keith Shortridge, AAO. + * + * History: + * 8th Sep 2009. Original version. KS. + * 24th Sep 2009. Added test that the calling thread still exists before + * trying to set the timed-out flag. KS. + * 2nd Oct 2009. No longer restores the original SIGUSR2 signal handler. + * See comments in the body of the code for more details. + * Prototypes for now discontinued internal routines removed. + * 12th Aug 2010. Added the cleanup handler, so that this code no longer + * leaks resources if the calling thread is cancelled. KS. + * 21st Sep 2011. Added copyright notice below. Modified header comments + * to describe the use of SIGUSR2 more accurately in the + * light of the 2/10/09 change above. Now undefs DEBUG + * before defining it, to avoid any possible clash. KS. + * 14th Feb 2012. Tidied out a number of TABs that had got into the + * code. KS. + * 6th May 2013. Copyright notice modified to one based on the MIT licence, + * which is more permissive than the previous notice. KS. + * + * Copyright (c) Australian Astronomical Observatory (AAO), (2013). + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifdef __APPLE__ + +#include <semaphore.h> +#include <time.h> +#include <sys/time.h> +#include <pthread.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <setjmp.h> + +#include "sem_timedwait.h" + +/* Some useful definitions - TRUE, FALSE, and DEBUG */ + +#undef TRUE +#define TRUE 1 +#undef FALSE +#define FALSE 0 +#undef DEBUG +#define DEBUG printf + +/* A structure of type timeoutDetails is passed to the thread used to + * implement the timeout. + */ + +typedef struct { + struct timespec delay; /* Specifies the delay, relative to now */ + pthread_t callingThread; /* The thread doing the sem_wait call */ + volatile short *timedOutShort; /* Address of a flag set to indicate that + * the timeout was triggered. */ +} timeoutDetails; + +/* A structure of type cleanupDetails is passed to the thread cleanup + * routine which is called at the end of the routine or if the thread calling + * it is cancelled. + */ + +typedef struct { + pthread_t *threadIdAddr; /* Address of the variable that holds + * the Id of the timeout thread. */ + struct sigaction *sigHandlerAddr; /* Address of the old signal action + * handler. */ + volatile short *timedOutShort; /* Address of a flag set to indicate that + * the timeout was triggered. */ +} cleanupDetails; + +/* Forward declarations of internal routines */ + +static void* timeoutThreadMain (void* passedPtr); +static int triggerSignal (int Signal, pthread_t Thread); +static void ignoreSignal (int Signal); +static void timeoutThreadCleanup (void* passedPtr); + +/* -------------------------------------------------------------------------- */ +/* + * s e m _ t i m e d w a i t + * + * This is the main code for the sem_timedwait() implementation. + */ + +int sem_timedwait ( + sem_t *sem, + const struct timespec *abs_timeout) +{ + int result = 0; /* Code returned by this routine 0 or -1 */ + + /* "Under no circumstances shall the function fail if the semaphore + * can be locked immediately". So we try to get it quickly to see if we + * can avoid all the timeout overheads. + */ + + if (sem_trywait(sem) == 0) { + + /* Yes, got it immediately. */ + + result = 0; + + } else { + + /* No, we've got to do it with a sem_wait() call and a thread to run + * the timeout. First, work out the time from now to the specified + * timeout, which we will pass to the timeout thread in a way that can + * be used to pass to nanosleep(). So we need this in seconds and + * nanoseconds. Along the way, we check for an invalid passed time, + * and for one that's already expired. + */ + + if ((abs_timeout->tv_nsec < 0) || (abs_timeout->tv_nsec > 1000000000)) { + + /* Passed time is invalid */ + + result = -1; + errno = EINVAL; + + } else { + + struct timeval currentTime; /* Time now */ + long secsToWait,nsecsToWait; /* Seconds and nsec to delay */ + gettimeofday (¤tTime,NULL); + secsToWait = abs_timeout->tv_sec - currentTime.tv_sec; + nsecsToWait = (abs_timeout->tv_nsec - (currentTime.tv_usec * 1000)); + while (nsecsToWait < 0) { + nsecsToWait += 1000000000; + secsToWait--; + } + if ((secsToWait < 0) || ((secsToWait == 0) && (nsecsToWait < 0))) { + + /* Time has passed. Report an immediate timeout. */ + + result = -1; + errno = ETIMEDOUT; + + } else { + + /* We're going to have to do a sem_wait() with a timeout thread. + * The thread will wait the specified time, then will issue a + * SIGUSR2 signal that will interrupt the sem_wait() call. + * We pass the thread the id of the current thread, the delay, + * and the address of a flag to set on a timeout, so we can + * distinguish an interrupt caused by the timeout thread from + * one caused by some other signal. + */ + + volatile short timedOut; /* Flag to set on timeout */ + timeoutDetails details; /* All the stuff the thread must know */ + struct sigaction oldSignalAction; /* Current signal setting */ + pthread_t timeoutThread; /* Id of timeout thread */ + cleanupDetails cleaningDetails; /* What the cleanup routine needs */ + int oldCancelState; /* Previous cancellation state */ + int ignoreCancelState; /* Used in call, but ignored */ + int createStatus; /* Status of pthread_create() call */ + + /* If the current thread is cancelled (and CML does do this) + * we don't want to leave our timer thread running - if we've + * started the thread we want to make sure we join it in order + * to release its resources. So we set a cleanup handler to + * do this. We pass it the address of the structure that will + * hold all it needs to know. While we set all this up, + * we prevent ourselves being cancelled, so all this data is + * coherent. + */ + + pthread_setcancelstate (PTHREAD_CANCEL_DISABLE,&oldCancelState); + timeoutThread = (pthread_t) 0; + cleaningDetails.timedOutShort = &timedOut; + cleaningDetails.threadIdAddr = &timeoutThread; + cleaningDetails.sigHandlerAddr = &oldSignalAction; + pthread_cleanup_push (timeoutThreadCleanup,&cleaningDetails); + + /* Set up the details for the thread. Clear the timeout flag, + * record the current SIGUSR2 action settings so we can restore + * them later. + */ + + details.delay.tv_sec = secsToWait; + details.delay.tv_nsec = nsecsToWait; + details.callingThread = pthread_self(); + details.timedOutShort = &timedOut; + timedOut = FALSE; + sigaction (SIGUSR2,NULL,&oldSignalAction); + + /* Start up the timeout thread. Once we've done that, we can + * restore the previous cancellation state. + */ + + createStatus = pthread_create(&timeoutThread,NULL, + timeoutThreadMain, (void*)&details); + pthread_setcancelstate (oldCancelState,&ignoreCancelState); + + if (createStatus < 0) { + + /* Failed to create thread. errno will already be set properly */ + + result = -1; + + } else { + + /* Thread created OK. This is where we wait for the semaphore. + */ + + if (sem_wait(sem) == 0) { + + /* Got the semaphore OK. We return zero, and all's well. */ + + result = 0; + + } else { + + /* If we got a -1 error from sem_wait(), it may be because + * it was interrupted by a timeout, or failed for some + * other reason. We check for the expected timeout + * condition, which is an 'interrupted' status and the + * timeout flag set by the timeout thread. We report that as + * a timeout error. Anything else is some other error and + * errno is already set properly. + */ + + result = -1; + if (errno == EINTR) { + if (timedOut) errno = ETIMEDOUT; + } + } + + } + + /* The cleanup routine - timeoutThreadCleanup() - packages up + * any tidying up that is needed, including joining with the + * timer thread. This will be called if the current thread is + * cancelled, but we need it to happen anyway, so we set the + * execute flag true here as we remove it from the list of + * cleanup routines to be called. So normally, this line amounts + * to calling timeoutThreadCleanup(). + */ + + pthread_cleanup_pop (TRUE); + } + } + } + return (result); +} + +/* -------------------------------------------------------------------------- */ +/* + * t i m e o u t T h r e a d C l e a n u p + * + * This internal routine tidies up at the end of a sem_timedwait() call. + * It is set as a cleanup routine for the current thread (not the timer + * thread) so it is executed even if the thread is cancelled. This is + * important, as we need to tidy up the timeout thread. If we took the + * semaphore (in other words, if we didn't timeout) then the timer thread + * will still be running, sitting in its nanosleep() call, and we need + * to cancel it. If the timer thread did signal a timeout then it will + * now be closing down. In either case, we need to join it (using a call + * to pthread_join()) or its resources will never be released. + * The single argument is a pointer to a cleanupDetails structure that has + * all the routine needs to know. + */ + +static void timeoutThreadCleanup (void* passedPtr) +{ + /* Get what we need from the structure we've been passed. */ + + cleanupDetails *detailsPtr = (cleanupDetails*) passedPtr; + short timedOut = *(detailsPtr->timedOutShort); + pthread_t timeoutThread = *(detailsPtr->threadIdAddr); + + /* If we created the thread, stop it - doesn't matter if it's no longer + * running, pthread_cancel can handle that. We make sure we wait for it + * to complete, because it is this pthread_join() call that releases any + * memory the thread may have allocated. Note that cancelling a thread is + * generally not a good idea, because of the difficulty of cleaning up + * after it, but this is a very simple thread that does nothing but call + * nanosleep(), and that we can cancel quite happily. + */ + + if (!timedOut) pthread_cancel(timeoutThread); + pthread_join(timeoutThread,NULL); + + /* The code originally restored the old action handler, which generally + * was the default handler that caused the task to exit. Just occasionally, + * there seem to be cases where the signal is still queued and ready to + * trigger even though the thread that presumably sent it off just before + * it was cancelled has finished. I had thought that once we'd joined + * that thread, we could be sure of not seeing the signal, but that seems + * not to be the case, and so restoring a handler that will allow the task + * to crash is not a good idea, and so the line below has been commented + * out. + * + * sigaction (SIGUSR2,detailsPtr->sigHandlerAddr,NULL); + */ +} + +/* -------------------------------------------------------------------------- */ +/* + * t i m e o u t T h r e a d M a i n + * + * This internal routine is the main code for the timeout thread. + * The single argument is a pointer to a timeoutDetails structure that has + * all the thread needs to know - thread to signal, delay time, and the + * address of a flag to set if it triggers a timeout. + */ + +static void* timeoutThreadMain (void* passedPtr) +{ + void* Return = (void*) 0; + + /* We grab all the data held in the calling thread right now. In some + * cases, we find that the calling thread has vanished and released + * its memory, including the details structure, by the time the timeout + * expires, and then we get an access violation when we try to set the + * 'timed out' flag. + */ + + timeoutDetails details = *((timeoutDetails*) passedPtr); + struct timespec requestedDelay = details.delay; + + /* We do a nanosleep() for the specified delay, and then trigger a + * timeout. Note that we allow for the case where the nanosleep() is + * interrupted, and restart it for the remaining time. If the + * thread that is doing the sem_wait() call gets the semaphore, it + * will cancel this thread, which is fine as we aren't doing anything + * other than a sleep and a signal. + */ + + for (;;) { + struct timespec remainingDelay; + if (nanosleep (&requestedDelay,&remainingDelay) == 0) { + break; + } else if (errno == EINTR) { + requestedDelay = remainingDelay; + } else { + Return = (void*) errno; + break; + } + } + + /* We've completed the delay without being cancelled, so we now trigger + * the timeout by sending a signal to the calling thread. And that's it, + * although we set the timeout flag first to indicate that it was us + * that interrupted the sem_wait() call. One precaution: before we + * try to set the timed-out flag, make sure the calling thread still + * exists - this may not be the case if things are closing down a bit + * messily. We check this quickly using a zero test signal. + */ + + if (pthread_kill(details.callingThread,0) == 0) { + *(details.timedOutShort) = TRUE; + if (triggerSignal (SIGUSR2,details.callingThread) < 0) { + Return = (void*) errno; + } + } + + return Return; +} + +/* -------------------------------------------------------------------------- */ +/* + * t r i g g e r S i g n a l + * + * This is a general purpose routine that sends a specified signal to + * a specified thread, setting up a signal handler that does nothing, + * and then giving the signal. The only effect will be to interrupt any + * operation that is currently blocking - in this case, we expect this to + * be a sem_wait() call. + */ + +static int triggerSignal (int Signal, pthread_t Thread) +{ + int Result = 0; + struct sigaction SignalDetails; + SignalDetails.sa_handler = ignoreSignal; + SignalDetails.sa_flags = 0; + (void) sigemptyset(&SignalDetails.sa_mask); + if ((Result = sigaction(Signal,&SignalDetails,NULL)) == 0) { + Result = pthread_kill(Thread,Signal); + } + return Result; +} + +/* -------------------------------------------------------------------------- */ +/* + * i g n o r e S i g n a l + * + * And this is the signal handler that does nothing. (It clears its argument, + * but this has no effect and prevents a compiler warning about an unused + * argument.) + */ + +static void ignoreSignal (int Signal) { + Signal = 0; +} + +#endif diff --git a/src/frontend/qt_sdl/sem_timedwait.h b/src/frontend/qt_sdl/sem_timedwait.h new file mode 100644 index 0000000..42ae201 --- /dev/null +++ b/src/frontend/qt_sdl/sem_timedwait.h @@ -0,0 +1,8 @@ +#ifndef __SEM_TIMEDWAIT_H +#define __SEM_TIMEDWAIT_H + +#ifdef __APPLE__ +int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); +#endif + +#endif diff --git a/src/melonDLDI.h b/src/melonDLDI.h index a770459..261f798 100644 --- a/src/melonDLDI.h +++ b/src/melonDLDI.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,9 +19,11 @@ #ifndef MELONDLDI_H #define MELONDLDI_H +#include "types.h" + const u8 melonDLDI[] = { - 0xED, 0xA5, 0x8D, 0xBF, 0x20, 0x43, 0x68, 0x69, 0x73, 0x68, 0x6D, 0x00, 0x01, 0x01, 0x00, 0x00, + 0xED, 0xA5, 0x8D, 0xBF, 0x20, 0x43, 0x68, 0x69, 0x73, 0x68, 0x6D, 0x00, 0x01, 0x09, 0x00, 0x00, 0x6D, 0x65, 0x6C, 0x6F, 0x6E, 0x44, 0x53, 0x20, 0x44, 0x4C, 0x44, 0x49, 0x20, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/src/teakra/CMakeLists.txt b/src/teakra/CMakeLists.txt index 046f8c4..e4ecb1a 100644 --- a/src/teakra/CMakeLists.txt +++ b/src/teakra/CMakeLists.txt @@ -58,6 +58,7 @@ else() -pedantic -pedantic-errors -Wfatal-errors + -Wno-error=maybe-uninitialized -Wno-missing-braces -Wno-unused-parameter) diff --git a/src/teakra/externals/catch/catch.hpp b/src/teakra/externals/catch/catch.hpp index b1b2411..71c863a 100644 --- a/src/teakra/externals/catch/catch.hpp +++ b/src/teakra/externals/catch/catch.hpp @@ -1,9 +1,9 @@ /* - * Catch v2.5.0 - * Generated: 2018-11-26 20:46:12.165372 + * Catch v2.13.8 + * Generated: 2022-01-03 21:20:09.589503 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved. + * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -14,8 +14,8 @@ #define CATCH_VERSION_MAJOR 2 -#define CATCH_VERSION_MINOR 5 -#define CATCH_VERSION_PATCH 0 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 8 #ifdef __clang__ # pragma clang system_header @@ -36,10 +36,11 @@ # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ - // GCC likes to warn on REQUIREs, and we cannot suppress them - // locally because g++'s support for _Pragma is lacking in older, - // still supported, versions -# pragma GCC diagnostic ignored "-Wparentheses" + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic ignored "-Wpadded" @@ -65,13 +66,16 @@ #if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ -# include <TargetConditionals.h> -# if TARGET_OS_OSX == 1 -# define CATCH_PLATFORM_MAC -# elif TARGET_OS_IPHONE == 1 -# define CATCH_PLATFORM_IPHONE -# endif +# include <TargetConditionals.h> +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX @@ -131,30 +135,51 @@ namespace Catch { #endif -#if defined(CATCH_CPP17_OR_GREATER) -# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + #endif -#ifdef __clang__ +#if defined(__clang__) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic pop" ) - -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic pop" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) -# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ - _Pragma( "clang diagnostic pop" ) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) #endif // __clang__ @@ -179,6 +204,7 @@ namespace Catch { // Android somehow still does not support std::to_string #if defined(__ANDROID__) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE #endif //////////////////////////////////////////////////////////////////////////////// @@ -203,20 +229,16 @@ namespace Catch { // some versions of cygwin (most) do not support std::to_string. Use the libstd check. // https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 # if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ - && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING # endif #endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ -#ifdef _MSC_VER - -# if _MSC_VER >= 1900 // Visual Studio 2015 or newer -# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -# endif +#if defined(_MSC_VER) // Universal Windows platform does not support SEH // Or console colours (or console at all...) @@ -226,13 +248,25 @@ namespace Catch { # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif +# if !defined(__clang__) // Handle Clang masquerading for msvc + // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor -# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) -# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -# endif +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL + +// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop` +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) +# endif // __clang__ + +#endif // _MSC_VER +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// @@ -265,30 +299,56 @@ namespace Catch { #endif //////////////////////////////////////////////////////////////////////////////// -// Check if string_view is available and usable -// The check is split apart to work around v140 (VS2015) preprocessor issue... -#if defined(__has_include) -#if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER) -# define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE #endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER #endif -//////////////////////////////////////////////////////////////////////////////// -// Check if variant is available and usable +// Various stdlib support checks that require __has_include #if defined(__has_include) -# if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) -# if defined(__clang__) && (__clang_major__ < 8) - // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 - // fix should be in clang 8, workaround in libstdc++ 8.2 -# include <ciso646> -# if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) -# define CATCH_CONFIG_NO_CPP17_VARIANT -# else -# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT -# endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) -# endif // defined(__clang__) && (__clang_major__ < 8) -# endif // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) -#endif // __has_include + // Check if string_view is available and usable + #if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER) + # include <cstddef> + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include <ciso646> + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) # define CATCH_CONFIG_COUNTER @@ -309,8 +369,8 @@ namespace Catch { # define CATCH_CONFIG_CPP11_TO_STRING #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) -# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) @@ -321,6 +381,10 @@ namespace Catch { # define CATCH_CONFIG_CPP17_VARIANT #endif +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + #if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) # define CATCH_INTERNAL_CONFIG_NEW_CAPTURE #endif @@ -337,17 +401,53 @@ namespace Catch { # define CATCH_CONFIG_POLYFILL_ISNAN #endif +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) @@ -407,12 +507,12 @@ namespace Catch { line( _line ) {} - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo( SourceLineInfo && ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo& operator = ( SourceLineInfo && ) = default; + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; - bool empty() const noexcept; + bool empty() const noexcept { return file[0] == '\0'; } bool operator == ( SourceLineInfo const& other ) const noexcept; bool operator < ( SourceLineInfo const& other ) const noexcept; @@ -453,9 +553,10 @@ namespace Catch { } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION // end catch_tag_alias_autoregistrar.h // start catch_test_registry.h @@ -463,7 +564,6 @@ namespace Catch { // start catch_interfaces_testcase.h #include <vector> -#include <memory> namespace Catch { @@ -474,8 +574,6 @@ namespace Catch { virtual ~ITestInvoker(); }; - using ITestCasePtr = std::shared_ptr<ITestInvoker>; - class TestCase; struct IConfig; @@ -485,6 +583,7 @@ namespace Catch { virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0; }; + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); @@ -497,55 +596,30 @@ namespace Catch { #include <cstddef> #include <string> #include <iosfwd> +#include <cassert> namespace Catch { - class StringData; - /// A non-owning string class (similar to the forthcoming std::string_view) /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. c_str() must return a null terminated - /// string, however, and so the StringRef will internally take ownership - /// (taking a copy), if necessary. In theory this ownership is not externally - /// visible - but it does mean (substring) StringRefs should not be shared between - /// threads. + /// it may not be null terminated. class StringRef { public: using size_type = std::size_t; + using const_iterator = const char*; private: - friend struct StringRefTestAccess; - - char const* m_start; - size_type m_size; - - char* m_data = nullptr; - - void takeOwnership(); - static constexpr char const* const s_empty = ""; - public: // construction/ assignment - StringRef() noexcept - : StringRef( s_empty, 0 ) - {} + char const* m_start = s_empty; + size_type m_size = 0; - StringRef( StringRef const& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ) - {} - - StringRef( StringRef&& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ), - m_data( other.m_data ) - { - other.m_data = nullptr; - } + public: // construction + constexpr StringRef() noexcept = default; StringRef( char const* rawChars ) noexcept; - StringRef( char const* rawChars, size_type size ) noexcept + constexpr StringRef( char const* rawChars, size_type size ) noexcept : m_start( rawChars ), m_size( size ) {} @@ -555,99 +629,64 @@ namespace Catch { m_size( stdString.size() ) {} - ~StringRef() noexcept { - delete[] m_data; - } - - auto operator = ( StringRef const &other ) noexcept -> StringRef& { - delete[] m_data; - m_data = nullptr; - m_start = other.m_start; - m_size = other.m_size; - return *this; + explicit operator std::string() const { + return std::string(m_start, m_size); } - operator std::string() const; - - void swap( StringRef& other ) noexcept; - public: // operators auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } - auto operator[] ( size_type index ) const noexcept -> char; + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } public: // named queries - auto empty() const noexcept -> bool { + constexpr auto empty() const noexcept -> bool { return m_size == 0; } - auto size() const noexcept -> size_type { + constexpr auto size() const noexcept -> size_type { return m_size; } - auto numberOfCharacters() const noexcept -> size_type; + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception auto c_str() const -> char const*; public: // substrings and searches - auto substr( size_type start, size_type size ) const noexcept -> StringRef; + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; - // Returns the current start pointer. - // Note that the pointer can change when if the StringRef is a substring - auto currentData() const noexcept -> char const*; + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; - private: // ownership queries - may not be consistent between calls - auto isOwned() const noexcept -> bool; - auto isSubstring() const noexcept -> bool; - }; + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } - auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; - auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; - auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { return StringRef( rawChars, size ); } - } // namespace Catch -inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { return Catch::StringRef( rawChars, size ); } // end catch_stringref.h -// start catch_type_traits.hpp - - -namespace Catch{ - -#ifdef CATCH_CPP17_OR_GREATER - template <typename...> - inline constexpr auto is_unique = std::true_type{}; - - template <typename T, typename... Rest> - inline constexpr auto is_unique<T, Rest...> = std::bool_constant< - (!std::is_same_v<T, Rest> && ...) && is_unique<Rest...> - >{}; -#else - -template <typename...> -struct is_unique : std::true_type{}; - -template <typename T0, typename T1, typename... Rest> -struct is_unique<T0, T1, Rest...> : std::integral_constant -<bool, - !std::is_same<T0, T1>::value - && is_unique<T0, Rest...>::value - && is_unique<T1, Rest...>::value ->{}; - -#endif -} - -// end catch_type_traits.hpp // start catch_preprocessor.hpp @@ -699,21 +738,224 @@ struct is_unique<T0, T1, Rest...> : std::integral_constant #define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ #define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ #define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) #define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name, __VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " - " #__VA_ARGS__ -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name,...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else -// MSVC is adding extra space and needs more calls to properly remove () -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " -" #__VA_ARGS__ -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, __VA_ARGS__) -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template<typename...> struct TypeList {};\ + template<typename...Ts>\ + constexpr auto get_wrapper() noexcept -> TypeList<Ts...> { return {}; }\ + template<template<typename...> class...> struct TemplateTypeList{};\ + template<template<typename...> class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList<Cs...> { return {}; }\ + template<typename...>\ + struct append;\ + template<typename...>\ + struct rewrap;\ + template<template<typename...> class, typename...>\ + struct create;\ + template<template<typename...> class, typename>\ + struct convert;\ + \ + template<typename T> \ + struct append<T> { using type = T; };\ + template< template<typename...> class L1, typename...E1, template<typename...> class L2, typename...E2, typename...Rest>\ + struct append<L1<E1...>, L2<E2...>, Rest...> { using type = typename append<L1<E1...,E2...>, Rest...>::type; };\ + template< template<typename...> class L1, typename...E1, typename...Rest>\ + struct append<L1<E1...>, TypeList<mpl_::na>, Rest...> { using type = L1<E1...>; };\ + \ + template< template<typename...> class Container, template<typename...> class List, typename...elems>\ + struct rewrap<TemplateTypeList<Container>, List<elems...>> { using type = TypeList<Container<elems...>>; };\ + template< template<typename...> class Container, template<typename...> class List, class...Elems, typename...Elements>\ + struct rewrap<TemplateTypeList<Container>, List<Elems...>, Elements...> { using type = typename append<TypeList<Container<Elems...>>, typename rewrap<TemplateTypeList<Container>, Elements...>::type>::type; };\ + \ + template<template <typename...> class Final, template< typename...> class...Containers, typename...Types>\ + struct create<Final, TemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<TemplateTypeList<Containers>, Types...>::type...>::type; };\ + template<template <typename...> class Final, template <typename...> class List, typename...Ts>\ + struct convert<Final, List<Ts...>> { using type = typename append<Final<>,TypeList<Ts>...>::type; }; + +#define INTERNAL_CATCH_NTTP_1(signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> struct Nttp{};\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \ + template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...> struct NttpTemplateTypeList{};\ + template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Cs>\ + constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList<Cs...> { return {}; } \ + \ + template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>> { using type = TypeList<Container<__VA_ARGS__>>; };\ + template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature), typename...Elements>\ + struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>, Elements...> { using type = typename append<TypeList<Container<__VA_ARGS__>>, typename rewrap<NttpTemplateTypeList<Container>, Elements...>::type>::type; };\ + template<template <typename...> class Final, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Containers, typename...Types>\ + struct create<Final, NttpTemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<NttpTemplateTypeList<Containers>, Types...>::type...>::type; }; + +#define INTERNAL_CATCH_DECLARE_SIG_TEST0(TestName) +#define INTERNAL_CATCH_DECLARE_SIG_TEST1(TestName, signature)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() +#define INTERNAL_CATCH_DECLARE_SIG_TEST_X(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() + +#define INTERNAL_CATCH_DEFINE_SIG_TEST0(TestName) +#define INTERNAL_CATCH_DEFINE_SIG_TEST1(TestName, signature)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() +#define INTERNAL_CATCH_DEFINE_SIG_TEST_X(TestName, signature,...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() + +#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\ + template<typename Type>\ + void reg_test(TypeList<Type>, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<Type>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + } + +#define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + } + +#define INTERNAL_CATCH_NTTP_REGISTER_METHOD0(TestName, signature, ...)\ + template<typename Type>\ + void reg_test(TypeList<Type>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestName<Type>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\ + } + +#define INTERNAL_CATCH_NTTP_REGISTER_METHOD(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + void reg_test(Nttp<__VA_ARGS__>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\ + } + +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0(TestName, ClassName) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1(TestName, ClassName, signature)\ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<TestType> { \ + void test();\ + } + +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X(TestName, ClassName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<__VA_ARGS__> { \ + void test();\ + } + +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0(TestName) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1(TestName, signature)\ + template<typename TestType> \ + void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<TestType>::test() +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \ + void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<__VA_ARGS__>::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_NTTP_0 +#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__),INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_0) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__) +#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) +#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__) +#else +#define INTERNAL_CATCH_NTTP_0(signature) +#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1,INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_0)( __VA_ARGS__)) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)) +#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) +#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)) #endif // end catch_preprocessor.hpp +// start catch_meta.hpp + + +#include <type_traits> + +namespace Catch { + template<typename T> + struct always_false : std::false_type {}; + + template <typename> struct true_given : std::true_type {}; + struct is_callable_tester { + template <typename Fun, typename... Args> + true_given<decltype(std::declval<Fun>()(std::declval<Args>()...))> static test(int); + template <typename...> + std::false_type static test(...); + }; + + template <typename T> + struct is_callable; + + template <typename Fun, typename... Args> + struct is_callable<Fun(Args...)> : decltype(is_callable_tester::test<Fun, Args...>(0)) {}; + +#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 + // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is + // replaced with std::invoke_result here. + template <typename Func, typename... U> + using FunctionReturnType = std::remove_reference_t<std::remove_cv_t<std::invoke_result_t<Func, U...>>>; +#else + // Keep ::type here because we still support C++11 + template <typename Func, typename... U> + using FunctionReturnType = typename std::remove_reference<typename std::remove_cv<typename std::result_of<Func(U...)>::type>::type>::type; +#endif + +} // namespace Catch + +namespace mpl_{ + struct na; +} + +// end catch_meta.hpp namespace Catch { template<typename C> @@ -758,38 +1000,70 @@ struct AutoReg : NonCopyable { }; \ } \ void TestName::test() - #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION( TestName, ... ) \ - template<typename TestType> \ - static void TestName() - #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( TestName, TestFunc, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \ namespace{ \ - template<typename TestType> \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ - void test(); \ - }; \ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature));\ } \ - template<typename TestType> \ - void TestName::test() + } \ + INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) + #endif + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) ) + #endif + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) + #endif + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) + #endif #endif /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ static void TestName(); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ - INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ \ struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ @@ -797,88 +1071,273 @@ struct AutoReg : NonCopyable { }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ void TestName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ - INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, ... )\ + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, Signature, ... )\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - template<typename TestType> \ - static void TestFunc();\ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + INTERNAL_CATCH_DECLARE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature));\ namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){\ + INTERNAL_CATCH_TYPE_GEN\ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + INTERNAL_CATCH_NTTP_REG_GEN(TestFunc,INTERNAL_CATCH_REMOVE_PARENS(Signature))\ template<typename...Types> \ struct TestName{\ - template<typename...Ts> \ - TestName(Ts...names){\ - CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \ + TestName(){\ + int index = 0; \ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\ using expander = int[];\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \ + (void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ }\ };\ - INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, __VA_ARGS__) \ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + TestName<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\ + return 0;\ + }();\ }\ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - template<typename TestType> \ - static void TestFunc() + }\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc,INTERNAL_CATCH_REMOVE_PARENS(Signature)) -#if defined(CATCH_CPP17_OR_GREATER) -#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>,"Duplicate type detected in declaration of template test case"); +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) #else -#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>::value,"Duplicate type detected in declaration of template test case"); + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR - #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) #else - #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) ) + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) ) #endif - #define INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, ...)\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ - TestName<CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)>(CATCH_REC_LIST_UD(INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME,Name, __VA_ARGS__));\ - return 0;\ - }(); + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature, TmplTypes, TypesList) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + template<typename TestType> static void TestFuncName(); \ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_TYPE_GEN \ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ + template<typename... Types> \ + struct TestName { \ + void reg_tests() { \ + int index = 0; \ + using expander = int[]; \ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ + constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ + constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++)... };/* NOLINT */\ + } \ + }; \ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = typename create<TestName, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \ + TestInit t; \ + t.reg_tests(); \ + return 0; \ + }(); \ + } \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + static void TestFuncName() - #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, ... ) \ +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename T,__VA_ARGS__) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename T, __VA_ARGS__ ) ) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList)\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ \ - template<typename TestType> \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + template<typename TestType> static void TestFunc(); \ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){\ + INTERNAL_CATCH_TYPE_GEN\ + template<typename... Types> \ + struct TestName { \ + void reg_tests() { \ + int index = 0; \ + using expander = int[]; \ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\ + } \ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = typename convert<TestName, TmplList>::type; \ + TestInit t; \ + t.reg_tests(); \ + return 0; \ + }(); \ + }}\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + static void TestFunc() + + #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList) \ + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, TmplList ) + + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \ + INTERNAL_CATCH_TYPE_GEN\ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature));\ + INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + template<typename...Types> \ + struct TestNameClass{\ + TestNameClass(){\ + int index = 0; \ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\ + using expander = int[];\ + (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ + }\ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + TestNameClass<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\ + return 0;\ + }();\ + }\ + }\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + template<typename TestType> \ struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ void test();\ };\ - template<typename...Types> \ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestNameClass) {\ + INTERNAL_CATCH_TYPE_GEN \ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + template<typename...Types>\ struct TestNameClass{\ - template<typename...Ts> \ - TestNameClass(Ts...names){\ - CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \ + void reg_tests(){\ + int index = 0;\ using expander = int[];\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ + constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ + constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++)... };/* NOLINT */ \ }\ };\ - INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestNameClass, Name, __VA_ARGS__)\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = typename create<TestNameClass, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;\ + TestInit t;\ + t.reg_tests();\ + return 0;\ + }(); \ + }\ }\ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ template<typename TestType> \ void TestName<TestType>::test() #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR - #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ ) + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, typename T, __VA_ARGS__ ) #else - #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ ) ) + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) ) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, Signature, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) ) #endif + #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, TmplList) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ + void test();\ + };\ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \ + INTERNAL_CATCH_TYPE_GEN\ + template<typename...Types>\ + struct TestNameClass{\ + void reg_tests(){\ + int index = 0;\ + using expander = int[];\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \ + }\ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = typename convert<TestNameClass, TmplList>::type;\ + TestInit t;\ + t.reg_tests();\ + return 0;\ + }(); \ + }}\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + void TestName<TestType>::test() + +#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \ + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, TmplList ) + // end catch_test_registry.h // start catch_capture.hpp @@ -978,7 +1437,7 @@ namespace Catch { auto makeStream( StringRef const &filename ) -> IStream const*; - class ReusableStringStream { + class ReusableStringStream : NonCopyable { std::size_t m_index; std::ostream* m_oss; public: @@ -997,6 +1456,42 @@ namespace Catch { } // end catch_stream.h +// start catch_interfaces_enum_values_registry.h + +#include <vector> + +namespace Catch { + + namespace Detail { + struct EnumInfo { + StringRef m_name; + std::vector<std::pair<int, StringRef>> m_values; + + ~EnumInfo(); + + StringRef lookup( int value ) const; + }; + } // namespace Detail + + struct IMutableEnumValuesRegistry { + virtual ~IMutableEnumValuesRegistry(); + + virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values ) = 0; + + template<typename E> + Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list<E> values ) { + static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int"); + std::vector<int> intValues; + intValues.reserve( values.size() ); + for( auto enumValue : values ) + intValues.push_back( static_cast<int>( enumValue ) ); + return registerEnum( enumName, allEnums, intValues ); + } + }; + +} // Catch + +// end catch_interfaces_enum_values_registry.h #ifdef CATCH_CONFIG_CPP17_STRING_VIEW #include <string_view> @@ -1067,9 +1562,9 @@ namespace Catch { template<typename T> class IsStreamInsertable { - template<typename SS, typename TT> + template<typename Stream, typename U> static auto test(int) - -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type()); + -> decltype(std::declval<Stream&>() << std::declval<U>(), std::true_type()); template<typename, typename> static auto test(...)->std::false_type; @@ -1231,6 +1726,12 @@ namespace Catch { } }; +#if defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker<std::byte> { + static std::string convert(std::byte value); + }; +#endif // defined(CATCH_CONFIG_CPP17_BYTE) template<> struct StringMaker<int> { static std::string convert(int value); @@ -1282,10 +1783,13 @@ namespace Catch { template<> struct StringMaker<float> { static std::string convert(float value); + static int precision; }; + template<> struct StringMaker<double> { static std::string convert(double value); + static int precision; }; template <typename T> @@ -1321,8 +1825,8 @@ namespace Catch { #endif namespace Detail { - template<typename InputIterator> - std::string rangeToString(InputIterator first, InputIterator last) { + template<typename InputIterator, typename Sentinel = InputIterator> + std::string rangeToString(InputIterator first, Sentinel last) { ReusableStringStream rss; rss << "{ "; if (first != last) { @@ -1370,6 +1874,7 @@ namespace Catch { # define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER # define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER # define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER #endif // Separate std::pair specialization @@ -1391,6 +1896,24 @@ namespace Catch { } #endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) +#include <optional> +namespace Catch { + template<typename T> + struct StringMaker<std::optional<T> > { + static std::string convert(const std::optional<T>& optional) { + ReusableStringStream rss; + if (optional.has_value()) { + rss << ::Catch::Detail::stringify(*optional); + } else { + rss << "{ }"; + } + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER + // Separate std::tuple specialization #if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) #include <tuple> @@ -1461,20 +1984,27 @@ namespace Catch { #endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER namespace Catch { - struct not_this_one {}; // Tag type for detecting which begin/ end are being selected - - // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace + // Import begin/ end from std here using std::begin; using std::end; - not_this_one begin( ... ); - not_this_one end( ... ); + namespace detail { + template <typename...> + struct void_type { + using type = void; + }; + + template <typename T, typename = void> + struct is_range_impl : std::false_type { + }; + + template <typename T> + struct is_range_impl<T, typename void_type<decltype(begin(std::declval<T>()))>::type> : std::true_type { + }; + } // namespace detail template <typename T> - struct is_range { - static const bool value = - !std::is_same<decltype(begin(std::declval<T>())), not_this_one>::value && - !std::is_same<decltype(end(std::declval<T>())), not_this_one>::value; + struct is_range : detail::is_range_impl<T> { }; #if defined(_MANAGED) // Managed types are never ranges @@ -1639,6 +2169,18 @@ struct ratio_string<std::milli> { } #endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ +namespace Catch { \ + template<> struct StringMaker<enumName> { \ + static std::string convert( enumName value ) { \ + static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ + return static_cast<std::string>(enumInfo.lookup( static_cast<int>( value ) )); \ + } \ + }; \ +} + +#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) + #ifdef _MSC_VER #pragma warning(pop) #endif @@ -1652,6 +2194,7 @@ struct ratio_string<std::milli> { #pragma warning(disable:4018) // more "signed/unsigned mismatch" #pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) #pragma warning(disable:4180) // qualifier applied to function type has no meaning +#pragma warning(disable:4800) // Forcing result to true or false #endif namespace Catch { @@ -1695,6 +2238,62 @@ namespace Catch { m_op( op ), m_rhs( rhs ) {} + + template<typename T> + auto operator && ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator || ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator == ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator != ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator > ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator < ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator >= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator <= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } }; template<typename LhsT> @@ -1707,7 +2306,7 @@ namespace Catch { public: explicit UnaryExpr( LhsT lhs ) - : ITransientExpression{ false, lhs ? true : false }, + : ITransientExpression{ false, static_cast<bool>(lhs) }, m_lhs( lhs ) {} }; @@ -1773,6 +2372,32 @@ namespace Catch { auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { return { static_cast<bool>(m_lhs <= rhs), m_lhs, "<=", rhs }; } + template <typename RhsT> + auto operator | (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs | rhs), m_lhs, "|", rhs }; + } + template <typename RhsT> + auto operator & (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs & rhs), m_lhs, "&", rhs }; + } + template <typename RhsT> + auto operator ^ (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs ^ rhs), m_lhs, "^", rhs }; + } + + template<typename RhsT> + auto operator && ( RhsT const& ) -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<RhsT>::value, + "operator&& is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename RhsT> + auto operator || ( RhsT const& ) -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<RhsT>::value, + "operator|| is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } auto makeUnaryExpr() const -> UnaryExpr<LhsT> { return UnaryExpr<LhsT>{ m_lhs }; @@ -1807,6 +2432,7 @@ namespace Catch { // start catch_interfaces_capture.h #include <string> +#include <chrono> namespace Catch { @@ -1815,15 +2441,20 @@ namespace Catch { struct SectionInfo; struct SectionEndInfo; struct MessageInfo; + struct MessageBuilder; struct Counts; - struct BenchmarkInfo; - struct BenchmarkStats; struct AssertionReaction; struct SourceLineInfo; struct ITransientExpression; struct IGeneratorTracker; +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + struct BenchmarkInfo; + template <typename Duration = std::chrono::duration<double, std::nano>> + struct BenchmarkStats; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + struct IResultCapture { virtual ~IResultCapture(); @@ -1833,14 +2464,20 @@ namespace Catch { virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; - virtual auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; + virtual auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + virtual void benchmarkPreparing( std::string const& name ) = 0; virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; - virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; + virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; + virtual void benchmarkFailed( std::string const& error ) = 0; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; + virtual void emplaceUnscopedMessage( MessageBuilder const& builder ) = 0; + virtual void handleFatalErrorCondition( StringRef message ) = 0; virtual void handleExpr @@ -2004,9 +2641,12 @@ namespace Catch { class ScopedMessage { public: explicit ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage& duplicate ) = delete; + ScopedMessage( ScopedMessage&& old ); ~ScopedMessage(); MessageInfo m_info; + bool m_moved; }; class Capturer { @@ -2062,15 +2702,16 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ do { \ + CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \ Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ - CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( (void)0, false && static_cast<bool>( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look - // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + } while( (void)0, (false) && static_cast<bool>( !!(__VA_ARGS__) ) ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ @@ -2151,6 +2792,10 @@ namespace Catch { Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); /////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ + Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + +/////////////////////////////////////////////////////////////////////////////// // Although this is matcher-based, it can be used with just a string #define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ do { \ @@ -2283,62 +2928,18 @@ namespace Catch { } // end namespace Catch #define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ - CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \ - CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION // end catch_section.h -// start catch_benchmark.h - -#include <cstdint> -#include <string> - -namespace Catch { - - class BenchmarkLooper { - - std::string m_name; - std::size_t m_count = 0; - std::size_t m_iterationsToRun = 1; - uint64_t m_resolution; - Timer m_timer; - - static auto getResolution() -> uint64_t; - public: - // Keep most of this inline as it's on the code path that is being timed - BenchmarkLooper( StringRef name ) - : m_name( name ), - m_resolution( getResolution() ) - { - reportStart(); - m_timer.start(); - } - - explicit operator bool() { - if( m_count < m_iterationsToRun ) - return true; - return needsMoreIterations(); - } - - void increment() { - ++m_count; - } - - void reportStart(); - auto needsMoreIterations() -> bool; - }; - -} // end namespace Catch - -#define BENCHMARK( name ) \ - for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) - -// end catch_benchmark.h // start catch_interfaces_exception.h // start catch_interfaces_registry_hub.h @@ -2355,6 +2956,8 @@ namespace Catch { struct IReporterRegistry; struct IReporterFactory; struct ITagAliasRegistry; + struct IMutableEnumValuesRegistry; + class StartupExceptionRegistry; using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; @@ -2365,7 +2968,6 @@ namespace Catch { virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; - virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; @@ -2379,6 +2981,7 @@ namespace Catch { virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; virtual void registerStartupException() noexcept = 0; + virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; }; IRegistryHub const& getRegistryHub(); @@ -2425,6 +3028,9 @@ namespace Catch { {} std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + return ""; +#else try { if( it == itEnd ) std::rethrow_exception(std::current_exception()); @@ -2434,6 +3040,7 @@ namespace Catch { catch( T& ex ) { return m_translateFunction( ex ); } +#endif } protected: @@ -2452,9 +3059,10 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ static std::string translatorName( signature ); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ static std::string translatorName( signature ) #define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) @@ -2485,7 +3093,7 @@ namespace Detail { Approx operator-() const; template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - Approx operator()( T const& value ) { + Approx operator()( T const& value ) const { Approx approx( static_cast<double>(value) ); approx.m_epsilon = m_epsilon; approx.m_margin = m_margin; @@ -2585,6 +3193,7 @@ struct StringMaker<Catch::Detail::Approx> { #include <string> #include <iosfwd> +#include <vector> namespace Catch { @@ -2595,7 +3204,13 @@ namespace Catch { bool contains( std::string const& s, std::string const& infix ); void toLowerInPlace( std::string& s ); std::string toLower( std::string const& s ); + //! Returns a new string without whitespace at the start/end std::string trim( std::string const& str ); + //! Returns a substring of the original ref without whitespace. Beware lifetimes! + StringRef trim(StringRef ref); + + // !!! Be aware, returns refs into original string - make sure original string outlives them + std::vector<StringRef> splitStringRef( StringRef str, char delimiter ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); struct pluralise { @@ -2648,6 +3263,15 @@ namespace Matchers { virtual bool match( ObjectT const& arg ) const = 0; }; +#if defined(__OBJC__) + // Hack to fix Catch GH issue #1661. Could use id for generic Object support. + // use of const for Object pointers is very uncommon and under ARC it causes some kind of signature mismatch that breaks compilation + template<> + struct MatcherMethod<NSString*> { + virtual bool match( NSString* arg ) const = 0; + }; +#endif + #ifdef __clang__ # pragma clang diagnostic pop #endif @@ -2685,9 +3309,10 @@ namespace Matchers { return description; } - MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) { - m_matchers.push_back( &other ); - return *this; + MatchAllOf<ArgT> operator && ( MatcherBase<ArgT> const& other ) { + auto copy(*this); + copy.m_matchers.push_back( &other ); + return copy; } std::vector<MatcherBase<ArgT> const*> m_matchers; @@ -2718,9 +3343,10 @@ namespace Matchers { return description; } - MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) { - m_matchers.push_back( &other ); - return *this; + MatchAnyOf<ArgT> operator || ( MatcherBase<ArgT> const& other ) { + auto copy(*this); + copy.m_matchers.push_back( &other ); + return copy; } std::vector<MatcherBase<ArgT> const*> m_matchers; @@ -2764,10 +3390,34 @@ using Matchers::Impl::MatcherBase; } // namespace Catch // end catch_matchers.h -// start catch_matchers_floating.h +// start catch_matchers_exception.hpp -#include <type_traits> -#include <cmath> +namespace Catch { +namespace Matchers { +namespace Exception { + +class ExceptionMessageMatcher : public MatcherBase<std::exception> { + std::string m_message; +public: + + ExceptionMessageMatcher(std::string const& message): + m_message(message) + {} + + bool match(std::exception const& ex) const override; + + std::string describe() const override; +}; + +} // namespace Exception + +Exception::ExceptionMessageMatcher Message(std::string const& message); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_exception.hpp +// start catch_matchers_floating.h namespace Catch { namespace Matchers { @@ -2786,22 +3436,43 @@ namespace Matchers { }; struct WithinUlpsMatcher : MatcherBase<double> { - WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType); + WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType); bool match(double const& matchee) const override; std::string describe() const override; private: double m_target; - int m_ulps; + uint64_t m_ulps; FloatingPointKind m_type; }; + // Given IEEE-754 format for floats and doubles, we can assume + // that float -> double promotion is lossless. Given this, we can + // assume that if we do the standard relative comparison of + // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get + // the same result if we do this for floats, as if we do this for + // doubles that were promoted from floats. + struct WithinRelMatcher : MatcherBase<double> { + WithinRelMatcher(double target, double epsilon); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_epsilon; + }; + } // namespace Floating // The following functions create the actual matcher objects. // This allows the types to be inferred - Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff); - Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff); + Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff); + Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff); Floating::WithinAbsMatcher WithinAbs(double target, double margin); + Floating::WithinRelMatcher WithinRel(double target, double eps); + // defaults epsilon to 100*numeric_limits<double>::epsilon() + Floating::WithinRelMatcher WithinRel(double target); + Floating::WithinRelMatcher WithinRel(float target, float eps); + // defaults epsilon to 100*numeric_limits<float>::epsilon() + Floating::WithinRelMatcher WithinRel(float target); } // namespace Matchers } // namespace Catch @@ -2844,7 +3515,7 @@ public: // The following functions create the actual matcher objects. // The user has to explicitly specify type to the function, because - // infering std::function<bool(T const&)> is hard (but possible) and + // inferring std::function<bool(T const&)> is hard (but possible) and // requires a lot of TMP. template<typename T> Generic::PredicateMatcher<T> Predicate(std::function<bool(T const&)> const& predicate, std::string const& description = "") { @@ -2932,34 +3603,12 @@ namespace Catch { namespace Matchers { namespace Vector { - namespace Detail { - template <typename InputIterator, typename T> - size_t count(InputIterator first, InputIterator last, T const& item) { - size_t cnt = 0; - for (; first != last; ++first) { - if (*first == item) { - ++cnt; - } - } - return cnt; - } - template <typename InputIterator, typename T> - bool contains(InputIterator first, InputIterator last, T const& item) { - for (; first != last; ++first) { - if (*first == item) { - return true; - } - } - return false; - } - } - - template<typename T> - struct ContainsElementMatcher : MatcherBase<std::vector<T>> { + template<typename T, typename Alloc> + struct ContainsElementMatcher : MatcherBase<std::vector<T, Alloc>> { ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} - bool match(std::vector<T> const &v) const override { + bool match(std::vector<T, Alloc> const &v) const override { for (auto const& el : v) { if (el == m_comparator) { return true; @@ -2975,12 +3624,12 @@ namespace Matchers { T const& m_comparator; }; - template<typename T> - struct ContainsMatcher : MatcherBase<std::vector<T>> { + template<typename T, typename AllocComp, typename AllocMatch> + struct ContainsMatcher : MatcherBase<std::vector<T, AllocMatch>> { - ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} + ContainsMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator( comparator ) {} - bool match(std::vector<T> const &v) const override { + bool match(std::vector<T, AllocMatch> const &v) const override { // !TBD: see note in EqualsMatcher if (m_comparator.size() > v.size()) return false; @@ -3002,18 +3651,18 @@ namespace Matchers { return "Contains: " + ::Catch::Detail::stringify( m_comparator ); } - std::vector<T> const& m_comparator; + std::vector<T, AllocComp> const& m_comparator; }; - template<typename T> - struct EqualsMatcher : MatcherBase<std::vector<T>> { + template<typename T, typename AllocComp, typename AllocMatch> + struct EqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> { - EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} + EqualsMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator( comparator ) {} - bool match(std::vector<T> const &v) const override { + bool match(std::vector<T, AllocMatch> const &v) const override { // !TBD: This currently works if all elements can be compared using != // - a more general approach would be via a compare template that defaults - // to using !=. but could be specialised for, e.g. std::vector<T> etc + // to using !=. but could be specialised for, e.g. std::vector<T, Alloc> etc // - then just call that directly if (m_comparator.size() != v.size()) return false; @@ -3025,47 +3674,60 @@ namespace Matchers { std::string describe() const override { return "Equals: " + ::Catch::Detail::stringify( m_comparator ); } - std::vector<T> const& m_comparator; + std::vector<T, AllocComp> const& m_comparator; }; - template<typename T> - struct UnorderedEqualsMatcher : MatcherBase<std::vector<T>> { - UnorderedEqualsMatcher(std::vector<T> const& target) : m_target(target) {} - bool match(std::vector<T> const& vec) const override { - // Note: This is a reimplementation of std::is_permutation, - // because I don't want to include <algorithm> inside the common path - if (m_target.size() != vec.size()) { - return false; - } - auto lfirst = m_target.begin(), llast = m_target.end(); - auto rfirst = vec.begin(), rlast = vec.end(); - // Cut common prefix to optimize checking of permuted parts - while (lfirst != llast && *lfirst == *rfirst) { - ++lfirst; ++rfirst; - } - if (lfirst == llast) { - return true; - } + template<typename T, typename AllocComp, typename AllocMatch> + struct ApproxMatcher : MatcherBase<std::vector<T, AllocMatch>> { - for (auto mid = lfirst; mid != llast; ++mid) { - // Skip already counted items - if (Detail::contains(lfirst, mid, *mid)) { - continue; - } - size_t num_vec = Detail::count(rfirst, rlast, *mid); - if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) { - return false; - } - } + ApproxMatcher(std::vector<T, AllocComp> const& comparator) : m_comparator( comparator ) {} + bool match(std::vector<T, AllocMatch> const &v) const override { + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != approx(v[i])) + return false; return true; } + std::string describe() const override { + return "is approx: " + ::Catch::Detail::stringify( m_comparator ); + } + template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + ApproxMatcher& epsilon( T const& newEpsilon ) { + approx.epsilon(newEpsilon); + return *this; + } + template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + ApproxMatcher& margin( T const& newMargin ) { + approx.margin(newMargin); + return *this; + } + template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + ApproxMatcher& scale( T const& newScale ) { + approx.scale(newScale); + return *this; + } + + std::vector<T, AllocComp> const& m_comparator; + mutable Catch::Detail::Approx approx = Catch::Detail::Approx::custom(); + }; + + template<typename T, typename AllocComp, typename AllocMatch> + struct UnorderedEqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> { + UnorderedEqualsMatcher(std::vector<T, AllocComp> const& target) : m_target(target) {} + bool match(std::vector<T, AllocMatch> const& vec) const override { + if (m_target.size() != vec.size()) { + return false; + } + return std::is_permutation(m_target.begin(), m_target.end(), vec.begin()); + } std::string describe() const override { return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); } private: - std::vector<T> const& m_target; + std::vector<T, AllocComp> const& m_target; }; } // namespace Vector @@ -3073,24 +3735,29 @@ namespace Matchers { // The following functions create the actual matcher objects. // This allows the types to be inferred - template<typename T> - Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) { - return Vector::ContainsMatcher<T>( comparator ); + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + Vector::ContainsMatcher<T, AllocComp, AllocMatch> Contains( std::vector<T, AllocComp> const& comparator ) { + return Vector::ContainsMatcher<T, AllocComp, AllocMatch>( comparator ); } - template<typename T> - Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) { - return Vector::ContainsElementMatcher<T>( comparator ); + template<typename T, typename Alloc = std::allocator<T>> + Vector::ContainsElementMatcher<T, Alloc> VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher<T, Alloc>( comparator ); } - template<typename T> - Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) { - return Vector::EqualsMatcher<T>( comparator ); + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + Vector::EqualsMatcher<T, AllocComp, AllocMatch> Equals( std::vector<T, AllocComp> const& comparator ) { + return Vector::EqualsMatcher<T, AllocComp, AllocMatch>( comparator ); } - template<typename T> - Vector::UnorderedEqualsMatcher<T> UnorderedEquals(std::vector<T> const& target) { - return Vector::UnorderedEqualsMatcher<T>(target); + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + Vector::ApproxMatcher<T, AllocComp, AllocMatch> Approx( std::vector<T, AllocComp> const& comparator ) { + return Vector::ApproxMatcher<T, AllocComp, AllocMatch>( comparator ); + } + + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch> UnorderedEquals(std::vector<T, AllocComp> const& target) { + return Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch>( target ); } } // namespace Matchers @@ -3175,16 +3842,17 @@ namespace Catch { namespace Catch { namespace Generators { - class GeneratorBase { - protected: - size_t m_size = 0; - + class GeneratorUntypedBase { public: - GeneratorBase( size_t size ) : m_size( size ) {} - virtual ~GeneratorBase(); - auto size() const -> size_t { return m_size; } + GeneratorUntypedBase() = default; + virtual ~GeneratorUntypedBase(); + // Attempts to move the generator to the next element + // + // Returns true iff the move succeeded (and a valid element + // can be retrieved). + virtual bool next() = 0; }; - using GeneratorBasePtr = std::unique_ptr<GeneratorBase>; + using GeneratorBasePtr = std::unique_ptr<GeneratorUntypedBase>; } // namespace Generators @@ -3193,7 +3861,6 @@ namespace Catch { virtual auto hasGenerator() const -> bool = 0; virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; - virtual auto getIndex() const -> std::size_t = 0; }; } // namespace Catch @@ -3201,7 +3868,7 @@ namespace Catch { // end catch_interfaces_generatortracker.h // start catch_enforce.h -#include <stdexcept> +#include <exception> namespace Catch { #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) @@ -3214,18 +3881,30 @@ namespace Catch { [[noreturn]] void throw_exception(std::exception const& e); #endif + + [[noreturn]] + void throw_logic_error(std::string const& msg); + [[noreturn]] + void throw_domain_error(std::string const& msg); + [[noreturn]] + void throw_runtime_error(std::string const& msg); + } // namespace Catch; -#define CATCH_PREPARE_EXCEPTION( type, msg ) \ - type( ( Catch::ReusableStringStream() << msg ).str() ) -#define CATCH_INTERNAL_ERROR( msg ) \ - Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg)) -#define CATCH_ERROR( msg ) \ - Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::domain_error, msg )) -#define CATCH_RUNTIME_ERROR( msg ) \ - Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::runtime_error, msg )) -#define CATCH_ENFORCE( condition, msg ) \ - do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) +#define CATCH_MAKE_MSG(...) \ + (Catch::ReusableStringStream() << __VA_ARGS__).str() + +#define CATCH_INTERNAL_ERROR(...) \ + Catch::throw_logic_error(CATCH_MAKE_MSG( CATCH_INTERNAL_LINEINFO << ": Internal Catch2 error: " << __VA_ARGS__)) + +#define CATCH_ERROR(...) \ + Catch::throw_domain_error(CATCH_MAKE_MSG( __VA_ARGS__ )) + +#define CATCH_RUNTIME_ERROR(...) \ + Catch::throw_runtime_error(CATCH_MAKE_MSG( __VA_ARGS__ )) + +#define CATCH_ENFORCE( condition, ... ) \ + do{ if( !(condition) ) CATCH_ERROR( __VA_ARGS__ ); } while(false) // end catch_enforce.h #include <memory> @@ -3233,8 +3912,21 @@ namespace Catch { #include <cassert> #include <utility> +#include <exception> namespace Catch { + +class GeneratorException : public std::exception { + const char* const m_msg = ""; + +public: + GeneratorException(const char* msg): + m_msg(msg) + {} + + const char* what() const noexcept override final; +}; + namespace Generators { // !TBD move this into its own location? @@ -3246,223 +3938,819 @@ namespace Generators { } template<typename T> - struct IGenerator { - virtual ~IGenerator() {} - virtual auto get( size_t index ) const -> T = 0; + struct IGenerator : GeneratorUntypedBase { + virtual ~IGenerator() = default; + + // Returns the current element of the generator + // + // \Precondition The generator is either freshly constructed, + // or the last call to `next()` returned true + virtual T const& get() const = 0; + using type = T; }; template<typename T> - class SingleValueGenerator : public IGenerator<T> { + class SingleValueGenerator final : public IGenerator<T> { T m_value; public: - SingleValueGenerator( T const& value ) : m_value( value ) {} + SingleValueGenerator(T&& value) : m_value(std::move(value)) {} - auto get( size_t ) const -> T override { + T const& get() const override { return m_value; } + bool next() override { + return false; + } }; template<typename T> - class FixedValuesGenerator : public IGenerator<T> { + class FixedValuesGenerator final : public IGenerator<T> { + static_assert(!std::is_same<T, bool>::value, + "FixedValuesGenerator does not support bools because of std::vector<bool>" + "specialization, use SingleValue Generator instead."); std::vector<T> m_values; - + size_t m_idx = 0; public: FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {} - auto get( size_t index ) const -> T override { - return m_values[index]; + T const& get() const override { + return m_values[m_idx]; + } + bool next() override { + ++m_idx; + return m_idx < m_values.size(); + } + }; + + template <typename T> + class GeneratorWrapper final { + std::unique_ptr<IGenerator<T>> m_generator; + public: + GeneratorWrapper(std::unique_ptr<IGenerator<T>> generator): + m_generator(std::move(generator)) + {} + T const& get() const { + return m_generator->get(); + } + bool next() { + return m_generator->next(); } }; + template <typename T> + GeneratorWrapper<T> value(T&& value) { + return GeneratorWrapper<T>(pf::make_unique<SingleValueGenerator<T>>(std::forward<T>(value))); + } + template <typename T> + GeneratorWrapper<T> values(std::initializer_list<T> values) { + return GeneratorWrapper<T>(pf::make_unique<FixedValuesGenerator<T>>(values)); + } + template<typename T> - class RangeGenerator : public IGenerator<T> { - T const m_first; - T const m_last; + class Generators : public IGenerator<T> { + std::vector<GeneratorWrapper<T>> m_generators; + size_t m_current = 0; + + void populate(GeneratorWrapper<T>&& generator) { + m_generators.emplace_back(std::move(generator)); + } + void populate(T&& val) { + m_generators.emplace_back(value(std::forward<T>(val))); + } + template<typename U> + void populate(U&& val) { + populate(T(std::forward<U>(val))); + } + template<typename U, typename... Gs> + void populate(U&& valueOrGenerator, Gs &&... moreGenerators) { + populate(std::forward<U>(valueOrGenerator)); + populate(std::forward<Gs>(moreGenerators)...); + } public: - RangeGenerator( T const& first, T const& last ) : m_first( first ), m_last( last ) { - assert( m_last > m_first ); + template <typename... Gs> + Generators(Gs &&... moreGenerators) { + m_generators.reserve(sizeof...(Gs)); + populate(std::forward<Gs>(moreGenerators)...); } - auto get( size_t index ) const -> T override { - // ToDo:: introduce a safe cast to catch potential overflows - return static_cast<T>(m_first+index); + T const& get() const override { + return m_generators[m_current].get(); } - }; - template<typename T> - struct NullGenerator : IGenerator<T> { - auto get( size_t ) const -> T override { - CATCH_INTERNAL_ERROR("A Null Generator is always empty"); + bool next() override { + if (m_current >= m_generators.size()) { + return false; + } + const bool current_status = m_generators[m_current].next(); + if (!current_status) { + ++m_current; + } + return m_current < m_generators.size(); } }; + template<typename... Ts> + GeneratorWrapper<std::tuple<Ts...>> table( std::initializer_list<std::tuple<typename std::decay<Ts>::type...>> tuples ) { + return values<std::tuple<Ts...>>( tuples ); + } + + // Tag type to signal that a generator sequence should convert arguments to a specific type + template <typename T> + struct as {}; + + template<typename T, typename... Gs> + auto makeGenerators( GeneratorWrapper<T>&& generator, Gs &&... moreGenerators ) -> Generators<T> { + return Generators<T>(std::move(generator), std::forward<Gs>(moreGenerators)...); + } template<typename T> - class Generator { - std::unique_ptr<IGenerator<T>> m_generator; - size_t m_size; + auto makeGenerators( GeneratorWrapper<T>&& generator ) -> Generators<T> { + return Generators<T>(std::move(generator)); + } + template<typename T, typename... Gs> + auto makeGenerators( T&& val, Gs &&... moreGenerators ) -> Generators<T> { + return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... ); + } + template<typename T, typename U, typename... Gs> + auto makeGenerators( as<T>, U&& val, Gs &&... moreGenerators ) -> Generators<T> { + return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... ); + } - public: - Generator( size_t size, std::unique_ptr<IGenerator<T>> generator ) - : m_generator( std::move( generator ) ), - m_size( size ) - {} + auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; + + template<typename L> + // Note: The type after -> is weird, because VS2015 cannot parse + // the expression used in the typedef inside, when it is in + // return type. Yeah. + auto generate( StringRef generatorName, SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval<decltype(generatorExpression())>().get()) { + using UnderlyingType = typename decltype(generatorExpression())::type; - auto size() const -> size_t { return m_size; } - auto operator[]( size_t index ) const -> T { - assert( index < m_size ); - return m_generator->get( index ); + IGeneratorTracker& tracker = acquireGeneratorTracker( generatorName, lineInfo ); + if (!tracker.hasGenerator()) { + tracker.setGenerator(pf::make_unique<Generators<UnderlyingType>>(generatorExpression())); } - }; - std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize ); + auto const& generator = static_cast<IGenerator<UnderlyingType> const&>( *tracker.getGenerator() ); + return generator.get(); + } - template<typename T> - class GeneratorRandomiser : public IGenerator<T> { - Generator<T> m_baseGenerator; +} // namespace Generators +} // namespace Catch + +#define GENERATE( ... ) \ + Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [ ]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) +#define GENERATE_COPY( ... ) \ + Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [=]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) +#define GENERATE_REF( ... ) \ + Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [&]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) + +// end catch_generators.hpp +// start catch_generators_generic.hpp - std::vector<size_t> m_indices; +namespace Catch { +namespace Generators { + + template <typename T> + class TakeGenerator : public IGenerator<T> { + GeneratorWrapper<T> m_generator; + size_t m_returned = 0; + size_t m_target; public: - GeneratorRandomiser( Generator<T>&& baseGenerator, size_t numberOfItems ) - : m_baseGenerator( std::move( baseGenerator ) ), - m_indices( randomiseIndices( numberOfItems, m_baseGenerator.size() ) ) - {} + TakeGenerator(size_t target, GeneratorWrapper<T>&& generator): + m_generator(std::move(generator)), + m_target(target) + { + assert(target != 0 && "Empty generators are not allowed"); + } + T const& get() const override { + return m_generator.get(); + } + bool next() override { + ++m_returned; + if (m_returned >= m_target) { + return false; + } - auto get( size_t index ) const -> T override { - return m_baseGenerator[m_indices[index]]; + const auto success = m_generator.next(); + // If the underlying generator does not contain enough values + // then we cut short as well + if (!success) { + m_returned = m_target; + } + return success; } }; - template<typename T> - struct RequiresASpecialisationFor; + template <typename T> + GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<T>(pf::make_unique<TakeGenerator<T>>(target, std::move(generator))); + } - template<typename T> - auto all() -> Generator<T> { return RequiresASpecialisationFor<T>(); } + template <typename T, typename Predicate> + class FilterGenerator : public IGenerator<T> { + GeneratorWrapper<T> m_generator; + Predicate m_predicate; + public: + template <typename P = Predicate> + FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator): + m_generator(std::move(generator)), + m_predicate(std::forward<P>(pred)) + { + if (!m_predicate(m_generator.get())) { + // It might happen that there are no values that pass the + // filter. In that case we throw an exception. + auto has_initial_value = nextImpl(); + if (!has_initial_value) { + Catch::throw_exception(GeneratorException("No valid value found in filtered generator")); + } + } + } - template<> - auto all<int>() -> Generator<int>; + T const& get() const override { + return m_generator.get(); + } - template<typename T> - auto range( T const& first, T const& last ) -> Generator<T> { - return Generator<T>( (last-first), pf::make_unique<RangeGenerator<T>>( first, last ) ); + bool next() override { + return nextImpl(); + } + + private: + bool nextImpl() { + bool success = m_generator.next(); + if (!success) { + return false; + } + while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true); + return success; + } + }; + + template <typename T, typename Predicate> + GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<T>(std::unique_ptr<IGenerator<T>>(pf::make_unique<FilterGenerator<T, Predicate>>(std::forward<Predicate>(pred), std::move(generator)))); } - template<typename T> - auto random( T const& first, T const& last ) -> Generator<T> { - auto gen = range( first, last ); - auto size = gen.size(); + template <typename T> + class RepeatGenerator : public IGenerator<T> { + static_assert(!std::is_same<T, bool>::value, + "RepeatGenerator currently does not support bools" + "because of std::vector<bool> specialization"); + GeneratorWrapper<T> m_generator; + mutable std::vector<T> m_returned; + size_t m_target_repeats; + size_t m_current_repeat = 0; + size_t m_repeat_index = 0; + public: + RepeatGenerator(size_t repeats, GeneratorWrapper<T>&& generator): + m_generator(std::move(generator)), + m_target_repeats(repeats) + { + assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); + } - return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( std::move( gen ), size ) ); + T const& get() const override { + if (m_current_repeat == 0) { + m_returned.push_back(m_generator.get()); + return m_returned.back(); + } + return m_returned[m_repeat_index]; + } + + bool next() override { + // There are 2 basic cases: + // 1) We are still reading the generator + // 2) We are reading our own cache + + // In the first case, we need to poke the underlying generator. + // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache + if (m_current_repeat == 0) { + const auto success = m_generator.next(); + if (!success) { + ++m_current_repeat; + } + return m_current_repeat < m_target_repeats; + } + + // In the second case, we need to move indices forward and check that we haven't run up against the end + ++m_repeat_index; + if (m_repeat_index == m_returned.size()) { + m_repeat_index = 0; + ++m_current_repeat; + } + return m_current_repeat < m_target_repeats; + } + }; + + template <typename T> + GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<T>(pf::make_unique<RepeatGenerator<T>>(repeats, std::move(generator))); } - template<typename T> - auto random( size_t size ) -> Generator<T> { - return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( all<T>(), size ) ); + + template <typename T, typename U, typename Func> + class MapGenerator : public IGenerator<T> { + // TBD: provide static assert for mapping function, for friendly error message + GeneratorWrapper<U> m_generator; + Func m_function; + // To avoid returning dangling reference, we have to save the values + T m_cache; + public: + template <typename F2 = Func> + MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) : + m_generator(std::move(generator)), + m_function(std::forward<F2>(function)), + m_cache(m_function(m_generator.get())) + {} + + T const& get() const override { + return m_cache; + } + bool next() override { + const auto success = m_generator.next(); + if (success) { + m_cache = m_function(m_generator.get()); + } + return success; + } + }; + + template <typename Func, typename U, typename T = FunctionReturnType<Func, U>> + GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { + return GeneratorWrapper<T>( + pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator)) + ); } - template<typename T> - auto values( std::initializer_list<T> values ) -> Generator<T> { - return Generator<T>( values.size(), pf::make_unique<FixedValuesGenerator<T>>( values ) ); + template <typename T, typename U, typename Func> + GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { + return GeneratorWrapper<T>( + pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator)) + ); } - template<typename T> - auto value( T const& val ) -> Generator<T> { - return Generator<T>( 1, pf::make_unique<SingleValueGenerator<T>>( val ) ); + + template <typename T> + class ChunkGenerator final : public IGenerator<std::vector<T>> { + std::vector<T> m_chunk; + size_t m_chunk_size; + GeneratorWrapper<T> m_generator; + bool m_used_up = false; + public: + ChunkGenerator(size_t size, GeneratorWrapper<T> generator) : + m_chunk_size(size), m_generator(std::move(generator)) + { + m_chunk.reserve(m_chunk_size); + if (m_chunk_size != 0) { + m_chunk.push_back(m_generator.get()); + for (size_t i = 1; i < m_chunk_size; ++i) { + if (!m_generator.next()) { + Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk")); + } + m_chunk.push_back(m_generator.get()); + } + } + } + std::vector<T> const& get() const override { + return m_chunk; + } + bool next() override { + m_chunk.clear(); + for (size_t idx = 0; idx < m_chunk_size; ++idx) { + if (!m_generator.next()) { + return false; + } + m_chunk.push_back(m_generator.get()); + } + return true; + } + }; + + template <typename T> + GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<std::vector<T>>( + pf::make_unique<ChunkGenerator<T>>(size, std::move(generator)) + ); } - template<typename T> - auto as() -> Generator<T> { - return Generator<T>( 0, pf::make_unique<NullGenerator<T>>() ); +} // namespace Generators +} // namespace Catch + +// end catch_generators_generic.hpp +// start catch_generators_specific.hpp + +// start catch_context.h + +#include <memory> + +namespace Catch { + + struct IResultCapture; + struct IRunner; + struct IConfig; + struct IMutableContext; + + using IConfigPtr = std::shared_ptr<IConfig const>; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual IConfigPtr const& getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( IConfigPtr const& config ) = 0; + + private: + static IMutableContext *currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); + }; + + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return *IMutableContext::currentContext; } - template<typename... Ts> - auto table( std::initializer_list<std::tuple<Ts...>>&& tuples ) -> Generator<std::tuple<Ts...>> { - return values<std::tuple<Ts...>>( std::forward<std::initializer_list<std::tuple<Ts...>>>( tuples ) ); + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); } - template<typename T> - struct Generators : GeneratorBase { - std::vector<Generator<T>> m_generators; + void cleanUpContext(); - using type = T; + class SimplePcg32; + SimplePcg32& rng(); +} - Generators() : GeneratorBase( 0 ) {} +// end catch_context.h +// start catch_interfaces_config.h + +// start catch_option.hpp + +namespace Catch { - void populate( T&& val ) { - m_size += 1; - m_generators.emplace_back( value( std::move( val ) ) ); + // An optional type + template<typename T> + class Option { + public: + Option() : nullableValue( nullptr ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) + {} + + ~Option() { + reset(); } - template<typename U> - void populate( U&& val ) { - populate( T( std::move( val ) ) ); + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; } - void populate( Generator<T>&& generator ) { - m_size += generator.size(); - m_generators.emplace_back( std::move( generator ) ); + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = nullptr; } - template<typename U, typename... Gs> - void populate( U&& valueOrGenerator, Gs... moreGenerators ) { - populate( std::forward<U>( valueOrGenerator ) ); - populate( std::forward<Gs>( moreGenerators )... ); + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; } - auto operator[]( size_t index ) const -> T { - size_t sizes = 0; - for( auto const& gen : m_generators ) { - auto localIndex = index-sizes; - sizes += gen.size(); - if( index < sizes ) - return gen[localIndex]; - } - CATCH_INTERNAL_ERROR("Index '" << index << "' is out of range (" << sizes << ')'); + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); } + + private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; }; - template<typename T, typename... Gs> - auto makeGenerators( Generator<T>&& generator, Gs... moreGenerators ) -> Generators<T> { - Generators<T> generators; - generators.m_generators.reserve( 1+sizeof...(Gs) ); - generators.populate( std::move( generator ), std::forward<Gs>( moreGenerators )... ); - return generators; +} // end namespace Catch + +// end catch_option.hpp +#include <chrono> +#include <iosfwd> +#include <string> +#include <vector> +#include <memory> + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01, + NoTests = 0x02 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + + struct IConfig : NonCopyable { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutNoTests() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual double minDuration() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual std::vector<std::string> const& getTestsOrTags() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector<std::string> const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + + virtual bool benchmarkNoAnalysis() const = 0; + virtual int benchmarkSamples() const = 0; + virtual double benchmarkConfidenceInterval() const = 0; + virtual unsigned int benchmarkResamples() const = 0; + virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0; + }; + + using IConfigPtr = std::shared_ptr<IConfig const>; +} + +// end catch_interfaces_config.h +// start catch_random_number_generator.h + +#include <cstdint> + +namespace Catch { + + // This is a simple implementation of C++11 Uniform Random Number + // Generator. It does not provide all operators, because Catch2 + // does not use it, but it should behave as expected inside stdlib's + // distributions. + // The implementation is based on the PCG family (http://pcg-random.org) + class SimplePcg32 { + using state_type = std::uint64_t; + public: + using result_type = std::uint32_t; + static constexpr result_type (min)() { + return 0; + } + static constexpr result_type (max)() { + return static_cast<result_type>(-1); + } + + // Provide some default initial state for the default constructor + SimplePcg32():SimplePcg32(0xed743cc4U) {} + + explicit SimplePcg32(result_type seed_); + + void seed(result_type seed_); + void discard(uint64_t skip); + + result_type operator()(); + + private: + friend bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs); + friend bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs); + + // In theory we also need operator<< and operator>> + // In practice we do not use them, so we will skip them for now + + std::uint64_t m_state; + // This part of the state determines which "stream" of the numbers + // is chosen -- we take it as a constant for Catch2, so we only + // need to deal with seeding the main state. + // Picked by reading 8 bytes from `/dev/random` :-) + static const std::uint64_t s_inc = (0x13ed0cc53f939476ULL << 1ULL) | 1ULL; + }; + +} // end namespace Catch + +// end catch_random_number_generator.h +#include <random> + +namespace Catch { +namespace Generators { + +template <typename Float> +class RandomFloatingGenerator final : public IGenerator<Float> { + Catch::SimplePcg32& m_rng; + std::uniform_real_distribution<Float> m_dist; + Float m_current_number; +public: + + RandomFloatingGenerator(Float a, Float b): + m_rng(rng()), + m_dist(a, b) { + static_cast<void>(next()); } - template<typename T> - auto makeGenerators( Generator<T>&& generator ) -> Generators<T> { - Generators<T> generators; - generators.populate( std::move( generator ) ); - return generators; + + Float const& get() const override { + return m_current_number; } - template<typename T, typename... Gs> - auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators<T> { - return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... ); + bool next() override { + m_current_number = m_dist(m_rng); + return true; } - template<typename T, typename U, typename... Gs> - auto makeGenerators( U&& val, Gs... moreGenerators ) -> Generators<T> { - return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... ); +}; + +template <typename Integer> +class RandomIntegerGenerator final : public IGenerator<Integer> { + Catch::SimplePcg32& m_rng; + std::uniform_int_distribution<Integer> m_dist; + Integer m_current_number; +public: + + RandomIntegerGenerator(Integer a, Integer b): + m_rng(rng()), + m_dist(a, b) { + static_cast<void>(next()); } - auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; + Integer const& get() const override { + return m_current_number; + } + bool next() override { + m_current_number = m_dist(m_rng); + return true; + } +}; - template<typename L> - // Note: The type after -> is weird, because VS2015 cannot parse - // the expression used in the typedef inside, when it is in - // return type. Yeah, ¯\_(ツ)_/¯ - auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval<decltype(generatorExpression())>()[0]) { - using UnderlyingType = typename decltype(generatorExpression())::type; +// TODO: Ideally this would be also constrained against the various char types, +// but I don't expect users to run into that in practice. +template <typename T> +typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value, +GeneratorWrapper<T>>::type +random(T a, T b) { + return GeneratorWrapper<T>( + pf::make_unique<RandomIntegerGenerator<T>>(a, b) + ); +} + +template <typename T> +typename std::enable_if<std::is_floating_point<T>::value, +GeneratorWrapper<T>>::type +random(T a, T b) { + return GeneratorWrapper<T>( + pf::make_unique<RandomFloatingGenerator<T>>(a, b) + ); +} + +template <typename T> +class RangeGenerator final : public IGenerator<T> { + T m_current; + T m_end; + T m_step; + bool m_positive; + +public: + RangeGenerator(T const& start, T const& end, T const& step): + m_current(start), + m_end(end), + m_step(step), + m_positive(m_step > T(0)) + { + assert(m_current != m_end && "Range start and end cannot be equal"); + assert(m_step != T(0) && "Step size cannot be zero"); + assert(((m_positive && m_current <= m_end) || (!m_positive && m_current >= m_end)) && "Step moves away from end"); + } + + RangeGenerator(T const& start, T const& end): + RangeGenerator(start, end, (start < end) ? T(1) : T(-1)) + {} + + T const& get() const override { + return m_current; + } + + bool next() override { + m_current += m_step; + return (m_positive) ? (m_current < m_end) : (m_current > m_end); + } +}; + +template <typename T> +GeneratorWrapper<T> range(T const& start, T const& end, T const& step) { + static_assert(std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, "Type must be numeric"); + return GeneratorWrapper<T>(pf::make_unique<RangeGenerator<T>>(start, end, step)); +} - IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo ); - if( !tracker.hasGenerator() ) - tracker.setGenerator( pf::make_unique<Generators<UnderlyingType>>( generatorExpression() ) ); +template <typename T> +GeneratorWrapper<T> range(T const& start, T const& end) { + static_assert(std::is_integral<T>::value && !std::is_same<T, bool>::value, "Type must be an integer"); + return GeneratorWrapper<T>(pf::make_unique<RangeGenerator<T>>(start, end)); +} - auto const& generator = static_cast<Generators<UnderlyingType> const&>( *tracker.getGenerator() ); - return generator[tracker.getIndex()]; +template <typename T> +class IteratorGenerator final : public IGenerator<T> { + static_assert(!std::is_same<T, bool>::value, + "IteratorGenerator currently does not support bools" + "because of std::vector<bool> specialization"); + + std::vector<T> m_elems; + size_t m_current = 0; +public: + template <typename InputIterator, typename InputSentinel> + IteratorGenerator(InputIterator first, InputSentinel last):m_elems(first, last) { + if (m_elems.empty()) { + Catch::throw_exception(GeneratorException("IteratorGenerator received no valid values")); + } + } + + T const& get() const override { + return m_elems[m_current]; } + bool next() override { + ++m_current; + return m_current != m_elems.size(); + } +}; + +template <typename InputIterator, + typename InputSentinel, + typename ResultType = typename std::iterator_traits<InputIterator>::value_type> +GeneratorWrapper<ResultType> from_range(InputIterator from, InputSentinel to) { + return GeneratorWrapper<ResultType>(pf::make_unique<IteratorGenerator<ResultType>>(from, to)); +} + +template <typename Container, + typename ResultType = typename Container::value_type> +GeneratorWrapper<ResultType> from_range(Container const& cnt) { + return GeneratorWrapper<ResultType>(pf::make_unique<IteratorGenerator<ResultType>>(cnt.begin(), cnt.end())); +} + } // namespace Generators } // namespace Catch -#define GENERATE( ... ) \ - Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) - -// end catch_generators.hpp +// end catch_generators_specific.hpp // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections @@ -3662,7 +4950,7 @@ namespace Catch { arcSafeRelease( m_substr ); } - bool match( NSString* arg ) const override { + bool match( NSString* str ) const override { return false; } @@ -3685,7 +4973,7 @@ namespace Catch { struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} - bool match( NSString* str ) const { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } @@ -3761,7 +5049,8 @@ return @ desc; \ // end catch_objc.hpp #endif -#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES +// Benchmarking needs the externally-facing parts of reporters to work +#if defined(CATCH_CONFIG_EXTERNAL_INTERFACES) || defined(CATCH_CONFIG_ENABLE_BENCHMARKING) // start catch_external_interfaces.h // start catch_reporter_bases.hpp @@ -3803,7 +5092,7 @@ namespace Catch virtual bool matches( std::string const& str ) const; private: - std::string adjustCase( std::string const& str ) const; + std::string normaliseString( std::string const& str ) const; CaseSensitive::Choice m_caseSensitivity; WildcardPosition m_wildcard = NoWildcard; std::string m_pattern; @@ -3817,36 +5106,40 @@ namespace Catch namespace Catch { + struct IConfig; + class TestSpec { - struct Pattern { + class Pattern { + public: + explicit Pattern( std::string const& name ); virtual ~Pattern(); virtual bool matches( TestCaseInfo const& testCase ) const = 0; + std::string const& name() const; + private: + std::string const m_name; }; using PatternPtr = std::shared_ptr<Pattern>; class NamePattern : public Pattern { public: - NamePattern( std::string const& name ); - virtual ~NamePattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; + explicit NamePattern( std::string const& name, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; private: WildcardPattern m_wildcardPattern; }; class TagPattern : public Pattern { public: - TagPattern( std::string const& tag ); - virtual ~TagPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; + explicit TagPattern( std::string const& tag, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; private: std::string m_tag; }; class ExcludedPattern : public Pattern { public: - ExcludedPattern( PatternPtr const& underlyingPattern ); - virtual ~ExcludedPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; + explicit ExcludedPattern( PatternPtr const& underlyingPattern ); + bool matches( TestCaseInfo const& testCase ) const override; private: PatternPtr m_underlyingPattern; }; @@ -3855,15 +5148,25 @@ namespace Catch { std::vector<PatternPtr> m_patterns; bool matches( TestCaseInfo const& testCase ) const; + std::string name() const; }; public: + struct FilterMatch { + std::string name; + std::vector<TestCase const*> tests; + }; + using Matches = std::vector<FilterMatch>; + using vectorStrings = std::vector<std::string>; + bool hasFilters() const; bool matches( TestCaseInfo const& testCase ) const; + Matches matchesByFilter( std::vector<TestCase> const& testCases, IConfig const& config ) const; + const vectorStrings & getInvalidArgs() const; private: std::vector<Filter> m_filters; - + std::vector<std::string> m_invalidArgs; friend class TestSpecParser; }; } @@ -3898,9 +5201,13 @@ namespace Catch { class TestSpecParser { enum Mode{ None, Name, QuotedName, Tag, EscapedName }; Mode m_mode = None; + Mode lastMode = None; bool m_exclusion = false; - std::size_t m_start = std::string::npos, m_pos = 0; + std::size_t m_pos = 0; + std::size_t m_realPatternPos = 0; std::string m_arg; + std::string m_substring; + std::string m_patternName; std::vector<std::size_t> m_escapeChars; TestSpec::Filter m_currentFilter; TestSpec m_testSpec; @@ -3913,32 +5220,32 @@ namespace Catch { TestSpec testSpec(); private: - void visitChar( char c ); - void startNewMode( Mode mode, std::size_t start ); + bool visitChar( char c ); + void startNewMode( Mode mode ); + bool processNoneChar( char c ); + void processNameChar( char c ); + bool processOtherChar( char c ); + void endMode(); void escape(); - std::string subString() const; + bool isControlChar( char c ) const; + void saveLastMode(); + void revertBackToLastMode(); + void addFilter(); + bool separate(); - template<typename T> - void addPattern() { - std::string token = subString(); - for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) - token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); - m_escapeChars.clear(); - if( startsWith( token, "exclude:" ) ) { - m_exclusion = true; - token = token.substr( 8 ); - } - if( !token.empty() ) { - TestSpec::PatternPtr pattern = std::make_shared<T>( token ); - if( m_exclusion ) - pattern = std::make_shared<TestSpec::ExcludedPattern>( pattern ); - m_currentFilter.m_patterns.push_back( pattern ); - } - m_exclusion = false; - m_mode = None; + // Handles common preprocessing of the pattern for name/tag patterns + std::string preprocessPattern(); + // Adds the current pattern as a test name + void addNamePattern(); + // Adds the current pattern as a tag + void addTagPattern(); + + inline void addCharToPattern(char c) { + m_substring += c; + m_patternName += c; + m_realPatternPos++; } - void addFilter(); }; TestSpec parseTestSpec( std::string const& arg ); @@ -3949,79 +5256,6 @@ namespace Catch { #endif // end catch_test_spec_parser.h -// start catch_interfaces_config.h - -#include <iosfwd> -#include <string> -#include <vector> -#include <memory> - -namespace Catch { - - enum class Verbosity { - Quiet = 0, - Normal, - High - }; - - struct WarnAbout { enum What { - Nothing = 0x00, - NoAssertions = 0x01, - NoTests = 0x02 - }; }; - - struct ShowDurations { enum OrNot { - DefaultForReporter, - Always, - Never - }; }; - struct RunTests { enum InWhatOrder { - InDeclarationOrder, - InLexicographicalOrder, - InRandomOrder - }; }; - struct UseColour { enum YesOrNo { - Auto, - Yes, - No - }; }; - struct WaitForKeypress { enum When { - Never, - BeforeStart = 1, - BeforeExit = 2, - BeforeStartAndExit = BeforeStart | BeforeExit - }; }; - - class TestSpec; - - struct IConfig : NonCopyable { - - virtual ~IConfig(); - - virtual bool allowThrows() const = 0; - virtual std::ostream& stream() const = 0; - virtual std::string name() const = 0; - virtual bool includeSuccessfulResults() const = 0; - virtual bool shouldDebugBreak() const = 0; - virtual bool warnAboutMissingAssertions() const = 0; - virtual bool warnAboutNoTests() const = 0; - virtual int abortAfter() const = 0; - virtual bool showInvisibles() const = 0; - virtual ShowDurations::OrNot showDurations() const = 0; - virtual TestSpec const& testSpec() const = 0; - virtual bool hasTestFilters() const = 0; - virtual RunTests::InWhatOrder runOrder() const = 0; - virtual unsigned int rngSeed() const = 0; - virtual int benchmarkResolutionMultiple() const = 0; - virtual UseColour::YesOrNo useColour() const = 0; - virtual std::vector<std::string> const& getSectionsToRun() const = 0; - virtual Verbosity verbosity() const = 0; - }; - - using IConfigPtr = std::shared_ptr<IConfig const>; -} - -// end catch_interfaces_config.h // Libstdc++ doesn't like incomplete classes for unique_ptr #include <memory> @@ -4052,11 +5286,17 @@ namespace Catch { int abortAfter = -1; unsigned int rngSeed = 0; - int benchmarkResolutionMultiple = 100; + + bool benchmarkNoAnalysis = false; + unsigned int benchmarkSamples = 100; + double benchmarkConfidenceInterval = 0.95; + unsigned int benchmarkResamples = 100000; + std::chrono::milliseconds::rep benchmarkWarmupTime = 100; Verbosity verbosity = Verbosity::Normal; WarnAbout::What warnings = WarnAbout::Nothing; ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + double minDuration = -1; RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; UseColour::YesOrNo useColour = UseColour::Auto; WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; @@ -4091,10 +5331,10 @@ namespace Catch { std::string getProcessName() const; std::string const& getReporterName() const; - std::vector<std::string> const& getTestsOrTags() const; + std::vector<std::string> const& getTestsOrTags() const override; std::vector<std::string> const& getSectionsToRun() const override; - virtual TestSpec const& testSpec() const override; + TestSpec const& testSpec() const override; bool hasTestFilters() const override; bool showHelp() const; @@ -4107,14 +5347,19 @@ namespace Catch { bool warnAboutMissingAssertions() const override; bool warnAboutNoTests() const override; ShowDurations::OrNot showDurations() const override; + double minDuration() const override; RunTests::InWhatOrder runOrder() const override; unsigned int rngSeed() const override; - int benchmarkResolutionMultiple() const override; UseColour::YesOrNo useColour() const override; bool shouldDebugBreak() const override; int abortAfter() const override; bool showInvisibles() const override; Verbosity verbosity() const override; + bool benchmarkNoAnalysis() const override; + int benchmarkSamples() const override; + double benchmarkConfidenceInterval() const override; + unsigned int benchmarkResamples() const override; + std::chrono::milliseconds benchmarkWarmupTime() const override; private: @@ -4175,76 +5420,61 @@ namespace Catch { } // end namespace Catch // end catch_assertionresult.h -// start catch_option.hpp - -namespace Catch { +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +// start catch_estimate.hpp - // An optional type - template<typename T> - class Option { - public: - Option() : nullableValue( nullptr ) {} - Option( T const& _value ) - : nullableValue( new( storage ) T( _value ) ) - {} - Option( Option const& _other ) - : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) - {} + // Statistics estimates - ~Option() { - reset(); - } - Option& operator= ( Option const& _other ) { - if( &_other != this ) { - reset(); - if( _other ) - nullableValue = new( storage ) T( *_other ); +namespace Catch { + namespace Benchmark { + template <typename Duration> + struct Estimate { + Duration point; + Duration lower_bound; + Duration upper_bound; + double confidence_interval; + + template <typename Duration2> + operator Estimate<Duration2>() const { + return { point, lower_bound, upper_bound, confidence_interval }; } - return *this; - } - Option& operator = ( T const& _value ) { - reset(); - nullableValue = new( storage ) T( _value ); - return *this; - } - - void reset() { - if( nullableValue ) - nullableValue->~T(); - nullableValue = nullptr; - } + }; + } // namespace Benchmark +} // namespace Catch - T& operator*() { return *nullableValue; } - T const& operator*() const { return *nullableValue; } - T* operator->() { return nullableValue; } - const T* operator->() const { return nullableValue; } +// end catch_estimate.hpp +// start catch_outlier_classification.hpp - T valueOr( T const& defaultValue ) const { - return nullableValue ? *nullableValue : defaultValue; - } +// Outlier information - bool some() const { return nullableValue != nullptr; } - bool none() const { return nullableValue == nullptr; } - - bool operator !() const { return nullableValue == nullptr; } - explicit operator bool() const { - return some(); - } +namespace Catch { + namespace Benchmark { + struct OutlierClassification { + int samples_seen = 0; + int low_severe = 0; // more than 3 times IQR below Q1 + int low_mild = 0; // 1.5 to 3 times IQR below Q1 + int high_mild = 0; // 1.5 to 3 times IQR above Q3 + int high_severe = 0; // more than 3 times IQR above Q3 + + int total() const { + return low_severe + low_mild + high_mild + high_severe; + } + }; + } // namespace Benchmark +} // namespace Catch - private: - T *nullableValue; - alignas(alignof(T)) char storage[sizeof(T)]; - }; +// end catch_outlier_classification.hpp -} // end namespace Catch +#include <iterator> +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING -// end catch_option.hpp #include <string> #include <iosfwd> #include <map> #include <set> #include <memory> +#include <algorithm> namespace Catch { @@ -4301,8 +5531,8 @@ namespace Catch { AssertionStats( AssertionStats const& ) = default; AssertionStats( AssertionStats && ) = default; - AssertionStats& operator = ( AssertionStats const& ) = default; - AssertionStats& operator = ( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = delete; + AssertionStats& operator = ( AssertionStats && ) = delete; virtual ~AssertionStats(); AssertionResult assertionResult; @@ -4380,14 +5610,43 @@ namespace Catch { bool aborting; }; +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) struct BenchmarkInfo { std::string name; + double estimatedDuration; + int iterations; + int samples; + unsigned int resamples; + double clockResolution; + double clockCost; }; + + template <class Duration> struct BenchmarkStats { BenchmarkInfo info; - std::size_t iterations; - uint64_t elapsedTimeInNanoseconds; + + std::vector<Duration> samples; + Benchmark::Estimate<Duration> mean; + Benchmark::Estimate<Duration> standardDeviation; + Benchmark::OutlierClassification outliers; + double outlierVariance; + + template <typename Duration2> + operator BenchmarkStats<Duration2>() const { + std::vector<Duration2> samples2; + samples2.reserve(samples.size()); + std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](Duration d) { return Duration2(d); }); + return { + info, + std::move(samples2), + mean, + standardDeviation, + outliers, + outlierVariance, + }; + } }; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING struct IStreamingReporter { virtual ~IStreamingReporter() = default; @@ -4400,23 +5659,26 @@ namespace Catch { virtual void noMatchingTestCases( std::string const& spec ) = 0; + virtual void reportInvalidArguments(std::string const&) {} + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; - // *** experimental *** +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + virtual void benchmarkPreparing( std::string const& ) {} virtual void benchmarkStarting( BenchmarkInfo const& ) {} + virtual void benchmarkEnded( BenchmarkStats<> const& ) {} + virtual void benchmarkFailed( std::string const& ) {} +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; - // *** experimental *** - virtual void benchmarkEnded( BenchmarkStats const& ) {} - virtual void sectionEnded( SectionStats const& sectionStats ) = 0; virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; @@ -4465,6 +5727,11 @@ namespace Catch { // Returns double formatted as %.3f (format expected on output) std::string getFormattedDuration( double duration ); + //! Should the reporter show + bool shouldShowDuration( IConfig const& config, double duration ); + + std::string serializeFilters( std::vector<std::string> const& container ); + template<typename DerivedT> struct StreamingReporterBase : IStreamingReporter { @@ -4489,9 +5756,12 @@ namespace Catch { void noMatchingTestCases(std::string const&) override {} + void reportInvalidArguments(std::string const&) override {} + void testRunStarting(TestRunInfo const& _testRunInfo) override { currentTestRunInfo = _testRunInfo; } + void testGroupStarting(GroupInfo const& _groupInfo) override { currentGroupInfo = _groupInfo; } @@ -4782,11 +6052,11 @@ namespace Catch { class ReporterFactory : public IReporterFactory { - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + IStreamingReporterPtr create( ReporterConfig const& config ) const override { return std::unique_ptr<T>( new T( config ) ); } - virtual std::string getDescription() const override { + std::string getDescription() const override { return T::getDescription(); } }; @@ -4803,10 +6073,10 @@ namespace Catch { class ListenerFactory : public IReporterFactory { - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + IStreamingReporterPtr create( ReporterConfig const& config ) const override { return std::unique_ptr<T>( new T( config ) ); } - virtual std::string getDescription() const override { + std::string getDescription() const override { return std::string(); } }; @@ -4822,14 +6092,16 @@ namespace Catch { #if !defined(CATCH_CONFIG_DISABLE) #define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #define CATCH_REGISTER_LISTENER( listenerType ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #else // CATCH_CONFIG_DISABLE #define CATCH_REGISTER_REPORTER(name, reporterType) @@ -4851,8 +6123,6 @@ namespace Catch { static std::string getDescription(); - ReporterPreferences getPreferences() const override; - void noMatchingTestCases(std::string const& spec) override; void assertionStarting(AssertionInfo const&) override; @@ -4891,6 +6161,8 @@ namespace Catch { void noMatchingTestCases(std::string const& spec) override; + void reportInvalidArguments(std::string const&arg) override; + void assertionStarting(AssertionInfo const&) override; bool assertionEnded(AssertionStats const& _assertionStats) override; @@ -4898,13 +6170,17 @@ namespace Catch { void sectionStarting(SectionInfo const& _sectionInfo) override; void sectionEnded(SectionStats const& _sectionStats) override; +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void benchmarkPreparing(std::string const& name) override; void benchmarkStarting(BenchmarkInfo const& info) override; - void benchmarkEnded(BenchmarkStats const& stats) override; + void benchmarkEnded(BenchmarkStats<> const& stats) override; + void benchmarkFailed(std::string const& error) override; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING void testCaseEnded(TestCaseStats const& _testCaseStats) override; void testGroupEnded(TestGroupStats const& _testGroupStats) override; void testRunEnded(TestRunStats const& _testRunStats) override; - + void testRunStarting(TestRunInfo const& _testRunInfo) override; private: void lazyPrint(); @@ -4926,6 +6202,7 @@ namespace Catch { void printTotalsDivider(Totals const& totals); void printSummaryDivider(); + void printTestFilters(); private: bool m_headerPrinted = false; @@ -4945,6 +6222,14 @@ namespace Catch { #include <vector> namespace Catch { + enum class XmlFormatting { + None = 0x00, + Indent = 0x01, + Newline = 0x02, + }; + + XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs); + XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs); class XmlEncode { public: @@ -4966,14 +6251,14 @@ namespace Catch { class ScopedElement { public: - ScopedElement( XmlWriter* writer ); + ScopedElement( XmlWriter* writer, XmlFormatting fmt ); ScopedElement( ScopedElement&& other ) noexcept; ScopedElement& operator=( ScopedElement&& other ) noexcept; ~ScopedElement(); - ScopedElement& writeText( std::string const& text, bool indent = true ); + ScopedElement& writeText( std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent ); template<typename T> ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { @@ -4983,6 +6268,7 @@ namespace Catch { private: mutable XmlWriter* m_writer = nullptr; + XmlFormatting m_fmt; }; XmlWriter( std::ostream& os = Catch::cout() ); @@ -4991,11 +6277,11 @@ namespace Catch { XmlWriter( XmlWriter const& ) = delete; XmlWriter& operator=( XmlWriter const& ) = delete; - XmlWriter& startElement( std::string const& name ); + XmlWriter& startElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - ScopedElement scopedElement( std::string const& name ); + ScopedElement scopedElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - XmlWriter& endElement(); + XmlWriter& endElement(XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); @@ -5008,9 +6294,9 @@ namespace Catch { return writeAttribute( name, rss.str() ); } - XmlWriter& writeText( std::string const& text, bool indent = true ); + XmlWriter& writeText( std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - XmlWriter& writeComment( std::string const& text ); + XmlWriter& writeComment(std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); void writeStylesheetRef( std::string const& url ); @@ -5020,6 +6306,8 @@ namespace Catch { private: + void applyFormatting(XmlFormatting fmt); + void writeDeclaration(); void newlineIfNecessary(); @@ -5063,9 +6351,10 @@ namespace Catch { void writeTestCase(TestCaseNode const& testCaseNode); - void writeSection(std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode); + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail ); void writeAssertions(SectionNode const& sectionNode); void writeAssertion(AssertionStats const& stats); @@ -5120,6 +6409,13 @@ namespace Catch { void testRunEnded(TestRunStats const& testRunStats) override; +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void benchmarkPreparing(std::string const& name) override; + void benchmarkStarting(BenchmarkInfo const&) override; + void benchmarkEnded(BenchmarkStats<> const&) override; + void benchmarkFailed(std::string const&) override; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + private: Timer m_testCaseTimer; XmlWriter m_xml; @@ -5133,6 +6429,1032 @@ namespace Catch { // end catch_external_interfaces.h #endif +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +// start catch_benchmarking_all.hpp + +// A proxy header that includes all of the benchmarking headers to allow +// concise include of the benchmarking features. You should prefer the +// individual includes in standard use. + +// start catch_benchmark.hpp + + // Benchmark + +// start catch_chronometer.hpp + +// User-facing chronometer + + +// start catch_clock.hpp + +// Clocks + + +#include <chrono> +#include <ratio> + +namespace Catch { + namespace Benchmark { + template <typename Clock> + using ClockDuration = typename Clock::duration; + template <typename Clock> + using FloatDuration = std::chrono::duration<double, typename Clock::period>; + + template <typename Clock> + using TimePoint = typename Clock::time_point; + + using default_clock = std::chrono::steady_clock; + + template <typename Clock> + struct now { + TimePoint<Clock> operator()() const { + return Clock::now(); + } + }; + + using fp_seconds = std::chrono::duration<double, std::ratio<1>>; + } // namespace Benchmark +} // namespace Catch + +// end catch_clock.hpp +// start catch_optimizer.hpp + + // Hinting the optimizer + + +#if defined(_MSC_VER) +# include <atomic> // atomic_thread_fence +#endif + +namespace Catch { + namespace Benchmark { +#if defined(__GNUC__) || defined(__clang__) + template <typename T> + inline void keep_memory(T* p) { + asm volatile("" : : "g"(p) : "memory"); + } + inline void keep_memory() { + asm volatile("" : : : "memory"); + } + + namespace Detail { + inline void optimizer_barrier() { keep_memory(); } + } // namespace Detail +#elif defined(_MSC_VER) + +#pragma optimize("", off) + template <typename T> + inline void keep_memory(T* p) { + // thanks @milleniumbug + *reinterpret_cast<char volatile*>(p) = *reinterpret_cast<char const volatile*>(p); + } + // TODO equivalent keep_memory() +#pragma optimize("", on) + + namespace Detail { + inline void optimizer_barrier() { + std::atomic_thread_fence(std::memory_order_seq_cst); + } + } // namespace Detail + +#endif + + template <typename T> + inline void deoptimize_value(T&& x) { + keep_memory(&x); + } + + template <typename Fn, typename... Args> + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> typename std::enable_if<!std::is_same<void, decltype(fn(args...))>::value>::type { + deoptimize_value(std::forward<Fn>(fn) (std::forward<Args...>(args...))); + } + + template <typename Fn, typename... Args> + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> typename std::enable_if<std::is_same<void, decltype(fn(args...))>::value>::type { + std::forward<Fn>(fn) (std::forward<Args...>(args...)); + } + } // namespace Benchmark +} // namespace Catch + +// end catch_optimizer.hpp +// start catch_complete_invoke.hpp + +// Invoke with a special case for void + + +#include <type_traits> +#include <utility> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename T> + struct CompleteType { using type = T; }; + template <> + struct CompleteType<void> { struct type {}; }; + + template <typename T> + using CompleteType_t = typename CompleteType<T>::type; + + template <typename Result> + struct CompleteInvoker { + template <typename Fun, typename... Args> + static Result invoke(Fun&& fun, Args&&... args) { + return std::forward<Fun>(fun)(std::forward<Args>(args)...); + } + }; + template <> + struct CompleteInvoker<void> { + template <typename Fun, typename... Args> + static CompleteType_t<void> invoke(Fun&& fun, Args&&... args) { + std::forward<Fun>(fun)(std::forward<Args>(args)...); + return {}; + } + }; + + // invoke and not return void :( + template <typename Fun, typename... Args> + CompleteType_t<FunctionReturnType<Fun, Args...>> complete_invoke(Fun&& fun, Args&&... args) { + return CompleteInvoker<FunctionReturnType<Fun, Args...>>::invoke(std::forward<Fun>(fun), std::forward<Args>(args)...); + } + + const std::string benchmarkErrorMsg = "a benchmark failed to run successfully"; + } // namespace Detail + + template <typename Fun> + Detail::CompleteType_t<FunctionReturnType<Fun>> user_code(Fun&& fun) { + CATCH_TRY{ + return Detail::complete_invoke(std::forward<Fun>(fun)); + } CATCH_CATCH_ALL{ + getResultCapture().benchmarkFailed(translateActiveException()); + CATCH_RUNTIME_ERROR(Detail::benchmarkErrorMsg); + } + } + } // namespace Benchmark +} // namespace Catch + +// end catch_complete_invoke.hpp +namespace Catch { + namespace Benchmark { + namespace Detail { + struct ChronometerConcept { + virtual void start() = 0; + virtual void finish() = 0; + virtual ~ChronometerConcept() = default; + }; + template <typename Clock> + struct ChronometerModel final : public ChronometerConcept { + void start() override { started = Clock::now(); } + void finish() override { finished = Clock::now(); } + + ClockDuration<Clock> elapsed() const { return finished - started; } + + TimePoint<Clock> started; + TimePoint<Clock> finished; + }; + } // namespace Detail + + struct Chronometer { + public: + template <typename Fun> + void measure(Fun&& fun) { measure(std::forward<Fun>(fun), is_callable<Fun(int)>()); } + + int runs() const { return k; } + + Chronometer(Detail::ChronometerConcept& meter, int k) + : impl(&meter) + , k(k) {} + + private: + template <typename Fun> + void measure(Fun&& fun, std::false_type) { + measure([&fun](int) { return fun(); }, std::true_type()); + } + + template <typename Fun> + void measure(Fun&& fun, std::true_type) { + Detail::optimizer_barrier(); + impl->start(); + for (int i = 0; i < k; ++i) invoke_deoptimized(fun, i); + impl->finish(); + Detail::optimizer_barrier(); + } + + Detail::ChronometerConcept* impl; + int k; + }; + } // namespace Benchmark +} // namespace Catch + +// end catch_chronometer.hpp +// start catch_environment.hpp + +// Environment information + + +namespace Catch { + namespace Benchmark { + template <typename Duration> + struct EnvironmentEstimate { + Duration mean; + OutlierClassification outliers; + + template <typename Duration2> + operator EnvironmentEstimate<Duration2>() const { + return { mean, outliers }; + } + }; + template <typename Clock> + struct Environment { + using clock_type = Clock; + EnvironmentEstimate<FloatDuration<Clock>> clock_resolution; + EnvironmentEstimate<FloatDuration<Clock>> clock_cost; + }; + } // namespace Benchmark +} // namespace Catch + +// end catch_environment.hpp +// start catch_execution_plan.hpp + + // Execution plan + + +// start catch_benchmark_function.hpp + + // Dumb std::function implementation for consistent call overhead + + +#include <cassert> +#include <type_traits> +#include <utility> +#include <memory> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename T> + using Decay = typename std::decay<T>::type; + template <typename T, typename U> + struct is_related + : std::is_same<Decay<T>, Decay<U>> {}; + + /// We need to reinvent std::function because every piece of code that might add overhead + /// in a measurement context needs to have consistent performance characteristics so that we + /// can account for it in the measurement. + /// Implementations of std::function with optimizations that aren't always applicable, like + /// small buffer optimizations, are not uncommon. + /// This is effectively an implementation of std::function without any such optimizations; + /// it may be slow, but it is consistently slow. + struct BenchmarkFunction { + private: + struct callable { + virtual void call(Chronometer meter) const = 0; + virtual callable* clone() const = 0; + virtual ~callable() = default; + }; + template <typename Fun> + struct model : public callable { + model(Fun&& fun) : fun(std::move(fun)) {} + model(Fun const& fun) : fun(fun) {} + + model<Fun>* clone() const override { return new model<Fun>(*this); } + + void call(Chronometer meter) const override { + call(meter, is_callable<Fun(Chronometer)>()); + } + void call(Chronometer meter, std::true_type) const { + fun(meter); + } + void call(Chronometer meter, std::false_type) const { + meter.measure(fun); + } + + Fun fun; + }; + + struct do_nothing { void operator()() const {} }; + + template <typename T> + BenchmarkFunction(model<T>* c) : f(c) {} + + public: + BenchmarkFunction() + : f(new model<do_nothing>{ {} }) {} + + template <typename Fun, + typename std::enable_if<!is_related<Fun, BenchmarkFunction>::value, int>::type = 0> + BenchmarkFunction(Fun&& fun) + : f(new model<typename std::decay<Fun>::type>(std::forward<Fun>(fun))) {} + + BenchmarkFunction(BenchmarkFunction&& that) + : f(std::move(that.f)) {} + + BenchmarkFunction(BenchmarkFunction const& that) + : f(that.f->clone()) {} + + BenchmarkFunction& operator=(BenchmarkFunction&& that) { + f = std::move(that.f); + return *this; + } + + BenchmarkFunction& operator=(BenchmarkFunction const& that) { + f.reset(that.f->clone()); + return *this; + } + + void operator()(Chronometer meter) const { f->call(meter); } + + private: + std::unique_ptr<callable> f; + }; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_benchmark_function.hpp +// start catch_repeat.hpp + +// repeat algorithm + + +#include <type_traits> +#include <utility> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Fun> + struct repeater { + void operator()(int k) const { + for (int i = 0; i < k; ++i) { + fun(); + } + } + Fun fun; + }; + template <typename Fun> + repeater<typename std::decay<Fun>::type> repeat(Fun&& fun) { + return { std::forward<Fun>(fun) }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_repeat.hpp +// start catch_run_for_at_least.hpp + +// Run a function for a minimum amount of time + + +// start catch_measure.hpp + +// Measure + + +// start catch_timing.hpp + +// Timing + + +#include <tuple> +#include <type_traits> + +namespace Catch { + namespace Benchmark { + template <typename Duration, typename Result> + struct Timing { + Duration elapsed; + Result result; + int iterations; + }; + template <typename Clock, typename Func, typename... Args> + using TimingOf = Timing<ClockDuration<Clock>, Detail::CompleteType_t<FunctionReturnType<Func, Args...>>>; + } // namespace Benchmark +} // namespace Catch + +// end catch_timing.hpp +#include <utility> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Clock, typename Fun, typename... Args> + TimingOf<Clock, Fun, Args...> measure(Fun&& fun, Args&&... args) { + auto start = Clock::now(); + auto&& r = Detail::complete_invoke(fun, std::forward<Args>(args)...); + auto end = Clock::now(); + auto delta = end - start; + return { delta, std::forward<decltype(r)>(r), 1 }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_measure.hpp +#include <utility> +#include <type_traits> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Clock, typename Fun> + TimingOf<Clock, Fun, int> measure_one(Fun&& fun, int iters, std::false_type) { + return Detail::measure<Clock>(fun, iters); + } + template <typename Clock, typename Fun> + TimingOf<Clock, Fun, Chronometer> measure_one(Fun&& fun, int iters, std::true_type) { + Detail::ChronometerModel<Clock> meter; + auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters)); + + return { meter.elapsed(), std::move(result), iters }; + } + + template <typename Clock, typename Fun> + using run_for_at_least_argument_t = typename std::conditional<is_callable<Fun(Chronometer)>::value, Chronometer, int>::type; + + struct optimized_away_error : std::exception { + const char* what() const noexcept override { + return "could not measure benchmark, maybe it was optimized away"; + } + }; + + template <typename Clock, typename Fun> + TimingOf<Clock, Fun, run_for_at_least_argument_t<Clock, Fun>> run_for_at_least(ClockDuration<Clock> how_long, int seed, Fun&& fun) { + auto iters = seed; + while (iters < (1 << 30)) { + auto&& Timing = measure_one<Clock>(fun, iters, is_callable<Fun(Chronometer)>()); + + if (Timing.elapsed >= how_long) { + return { Timing.elapsed, std::move(Timing.result), iters }; + } + iters *= 2; + } + Catch::throw_exception(optimized_away_error{}); + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_run_for_at_least.hpp +#include <algorithm> +#include <iterator> + +namespace Catch { + namespace Benchmark { + template <typename Duration> + struct ExecutionPlan { + int iterations_per_sample; + Duration estimated_duration; + Detail::BenchmarkFunction benchmark; + Duration warmup_time; + int warmup_iterations; + + template <typename Duration2> + operator ExecutionPlan<Duration2>() const { + return { iterations_per_sample, estimated_duration, benchmark, warmup_time, warmup_iterations }; + } + + template <typename Clock> + std::vector<FloatDuration<Clock>> run(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const { + // warmup a bit + Detail::run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_iterations, Detail::repeat(now<Clock>{})); + + std::vector<FloatDuration<Clock>> times; + times.reserve(cfg.benchmarkSamples()); + std::generate_n(std::back_inserter(times), cfg.benchmarkSamples(), [this, env] { + Detail::ChronometerModel<Clock> model; + this->benchmark(Chronometer(model, iterations_per_sample)); + auto sample_time = model.elapsed() - env.clock_cost.mean; + if (sample_time < FloatDuration<Clock>::zero()) sample_time = FloatDuration<Clock>::zero(); + return sample_time / iterations_per_sample; + }); + return times; + } + }; + } // namespace Benchmark +} // namespace Catch + +// end catch_execution_plan.hpp +// start catch_estimate_clock.hpp + + // Environment measurement + + +// start catch_stats.hpp + +// Statistical analysis tools + + +#include <algorithm> +#include <functional> +#include <vector> +#include <iterator> +#include <numeric> +#include <tuple> +#include <cmath> +#include <utility> +#include <cstddef> +#include <random> + +namespace Catch { + namespace Benchmark { + namespace Detail { + using sample = std::vector<double>; + + double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last); + + template <typename Iterator> + OutlierClassification classify_outliers(Iterator first, Iterator last) { + std::vector<double> copy(first, last); + + auto q1 = weighted_average_quantile(1, 4, copy.begin(), copy.end()); + auto q3 = weighted_average_quantile(3, 4, copy.begin(), copy.end()); + auto iqr = q3 - q1; + auto los = q1 - (iqr * 3.); + auto lom = q1 - (iqr * 1.5); + auto him = q3 + (iqr * 1.5); + auto his = q3 + (iqr * 3.); + + OutlierClassification o; + for (; first != last; ++first) { + auto&& t = *first; + if (t < los) ++o.low_severe; + else if (t < lom) ++o.low_mild; + else if (t > his) ++o.high_severe; + else if (t > him) ++o.high_mild; + ++o.samples_seen; + } + return o; + } + + template <typename Iterator> + double mean(Iterator first, Iterator last) { + auto count = last - first; + double sum = std::accumulate(first, last, 0.); + return sum / count; + } + + template <typename URng, typename Iterator, typename Estimator> + sample resample(URng& rng, int resamples, Iterator first, Iterator last, Estimator& estimator) { + auto n = last - first; + std::uniform_int_distribution<decltype(n)> dist(0, n - 1); + + sample out; + out.reserve(resamples); + std::generate_n(std::back_inserter(out), resamples, [n, first, &estimator, &dist, &rng] { + std::vector<double> resampled; + resampled.reserve(n); + std::generate_n(std::back_inserter(resampled), n, [first, &dist, &rng] { return first[dist(rng)]; }); + return estimator(resampled.begin(), resampled.end()); + }); + std::sort(out.begin(), out.end()); + return out; + } + + template <typename Estimator, typename Iterator> + sample jackknife(Estimator&& estimator, Iterator first, Iterator last) { + auto n = last - first; + auto second = std::next(first); + sample results; + results.reserve(n); + + for (auto it = first; it != last; ++it) { + std::iter_swap(it, first); + results.push_back(estimator(second, last)); + } + + return results; + } + + inline double normal_cdf(double x) { + return std::erfc(-x / std::sqrt(2.0)) / 2.0; + } + + double erfc_inv(double x); + + double normal_quantile(double p); + + template <typename Iterator, typename Estimator> + Estimate<double> bootstrap(double confidence_level, Iterator first, Iterator last, sample const& resample, Estimator&& estimator) { + auto n_samples = last - first; + + double point = estimator(first, last); + // Degenerate case with a single sample + if (n_samples == 1) return { point, point, point, confidence_level }; + + sample jack = jackknife(estimator, first, last); + double jack_mean = mean(jack.begin(), jack.end()); + double sum_squares, sum_cubes; + std::tie(sum_squares, sum_cubes) = std::accumulate(jack.begin(), jack.end(), std::make_pair(0., 0.), [jack_mean](std::pair<double, double> sqcb, double x) -> std::pair<double, double> { + auto d = jack_mean - x; + auto d2 = d * d; + auto d3 = d2 * d; + return { sqcb.first + d2, sqcb.second + d3 }; + }); + + double accel = sum_cubes / (6 * std::pow(sum_squares, 1.5)); + int n = static_cast<int>(resample.size()); + double prob_n = std::count_if(resample.begin(), resample.end(), [point](double x) { return x < point; }) / (double)n; + // degenerate case with uniform samples + if (prob_n == 0) return { point, point, point, confidence_level }; + + double bias = normal_quantile(prob_n); + double z1 = normal_quantile((1. - confidence_level) / 2.); + + auto cumn = [n](double x) -> int { + return std::lround(normal_cdf(x) * n); }; + auto a = [bias, accel](double b) { return bias + b / (1. - accel * b); }; + double b1 = bias + z1; + double b2 = bias - z1; + double a1 = a(b1); + double a2 = a(b2); + auto lo = (std::max)(cumn(a1), 0); + auto hi = (std::min)(cumn(a2), n - 1); + + return { point, resample[lo], resample[hi], confidence_level }; + } + + double outlier_variance(Estimate<double> mean, Estimate<double> stddev, int n); + + struct bootstrap_analysis { + Estimate<double> mean; + Estimate<double> standard_deviation; + double outlier_variance; + }; + + bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector<double>::iterator first, std::vector<double>::iterator last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_stats.hpp +#include <algorithm> +#include <iterator> +#include <tuple> +#include <vector> +#include <cmath> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Clock> + std::vector<double> resolution(int k) { + std::vector<TimePoint<Clock>> times; + times.reserve(k + 1); + std::generate_n(std::back_inserter(times), k + 1, now<Clock>{}); + + std::vector<double> deltas; + deltas.reserve(k); + std::transform(std::next(times.begin()), times.end(), times.begin(), + std::back_inserter(deltas), + [](TimePoint<Clock> a, TimePoint<Clock> b) { return static_cast<double>((a - b).count()); }); + + return deltas; + } + + const auto warmup_iterations = 10000; + const auto warmup_time = std::chrono::milliseconds(100); + const auto minimum_ticks = 1000; + const auto warmup_seed = 10000; + const auto clock_resolution_estimation_time = std::chrono::milliseconds(500); + const auto clock_cost_estimation_time_limit = std::chrono::seconds(1); + const auto clock_cost_estimation_tick_limit = 100000; + const auto clock_cost_estimation_time = std::chrono::milliseconds(10); + const auto clock_cost_estimation_iterations = 10000; + + template <typename Clock> + int warmup() { + return run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_seed, &resolution<Clock>) + .iterations; + } + template <typename Clock> + EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_resolution(int iterations) { + auto r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_resolution_estimation_time), iterations, &resolution<Clock>) + .result; + return { + FloatDuration<Clock>(mean(r.begin(), r.end())), + classify_outliers(r.begin(), r.end()), + }; + } + template <typename Clock> + EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(FloatDuration<Clock> resolution) { + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FloatDuration<Clock>(clock_cost_estimation_time_limit)); + auto time_clock = [](int k) { + return Detail::measure<Clock>([k] { + for (int i = 0; i < k; ++i) { + volatile auto ignored = Clock::now(); + (void)ignored; + } + }).elapsed; + }; + time_clock(1); + int iters = clock_cost_estimation_iterations; + auto&& r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_cost_estimation_time), iters, time_clock); + std::vector<double> times; + int nsamples = static_cast<int>(std::ceil(time_limit / r.elapsed)); + times.reserve(nsamples); + std::generate_n(std::back_inserter(times), nsamples, [time_clock, &r] { + return static_cast<double>((time_clock(r.iterations) / r.iterations).count()); + }); + return { + FloatDuration<Clock>(mean(times.begin(), times.end())), + classify_outliers(times.begin(), times.end()), + }; + } + + template <typename Clock> + Environment<FloatDuration<Clock>> measure_environment() { + static Environment<FloatDuration<Clock>>* env = nullptr; + if (env) { + return *env; + } + + auto iters = Detail::warmup<Clock>(); + auto resolution = Detail::estimate_clock_resolution<Clock>(iters); + auto cost = Detail::estimate_clock_cost<Clock>(resolution.mean); + + env = new Environment<FloatDuration<Clock>>{ resolution, cost }; + return *env; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_estimate_clock.hpp +// start catch_analyse.hpp + + // Run and analyse one benchmark + + +// start catch_sample_analysis.hpp + +// Benchmark results + + +#include <algorithm> +#include <vector> +#include <string> +#include <iterator> + +namespace Catch { + namespace Benchmark { + template <typename Duration> + struct SampleAnalysis { + std::vector<Duration> samples; + Estimate<Duration> mean; + Estimate<Duration> standard_deviation; + OutlierClassification outliers; + double outlier_variance; + + template <typename Duration2> + operator SampleAnalysis<Duration2>() const { + std::vector<Duration2> samples2; + samples2.reserve(samples.size()); + std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](Duration d) { return Duration2(d); }); + return { + std::move(samples2), + mean, + standard_deviation, + outliers, + outlier_variance, + }; + } + }; + } // namespace Benchmark +} // namespace Catch + +// end catch_sample_analysis.hpp +#include <algorithm> +#include <iterator> +#include <vector> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Duration, typename Iterator> + SampleAnalysis<Duration> analyse(const IConfig &cfg, Environment<Duration>, Iterator first, Iterator last) { + if (!cfg.benchmarkNoAnalysis()) { + std::vector<double> samples; + samples.reserve(last - first); + std::transform(first, last, std::back_inserter(samples), [](Duration d) { return d.count(); }); + + auto analysis = Catch::Benchmark::Detail::analyse_samples(cfg.benchmarkConfidenceInterval(), cfg.benchmarkResamples(), samples.begin(), samples.end()); + auto outliers = Catch::Benchmark::Detail::classify_outliers(samples.begin(), samples.end()); + + auto wrap_estimate = [](Estimate<double> e) { + return Estimate<Duration> { + Duration(e.point), + Duration(e.lower_bound), + Duration(e.upper_bound), + e.confidence_interval, + }; + }; + std::vector<Duration> samples2; + samples2.reserve(samples.size()); + std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](double d) { return Duration(d); }); + return { + std::move(samples2), + wrap_estimate(analysis.mean), + wrap_estimate(analysis.standard_deviation), + outliers, + analysis.outlier_variance, + }; + } else { + std::vector<Duration> samples; + samples.reserve(last - first); + + Duration mean = Duration(0); + int i = 0; + for (auto it = first; it < last; ++it, ++i) { + samples.push_back(Duration(*it)); + mean += Duration(*it); + } + mean /= i; + + return { + std::move(samples), + Estimate<Duration>{mean, mean, mean, 0.0}, + Estimate<Duration>{Duration(0), Duration(0), Duration(0), 0.0}, + OutlierClassification{}, + 0.0 + }; + } + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_analyse.hpp +#include <algorithm> +#include <functional> +#include <string> +#include <vector> +#include <cmath> + +namespace Catch { + namespace Benchmark { + struct Benchmark { + Benchmark(std::string &&name) + : name(std::move(name)) {} + + template <class FUN> + Benchmark(std::string &&name, FUN &&func) + : fun(std::move(func)), name(std::move(name)) {} + + template <typename Clock> + ExecutionPlan<FloatDuration<Clock>> prepare(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const { + auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = std::max(min_time, std::chrono::duration_cast<decltype(min_time)>(cfg.benchmarkWarmupTime())); + auto&& test = Detail::run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(run_time), 1, fun); + int new_iters = static_cast<int>(std::ceil(min_time * test.iterations / test.elapsed)); + return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun, std::chrono::duration_cast<FloatDuration<Clock>>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; + } + + template <typename Clock = default_clock> + void run() { + IConfigPtr cfg = getCurrentContext().getConfig(); + + auto env = Detail::measure_environment<Clock>(); + + getResultCapture().benchmarkPreparing(name); + CATCH_TRY{ + auto plan = user_code([&] { + return prepare<Clock>(*cfg, env); + }); + + BenchmarkInfo info { + name, + plan.estimated_duration.count(), + plan.iterations_per_sample, + cfg->benchmarkSamples(), + cfg->benchmarkResamples(), + env.clock_resolution.mean.count(), + env.clock_cost.mean.count() + }; + + getResultCapture().benchmarkStarting(info); + + auto samples = user_code([&] { + return plan.template run<Clock>(*cfg, env); + }); + + auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end()); + BenchmarkStats<FloatDuration<Clock>> stats{ info, analysis.samples, analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; + getResultCapture().benchmarkEnded(stats); + + } CATCH_CATCH_ALL{ + if (translateActiveException() != Detail::benchmarkErrorMsg) // benchmark errors have been reported, otherwise rethrow. + std::rethrow_exception(std::current_exception()); + } + } + + // sets lambda to be used in fun *and* executes benchmark! + template <typename Fun, + typename std::enable_if<!Detail::is_related<Fun, Benchmark>::value, int>::type = 0> + Benchmark & operator=(Fun func) { + fun = Detail::BenchmarkFunction(func); + run(); + return *this; + } + + explicit operator bool() { + return true; + } + + private: + Detail::BenchmarkFunction fun; + std::string name; + }; + } +} // namespace Catch + +#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 +#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 + +#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&](int benchmarkIndex) + +#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&] + +// end catch_benchmark.hpp +// start catch_constructor.hpp + +// Constructor and destructor helpers + + +#include <type_traits> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename T, bool Destruct> + struct ObjectStorage + { + using TStorage = typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type; + + ObjectStorage() : data() {} + + ObjectStorage(const ObjectStorage& other) + { + new(&data) T(other.stored_object()); + } + + ObjectStorage(ObjectStorage&& other) + { + new(&data) T(std::move(other.stored_object())); + } + + ~ObjectStorage() { destruct_on_exit<T>(); } + + template <typename... Args> + void construct(Args&&... args) + { + new (&data) T(std::forward<Args>(args)...); + } + + template <bool AllowManualDestruction = !Destruct> + typename std::enable_if<AllowManualDestruction>::type destruct() + { + stored_object().~T(); + } + + private: + // If this is a constructor benchmark, destruct the underlying object + template <typename U> + void destruct_on_exit(typename std::enable_if<Destruct, U>::type* = 0) { destruct<true>(); } + // Otherwise, don't + template <typename U> + void destruct_on_exit(typename std::enable_if<!Destruct, U>::type* = 0) { } + + T& stored_object() { + return *static_cast<T*>(static_cast<void*>(&data)); + } + + T const& stored_object() const { + return *static_cast<T*>(static_cast<void*>(&data)); + } + + TStorage data; + }; + } + + template <typename T> + using storage_for = Detail::ObjectStorage<T, true>; + + template <typename T> + using destructable_object = Detail::ObjectStorage<T, false>; + } +} + +// end catch_constructor.hpp +// end catch_benchmarking_all.hpp +#endif + #endif // ! CATCH_CONFIG_IMPL_ONLY #ifdef CATCH_IMPL @@ -5158,23 +7480,37 @@ namespace TestCaseTracking { SourceLineInfo location; NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); + friend bool operator==(NameAndLocation const& lhs, NameAndLocation const& rhs) { + return lhs.name == rhs.name + && lhs.location == rhs.location; + } }; - struct ITracker; + class ITracker; using ITrackerPtr = std::shared_ptr<ITracker>; - struct ITracker { - virtual ~ITracker(); + class ITracker { + NameAndLocation m_nameAndLocation; + + public: + ITracker(NameAndLocation const& nameAndLoc) : + m_nameAndLocation(nameAndLoc) + {} // static queries - virtual NameAndLocation const& nameAndLocation() const = 0; + NameAndLocation const& nameAndLocation() const { + return m_nameAndLocation; + } + + virtual ~ITracker(); // dynamic queries virtual bool isComplete() const = 0; // Successfully completed or failed virtual bool isSuccessfullyCompleted() const = 0; virtual bool isOpen() const = 0; // Started but not complete virtual bool hasChildren() const = 0; + virtual bool hasStarted() const = 0; virtual ITracker& parent() = 0; @@ -5189,7 +7525,7 @@ namespace TestCaseTracking { // Debug/ checking virtual bool isSectionTracker() const = 0; - virtual bool isIndexTracker() const = 0; + virtual bool isGeneratorTracker() const = 0; }; class TrackerContext { @@ -5206,8 +7542,6 @@ namespace TestCaseTracking { public: - static TrackerContext& instance(); - ITracker& startRun(); void endRun(); @@ -5231,7 +7565,6 @@ namespace TestCaseTracking { }; using Children = std::vector<ITrackerPtr>; - NameAndLocation m_nameAndLocation; TrackerContext& m_ctx; ITracker* m_parent; Children m_children; @@ -5240,11 +7573,13 @@ namespace TestCaseTracking { public: TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - NameAndLocation const& nameAndLocation() const override; bool isComplete() const override; bool isSuccessfullyCompleted() const override; bool isOpen() const override; bool hasChildren() const override; + bool hasStarted() const override { + return m_runState != NotStarted; + } void addChild( ITrackerPtr const& child ) override; @@ -5254,7 +7589,7 @@ namespace TestCaseTracking { void openChild() override; bool isSectionTracker() const override; - bool isIndexTracker() const override; + bool isGeneratorTracker() const override; void open(); @@ -5269,33 +7604,24 @@ namespace TestCaseTracking { class SectionTracker : public TrackerBase { std::vector<std::string> m_filters; + std::string m_trimmed_name; public: SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); bool isSectionTracker() const override; + bool isComplete() const override; + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); void tryOpen(); void addInitialFilters( std::vector<std::string> const& filters ); void addNextFilters( std::vector<std::string> const& filters ); - }; - - class IndexTracker : public TrackerBase { - int m_size; - int m_index = -1; - public: - IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ); - - bool isIndexTracker() const override; - void close() override; - - static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ); - - int index() const; - - void moveNext(); + //! Returns filters active in this tracker + std::vector<std::string> const& getFilters() const; + //! Returns whitespace-trimmed name of the tracked section + std::string const& trimmedName() const; }; } // namespace TestCaseTracking @@ -5303,7 +7629,6 @@ namespace TestCaseTracking { using TestCaseTracking::ITracker; using TestCaseTracking::TrackerContext; using TestCaseTracking::SectionTracker; -using TestCaseTracking::IndexTracker; } // namespace Catch @@ -5321,6 +7646,217 @@ namespace Catch { } // end catch_leak_detector.h // Cpp files will be included in the single-header file here +// start catch_stats.cpp + +// Statistical analysis tools + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + +#include <cassert> +#include <random> + +#if defined(CATCH_CONFIG_USE_ASYNC) +#include <future> +#endif + +namespace { + double erf_inv(double x) { + // Code accompanying the article "Approximating the erfinv function" in GPU Computing Gems, Volume 2 + double w, p; + + w = -log((1.0 - x) * (1.0 + x)); + + if (w < 6.250000) { + w = w - 3.125000; + p = -3.6444120640178196996e-21; + p = -1.685059138182016589e-19 + p * w; + p = 1.2858480715256400167e-18 + p * w; + p = 1.115787767802518096e-17 + p * w; + p = -1.333171662854620906e-16 + p * w; + p = 2.0972767875968561637e-17 + p * w; + p = 6.6376381343583238325e-15 + p * w; + p = -4.0545662729752068639e-14 + p * w; + p = -8.1519341976054721522e-14 + p * w; + p = 2.6335093153082322977e-12 + p * w; + p = -1.2975133253453532498e-11 + p * w; + p = -5.4154120542946279317e-11 + p * w; + p = 1.051212273321532285e-09 + p * w; + p = -4.1126339803469836976e-09 + p * w; + p = -2.9070369957882005086e-08 + p * w; + p = 4.2347877827932403518e-07 + p * w; + p = -1.3654692000834678645e-06 + p * w; + p = -1.3882523362786468719e-05 + p * w; + p = 0.0001867342080340571352 + p * w; + p = -0.00074070253416626697512 + p * w; + p = -0.0060336708714301490533 + p * w; + p = 0.24015818242558961693 + p * w; + p = 1.6536545626831027356 + p * w; + } else if (w < 16.000000) { + w = sqrt(w) - 3.250000; + p = 2.2137376921775787049e-09; + p = 9.0756561938885390979e-08 + p * w; + p = -2.7517406297064545428e-07 + p * w; + p = 1.8239629214389227755e-08 + p * w; + p = 1.5027403968909827627e-06 + p * w; + p = -4.013867526981545969e-06 + p * w; + p = 2.9234449089955446044e-06 + p * w; + p = 1.2475304481671778723e-05 + p * w; + p = -4.7318229009055733981e-05 + p * w; + p = 6.8284851459573175448e-05 + p * w; + p = 2.4031110387097893999e-05 + p * w; + p = -0.0003550375203628474796 + p * w; + p = 0.00095328937973738049703 + p * w; + p = -0.0016882755560235047313 + p * w; + p = 0.0024914420961078508066 + p * w; + p = -0.0037512085075692412107 + p * w; + p = 0.005370914553590063617 + p * w; + p = 1.0052589676941592334 + p * w; + p = 3.0838856104922207635 + p * w; + } else { + w = sqrt(w) - 5.000000; + p = -2.7109920616438573243e-11; + p = -2.5556418169965252055e-10 + p * w; + p = 1.5076572693500548083e-09 + p * w; + p = -3.7894654401267369937e-09 + p * w; + p = 7.6157012080783393804e-09 + p * w; + p = -1.4960026627149240478e-08 + p * w; + p = 2.9147953450901080826e-08 + p * w; + p = -6.7711997758452339498e-08 + p * w; + p = 2.2900482228026654717e-07 + p * w; + p = -9.9298272942317002539e-07 + p * w; + p = 4.5260625972231537039e-06 + p * w; + p = -1.9681778105531670567e-05 + p * w; + p = 7.5995277030017761139e-05 + p * w; + p = -0.00021503011930044477347 + p * w; + p = -0.00013871931833623122026 + p * w; + p = 1.0103004648645343977 + p * w; + p = 4.8499064014085844221 + p * w; + } + return p * x; + } + + double standard_deviation(std::vector<double>::iterator first, std::vector<double>::iterator last) { + auto m = Catch::Benchmark::Detail::mean(first, last); + double variance = std::accumulate(first, last, 0., [m](double a, double b) { + double diff = b - m; + return a + diff * diff; + }) / (last - first); + return std::sqrt(variance); + } + +} + +namespace Catch { + namespace Benchmark { + namespace Detail { + + double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last) { + auto count = last - first; + double idx = (count - 1) * k / static_cast<double>(q); + int j = static_cast<int>(idx); + double g = idx - j; + std::nth_element(first, first + j, last); + auto xj = first[j]; + if (g == 0) return xj; + + auto xj1 = *std::min_element(first + (j + 1), last); + return xj + g * (xj1 - xj); + } + + double erfc_inv(double x) { + return erf_inv(1.0 - x); + } + + double normal_quantile(double p) { + static const double ROOT_TWO = std::sqrt(2.0); + + double result = 0.0; + assert(p >= 0 && p <= 1); + if (p < 0 || p > 1) { + return result; + } + + result = -erfc_inv(2.0 * p); + // result *= normal distribution standard deviation (1.0) * sqrt(2) + result *= /*sd * */ ROOT_TWO; + // result += normal disttribution mean (0) + return result; + } + + double outlier_variance(Estimate<double> mean, Estimate<double> stddev, int n) { + double sb = stddev.point; + double mn = mean.point / n; + double mg_min = mn / 2.; + double sg = (std::min)(mg_min / 4., sb / std::sqrt(n)); + double sg2 = sg * sg; + double sb2 = sb * sb; + + auto c_max = [n, mn, sb2, sg2](double x) -> double { + double k = mn - x; + double d = k * k; + double nd = n * d; + double k0 = -n * nd; + double k1 = sb2 - n * sg2 + nd; + double det = k1 * k1 - 4 * sg2 * k0; + return (int)(-2. * k0 / (k1 + std::sqrt(det))); + }; + + auto var_out = [n, sb2, sg2](double c) { + double nc = n - c; + return (nc / n) * (sb2 - nc * sg2); + }; + + return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2; + } + + bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector<double>::iterator first, std::vector<double>::iterator last) { + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + static std::random_device entropy; + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + auto n = static_cast<int>(last - first); // seriously, one can't use integral types without hell in C++ + + auto mean = &Detail::mean<std::vector<double>::iterator>; + auto stddev = &standard_deviation; + +#if defined(CATCH_CONFIG_USE_ASYNC) + auto Estimate = [=](double(*f)(std::vector<double>::iterator, std::vector<double>::iterator)) { + auto seed = entropy(); + return std::async(std::launch::async, [=] { + std::mt19937 rng(seed); + auto resampled = resample(rng, n_resamples, first, last, f); + return bootstrap(confidence_level, first, last, resampled, f); + }); + }; + + auto mean_future = Estimate(mean); + auto stddev_future = Estimate(stddev); + + auto mean_estimate = mean_future.get(); + auto stddev_estimate = stddev_future.get(); +#else + auto Estimate = [=](double(*f)(std::vector<double>::iterator, std::vector<double>::iterator)) { + auto seed = entropy(); + std::mt19937 rng(seed); + auto resampled = resample(rng, n_resamples, first, last, f); + return bootstrap(confidence_level, first, last, resampled, f); + }; + + auto mean_estimate = Estimate(mean); + auto stddev_estimate = Estimate(stddev); +#endif // CATCH_USE_ASYNC + + double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n); + + return { mean_estimate, stddev_estimate, outlier_variance }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING +// end catch_stats.cpp // start catch_approx.cpp #include <cmath> @@ -5365,21 +7901,22 @@ namespace Detail { bool Approx::equalityComparisonImpl(const double other) const { // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value // Thanks to Richard Harris for his help refining the scaled margin value - return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); + return marginComparison(m_value, other, m_margin) + || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value))); } - void Approx::setMargin(double margin) { - CATCH_ENFORCE(margin >= 0, - "Invalid Approx::margin: " << margin << '.' + void Approx::setMargin(double newMargin) { + CATCH_ENFORCE(newMargin >= 0, + "Invalid Approx::margin: " << newMargin << '.' << " Approx::Margin has to be non-negative."); - m_margin = margin; + m_margin = newMargin; } - void Approx::setEpsilon(double epsilon) { - CATCH_ENFORCE(epsilon >= 0 && epsilon <= 1.0, - "Invalid Approx::epsilon: " << epsilon << '.' + void Approx::setEpsilon(double newEpsilon) { + CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0, + "Invalid Approx::epsilon: " << newEpsilon << '.' << " Approx::epsilon has to be in [0, 1]"); - m_epsilon = epsilon; + m_epsilon = newEpsilon; } } // end namespace Detail @@ -5401,58 +7938,6 @@ std::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx co // end catch_approx.cpp // start catch_assertionhandler.cpp -// start catch_context.h - -#include <memory> - -namespace Catch { - - struct IResultCapture; - struct IRunner; - struct IConfig; - struct IMutableContext; - - using IConfigPtr = std::shared_ptr<IConfig const>; - - struct IContext - { - virtual ~IContext(); - - virtual IResultCapture* getResultCapture() = 0; - virtual IRunner* getRunner() = 0; - virtual IConfigPtr const& getConfig() const = 0; - }; - - struct IMutableContext : IContext - { - virtual ~IMutableContext(); - virtual void setResultCapture( IResultCapture* resultCapture ) = 0; - virtual void setRunner( IRunner* runner ) = 0; - virtual void setConfig( IConfigPtr const& config ) = 0; - - private: - static IMutableContext *currentContext; - friend IMutableContext& getCurrentMutableContext(); - friend void cleanUpContext(); - static void createContext(); - }; - - inline IMutableContext& getCurrentMutableContext() - { - if( !IMutableContext::currentContext ) - IMutableContext::createContext(); - return *IMutableContext::currentContext; - } - - inline IContext& getCurrentContext() - { - return getCurrentMutableContext(); - } - - void cleanUpContext(); -} - -// end catch_context.h // start catch_debugger.h namespace Catch { @@ -5461,7 +7946,24 @@ namespace Catch { #ifdef CATCH_PLATFORM_MAC - #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + #if defined(__i386__) || defined(__x86_64__) + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + #elif defined(__aarch64__) + #define CATCH_TRAP() __asm__(".inst 0xd4200000") + #endif + +#elif defined(CATCH_PLATFORM_IPHONE) + + // use inline assembler + #if defined(__i386__) || defined(__x86_64__) + #define CATCH_TRAP() __asm__("int $3") + #elif defined(__aarch64__) + #define CATCH_TRAP() __asm__(".inst 0xd4200000") + #elif defined(__arm__) && !defined(__thumb__) + #define CATCH_TRAP() __asm__(".inst 0xe7f001f0") + #elif defined(__arm__) && defined(__thumb__) + #define CATCH_TRAP() __asm__(".inst 0xde01") + #endif #elif defined(CATCH_PLATFORM_LINUX) // If we can use inline assembler, do it because this allows us to break @@ -5481,13 +7983,12 @@ namespace Catch { #define CATCH_TRAP() DebugBreak() #endif -#ifdef CATCH_TRAP - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } -#else - namespace Catch { - inline void doNothing() {} - } - #define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing() +#ifndef CATCH_BREAK_INTO_DEBUGGER + #ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }() + #else + #define CATCH_BREAK_INTO_DEBUGGER() []{}() + #endif #endif // end catch_debugger.h @@ -5495,86 +7996,58 @@ namespace Catch { // start catch_fatal_condition.h -// start catch_windows_h_proxy.h - - -#if defined(CATCH_PLATFORM_WINDOWS) - -#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -# define CATCH_DEFINED_NOMINMAX -# define NOMINMAX -#endif -#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) -# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif - -#ifdef __AFXDLL -#include <AfxWin.h> -#else -#include <windows.h> -#endif - -#ifdef CATCH_DEFINED_NOMINMAX -# undef NOMINMAX -#endif -#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# undef WIN32_LEAN_AND_MEAN -#endif - -#endif // defined(CATCH_PLATFORM_WINDOWS) - -// end catch_windows_h_proxy.h -#if defined( CATCH_CONFIG_WINDOWS_SEH ) +#include <cassert> namespace Catch { - struct FatalConditionHandler { - - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + // Wrapper for platform-specific fatal error (signals/SEH) handlers + // + // Tries to be cooperative with other handlers, and not step over + // other handlers. This means that unknown structured exceptions + // are passed on, previous signal handlers are called, and so on. + // + // Can only be instantiated once, and assumes that once a signal + // is caught, the binary will end up terminating. Thus, there + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + void engage_platform(); + void disengage_platform(); + public: + // Should also have platform-specific implementations as needed FatalConditionHandler(); - static void reset(); ~FatalConditionHandler(); - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; - -} // namespace Catch - -#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) - -#include <signal.h> - -namespace Catch { - - struct FatalConditionHandler { - - static bool isSet; - static struct sigaction oldSigActions[]; - static stack_t oldSigStack; - static char altStackMem[]; - - static void handleSignal( int sig ); + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } - FatalConditionHandler(); - ~FatalConditionHandler(); - static void reset(); + void disengage() { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } }; -} // namespace Catch - -#else - -namespace Catch { - struct FatalConditionHandler { - void reset(); + //! Simple RAII guard for (dis)engaging the FatalConditionHandler + class FatalConditionHandlerGuard { + FatalConditionHandler* m_handler; + public: + FatalConditionHandlerGuard(FatalConditionHandler* handler): + m_handler(handler) { + m_handler->engage(); + } + ~FatalConditionHandlerGuard() { + m_handler->disengage(); + } }; -} -#endif +} // end namespace Catch // end catch_fatal_condition.h #include <string> @@ -5634,14 +8107,20 @@ namespace Catch { void sectionEnded( SectionEndInfo const& endInfo ) override; void sectionEndedEarly( SectionEndInfo const& endInfo ) override; - auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; + auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void benchmarkPreparing( std::string const& name ) override; void benchmarkStarting( BenchmarkInfo const& info ) override; - void benchmarkEnded( BenchmarkStats const& stats ) override; + void benchmarkEnded( BenchmarkStats<> const& stats ) override; + void benchmarkFailed( std::string const& error ) override; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING void pushScopedMessage( MessageInfo const& message ) override; void popScopedMessage( MessageInfo const& message ) override; + void emplaceUnscopedMessage( MessageBuilder const& builder ) override; + std::string getCurrentTestName() const override; const AssertionResult* getLastResult() const override; @@ -5682,22 +8161,26 @@ namespace Catch { TestRunInfo m_runInfo; IMutableContext& m_context; TestCase const* m_activeTestCase = nullptr; - ITracker* m_testCaseTracker; + ITracker* m_testCaseTracker = nullptr; Option<AssertionResult> m_lastResult; IConfigPtr m_config; Totals m_totals; IStreamingReporterPtr m_reporter; std::vector<MessageInfo> m_messages; + std::vector<ScopedMessage> m_messageScopes; /* Keeps owners of so-called unscoped messages. */ AssertionInfo m_lastAssertionInfo; std::vector<SectionEndInfo> m_unfinishedSections; std::vector<ITracker*> m_activeSections; TrackerContext m_trackerContext; + FatalConditionHandler m_fatalConditionhandler; bool m_lastAssertionPassed = false; bool m_shouldReportUnexpected = true; bool m_includeSuccessfulResults; }; + void seedRng(IConfig const& config); + unsigned int rngSeed(); } // end namespace Catch // end catch_run_context.h @@ -5844,7 +8327,7 @@ namespace Catch { } bool AssertionResult::hasExpression() const { - return m_info.capturedExpression[0] != 0; + return !m_info.capturedExpression.empty(); } bool AssertionResult::hasMessage() const { @@ -5852,16 +8335,22 @@ namespace Catch { } std::string AssertionResult::getExpression() const { - if( isFalseTest( m_info.resultDisposition ) ) - return "!(" + m_info.capturedExpression + ")"; - else - return m_info.capturedExpression; + // Possibly overallocating by 3 characters should be basically free + std::string expr; expr.reserve(m_info.capturedExpression.size() + 3); + if (isFalseTest(m_info.resultDisposition)) { + expr += "!("; + } + expr += m_info.capturedExpression; + if (isFalseTest(m_info.resultDisposition)) { + expr += ')'; + } + return expr; } std::string AssertionResult::getExpressionInMacro() const { std::string expr; - if( m_info.macroName[0] == 0 ) - expr = m_info.capturedExpression; + if( m_info.macroName.empty() ) + expr = static_cast<std::string>(m_info.capturedExpression); else { expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); expr += m_info.macroName; @@ -5896,32 +8385,6 @@ namespace Catch { } // end namespace Catch // end catch_assertionresult.cpp -// start catch_benchmark.cpp - -namespace Catch { - - auto BenchmarkLooper::getResolution() -> uint64_t { - return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); - } - - void BenchmarkLooper::reportStart() { - getResultCapture().benchmarkStarting( { m_name } ); - } - auto BenchmarkLooper::needsMoreIterations() -> bool { - auto elapsed = m_timer.getElapsedNanoseconds(); - - // Exponentially increasing iterations until we're confident in our timer resolution - if( elapsed < m_resolution ) { - m_iterationsToRun *= 10; - return true; - } - - getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); - return false; - } - -} // end namespace Catch -// end catch_benchmark.cpp // start catch_capture_matchers.cpp namespace Catch { @@ -6067,6 +8530,9 @@ public: m_suffix = false; auto width = m_column.m_width - indent(); m_end = m_pos; + if (line()[m_pos] == '\n') { + ++m_end; + } while (m_end < line().size() && line()[m_end] != '\n') ++m_end; @@ -6325,6 +8791,7 @@ inline auto Column::operator + (Column const& other) -> Columns { // ----------- end of #include from clara_textflow.hpp ----------- // ........... back in clara.hpp +#include <cctype> #include <string> #include <memory> #include <set> @@ -6617,7 +9084,7 @@ namespace detail { } inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { std::string srcLC = source; - std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast<char>( ::tolower(c) ); } ); + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( unsigned char c ) { return static_cast<char>( std::tolower(c) ); } ); if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") target = true; else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") @@ -7265,9 +9732,14 @@ namespace Catch { if( !line.empty() && !startsWith( line, '#' ) ) { if( !startsWith( line, '"' ) ) line = '"' + line + '"'; - config.testsOrTags.push_back( line + ',' ); + config.testsOrTags.push_back( line ); + config.testsOrTags.emplace_back( "," ); } } + //Remove comma in the end + if(!config.testsOrTags.empty()) + config.testsOrTags.erase( config.testsOrTags.end()-1 ); + return ParserResult::ok( ParseResultType::Matched ); }; auto const setTestOrder = [&]( std::string const& order ) { @@ -7302,14 +9774,16 @@ namespace Catch { }; auto const setWaitForKeypress = [&]( std::string const& keypress ) { auto keypressLc = toLower( keypress ); - if( keypressLc == "start" ) + if (keypressLc == "never") + config.waitForKeypress = WaitForKeypress::Never; + else if( keypressLc == "start" ) config.waitForKeypress = WaitForKeypress::BeforeStart; else if( keypressLc == "exit" ) config.waitForKeypress = WaitForKeypress::BeforeExit; else if( keypressLc == "both" ) config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; else - return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::runtimeError( "keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised" ); return ParserResult::ok( ParseResultType::Matched ); }; auto const setVerbosity = [&]( std::string const& verbosity ) { @@ -7379,6 +9853,9 @@ namespace Catch { | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) ["-d"]["--durations"] ( "show test durations" ) + | Opt( config.minDuration, "seconds" ) + ["-D"]["--min-duration"] + ( "show test durations for tests taking at least the given number of seconds" ) | Opt( loadTestNamesFromFile, "filename" ) ["-f"]["--input-file"] ( "load test names to run from a file" ) @@ -7409,13 +9886,24 @@ namespace Catch { | Opt( config.libIdentify ) ["--libidentify"] ( "report name and version according to libidentify standard" ) - | Opt( setWaitForKeypress, "start|exit|both" ) + | Opt( setWaitForKeypress, "never|start|exit|both" ) ["--wait-for-keypress"] ( "waits for a keypress before exiting" ) - | Opt( config.benchmarkResolutionMultiple, "multiplier" ) - ["--benchmark-resolution-multiple"] - ( "multiple of clock resolution to run benchmarks" ) - + | Opt( config.benchmarkSamples, "samples" ) + ["--benchmark-samples"] + ( "number of samples to collect (default: 100)" ) + | Opt( config.benchmarkResamples, "resamples" ) + ["--benchmark-resamples"] + ( "number of resamples for the bootstrap (default: 100000)" ) + | Opt( config.benchmarkConfidenceInterval, "confidence interval" ) + ["--benchmark-confidence-interval"] + ( "confidence interval for the bootstrap (between 0 and 1, default: 0.95)" ) + | Opt( config.benchmarkNoAnalysis ) + ["--benchmark-no-analysis"] + ( "perform only measurements; do not perform any analysis" ) + | Opt( config.benchmarkWarmupTime, "benchmarkWarmupTime" ) + ["--benchmark-warmup-time"] + ( "amount of time in milliseconds spent on warming up each test (default: 100)" ) | Arg( config.testsOrTags, "test name|pattern|tags" ) ( "which test or tests to use" ); @@ -7431,9 +9919,6 @@ namespace Catch { namespace Catch { - bool SourceLineInfo::empty() const noexcept { - return file[0] == '\0'; - } bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); } @@ -7469,14 +9954,23 @@ namespace Catch { : m_data( data ), m_stream( openStream() ) { - TestSpecParser parser(ITagAliasRegistry::get()); - if (data.testsOrTags.empty()) { - parser.parse("~[.]"); // All not hidden tests + // We need to trim filter specs to avoid trouble with superfluous + // whitespace (esp. important for bdd macros, as those are manually + // aligned with whitespace). + + for (auto& elem : m_data.testsOrTags) { + elem = trim(elem); } - else { + for (auto& elem : m_data.sectionsToRun) { + elem = trim(elem); + } + + TestSpecParser parser(ITagAliasRegistry::get()); + if (!m_data.testsOrTags.empty()) { m_hasTestFilters = true; - for( auto const& testOrTags : data.testsOrTags ) - parser.parse( testOrTags ); + for (auto const& testOrTags : m_data.testsOrTags) { + parser.parse(testOrTags); + } } m_testSpec = parser.testSpec(); } @@ -7509,15 +10003,21 @@ namespace Catch { bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } + double Config::minDuration() const { return m_data.minDuration; } RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } unsigned int Config::rngSeed() const { return m_data.rngSeed; } - int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } int Config::abortAfter() const { return m_data.abortAfter; } bool Config::showInvisibles() const { return m_data.showInvisibles; } Verbosity Config::verbosity() const { return m_data.verbosity; } + bool Config::benchmarkNoAnalysis() const { return m_data.benchmarkNoAnalysis; } + int Config::benchmarkSamples() const { return m_data.benchmarkSamples; } + double Config::benchmarkConfidenceInterval() const { return m_data.benchmarkConfidenceInterval; } + unsigned int Config::benchmarkResamples() const { return m_data.benchmarkResamples; } + std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); } + IStream const* Config::openStream() { return Catch::makeStream(m_data.outputFilename); } @@ -7546,6 +10046,36 @@ namespace Catch { } // end catch_errno_guard.h +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include <AfxWin.h> +#else +#include <windows.h> +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h #include <sstream> namespace Catch { @@ -7557,7 +10087,7 @@ namespace Catch { }; struct NoColourImpl : IColourImpl { - void use( Colour::Code ) {} + void use( Colour::Code ) override {} static IColourImpl* instance() { static NoColourImpl s_instance; @@ -7591,7 +10121,7 @@ namespace { originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); } - virtual void use( Colour::Code _colourCode ) override { + void use( Colour::Code _colourCode ) override { switch( _colourCode ) { case Colour::None: return setTextAttribute( originalForegroundAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); @@ -7654,7 +10184,7 @@ namespace { // https://github.com/philsquared/Catch/pull/131 class PosixColourImpl : public IColourImpl { public: - virtual void use( Colour::Code _colourCode ) override { + void use( Colour::Code _colourCode ) override { switch( _colourCode ) { case Colour::None: case Colour::White: return setColour( "[0m" ); @@ -7682,13 +10212,14 @@ namespace { private: void setColour( const char* _escapeCode ) { - Catch::cout() << '\033' << _escapeCode; + getCurrentContext().getConfig()->stream() + << '\033' << _escapeCode; } }; bool useColourOnPlatform() { return -#ifdef CATCH_PLATFORM_MAC +#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) !isDebuggerActive() && #endif #if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) @@ -7729,13 +10260,13 @@ namespace Catch { namespace Catch { Colour::Colour( Code _colourCode ) { use( _colourCode ); } - Colour::Colour( Colour&& rhs ) noexcept { - m_moved = rhs.m_moved; - rhs.m_moved = true; + Colour::Colour( Colour&& other ) noexcept { + m_moved = other.m_moved; + other.m_moved = true; } - Colour& Colour::operator=( Colour&& rhs ) noexcept { - m_moved = rhs.m_moved; - rhs.m_moved = true; + Colour& Colour::operator=( Colour&& other ) noexcept { + m_moved = other.m_moved; + other.m_moved = true; return *this; } @@ -7743,7 +10274,13 @@ namespace Catch { void Colour::use( Code _colourCode ) { static IColourImpl* impl = platformColourInstance(); - impl->use( _colourCode ); + // Strictly speaking, this cannot possibly happen. + // However, under some conditions it does happen (see #1626), + // and this change is small enough that we can let practicality + // triumph over purity in this case. + if (impl != nullptr) { + impl->use( _colourCode ); + } } std::ostream& operator << ( std::ostream& os, Colour const& ) { @@ -7764,27 +10301,27 @@ namespace Catch { class Context : public IMutableContext, NonCopyable { public: // IContext - virtual IResultCapture* getResultCapture() override { + IResultCapture* getResultCapture() override { return m_resultCapture; } - virtual IRunner* getRunner() override { + IRunner* getRunner() override { return m_runner; } - virtual IConfigPtr const& getConfig() const override { + IConfigPtr const& getConfig() const override { return m_config; } - virtual ~Context() override; + ~Context() override; public: // IMutableContext - virtual void setResultCapture( IResultCapture* resultCapture ) override { + void setResultCapture( IResultCapture* resultCapture ) override { m_resultCapture = resultCapture; } - virtual void setRunner( IRunner* runner ) override { + void setRunner( IRunner* runner ) override { m_runner = runner; } - virtual void setConfig( IConfigPtr const& config ) override { + void setConfig( IConfigPtr const& config ) override { m_config = config; } @@ -7810,6 +10347,12 @@ namespace Catch { IContext::~IContext() = default; IMutableContext::~IMutableContext() = default; Context::~Context() = default; + + SimplePcg32& rng() { + static SimplePcg32 s_rng; + return s_rng; + } + } // end catch_context.cpp // start catch_debug_console.cpp @@ -7823,7 +10366,16 @@ namespace Catch { } // end catch_debug_console.h -#ifdef CATCH_PLATFORM_WINDOWS +#if defined(CATCH_CONFIG_ANDROID_LOGWRITE) +#include <android/log.h> + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + __android_log_write( ANDROID_LOG_DEBUG, "Catch", text.c_str() ); + } + } + +#elif defined(CATCH_PLATFORM_WINDOWS) namespace Catch { void writeToDebugConsole( std::string const& text ) { @@ -7844,25 +10396,28 @@ namespace Catch { // end catch_debug_console.cpp // start catch_debugger.cpp -#ifdef CATCH_PLATFORM_MAC +#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) -# include <assert.h> -# include <stdbool.h> +# include <cassert> # include <sys/types.h> # include <unistd.h> -# include <sys/sysctl.h> # include <cstddef> # include <ostream> -namespace Catch { +#ifdef __apple_build_version__ + // These headers will only compile with AppleClang (XCode) + // For other compilers (Clang, GCC, ... ) we need to exclude them +# include <sys/sysctl.h> +#endif + namespace Catch { + #ifdef __apple_build_version__ // The following function is taken directly from the following technical note: - // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + // https://developer.apple.com/library/archive/qa/qa1361/_index.html // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). bool isDebuggerActive(){ - int mib[4]; struct kinfo_proc info; std::size_t size; @@ -7892,6 +10447,12 @@ namespace Catch { return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); } + #else + bool isDebuggerActive() { + // We need to find another way to determine this for non-appleclang compilers on macOS + return false; + } + #endif } // namespace Catch #elif defined(CATCH_PLATFORM_LINUX) @@ -7962,6 +10523,8 @@ namespace Catch { // end catch_decomposer.cpp // start catch_enforce.cpp +#include <stdexcept> + namespace Catch { #if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) [[noreturn]] @@ -7971,8 +10534,116 @@ namespace Catch { std::terminate(); } #endif + + [[noreturn]] + void throw_logic_error(std::string const& msg) { + throw_exception(std::logic_error(msg)); + } + + [[noreturn]] + void throw_domain_error(std::string const& msg) { + throw_exception(std::domain_error(msg)); + } + + [[noreturn]] + void throw_runtime_error(std::string const& msg) { + throw_exception(std::runtime_error(msg)); + } + } // namespace Catch; // end catch_enforce.cpp +// start catch_enum_values_registry.cpp +// start catch_enum_values_registry.h + +#include <vector> +#include <memory> + +namespace Catch { + + namespace Detail { + + std::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ); + + class EnumValuesRegistry : public IMutableEnumValuesRegistry { + + std::vector<std::unique_ptr<EnumInfo>> m_enumInfos; + + EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values) override; + }; + + std::vector<StringRef> parseEnums( StringRef enums ); + + } // Detail + +} // Catch + +// end catch_enum_values_registry.h + +#include <map> +#include <cassert> + +namespace Catch { + + IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {} + + namespace Detail { + + namespace { + // Extracts the actual name part of an enum instance + // In other words, it returns the Blue part of Bikeshed::Colour::Blue + StringRef extractInstanceName(StringRef enumInstance) { + // Find last occurrence of ":" + size_t name_start = enumInstance.size(); + while (name_start > 0 && enumInstance[name_start - 1] != ':') { + --name_start; + } + return enumInstance.substr(name_start, enumInstance.size() - name_start); + } + } + + std::vector<StringRef> parseEnums( StringRef enums ) { + auto enumValues = splitStringRef( enums, ',' ); + std::vector<StringRef> parsed; + parsed.reserve( enumValues.size() ); + for( auto const& enumValue : enumValues ) { + parsed.push_back(trim(extractInstanceName(enumValue))); + } + return parsed; + } + + EnumInfo::~EnumInfo() {} + + StringRef EnumInfo::lookup( int value ) const { + for( auto const& valueToName : m_values ) { + if( valueToName.first == value ) + return valueToName.second; + } + return "{** unexpected enum value **}"_sr; + } + + std::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) { + std::unique_ptr<EnumInfo> enumInfo( new EnumInfo ); + enumInfo->m_name = enumName; + enumInfo->m_values.reserve( values.size() ); + + const auto valueNames = Catch::Detail::parseEnums( allValueNames ); + assert( valueNames.size() == values.size() ); + std::size_t i = 0; + for( auto value : values ) + enumInfo->m_values.emplace_back(value, valueNames[i++]); + + return enumInfo; + } + + EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) { + m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values)); + return *m_enumInfos.back(); + } + + } // Detail +} // Catch + +// end catch_enum_values_registry.cpp // start catch_errno_guard.cpp #include <cerrno> @@ -7996,7 +10667,7 @@ namespace Catch { public: ~ExceptionTranslatorRegistry(); virtual void registerTranslator( const IExceptionTranslator* translator ); - virtual std::string translateActiveException() const override; + std::string translateActiveException() const override; std::string tryTranslators() const; private: @@ -8061,54 +10732,82 @@ namespace Catch { } } + std::string ExceptionTranslatorRegistry::tryTranslators() const { + if (m_translators.empty()) { + std::rethrow_exception(std::current_exception()); + } else { + return m_translators[0]->translate(m_translators.begin() + 1, m_translators.end()); + } + } + #else // ^^ Exceptions are enabled // Exceptions are disabled vv std::string ExceptionTranslatorRegistry::translateActiveException() const { CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); } -#endif std::string ExceptionTranslatorRegistry::tryTranslators() const { - if( m_translators.empty() ) - std::rethrow_exception(std::current_exception()); - else - return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); + CATCH_INTERNAL_ERROR("Attempted to use exception translators under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); } +#endif + } // end catch_exception_translator_registry.cpp // start catch_fatal_condition.cpp -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif +#include <algorithm> + +#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + // If neither SEH nor signal handling is required, the handler impls + // do not have to do anything, and can be empty. + void FatalConditionHandler::engage_platform() {} + void FatalConditionHandler::disengage_platform() {} + FatalConditionHandler::FatalConditionHandler() = default; + FatalConditionHandler::~FatalConditionHandler() = default; + +} // end namespace Catch + +#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) +#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" +#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) namespace { - // Report the error condition + //! Signals fatal error message to the run context void reportFatal( char const * const message ) { Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); } -} -#endif // signals/SEH handling + //! Minimal size Catch2 needs for its own fatal error handling. + //! Picked anecdotally, so it might not be sufficient on all + //! platforms, and for all configurations. + constexpr std::size_t minStackSizeForErrors = 32 * 1024; +} // end unnamed namespace + +#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; // There is no 1-1 mapping between signals and windows exceptions. // Windows can easily distinguish between SO and SigSegV, // but SigInt, SigTerm, etc are handled differently. static SignalDefs signalDefs[] = { - { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, - { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, - { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, - { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + { static_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION), "SIGILL - Illegal instruction signal" }, + { static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow" }, + { static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION), "SIGSEGV - Segmentation violation signal" }, + { static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, }; - LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { for (auto const& def : signalDefs) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { reportFatal(def.name); @@ -8119,38 +10818,50 @@ namespace Catch { return EXCEPTION_CONTINUE_SEARCH; } + // Since we do not support multiple instantiations, we put these + // into global variables and rely on cleaning them up in outlined + // constructors/destructors + static PVOID exceptionHandlerHandle = nullptr; + + // For MSVC, we reserve part of the stack memory for handling + // memory overflow structured exception. FatalConditionHandler::FatalConditionHandler() { - isSet = true; - // 32k seems enough for Catch to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - exceptionHandlerHandle = nullptr; + ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors); + if (!SetThreadStackGuarantee(&guaranteeSize)) { + // We do not want to fully error out, because needing + // the stack reserve should be rare enough anyway. + Catch::cerr() + << "Failed to reserve piece of stack." + << " Stack overflows will not be reported successfully."; + } + } + + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + FatalConditionHandler::~FatalConditionHandler() = default; + + void FatalConditionHandler::engage_platform() { // Register as first handler in current chain exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); + if (!exceptionHandlerHandle) { + CATCH_RUNTIME_ERROR("Could not register vectored exception handler"); + } } - void FatalConditionHandler::reset() { - if (isSet) { - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = nullptr; - isSet = false; + void FatalConditionHandler::disengage_platform() { + if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) { + CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler"); } + exceptionHandlerHandle = nullptr; } - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } +} // end namespace Catch -bool FatalConditionHandler::isSet = false; -ULONG FatalConditionHandler::guaranteeSize = 0; -PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; +#endif // CATCH_CONFIG_WINDOWS_SEH -} // namespace Catch +#if defined( CATCH_CONFIG_POSIX_SIGNALS ) -#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) +#include <signal.h> namespace Catch { @@ -8159,10 +10870,6 @@ namespace Catch { const char* name; }; - // 32kb for the alternate stack seems to be sufficient. However, this value - // is experimentally determined, so that's not guaranteed. - constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; - static SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, @@ -8172,7 +10879,32 @@ namespace Catch { { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; - void FatalConditionHandler::handleSignal( int sig ) { +// Older GCCs trigger -Wmissing-field-initializers for T foo = {} +// which is zero initialization, but not explicit. We want to avoid +// that. +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + + static char* altStackMem = nullptr; + static std::size_t altStackSize = 0; + static stack_t oldSigStack{}; + static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; + + static void restorePreviousSignalHandlers() { + // We set signal handlers back to the previous ones. Hopefully + // nobody overwrote them in the meantime, and doesn't expect + // their signal handlers to live past ours given that they + // installed them after ours.. + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + } + + static void handleSignal( int sig ) { char const * name = "<unknown signal>"; for (auto const& def : signalDefs) { if (sig == def.id) { @@ -8180,16 +10912,33 @@ namespace Catch { break; } } - reset(); - reportFatal(name); + // We need to restore previous signal handlers and let them do + // their thing, so that the users can have the debugger break + // when a signal is raised, and so on. + restorePreviousSignalHandlers(); + reportFatal( name ); raise( sig ); } FatalConditionHandler::FatalConditionHandler() { - isSet = true; + assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors); + } + altStackMem = new char[altStackSize](); + } + + FatalConditionHandler::~FatalConditionHandler() { + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = nullptr; + } + + void FatalConditionHandler::engage_platform() { stack_t sigStack; sigStack.ss_sp = altStackMem; - sigStack.ss_size = sigStackSize; + sigStack.ss_size = altStackSize; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = { }; @@ -8201,59 +10950,20 @@ namespace Catch { } } - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } - - void FatalConditionHandler::reset() { - if( isSet ) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - isSet = false; - } - } - - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[sigStackSize] = {}; - -} // namespace Catch - -#else - -namespace Catch { - void FatalConditionHandler::reset() {} -} - -#endif // signals/SEH handling - #if defined(__GNUC__) # pragma GCC diagnostic pop #endif -// end catch_fatal_condition.cpp -// start catch_generators.cpp -// start catch_random_number_generator.h - -#include <algorithm> -#include <random> - -namespace Catch { - - struct IConfig; + void FatalConditionHandler::disengage_platform() { + restorePreviousSignalHandlers(); + } - std::mt19937& rng(); - void seedRng( IConfig const& config ); - unsigned int rngSeed(); +} // end namespace Catch -} +#endif // CATCH_CONFIG_POSIX_SIGNALS +// end catch_fatal_condition.cpp +// start catch_generators.cpp -// end catch_random_number_generator.h #include <limits> #include <set> @@ -8261,34 +10971,16 @@ namespace Catch { IGeneratorTracker::~IGeneratorTracker() {} -namespace Generators { - - GeneratorBase::~GeneratorBase() {} - - std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize ) { - - assert( selectionSize <= sourceSize ); - std::vector<size_t> indices; - indices.reserve( selectionSize ); - std::uniform_int_distribution<size_t> uid( 0, sourceSize-1 ); +const char* GeneratorException::what() const noexcept { + return m_msg; +} - std::set<size_t> seen; - // !TBD: improve this algorithm - while( indices.size() < selectionSize ) { - auto index = uid( rng() ); - if( seen.insert( index ).second ) - indices.push_back( index ); - } - return indices; - } +namespace Generators { - auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { - return getResultCapture().acquireGeneratorTracker( lineInfo ); - } + GeneratorUntypedBase::~GeneratorUntypedBase() {} - template<> - auto all<int>() -> Generator<int> { - return range( std::numeric_limits<int>::min(), std::numeric_limits<int>::max() ); + auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + return getResultCapture().acquireGeneratorTracker( generatorName, lineInfo ); } } // namespace Generators @@ -8344,10 +11036,16 @@ namespace Catch { void noMatchingTestCases( std::string const& spec ) override; + void reportInvalidArguments(std::string const&arg) override; + static std::set<Verbosity> getSupportedVerbosities(); +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void benchmarkPreparing(std::string const& name) override; void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; - void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; + void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override; + void benchmarkFailed(std::string const&) override; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING void testRunStarting( TestRunInfo const& testRunInfo ) override; void testGroupStarting( GroupInfo const& groupInfo ) override; @@ -8538,7 +11236,7 @@ namespace Catch { std::size_t listReporters(); - Option<std::size_t> list( Config const& config ); + Option<std::size_t> list( std::shared_ptr<Config> const& config ); } // end namespace Catch @@ -8557,7 +11255,7 @@ namespace Catch { namespace Catch { std::size_t listTests( Config const& config ) { - TestSpec testSpec = config.testSpec(); + TestSpec const& testSpec = config.testSpec(); if( config.hasTestFilters() ) Catch::cout() << "Matching test cases:\n"; else { @@ -8591,7 +11289,7 @@ namespace Catch { } std::size_t listTestsNamesOnly( Config const& config ) { - TestSpec testSpec = config.testSpec(); + TestSpec const& testSpec = config.testSpec(); std::size_t matchedTests = 0; std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( auto const& testCaseInfo : matchedTestCases ) { @@ -8613,14 +11311,23 @@ namespace Catch { } std::string TagInfo::all() const { - std::string out; - for( auto const& spelling : spellings ) - out += "[" + spelling + "]"; + size_t size = 0; + for (auto const& spelling : spellings) { + // Add 2 for the brackes + size += spelling.size() + 2; + } + + std::string out; out.reserve(size); + for (auto const& spelling : spellings) { + out += '['; + out += spelling; + out += ']'; + } return out; } std::size_t listTags( Config const& config ) { - TestSpec testSpec = config.testSpec(); + TestSpec const& testSpec = config.testSpec(); if( config.hasTestFilters() ) Catch::cout() << "Tags for matching test cases:\n"; else { @@ -8676,15 +11383,16 @@ namespace Catch { return factories.size(); } - Option<std::size_t> list( Config const& config ) { + Option<std::size_t> list( std::shared_ptr<Config> const& config ) { Option<std::size_t> listedCount; - if( config.listTests() ) - listedCount = listedCount.valueOr(0) + listTests( config ); - if( config.listTestNamesOnly() ) - listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); - if( config.listTags() ) - listedCount = listedCount.valueOr(0) + listTags( config ); - if( config.listReporters() ) + getCurrentMutableContext().setConfig( config ); + if( config->listTests() ) + listedCount = listedCount.valueOr(0) + listTests( *config ); + if( config->listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( *config ); + if( config->listTags() ) + listedCount = listedCount.valueOr(0) + listTags( *config ); + if( config->listReporters() ) listedCount = listedCount.valueOr(0) + listReporters(); return listedCount; } @@ -8713,6 +11421,29 @@ using Matchers::Impl::MatcherBase; } // namespace Catch // end catch_matchers.cpp +// start catch_matchers_exception.cpp + +namespace Catch { +namespace Matchers { +namespace Exception { + +bool ExceptionMessageMatcher::match(std::exception const& ex) const { + return ex.what() == m_message; +} + +std::string ExceptionMessageMatcher::describe() const { + return "exception message matches \"" + m_message + "\""; +} + +} +Exception::ExceptionMessageMatcher Message(std::string const& message) { + return Exception::ExceptionMessageMatcher(message); +} + +// namespace Exception +} // namespace Matchers +} // namespace Catch +// end catch_matchers_exception.cpp // start catch_matchers_floating.cpp // start catch_polyfills.hpp @@ -8741,74 +11472,101 @@ namespace Catch { } // end namespace Catch // end catch_to_string.hpp +#include <algorithm> +#include <cmath> #include <cstdlib> #include <cstdint> #include <cstring> +#include <sstream> +#include <type_traits> +#include <iomanip> +#include <limits> namespace Catch { -namespace Matchers { -namespace Floating { -enum class FloatingPointKind : uint8_t { - Float, - Double -}; -} -} -} - namespace { -template <typename T> -struct Converter; - -template <> -struct Converter<float> { - static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); - Converter(float f) { + int32_t convert(float f) { + static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); + int32_t i; std::memcpy(&i, &f, sizeof(f)); + return i; } - int32_t i; -}; -template <> -struct Converter<double> { - static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); - Converter(double d) { + int64_t convert(double d) { + static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); + int64_t i; std::memcpy(&i, &d, sizeof(d)); + return i; } - int64_t i; -}; -template <typename T> -auto convert(T t) -> Converter<T> { - return Converter<T>(t); -} + template <typename FP> + bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (Catch::isnan(lhs) || Catch::isnan(rhs)) { + return false; + } -template <typename FP> -bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) { - // Comparison with NaN should always be false. - // This way we can rule it out before getting into the ugly details - if (Catch::isnan(lhs) || Catch::isnan(rhs)) { - return false; + auto lc = convert(lhs); + auto rc = convert(rhs); + + if ((lc < 0) != (rc < 0)) { + // Potentially we can have +0 and -0 + return lhs == rhs; + } + + // static cast as a workaround for IBM XLC + auto ulpDiff = std::abs(static_cast<FP>(lc - rc)); + return static_cast<uint64_t>(ulpDiff) <= maxUlpDiff; + } + +#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) + + float nextafter(float x, float y) { + return ::nextafterf(x, y); } - auto lc = convert(lhs); - auto rc = convert(rhs); + double nextafter(double x, double y) { + return ::nextafter(x, y); + } - if ((lc.i < 0) != (rc.i < 0)) { - // Potentially we can have +0 and -0 - return lhs == rhs; +#endif // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^ + +template <typename FP> +FP step(FP start, FP direction, uint64_t steps) { + for (uint64_t i = 0; i < steps; ++i) { +#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) + start = Catch::nextafter(start, direction); +#else + start = std::nextafter(start, direction); +#endif } + return start; +} - auto ulpDiff = std::abs(lc.i - rc.i); - return ulpDiff <= maxUlpDiff; +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); } +template <typename FloatingPoint> +void write(std::ostream& out, FloatingPoint num) { + out << std::scientific + << std::setprecision(std::numeric_limits<FloatingPoint>::max_digits10 - 1) + << num; } -namespace Catch { +} // end anonymous namespace + namespace Matchers { namespace Floating { + + enum class FloatingPointKind : uint8_t { + Float, + Double + }; + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) :m_target{ target }, m_margin{ margin } { CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' @@ -8825,10 +11583,11 @@ namespace Floating { return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); } - WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) + WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType) :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { - CATCH_ENFORCE(ulps >= 0, "Invalid ULP setting: " << ulps << '.' - << " ULPs have to be non-negative."); + CATCH_ENFORCE(m_type == FloatingPointKind::Double + || m_ulps < (std::numeric_limits<uint32_t>::max)(), + "Provided ULP is impossibly large for a float comparison."); } #if defined(__clang__) @@ -8853,16 +11612,59 @@ namespace Floating { #endif std::string WithinUlpsMatcher::describe() const { - return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); + std::stringstream ret; + + ret << "is within " << m_ulps << " ULPs of "; + + if (m_type == FloatingPointKind::Float) { + write(ret, static_cast<float>(m_target)); + ret << 'f'; + } else { + write(ret, m_target); + } + + ret << " (["; + if (m_type == FloatingPointKind::Double) { + write(ret, step(m_target, static_cast<double>(-INFINITY), m_ulps)); + ret << ", "; + write(ret, step(m_target, static_cast<double>( INFINITY), m_ulps)); + } else { + // We have to cast INFINITY to float because of MinGW, see #1782 + write(ret, step(static_cast<float>(m_target), static_cast<float>(-INFINITY), m_ulps)); + ret << ", "; + write(ret, step(static_cast<float>(m_target), static_cast<float>( INFINITY), m_ulps)); + } + ret << "])"; + + return ret.str(); + } + + WithinRelMatcher::WithinRelMatcher(double target, double epsilon): + m_target(target), + m_epsilon(epsilon){ + CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense."); + CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense."); + } + + bool WithinRelMatcher::match(double const& matchee) const { + const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target)); + return marginComparison(matchee, m_target, + std::isinf(relMargin)? 0 : relMargin); + } + + std::string WithinRelMatcher::describe() const { + Catch::ReusableStringStream sstr; + sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other"; + return sstr.str(); } }// namespace Floating -Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) { +Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) { return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); } -Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) { +Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) { return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); } @@ -8870,9 +11672,24 @@ Floating::WithinAbsMatcher WithinAbs(double target, double margin) { return Floating::WithinAbsMatcher(target, margin); } +Floating::WithinRelMatcher WithinRel(double target, double eps) { + return Floating::WithinRelMatcher(target, eps); +} + +Floating::WithinRelMatcher WithinRel(double target) { + return Floating::WithinRelMatcher(target, std::numeric_limits<double>::epsilon() * 100); +} + +Floating::WithinRelMatcher WithinRel(float target, float eps) { + return Floating::WithinRelMatcher(target, eps); +} + +Floating::WithinRelMatcher WithinRel(float target) { + return Floating::WithinRelMatcher(target, std::numeric_limits<float>::epsilon() * 100); +} + } // namespace Matchers } // namespace Catch - // end catch_matchers_floating.cpp // start catch_matchers_generic.cpp @@ -9030,28 +11847,43 @@ namespace Catch { //////////////////////////////////////////////////////////////////////////// ScopedMessage::ScopedMessage( MessageBuilder const& builder ) - : m_info( builder.m_info ) + : m_info( builder.m_info ), m_moved() { m_info.message = builder.m_stream.str(); getResultCapture().pushScopedMessage( m_info ); } + ScopedMessage::ScopedMessage( ScopedMessage&& old ) + : m_info( old.m_info ), m_moved() + { + old.m_moved = true; + } + ScopedMessage::~ScopedMessage() { - if ( !uncaught_exceptions() ){ + if ( !uncaught_exceptions() && !m_moved ){ getResultCapture().popScopedMessage(m_info); } } Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) { auto trimmed = [&] (size_t start, size_t end) { - while (names[start] == ',' || isspace(names[start])) { + while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) { ++start; } - while (names[end] == ',' || isspace(names[end])) { + while (names[end] == ',' || isspace(static_cast<unsigned char>(names[end]))) { --end; } return names.substr(start, end - start + 1); }; + auto skipq = [&] (size_t start, char quote) { + for (auto i = start + 1; i < names.size() ; ++i) { + if (names[i] == quote) + return i; + if (names[i] == '\\') + ++i; + } + CATCH_INTERNAL_ERROR("CAPTURE parsing encountered unmatched quote"); + }; size_t start = 0; std::stack<char> openings; @@ -9072,18 +11904,22 @@ namespace Catch { // case '>': openings.pop(); break; + case '"': + case '\'': + pos = skipq(pos, c); + break; case ',': - if (start != pos && openings.size() == 0) { + if (start != pos && openings.empty()) { m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = trimmed(start, pos); + m_messages.back().message = static_cast<std::string>(trimmed(start, pos)); m_messages.back().message += " := "; start = pos; } } } - assert(openings.size() == 0 && "Mismatched openings"); + assert(openings.empty() && "Mismatched openings"); m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = trimmed(start, names.size() - 1); + m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1)); m_messages.back().message += " := "; } Capturer::~Capturer() { @@ -9145,6 +11981,22 @@ namespace Catch { auto str() const -> std::string; }; + class RedirectedStreams { + public: + RedirectedStreams(RedirectedStreams const&) = delete; + RedirectedStreams& operator=(RedirectedStreams const&) = delete; + RedirectedStreams(RedirectedStreams&&) = delete; + RedirectedStreams& operator=(RedirectedStreams&&) = delete; + + RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr); + ~RedirectedStreams(); + private: + std::string& m_redirectedCout; + std::string& m_redirectedCerr; + RedirectedStdOut m_redirectedStdOut; + RedirectedStdErr m_redirectedStdErr; + }; + #if defined(CATCH_CONFIG_NEW_CAPTURE) // Windows's implementation of std::tmpfile is terrible (it tries @@ -9236,6 +12088,16 @@ namespace Catch { {} auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + RedirectedStreams::RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr) + : m_redirectedCout(redirectedCout), + m_redirectedCerr(redirectedCerr) + {} + + RedirectedStreams::~RedirectedStreams() { + m_redirectedCout += m_redirectedStdOut.str(); + m_redirectedCerr += m_redirectedStdErr.str(); + } + #if defined(CATCH_CONFIG_NEW_CAPTURE) #if defined(_MSC_VER) @@ -9243,12 +12105,12 @@ namespace Catch { if (tmpnam_s(m_buffer)) { CATCH_RUNTIME_ERROR("Could not get a temp filename"); } - if (fopen_s(&m_file, m_buffer, "w")) { + if (fopen_s(&m_file, m_buffer, "w+")) { char buffer[100]; if (strerror_s(buffer, errno)) { CATCH_RUNTIME_ERROR("Could not translate errno to a string"); } - CATCH_RUNTIME_ERROR("Coul dnot open the temp file: '" << m_buffer << "' because: " << buffer); + CATCH_RUNTIME_ERROR("Could not open the temp file: '" << m_buffer << "' because: " << buffer); } } #else @@ -9351,20 +12213,61 @@ namespace Catch { namespace Catch { - std::mt19937& rng() { - static std::mt19937 s_rng; - return s_rng; +namespace { + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4146) // we negate uint32 during the rotate +#endif + // Safe rotr implementation thanks to John Regehr + uint32_t rotate_right(uint32_t val, uint32_t count) { + const uint32_t mask = 31; + count &= mask; + return (val >> count) | (val << (-count & mask)); + } + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +} + + SimplePcg32::SimplePcg32(result_type seed_) { + seed(seed_); + } + + void SimplePcg32::seed(result_type seed_) { + m_state = 0; + (*this)(); + m_state += seed_; + (*this)(); } - void seedRng( IConfig const& config ) { - if( config.rngSeed() != 0 ) { - std::srand( config.rngSeed() ); - rng().seed( config.rngSeed() ); + void SimplePcg32::discard(uint64_t skip) { + // We could implement this to run in O(log n) steps, but this + // should suffice for our use case. + for (uint64_t s = 0; s < skip; ++s) { + static_cast<void>((*this)()); } } - unsigned int rngSeed() { - return getCurrentContext().getConfig()->rngSeed(); + SimplePcg32::result_type SimplePcg32::operator()() { + // prepare the output value + const uint32_t xorshifted = static_cast<uint32_t>(((m_state >> 18u) ^ m_state) >> 27u); + const auto output = rotate_right(xorshifted, m_state >> 59u); + + // advance state + m_state = m_state * 6364136223846793005ULL + s_inc; + + return output; + } + + bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { + return lhs.m_state == rhs.m_state; + } + + bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { + return lhs.m_state != rhs.m_state; } } // end catch_random_number_generator.cpp @@ -9383,6 +12286,8 @@ namespace Catch { struct IConfig; std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ); + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ); @@ -9495,11 +12400,13 @@ namespace Catch { namespace Catch { class StartupExceptionRegistry { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) public: void add(std::exception_ptr const& exception) noexcept; std::vector<std::exception_ptr> const& getExceptions() const noexcept; private: std::vector<std::exception_ptr> m_exceptions; +#endif }; } // end namespace Catch @@ -9582,7 +12489,14 @@ namespace Catch { m_tagAliasRegistry.add( alias, tag, lineInfo ); } void registerStartupException() noexcept override { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) m_exceptionRegistry.add(std::current_exception()); +#else + CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); +#endif + } + IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override { + return m_enumValuesRegistry; } private: @@ -9591,6 +12505,7 @@ namespace Catch { ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; TagAliasRegistry m_tagAliasRegistry; StartupExceptionRegistry m_exceptionRegistry; + Detail::EnumValuesRegistry m_enumValuesRegistry; }; } @@ -9671,7 +12586,6 @@ namespace Catch { namespace Generators { struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { - size_t m_index = static_cast<size_t>( -1 ); GeneratorBasePtr m_generator; GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) @@ -9683,39 +12597,110 @@ namespace Catch { std::shared_ptr<GeneratorTracker> tracker; ITracker& currentTracker = ctx.currentTracker(); - if( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + // Under specific circumstances, the generator we want + // to acquire is also the current tracker. If this is + // the case, we have to avoid looking through current + // tracker's children, and instead return the current + // tracker. + // A case where this check is important is e.g. + // for (int i = 0; i < 5; ++i) { + // int n = GENERATE(1, 2); + // } + // + // without it, the code above creates 5 nested generators. + if (currentTracker.nameAndLocation() == nameAndLocation) { + auto thisTracker = currentTracker.parent().findChild(nameAndLocation); + assert(thisTracker); + assert(thisTracker->isGeneratorTracker()); + tracker = std::static_pointer_cast<GeneratorTracker>(thisTracker); + } else if ( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { assert( childTracker ); - assert( childTracker->isIndexTracker() ); + assert( childTracker->isGeneratorTracker() ); tracker = std::static_pointer_cast<GeneratorTracker>( childTracker ); - } - else { + } else { tracker = std::make_shared<GeneratorTracker>( nameAndLocation, ctx, ¤tTracker ); currentTracker.addChild( tracker ); } - if( !ctx.completedCycle() && !tracker->isComplete() ) { - if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) - tracker->moveNext(); + if( !tracker->isComplete() ) { tracker->open(); } return *tracker; } - void moveNext() { - m_index++; - m_children.clear(); - } - // TrackerBase interface - bool isIndexTracker() const override { return true; } + bool isGeneratorTracker() const override { return true; } auto hasGenerator() const -> bool override { return !!m_generator; } void close() override { TrackerBase::close(); - if( m_runState == CompletedSuccessfully && m_index < m_generator->size()-1 ) + // If a generator has a child (it is followed by a section) + // and none of its children have started, then we must wait + // until later to start consuming its values. + // This catches cases where `GENERATE` is placed between two + // `SECTION`s. + // **The check for m_children.empty cannot be removed**. + // doing so would break `GENERATE` _not_ followed by `SECTION`s. + const bool should_wait_for_child = [&]() { + // No children -> nobody to wait for + if ( m_children.empty() ) { + return false; + } + // If at least one child started executing, don't wait + if ( std::find_if( + m_children.begin(), + m_children.end(), + []( TestCaseTracking::ITrackerPtr tracker ) { + return tracker->hasStarted(); + } ) != m_children.end() ) { + return false; + } + + // No children have started. We need to check if they _can_ + // start, and thus we should wait for them, or they cannot + // start (due to filters), and we shouldn't wait for them + auto* parent = m_parent; + // This is safe: there is always at least one section + // tracker in a test case tracking tree + while ( !parent->isSectionTracker() ) { + parent = &( parent->parent() ); + } + assert( parent && + "Missing root (test case) level section" ); + + auto const& parentSection = + static_cast<SectionTracker&>( *parent ); + auto const& filters = parentSection.getFilters(); + // No filters -> no restrictions on running sections + if ( filters.empty() ) { + return true; + } + + for ( auto const& child : m_children ) { + if ( child->isSectionTracker() && + std::find( filters.begin(), + filters.end(), + static_cast<SectionTracker&>( *child ) + .trimmedName() ) != + filters.end() ) { + return true; + } + } + return false; + }(); + + // This check is a bit tricky, because m_generator->next() + // has a side-effect, where it consumes generator's current + // value, but we do not want to invoke the side-effect if + // this generator is still waiting for any child to start. + if ( should_wait_for_child || + ( m_runState == CompletedSuccessfully && + m_generator->next() ) ) { + m_children.clear(); m_runState = Executing; + } } // IGeneratorTracker interface @@ -9725,9 +12710,6 @@ namespace Catch { void setGenerator( GeneratorBasePtr&& generator ) override { m_generator = std::move( generator ); } - auto getIndex() const -> size_t override { - return m_index; - } }; GeneratorTracker::~GeneratorTracker() {} } @@ -9825,6 +12807,9 @@ namespace Catch { // and should be let to clear themselves out. static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + if (result.getResultType() != ResultWas::Warning) + m_messageScopes.clear(); + // Reset working state resetAssertionInfo(); m_lastResult = result; @@ -9848,10 +12833,10 @@ namespace Catch { return true; } - auto RunContext::acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + auto RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { using namespace Generators; - GeneratorTracker& tracker = GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( "generator", lineInfo ) ); - assert( tracker.isOpen() ); + GeneratorTracker& tracker = GeneratorTracker::acquire(m_trackerContext, + TestCaseTracking::NameAndLocation( static_cast<std::string>(generatorName), lineInfo ) ); m_lastAssertionInfo.lineInfo = lineInfo; return tracker; } @@ -9879,6 +12864,7 @@ namespace Catch { m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); m_messages.clear(); + m_messageScopes.clear(); } void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { @@ -9890,12 +12876,21 @@ namespace Catch { m_unfinishedSections.push_back(endInfo); } + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void RunContext::benchmarkPreparing(std::string const& name) { + m_reporter->benchmarkPreparing(name); + } void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { m_reporter->benchmarkStarting( info ); } - void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { + void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) { m_reporter->benchmarkEnded( stats ); } + void RunContext::benchmarkFailed(std::string const & error) { + m_reporter->benchmarkFailed(error); + } +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING void RunContext::pushScopedMessage(MessageInfo const & message) { m_messages.push_back(message); @@ -9905,6 +12900,10 @@ namespace Catch { m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); } + void RunContext::emplaceUnscopedMessage( MessageBuilder const& builder ) { + m_messageScopes.emplace_back( builder ); + } + std::string RunContext::getCurrentTestName() const { return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name @@ -9926,7 +12925,7 @@ namespace Catch { // Don't rebuild the result -- the stringification itself can cause more fatal errors // Instead, fake a result data. AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); - tempResult.message = message; + tempResult.message = static_cast<std::string>(message); AssertionResult result(m_lastAssertionInfo, tempResult); assertionEnded(result); @@ -9965,6 +12964,7 @@ namespace Catch { m_lastAssertionPassed = true; ++m_totals.assertions.passed; resetAssertionInfo(); + m_messageScopes.clear(); } bool RunContext::aborting() const { @@ -9986,13 +12986,10 @@ namespace Catch { CATCH_TRY { if (m_reporter->getPreferences().shouldRedirectStdOut) { #if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) - RedirectedStdOut redirectedStdOut; - RedirectedStdErr redirectedStdErr; + RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr); timer.start(); invokeActiveTestCase(); - redirectedCout += redirectedStdOut.str(); - redirectedCerr += redirectedStdErr.str(); #else OutputRedirect r(redirectedCout, redirectedCerr); timer.start(); @@ -10019,15 +13016,15 @@ namespace Catch { m_testCaseTracker->close(); handleUnfinishedSections(); m_messages.clear(); + m_messageScopes.clear(); SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); m_reporter->sectionEnded(testCaseSectionStats); } void RunContext::invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals + FatalConditionHandlerGuard _(&m_fatalConditionhandler); m_activeTestCase->invoke(); - fatalConditionHandler.reset(); } void RunContext::handleUnfinishedSections() { @@ -10090,7 +13087,7 @@ namespace Catch { m_lastAssertionInfo = info; AssertionResultData data( resultType, LazyExpression( false ) ); - data.message = message; + data.message = static_cast<std::string>(message); AssertionResult assertionResult{ m_lastAssertionInfo, data }; assertionEnded( assertionResult ); if( !assertionResult.isOk() ) @@ -10153,6 +13150,18 @@ namespace Catch { else CATCH_INTERNAL_ERROR("No result capture instance"); } + + void seedRng(IConfig const& config) { + if (config.rngSeed() != 0) { + std::srand(config.rngSeed()); + rng().seed(config.rngSeed()); + } + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + } // end catch_run_context.cpp // start catch_section.cpp @@ -10214,7 +13223,7 @@ namespace Catch { void libIdentify(); int applyCommandLine( int argc, char const * const * argv ); - #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) int applyCommandLine( int argc, wchar_t const * const * argv ); #endif @@ -10281,6 +13290,8 @@ namespace Catch { // end catch_version.h #include <cstdlib> #include <iomanip> +#include <set> +#include <iterator> namespace Catch { @@ -10299,52 +13310,76 @@ namespace Catch { return createReporter(config->getReporterName(), config); } - auto multi = std::unique_ptr<ListeningReporter>(new ListeningReporter); - + // On older platforms, returning std::unique_ptr<ListeningReporter> + // when the return type is std::unique_ptr<IStreamingReporter> + // doesn't compile without a std::move call. However, this causes + // a warning on newer platforms. Thus, we have to work around + // it a bit and downcast the pointer manually. + auto ret = std::unique_ptr<IStreamingReporter>(new ListeningReporter); + auto& multi = static_cast<ListeningReporter&>(*ret); auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); for (auto const& listener : listeners) { - multi->addListener(listener->create(Catch::ReporterConfig(config))); + multi.addListener(listener->create(Catch::ReporterConfig(config))); } - multi->addReporter(createReporter(config->getReporterName(), config)); - return std::move(multi); + multi.addReporter(createReporter(config->getReporterName(), config)); + return ret; } - Catch::Totals runTests(std::shared_ptr<Config> const& config) { - auto reporter = makeReporter(config); - - RunContext context(config, std::move(reporter)); - - Totals totals; - - context.testGroupStarting(config->name(), 1, 1); - - TestSpec testSpec = config->testSpec(); - - auto const& allTestCases = getAllTestCasesSorted(*config); - for (auto const& testCase : allTestCases) { - if (!context.aborting() && matchTest(testCase, testSpec, *config)) - totals += context.runTest(testCase); - else - context.reporter().skipTest(testCase); + class TestGroup { + public: + explicit TestGroup(std::shared_ptr<Config> const& config) + : m_config{config} + , m_context{config, makeReporter(config)} + { + auto const& allTestCases = getAllTestCasesSorted(*m_config); + m_matches = m_config->testSpec().matchesByFilter(allTestCases, *m_config); + auto const& invalidArgs = m_config->testSpec().getInvalidArgs(); + + if (m_matches.empty() && invalidArgs.empty()) { + for (auto const& test : allTestCases) + if (!test.isHidden()) + m_tests.emplace(&test); + } else { + for (auto const& match : m_matches) + m_tests.insert(match.tests.begin(), match.tests.end()); + } } - if (config->warnAboutNoTests() && totals.testCases.total() == 0) { - ReusableStringStream testConfig; + Totals execute() { + auto const& invalidArgs = m_config->testSpec().getInvalidArgs(); + Totals totals; + m_context.testGroupStarting(m_config->name(), 1, 1); + for (auto const& testCase : m_tests) { + if (!m_context.aborting()) + totals += m_context.runTest(*testCase); + else + m_context.reporter().skipTest(*testCase); + } + + for (auto const& match : m_matches) { + if (match.tests.empty()) { + m_context.reporter().noMatchingTestCases(match.name); + totals.error = -1; + } + } - bool first = true; - for (const auto& input : config->getTestsOrTags()) { - if (!first) { testConfig << ' '; } - first = false; - testConfig << input; + if (!invalidArgs.empty()) { + for (auto const& invalidArg: invalidArgs) + m_context.reporter().reportInvalidArguments(invalidArg); } - context.reporter().noMatchingTestCases(testConfig.str()); - totals.error = -1; + m_context.testGroupEnded(m_config->name(), totals, 1, 1); + return totals; } - context.testGroupEnded(config->name(), totals, 1, 1); - return totals; - } + private: + using Tests = std::set<TestCase const*>; + + std::shared_ptr<Config> m_config; + RunContext m_context; + Tests m_tests; + TestSpec::Matches m_matches; + }; void applyFilenamesAsTags(Catch::IConfig const& config) { auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config)); @@ -10381,6 +13416,9 @@ namespace Catch { #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); if ( !exceptions.empty() ) { + config(); + getCurrentMutableContext().setConfig(m_config); + m_startupExceptions = true; Colour colourGuard( Colour::Red ); Catch::cerr() << "Errors occurred during startup!" << '\n'; @@ -10410,7 +13448,7 @@ namespace Catch { } void Session::libIdentify() { Catch::cout() - << std::left << std::setw(16) << "description: " << "A Catch test executable\n" + << std::left << std::setw(16) << "description: " << "A Catch2 test executable\n" << std::left << std::setw(16) << "category: " << "testframework\n" << std::left << std::setw(16) << "framework: " << "Catch Test\n" << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; @@ -10422,6 +13460,8 @@ namespace Catch { auto result = m_cli.parse( clara::Args( argc, argv ) ); if( !result ) { + config(); + getCurrentMutableContext().setConfig(m_config); Catch::cerr() << Colour( Colour::Red ) << "\nError(s) in input:\n" @@ -10439,17 +13479,17 @@ namespace Catch { return 0; } -#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) +#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { char **utf8Argv = new char *[ argc ]; for ( int i = 0; i < argc; ++i ) { - int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr ); utf8Argv[ i ] = new char[ bufSize ]; - WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr ); } int returnCode = applyCommandLine( argc, utf8Argv ); @@ -10513,10 +13553,15 @@ namespace Catch { applyFilenamesAsTags( *m_config ); // Handle list request - if( Option<std::size_t> listed = list( config() ) ) + if( Option<std::size_t> listed = list( m_config ) ) return static_cast<int>( *listed ); - auto totals = runTests( m_config ); + TestGroup tests { m_config }; + auto const totals = tests.execute(); + + if( m_config->warnAboutNoTests() && totals.error == -1 ) + return 2; + // Note that on unices only the lower 8 bits are usually used, clamping // the return value to 255 prevents false negative when some multiple // of 256 tests has failed @@ -10564,6 +13609,7 @@ namespace Catch { // end catch_singletons.cpp // start catch_startup_exception_registry.cpp +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) namespace Catch { void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { CATCH_TRY { @@ -10579,6 +13625,7 @@ void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexce } } // end namespace Catch +#endif // end catch_startup_exception_registry.cpp // start catch_stream.cpp @@ -10593,7 +13640,7 @@ namespace Catch { Catch::IStream::~IStream() = default; - namespace detail { namespace { + namespace Detail { namespace { template<typename WriterF, std::size_t bufferSize=256> class StreamBufImpl : public std::streambuf { char data[bufferSize]; @@ -10692,15 +13739,15 @@ namespace Catch { auto makeStream( StringRef const &filename ) -> IStream const* { if( filename.empty() ) - return new detail::CoutStream(); + return new Detail::CoutStream(); else if( filename[0] == '%' ) { if( filename == "%debug" ) - return new detail::DebugOutStream(); + return new Detail::DebugOutStream(); else CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); } else - return new detail::FileStream( filename ); + return new Detail::FileStream( filename ); } // This class encapsulates the idea of a pool of ostringstreams that can be reused. @@ -10757,12 +13804,13 @@ namespace Catch { #include <ostream> #include <cstring> #include <cctype> +#include <vector> namespace Catch { namespace { char toLowerCh(char c) { - return static_cast<char>( std::tolower( c ) ); + return static_cast<char>( std::tolower( static_cast<unsigned char>(c) ) ); } } @@ -10797,6 +13845,18 @@ namespace Catch { return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); } + StringRef trim(StringRef ref) { + const auto is_ws = [](char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + }; + size_t real_begin = 0; + while (real_begin < ref.size() && is_ws(ref[real_begin])) { ++real_begin; } + size_t real_end = ref.size(); + while (real_end > real_begin && is_ws(ref[real_end - 1])) { --real_end; } + + return ref.substr(real_begin, real_end - real_begin); + } + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { bool replaced = false; std::size_t i = str.find( replaceThis ); @@ -10811,6 +13871,21 @@ namespace Catch { return replaced; } + std::vector<StringRef> splitStringRef( StringRef str, char delimiter ) { + std::vector<StringRef> subStrings; + std::size_t start = 0; + for(std::size_t pos = 0; pos < str.size(); ++pos ) { + if( str[pos] == delimiter ) { + if( pos - start > 1 ) + subStrings.push_back( str.substr( start, pos-start ) ); + start = pos+1; + } + } + if( start < str.size() ) + subStrings.push_back( str.substr( start, str.size()-start ) ); + return subStrings; + } + pluralise::pluralise( std::size_t count, std::string const& label ) : m_count( count ), m_label( label ) @@ -10827,123 +13902,46 @@ namespace Catch { // end catch_string_manip.cpp // start catch_stringref.cpp -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - +#include <algorithm> #include <ostream> #include <cstring> #include <cstdint> -namespace { - const uint32_t byte_2_lead = 0xC0; - const uint32_t byte_3_lead = 0xE0; - const uint32_t byte_4_lead = 0xF0; -} - namespace Catch { StringRef::StringRef( char const* rawChars ) noexcept : StringRef( rawChars, static_cast<StringRef::size_type>(std::strlen(rawChars) ) ) {} - StringRef::operator std::string() const { - return std::string( m_start, m_size ); - } - - void StringRef::swap( StringRef& other ) noexcept { - std::swap( m_start, other.m_start ); - std::swap( m_size, other.m_size ); - std::swap( m_data, other.m_data ); - } - auto StringRef::c_str() const -> char const* { - if( isSubstring() ) - const_cast<StringRef*>( this )->takeOwnership(); + CATCH_ENFORCE(isNullTerminated(), "Called StringRef::c_str() on a non-null-terminated instance"); return m_start; } - auto StringRef::currentData() const noexcept -> char const* { + auto StringRef::data() const noexcept -> char const* { return m_start; } - auto StringRef::isOwned() const noexcept -> bool { - return m_data != nullptr; - } - auto StringRef::isSubstring() const noexcept -> bool { - return m_start[m_size] != '\0'; - } - - void StringRef::takeOwnership() { - if( !isOwned() ) { - m_data = new char[m_size+1]; - memcpy( m_data, m_start, m_size ); - m_data[m_size] = '\0'; - m_start = m_data; - } - } auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { - if( start < m_size ) - return StringRef( m_start+start, size ); - else + if (start < m_size) { + return StringRef(m_start + start, (std::min)(m_size - start, size)); + } else { return StringRef(); - } - auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { - return - size() == other.size() && - (std::strncmp( m_start, other.m_start, size() ) == 0); - } - auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { - return !operator==( other ); - } - - auto StringRef::operator[](size_type index) const noexcept -> char { - return m_start[index]; - } - - auto StringRef::numberOfCharacters() const noexcept -> size_type { - size_type noChars = m_size; - // Make adjustments for uft encodings - for( size_type i=0; i < m_size; ++i ) { - char c = m_start[i]; - if( ( c & byte_2_lead ) == byte_2_lead ) { - noChars--; - if (( c & byte_3_lead ) == byte_3_lead ) - noChars--; - if( ( c & byte_4_lead ) == byte_4_lead ) - noChars--; - } } - return noChars; - } - - auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { - std::string str; - str.reserve( lhs.size() + rhs.size() ); - str += lhs; - str += rhs; - return str; - } - auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { - return std::string( lhs ) + std::string( rhs ); } - auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { - return std::string( lhs ) + std::string( rhs ); + auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + return m_size == other.m_size + && (std::memcmp( m_start, other.m_start, m_size ) == 0); } auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { - return os.write(str.currentData(), str.size()); + return os.write(str.data(), str.size()); } auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& { - lhs.append(rhs.currentData(), rhs.size()); + lhs.append(rhs.data(), rhs.size()); return lhs; } } // namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif // end catch_stringref.cpp // start catch_tag_alias.cpp @@ -11046,7 +14044,7 @@ namespace Catch { void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { CATCH_ENFORCE( !isReservedTag(tag), "Tag name: [" << tag << "] is not allowed.\n" - << "Tag names starting with non alpha-numeric characters are reserved\n" + << "Tag names starting with non alphanumeric characters are reserved\n" << _lineInfo ); } } @@ -11062,8 +14060,7 @@ namespace Catch { std::vector<std::string> tags; std::string desc, tag; bool inTag = false; - std::string _descOrTags = nameAndTags.tags; - for (char c : _descOrTags) { + for (char c : nameAndTags.tags) { if( !inTag ) { if( c == '[' ) inTag = true; @@ -11078,6 +14075,12 @@ namespace Catch { else if( prop == TestCaseInfo::None ) enforceNotReservedTag( tag, _lineInfo ); + // Merged hide tags like `[.approvals]` should be added as + // `[.][approvals]`. The `[.]` is added at later point, so + // we only strip the prefix + if (startsWith(tag, '.') && tag.size() > 1) { + tag.erase(0, 1); + } tags.push_back( tag ); tag.clear(); inTag = false; @@ -11087,10 +14090,11 @@ namespace Catch { } } if( isHidden ) { - tags.push_back( "." ); + // Add all "hidden" tags to make them behave identically + tags.insert( tags.end(), { ".", "!hide" } ); } - TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo ); + TestCaseInfo info( static_cast<std::string>(nameAndTags.name), _className, desc, tags, _lineInfo ); return TestCase( _testCase, std::move(info) ); } @@ -11182,30 +14186,89 @@ namespace Catch { // end catch_test_case_info.cpp // start catch_test_case_registry_impl.cpp +#include <algorithm> #include <sstream> namespace Catch { - std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) { + namespace { + struct TestHasher { + using hash_t = uint64_t; + + explicit TestHasher( hash_t hashSuffix ): + m_hashSuffix{ hashSuffix } {} + + uint32_t operator()( TestCase const& t ) const { + // FNV-1a hash with multiplication fold. + const hash_t prime = 1099511628211u; + hash_t hash = 14695981039346656037u; + for ( const char c : t.name ) { + hash ^= c; + hash *= prime; + } + hash ^= m_hashSuffix; + hash *= prime; + const uint32_t low{ static_cast<uint32_t>( hash ) }; + const uint32_t high{ static_cast<uint32_t>( hash >> 32 ) }; + return low * high; + } - std::vector<TestCase> sorted = unsortedTestCases; + private: + hash_t m_hashSuffix; + }; + } // end unnamed namespace + std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) { switch( config.runOrder() ) { - case RunTests::InLexicographicalOrder: - std::sort( sorted.begin(), sorted.end() ); - break; - case RunTests::InRandomOrder: - seedRng( config ); - std::shuffle( sorted.begin(), sorted.end(), rng() ); - break; case RunTests::InDeclarationOrder: // already in declaration order break; + + case RunTests::InLexicographicalOrder: { + std::vector<TestCase> sorted = unsortedTestCases; + std::sort( sorted.begin(), sorted.end() ); + return sorted; + } + + case RunTests::InRandomOrder: { + seedRng( config ); + TestHasher h{ config.rngSeed() }; + + using hashedTest = std::pair<TestHasher::hash_t, TestCase const*>; + std::vector<hashedTest> indexed_tests; + indexed_tests.reserve( unsortedTestCases.size() ); + + for (auto const& testCase : unsortedTestCases) { + indexed_tests.emplace_back(h(testCase), &testCase); + } + + std::sort(indexed_tests.begin(), indexed_tests.end(), + [](hashedTest const& lhs, hashedTest const& rhs) { + if (lhs.first == rhs.first) { + return lhs.second->name < rhs.second->name; + } + return lhs.first < rhs.first; + }); + + std::vector<TestCase> sorted; + sorted.reserve( indexed_tests.size() ); + + for (auto const& hashed : indexed_tests) { + sorted.emplace_back(*hashed.second); + } + + return sorted; + } } - return sorted; + return unsortedTestCases; } + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ) { + return !testCase.throws() || config.allowThrows(); + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { - return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + return testSpec.matches( testCase ) && isThrowSafe( testCase, config ); } void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) { @@ -11222,9 +14285,12 @@ namespace Catch { std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) { std::vector<TestCase> filtered; filtered.reserve( testCases.size() ); - for( auto const& testCase : testCases ) - if( matchTest( testCase, testSpec, config ) ) - filtered.push_back( testCase ); + for (auto const& testCase : testCases) { + if ((!testSpec.hasFilters() && !testCase.isHidden()) || + (testSpec.hasFilters() && matchTest(testCase, testSpec, config))) { + filtered.push_back(testCase); + } + } return filtered; } std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) { @@ -11263,7 +14329,7 @@ namespace Catch { } std::string extractClassName( StringRef const& classOrQualifiedMethodName ) { - std::string className = classOrQualifiedMethodName; + std::string className(classOrQualifiedMethodName); if( startsWith( className, '&' ) ) { std::size_t lastColons = className.rfind( "::" ); @@ -11300,11 +14366,6 @@ namespace TestCaseTracking { ITracker::~ITracker() = default; - TrackerContext& TrackerContext::instance() { - static TrackerContext s_instance; - return s_instance; - } - ITracker& TrackerContext::startRun() { m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); m_currentTracker = nullptr; @@ -11336,15 +14397,12 @@ namespace TestCaseTracking { m_currentTracker = tracker; } - TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : m_nameAndLocation( nameAndLocation ), + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ): + ITracker(nameAndLocation), m_ctx( ctx ), m_parent( parent ) {} - NameAndLocation const& TrackerBase::nameAndLocation() const { - return m_nameAndLocation; - } bool TrackerBase::isComplete() const { return m_runState == CompletedSuccessfully || m_runState == Failed; } @@ -11387,7 +14445,7 @@ namespace TestCaseTracking { } bool TrackerBase::isSectionTracker() const { return false; } - bool TrackerBase::isIndexTracker() const { return false; } + bool TrackerBase::isGeneratorTracker() const { return false; } void TrackerBase::open() { m_runState = Executing; @@ -11410,7 +14468,7 @@ namespace TestCaseTracking { m_runState = CompletedSuccessfully; break; case ExecutingChildren: - if( m_children.empty() || m_children.back()->isComplete() ) + if( std::all_of(m_children.begin(), m_children.end(), [](ITrackerPtr const& t){ return t->isComplete(); }) ) m_runState = CompletedSuccessfully; break; @@ -11445,7 +14503,8 @@ namespace TestCaseTracking { } SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : TrackerBase( nameAndLocation, ctx, parent ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_trimmed_name(trim(nameAndLocation.name)) { if( parent ) { while( !parent->isSectionTracker() ) @@ -11456,6 +14515,17 @@ namespace TestCaseTracking { } } + bool SectionTracker::isComplete() const { + bool complete = true; + + if (m_filters.empty() + || m_filters[0] == "" + || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) { + complete = TrackerBase::isComplete(); + } + return complete; + } + bool SectionTracker::isSectionTracker() const { return true; } SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { @@ -11477,63 +14547,29 @@ namespace TestCaseTracking { } void SectionTracker::tryOpen() { - if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + if( !isComplete() ) open(); } void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) { if( !filters.empty() ) { - m_filters.push_back(""); // Root - should never be consulted - m_filters.push_back(""); // Test Case - not a section filter + m_filters.reserve( m_filters.size() + filters.size() + 2 ); + m_filters.emplace_back(""); // Root - should never be consulted + m_filters.emplace_back(""); // Test Case - not a section filter m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); } } void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) { if( filters.size() > 1 ) - m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() ); } - IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) - : TrackerBase( nameAndLocation, ctx, parent ), - m_size( size ) - {} - - bool IndexTracker::isIndexTracker() const { return true; } - - IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { - std::shared_ptr<IndexTracker> tracker; - - ITracker& currentTracker = ctx.currentTracker(); - if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isIndexTracker() ); - tracker = std::static_pointer_cast<IndexTracker>( childTracker ); - } - else { - tracker = std::make_shared<IndexTracker>( nameAndLocation, ctx, ¤tTracker, size ); - currentTracker.addChild( tracker ); - } - - if( !ctx.completedCycle() && !tracker->isComplete() ) { - if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) - tracker->moveNext(); - tracker->open(); - } - - return *tracker; + std::vector<std::string> const& SectionTracker::getFilters() const { + return m_filters; } - int IndexTracker::index() const { return m_index; } - - void IndexTracker::moveNext() { - m_index++; - m_children.clear(); - } - - void IndexTracker::close() { - TrackerBase::close(); - if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) - m_runState = Executing; + std::string const& SectionTracker::trimmedName() const { + return m_trimmed_name; } } // namespace TestCaseTracking @@ -11541,7 +14577,6 @@ namespace TestCaseTracking { using TestCaseTracking::ITracker; using TestCaseTracking::TrackerContext; using TestCaseTracking::SectionTracker; -using TestCaseTracking::IndexTracker; } // namespace Catch @@ -11586,47 +14621,81 @@ namespace Catch { namespace Catch { + TestSpec::Pattern::Pattern( std::string const& name ) + : m_name( name ) + {} + TestSpec::Pattern::~Pattern() = default; - TestSpec::NamePattern::~NamePattern() = default; - TestSpec::TagPattern::~TagPattern() = default; - TestSpec::ExcludedPattern::~ExcludedPattern() = default; - TestSpec::NamePattern::NamePattern( std::string const& name ) - : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + std::string const& TestSpec::Pattern::name() const { + return m_name; + } + + TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString ) + : Pattern( filterString ) + , m_wildcardPattern( toLower( name ), CaseSensitive::No ) {} + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { - return m_wildcardPattern.matches( toLower( testCase.name ) ); + return m_wildcardPattern.matches( testCase.name ); } - TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString ) + : Pattern( filterString ) + , m_tag( toLower( tag ) ) + {} + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { return std::find(begin(testCase.lcaseTags), end(testCase.lcaseTags), m_tag) != end(testCase.lcaseTags); } - TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} - bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) + : Pattern( underlyingPattern->name() ) + , m_underlyingPattern( underlyingPattern ) + {} + + bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { + return !m_underlyingPattern->matches( testCase ); + } bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { - // All patterns in a filter must match for the filter to be a match - for( auto const& pattern : m_patterns ) { - if( !pattern->matches( testCase ) ) - return false; - } - return true; + return std::all_of( m_patterns.begin(), m_patterns.end(), [&]( PatternPtr const& p ){ return p->matches( testCase ); } ); + } + + std::string TestSpec::Filter::name() const { + std::string name; + for( auto const& p : m_patterns ) + name += p->name(); + return name; } bool TestSpec::hasFilters() const { return !m_filters.empty(); } + bool TestSpec::matches( TestCaseInfo const& testCase ) const { - // A TestSpec matches if any filter matches - for( auto const& filter : m_filters ) - if( filter.matches( testCase ) ) - return true; - return false; + return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } ); + } + + TestSpec::Matches TestSpec::matchesByFilter( std::vector<TestCase> const& testCases, IConfig const& config ) const + { + Matches matches( m_filters.size() ); + std::transform( m_filters.begin(), m_filters.end(), matches.begin(), [&]( Filter const& filter ){ + std::vector<TestCase const*> currentMatches; + for( auto const& test : testCases ) + if( isThrowSafe( test, config ) && filter.matches( test ) ) + currentMatches.emplace_back( &test ); + return FilterMatch{ filter.name(), currentMatches }; + } ); + return matches; } + + const TestSpec::vectorStrings& TestSpec::getInvalidArgs() const{ + return (m_invalidArgs); + } + } // end catch_test_spec.cpp // start catch_test_spec_parser.cpp @@ -11638,64 +14707,136 @@ namespace Catch { TestSpecParser& TestSpecParser::parse( std::string const& arg ) { m_mode = None; m_exclusion = false; - m_start = std::string::npos; m_arg = m_tagAliases->expandAliases( arg ); m_escapeChars.clear(); + m_substring.reserve(m_arg.size()); + m_patternName.reserve(m_arg.size()); + m_realPatternPos = 0; + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) - visitChar( m_arg[m_pos] ); - if( m_mode == Name ) - addPattern<TestSpec::NamePattern>(); + //if visitChar fails + if( !visitChar( m_arg[m_pos] ) ){ + m_testSpec.m_invalidArgs.push_back(arg); + break; + } + endMode(); return *this; } TestSpec TestSpecParser::testSpec() { addFilter(); return m_testSpec; } + bool TestSpecParser::visitChar( char c ) { + if( (m_mode != EscapedName) && (c == '\\') ) { + escape(); + addCharToPattern(c); + return true; + }else if((m_mode != EscapedName) && (c == ',') ) { + return separate(); + } - void TestSpecParser::visitChar( char c ) { - if( m_mode == None ) { - switch( c ) { - case ' ': return; - case '~': m_exclusion = true; return; - case '[': return startNewMode( Tag, ++m_pos ); - case '"': return startNewMode( QuotedName, ++m_pos ); - case '\\': return escape(); - default: startNewMode( Name, m_pos ); break; - } + switch( m_mode ) { + case None: + if( processNoneChar( c ) ) + return true; + break; + case Name: + processNameChar( c ); + break; + case EscapedName: + endMode(); + addCharToPattern(c); + return true; + default: + case Tag: + case QuotedName: + if( processOtherChar( c ) ) + return true; + break; } - if( m_mode == Name ) { - if( c == ',' ) { - addPattern<TestSpec::NamePattern>(); - addFilter(); - } - else if( c == '[' ) { - if( subString() == "exclude:" ) - m_exclusion = true; - else - addPattern<TestSpec::NamePattern>(); - startNewMode( Tag, ++m_pos ); - } - else if( c == '\\' ) - escape(); + + m_substring += c; + if( !isControlChar( c ) ) { + m_patternName += c; + m_realPatternPos++; } - else if( m_mode == EscapedName ) - m_mode = Name; - else if( m_mode == QuotedName && c == '"' ) - addPattern<TestSpec::NamePattern>(); - else if( m_mode == Tag && c == ']' ) - addPattern<TestSpec::TagPattern>(); + return true; } - void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { + // Two of the processing methods return true to signal the caller to return + // without adding the given character to the current pattern strings + bool TestSpecParser::processNoneChar( char c ) { + switch( c ) { + case ' ': + return true; + case '~': + m_exclusion = true; + return false; + case '[': + startNewMode( Tag ); + return false; + case '"': + startNewMode( QuotedName ); + return false; + default: + startNewMode( Name ); + return false; + } + } + void TestSpecParser::processNameChar( char c ) { + if( c == '[' ) { + if( m_substring == "exclude:" ) + m_exclusion = true; + else + endMode(); + startNewMode( Tag ); + } + } + bool TestSpecParser::processOtherChar( char c ) { + if( !isControlChar( c ) ) + return false; + m_substring += c; + endMode(); + return true; + } + void TestSpecParser::startNewMode( Mode mode ) { m_mode = mode; - m_start = start; + } + void TestSpecParser::endMode() { + switch( m_mode ) { + case Name: + case QuotedName: + return addNamePattern(); + case Tag: + return addTagPattern(); + case EscapedName: + revertBackToLastMode(); + return; + case None: + default: + return startNewMode( None ); + } } void TestSpecParser::escape() { - if( m_mode == None ) - m_start = m_pos; + saveLastMode(); m_mode = EscapedName; - m_escapeChars.push_back( m_pos ); + m_escapeChars.push_back(m_realPatternPos); + } + bool TestSpecParser::isControlChar( char c ) const { + switch( m_mode ) { + default: + return false; + case None: + return c == '~'; + case Name: + return c == '['; + case EscapedName: + return true; + case QuotedName: + return c == '"'; + case Tag: + return c == '[' || c == ']'; + } } - std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } void TestSpecParser::addFilter() { if( !m_currentFilter.m_patterns.empty() ) { @@ -11704,6 +14845,86 @@ namespace Catch { } } + void TestSpecParser::saveLastMode() { + lastMode = m_mode; + } + + void TestSpecParser::revertBackToLastMode() { + m_mode = lastMode; + } + + bool TestSpecParser::separate() { + if( (m_mode==QuotedName) || (m_mode==Tag) ){ + //invalid argument, signal failure to previous scope. + m_mode = None; + m_pos = m_arg.size(); + m_substring.clear(); + m_patternName.clear(); + m_realPatternPos = 0; + return false; + } + endMode(); + addFilter(); + return true; //success + } + + std::string TestSpecParser::preprocessPattern() { + std::string token = m_patternName; + for (std::size_t i = 0; i < m_escapeChars.size(); ++i) + token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1); + m_escapeChars.clear(); + if (startsWith(token, "exclude:")) { + m_exclusion = true; + token = token.substr(8); + } + + m_patternName.clear(); + m_realPatternPos = 0; + + return token; + } + + void TestSpecParser::addNamePattern() { + auto token = preprocessPattern(); + + if (!token.empty()) { + TestSpec::PatternPtr pattern = std::make_shared<TestSpec::NamePattern>(token, m_substring); + if (m_exclusion) + pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern); + m_currentFilter.m_patterns.push_back(pattern); + } + m_substring.clear(); + m_exclusion = false; + m_mode = None; + } + + void TestSpecParser::addTagPattern() { + auto token = preprocessPattern(); + + if (!token.empty()) { + // If the tag pattern is the "hide and tag" shorthand (e.g. [.foo]) + // we have to create a separate hide tag and shorten the real one + if (token.size() > 1 && token[0] == '.') { + token.erase(token.begin()); + TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(".", m_substring); + if (m_exclusion) { + pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern); + } + m_currentFilter.m_patterns.push_back(pattern); + } + + TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(token, m_substring); + + if (m_exclusion) { + pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern); + } + m_currentFilter.m_patterns.push_back(pattern); + } + m_substring.clear(); + m_exclusion = false; + m_mode = None; + } + TestSpec parseTestSpec( std::string const& arg ) { return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); } @@ -11744,7 +14965,7 @@ namespace Catch { // is terrible and we should move on. // TBD: How to signal that the measured resolution is probably wrong? if (ticks > startTime + 3 * nanosecondsInSecond) { - return sum / i; + return sum / ( i + 1u ); } } @@ -11805,13 +15026,11 @@ namespace Detail { enum Arch { Big, Little }; static Arch which() { - union _{ - int asInt; - char asChar[sizeof (int)]; - } u; - - u.asInt = 1; - return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + int one = 1; + // If the lowest byte we read is non-zero, we can assume + // that little endian format is used. + auto value = *reinterpret_cast<char*>(&one); + return value ? Little : Big; } }; } @@ -11935,6 +15154,13 @@ std::string StringMaker<wchar_t *>::convert(wchar_t * str) { } #endif +#if defined(CATCH_CONFIG_CPP17_BYTE) +#include <cstddef> +std::string StringMaker<std::byte>::convert(std::byte value) { + return ::Catch::Detail::stringify(std::to_integer<unsigned long long>(value)); +} +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + std::string StringMaker<int>::convert(int value) { return ::Catch::Detail::stringify(static_cast<long long>(value)); } @@ -11997,11 +15223,16 @@ std::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) { return "nullptr"; } +int StringMaker<float>::precision = 5; + std::string StringMaker<float>::convert(float value) { - return fpToString(value, 5) + 'f'; + return fpToString(value, precision) + 'f'; } + +int StringMaker<double>::precision = 10; + std::string StringMaker<double>::convert(double value) { - return fpToString(value, 10); + return fpToString(value, precision); } std::string ratio_string<std::atto>::symbol() { return "a"; } @@ -12075,11 +15306,48 @@ namespace Catch { // end catch_totals.cpp // start catch_uncaught_exceptions.cpp +// start catch_config_uncaught_exceptions.hpp + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP +#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP + +#if defined(_MSC_VER) +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif +#endif + +#include <exception> + +#if defined(__cpp_lib_uncaught_exceptions) \ + && !defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif // __cpp_lib_uncaught_exceptions + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) \ + && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) \ + && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP +// end catch_config_uncaught_exceptions.hpp #include <exception> namespace Catch { bool uncaught_exceptions() { -#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + return false; +#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) return std::uncaught_exceptions() > 0; #else return std::uncaught_exception(); @@ -12119,7 +15387,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 5, 0, "", 0 ); + static Version version( 2, 13, 8, "", 0 ); return version; } @@ -12127,14 +15395,12 @@ namespace Catch { // end catch_version.cpp // start catch_wildcard_pattern.cpp -#include <sstream> - namespace Catch { WildcardPattern::WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) : m_caseSensitivity( caseSensitivity ), - m_pattern( adjustCase( pattern ) ) + m_pattern( normaliseString( pattern ) ) { if( startsWith( m_pattern, '*' ) ) { m_pattern = m_pattern.substr( 1 ); @@ -12149,28 +15415,27 @@ namespace Catch { bool WildcardPattern::matches( std::string const& str ) const { switch( m_wildcard ) { case NoWildcard: - return m_pattern == adjustCase( str ); + return m_pattern == normaliseString( str ); case WildcardAtStart: - return endsWith( adjustCase( str ), m_pattern ); + return endsWith( normaliseString( str ), m_pattern ); case WildcardAtEnd: - return startsWith( adjustCase( str ), m_pattern ); + return startsWith( normaliseString( str ), m_pattern ); case WildcardAtBothEnds: - return contains( adjustCase( str ), m_pattern ); + return contains( normaliseString( str ), m_pattern ); default: CATCH_INTERNAL_ERROR( "Unknown enum" ); } } - std::string WildcardPattern::adjustCase( std::string const& str ) const { - return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + std::string WildcardPattern::normaliseString( std::string const& str ) const { + return trim( m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str ); } } // end catch_wildcard_pattern.cpp // start catch_xmlwriter.cpp #include <iomanip> - -using uchar = unsigned char; +#include <type_traits> namespace Catch { @@ -12203,13 +15468,37 @@ namespace { } void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(c); + os.flags(f); + } + + bool shouldNewline(XmlFormatting fmt) { + return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Newline)); + } + + bool shouldIndent(XmlFormatting fmt) { + return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Indent)); } } // anonymous namespace + XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs) { + return static_cast<XmlFormatting>( + static_cast<std::underlying_type<XmlFormatting>::type>(lhs) | + static_cast<std::underlying_type<XmlFormatting>::type>(rhs) + ); + } + + XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs) { + return static_cast<XmlFormatting>( + static_cast<std::underlying_type<XmlFormatting>::type>(lhs) & + static_cast<std::underlying_type<XmlFormatting>::type>(rhs) + ); + } + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) : m_str( str ), m_forWhat( forWhat ) @@ -12220,7 +15509,7 @@ namespace { // (see: http://www.w3.org/TR/xml/#syntax) for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { - uchar c = m_str[idx]; + unsigned char c = m_str[idx]; switch (c) { case '<': os << "<"; break; case '&': os << "&"; break; @@ -12280,7 +15569,7 @@ namespace { bool valid = true; uint32_t value = headerValue(c); for (std::size_t n = 1; n < encBytes; ++n) { - uchar nc = m_str[idx + n]; + unsigned char nc = m_str[idx + n]; valid &= ((nc & 0xC0) == 0x80); value = (value << 6) | (nc & 0x3F); } @@ -12314,13 +15603,17 @@ namespace { return os; } - XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) - : m_writer( writer ) + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer, XmlFormatting fmt ) + : m_writer( writer ), + m_fmt(fmt) {} XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept - : m_writer( other.m_writer ){ + : m_writer( other.m_writer ), + m_fmt(other.m_fmt) + { other.m_writer = nullptr; + other.m_fmt = XmlFormatting::None; } XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { if ( m_writer ) { @@ -12328,16 +15621,19 @@ namespace { } m_writer = other.m_writer; other.m_writer = nullptr; + m_fmt = other.m_fmt; + other.m_fmt = XmlFormatting::None; return *this; } XmlWriter::ScopedElement::~ScopedElement() { - if( m_writer ) - m_writer->endElement(); + if (m_writer) { + m_writer->endElement(m_fmt); + } } - XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { - m_writer->writeText( text, indent ); + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, XmlFormatting fmt ) { + m_writer->writeText( text, fmt ); return *this; } @@ -12347,37 +15643,47 @@ namespace { } XmlWriter::~XmlWriter() { - while( !m_tags.empty() ) + while (!m_tags.empty()) { endElement(); + } + newlineIfNecessary(); } - XmlWriter& XmlWriter::startElement( std::string const& name ) { + XmlWriter& XmlWriter::startElement( std::string const& name, XmlFormatting fmt ) { ensureTagClosed(); newlineIfNecessary(); - m_os << m_indent << '<' << name; + if (shouldIndent(fmt)) { + m_os << m_indent; + m_indent += " "; + } + m_os << '<' << name; m_tags.push_back( name ); - m_indent += " "; m_tagIsOpen = true; + applyFormatting(fmt); return *this; } - XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { - ScopedElement scoped( this ); - startElement( name ); + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name, XmlFormatting fmt ) { + ScopedElement scoped( this, fmt ); + startElement( name, fmt ); return scoped; } - XmlWriter& XmlWriter::endElement() { - newlineIfNecessary(); - m_indent = m_indent.substr( 0, m_indent.size()-2 ); + XmlWriter& XmlWriter::endElement(XmlFormatting fmt) { + m_indent = m_indent.substr(0, m_indent.size() - 2); + if( m_tagIsOpen ) { m_os << "/>"; m_tagIsOpen = false; + } else { + newlineIfNecessary(); + if (shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << "</" << m_tags.back() << ">"; } - else { - m_os << m_indent << "</" << m_tags.back() << ">"; - } - m_os << std::endl; + m_os << std::flush; + applyFormatting(fmt); m_tags.pop_back(); return *this; } @@ -12393,22 +15699,26 @@ namespace { return *this; } - XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + XmlWriter& XmlWriter::writeText( std::string const& text, XmlFormatting fmt) { if( !text.empty() ){ bool tagWasOpen = m_tagIsOpen; ensureTagClosed(); - if( tagWasOpen && indent ) + if (tagWasOpen && shouldIndent(fmt)) { m_os << m_indent; + } m_os << XmlEncode( text ); - m_needsNewline = true; + applyFormatting(fmt); } return *this; } - XmlWriter& XmlWriter::writeComment( std::string const& text ) { + XmlWriter& XmlWriter::writeComment( std::string const& text, XmlFormatting fmt) { ensureTagClosed(); - m_os << m_indent << "<!--" << text << "-->"; - m_needsNewline = true; + if (shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << "<!--" << text << "-->"; + applyFormatting(fmt); return *this; } @@ -12424,11 +15734,16 @@ namespace { void XmlWriter::ensureTagClosed() { if( m_tagIsOpen ) { - m_os << ">" << std::endl; + m_os << '>' << std::flush; + newlineIfNecessary(); m_tagIsOpen = false; } } + void XmlWriter::applyFormatting(XmlFormatting fmt) { + m_needsNewline = shouldNewline(fmt); + } + void XmlWriter::writeDeclaration() { m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; } @@ -12469,11 +15784,37 @@ namespace Catch { #ifdef _MSC_VER sprintf_s(buffer, "%.3f", duration); #else - sprintf(buffer, "%.3f", duration); + std::sprintf(buffer, "%.3f", duration); #endif return std::string(buffer); } + bool shouldShowDuration( IConfig const& config, double duration ) { + if ( config.showDurations() == ShowDurations::Always ) { + return true; + } + if ( config.showDurations() == ShowDurations::Never ) { + return false; + } + const double min = config.minDuration(); + return min >= 0 && duration >= min; + } + + std::string serializeFilters( std::vector<std::string> const& container ) { + ReusableStringStream oss; + bool first = true; + for (auto&& filter : container) + { + if (!first) + oss << ' '; + else + first = false; + + oss << filter; + } + return oss.str(); + } + TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) :StreamingReporterBase(_config) {} @@ -12689,24 +16030,25 @@ private: if (itMessage == messages.end()) return; - // using messages.end() directly yields (or auto) compilation error: - std::vector<MessageInfo>::const_iterator itEnd = messages.end(); - const std::size_t N = static_cast<std::size_t>(std::distance(itMessage, itEnd)); + const auto itEnd = messages.cend(); + const auto N = static_cast<std::size_t>(std::distance(itMessage, itEnd)); { Colour colourGuard(colour); stream << " with " << pluralise(N, "message") << ':'; } - for (; itMessage != itEnd; ) { + while (itMessage != itEnd) { // If this assertion is a warning ignore any INFO messages if (printInfoMessages || itMessage->type != ResultWas::Info) { - stream << " '" << itMessage->message << '\''; - if (++itMessage != itEnd) { + printMessage(); + if (itMessage != itEnd) { Colour colourGuard(dimColour()); stream << " and"; } + continue; } + ++itMessage; } } @@ -12724,10 +16066,6 @@ private: return "Reports test results on a single line, suitable for IDEs"; } - ReporterPreferences CompactReporter::getPreferences() const { - return m_reporterPrefs; - } - void CompactReporter::noMatchingTestCases( std::string const& spec ) { stream << "No test cases matched '" << spec << '\'' << std::endl; } @@ -12754,8 +16092,9 @@ private: } void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { - if (m_config->showDurations() == ShowDurations::Always) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + double dur = _sectionStats.durationInSeconds; + if ( shouldShowDuration( *m_config, dur ) ) { + stream << getFormattedDuration( dur ) << " s: " << _sectionStats.sectionInfo.name << std::endl; } } @@ -12779,8 +16118,13 @@ private: #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch - // Note that 4062 (not all labels are handled - // and default is missing) is enabled + // Note that 4062 (not all labels are handled and default is missing) is enabled +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +// For simplicity, benchmarking-only helpers are always enabled +# pragma clang diagnostic ignored "-Wunused-function" #endif namespace Catch { @@ -12962,11 +16306,11 @@ class Duration { static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; - uint64_t m_inNanoseconds; + double m_inNanoseconds; Unit m_units; public: - explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto) + explicit Duration(double inNanoseconds, Unit units = Unit::Auto) : m_inNanoseconds(inNanoseconds), m_units(units) { if (m_units == Unit::Auto) { @@ -12995,7 +16339,7 @@ public: case Unit::Minutes: return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute); default: - return static_cast<double>(m_inNanoseconds); + return m_inNanoseconds; } } auto unitsAsString() const -> std::string { @@ -13003,7 +16347,7 @@ public: case Unit::Nanoseconds: return "ns"; case Unit::Microseconds: - return "µs"; + return "us"; case Unit::Milliseconds: return "ms"; case Unit::Seconds: @@ -13016,7 +16360,7 @@ public: } friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { - return os << duration.value() << " " << duration.unitsAsString(); + return os << duration.value() << ' ' << duration.unitsAsString(); } }; } // end anon namespace @@ -13041,10 +16385,16 @@ public: if (!m_isOpen) { m_isOpen = true; *this << RowBreak(); - for (auto const& info : m_columnInfos) - *this << info.name << ColumnBreak(); - *this << RowBreak(); - m_os << Catch::getLineOfChars<'-'>() << "\n"; + + Columns headerCols; + Spacer spacer(2); + for (auto const& info : m_columnInfos) { + headerCols += Column(info.name).width(static_cast<std::size_t>(info.width - 2)); + headerCols += spacer; + } + m_os << headerCols << '\n'; + + m_os << Catch::getLineOfChars<'-'>() << '\n'; } } void close() { @@ -13063,30 +16413,29 @@ public: friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { auto colStr = tp.m_oss.str(); - // This takes account of utf8 encodings - auto strSize = Catch::StringRef(colStr).numberOfCharacters(); + const auto strSize = colStr.size(); tp.m_oss.str(""); tp.open(); if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) { tp.m_currentColumn = -1; - tp.m_os << "\n"; + tp.m_os << '\n'; } tp.m_currentColumn++; auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; - auto padding = (strSize + 2 < static_cast<std::size_t>(colInfo.width)) - ? std::string(colInfo.width - (strSize + 2), ' ') + auto padding = (strSize + 1 < static_cast<std::size_t>(colInfo.width)) + ? std::string(colInfo.width - (strSize + 1), ' ') : std::string(); if (colInfo.justification == ColumnInfo::Left) - tp.m_os << colStr << padding << " "; + tp.m_os << colStr << padding << ' '; else - tp.m_os << padding << colStr << " "; + tp.m_os << padding << colStr << ' '; return tp; } friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { if (tp.m_currentColumn > 0) { - tp.m_os << "\n"; + tp.m_os << '\n'; tp.m_currentColumn = -1; } return tp; @@ -13096,12 +16445,26 @@ public: ConsoleReporter::ConsoleReporter(ReporterConfig const& config) : StreamingReporterBase(config), m_tablePrinter(new TablePrinter(config.stream(), - { - { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left }, - { "iters", 8, ColumnInfo::Right }, - { "elapsed ns", 14, ColumnInfo::Right }, - { "average", 14, ColumnInfo::Right } - })) {} + [&config]() -> std::vector<ColumnInfo> { + if (config.fullConfig()->benchmarkNoAnalysis()) + { + return{ + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left }, + { " samples", 14, ColumnInfo::Right }, + { " iterations", 14, ColumnInfo::Right }, + { " mean", 14, ColumnInfo::Right } + }; + } + else + { + return{ + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left }, + { "samples mean std dev", 14, ColumnInfo::Right }, + { "iterations low mean low std dev", 14, ColumnInfo::Right }, + { "estimated high mean high std dev", 14, ColumnInfo::Right } + }; + } + }())) {} ConsoleReporter::~ConsoleReporter() = default; std::string ConsoleReporter::getDescription() { @@ -13112,6 +16475,10 @@ void ConsoleReporter::noMatchingTestCases(std::string const& spec) { stream << "No test cases matched '" << spec << '\'' << std::endl; } +void ConsoleReporter::reportInvalidArguments(std::string const&arg){ + stream << "Invalid Filter: " << arg << std::endl; +} + void ConsoleReporter::assertionStarting(AssertionInfo const&) {} bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { @@ -13132,6 +16499,7 @@ bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { } void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_tablePrinter->close(); m_headerPrinted = false; StreamingReporterBase::sectionStarting(_sectionInfo); } @@ -13146,8 +16514,9 @@ void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { stream << "\nNo assertions in test case"; stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } - if (m_config->showDurations() == ShowDurations::Always) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + double dur = _sectionStats.durationInSeconds; + if (shouldShowDuration(*m_config, dur)) { + stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << std::endl; } if (m_headerPrinted) { m_headerPrinted = false; @@ -13155,28 +16524,53 @@ void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { StreamingReporterBase::sectionEnded(_sectionStats); } -void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { - lazyPrintWithoutClosingBenchmarkTable(); +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +void ConsoleReporter::benchmarkPreparing(std::string const& name) { + lazyPrintWithoutClosingBenchmarkTable(); - auto nameCol = Column( info.name ).width( static_cast<std::size_t>( m_tablePrinter->columnInfos()[0].width - 2 ) ); + auto nameCol = Column(name).width(static_cast<std::size_t>(m_tablePrinter->columnInfos()[0].width - 2)); - bool firstLine = true; - for (auto line : nameCol) { - if (!firstLine) - (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); - else - firstLine = false; + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; - (*m_tablePrinter) << line << ColumnBreak(); + (*m_tablePrinter) << line << ColumnBreak(); + } +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + (*m_tablePrinter) << info.samples << ColumnBreak() + << info.iterations << ColumnBreak(); + if (!m_config->benchmarkNoAnalysis()) + (*m_tablePrinter) << Duration(info.estimatedDuration) << ColumnBreak(); +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) { + if (m_config->benchmarkNoAnalysis()) + { + (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak(); + } + else + { + (*m_tablePrinter) << ColumnBreak() + << Duration(stats.mean.point.count()) << ColumnBreak() + << Duration(stats.mean.lower_bound.count()) << ColumnBreak() + << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak() + << Duration(stats.standardDeviation.point.count()) << ColumnBreak() + << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak() + << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak(); } } -void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) { - Duration average(stats.elapsedTimeInNanoseconds / stats.iterations); + +void ConsoleReporter::benchmarkFailed(std::string const& error) { + Colour colour(Colour::Red); (*m_tablePrinter) - << stats.iterations << ColumnBreak() - << stats.elapsedTimeInNanoseconds << ColumnBreak() - << average << ColumnBreak(); + << "Benchmark failed (" << error << ')' + << ColumnBreak() << RowBreak(); } +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { m_tablePrinter->close(); @@ -13198,6 +16592,10 @@ void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { stream << std::endl; StreamingReporterBase::testRunEnded(_testRunStats); } +void ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) { + StreamingReporterBase::testRunStarting(_testInfo); + printTestFilters(); +} void ConsoleReporter::lazyPrint() { @@ -13251,11 +16649,9 @@ void ConsoleReporter::printTestCaseAndSectionHeader() { SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; - if (!lineInfo.empty()) { - stream << getLineOfChars<'-'>() << '\n'; - Colour colourGuard(Colour::FileName); - stream << lineInfo << '\n'; - } + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard(Colour::FileName); + stream << lineInfo << '\n'; stream << getLineOfChars<'.'>() << '\n' << std::endl; } @@ -13379,6 +16775,13 @@ void ConsoleReporter::printSummaryDivider() { stream << getLineOfChars<'-'>() << '\n'; } +void ConsoleReporter::printTestFilters() { + if (m_config->testSpec().hasFilters()) { + Colour guard(Colour::BrightYellow); + stream << "Filters: " << serializeFilters(m_config->getTestsOrTags()) << '\n'; + } +} + CATCH_REGISTER_REPORTER("console", ConsoleReporter) } // end namespace Catch @@ -13386,6 +16789,10 @@ CATCH_REGISTER_REPORTER("console", ConsoleReporter) #if defined(_MSC_VER) #pragma warning(pop) #endif + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif // end catch_reporter_console.cpp // start catch_reporter_junit.cpp @@ -13393,6 +16800,7 @@ CATCH_REGISTER_REPORTER("console", ConsoleReporter) #include <sstream> #include <ctime> #include <algorithm> +#include <iomanip> namespace Catch { @@ -13420,7 +16828,7 @@ namespace Catch { #else std::strftime(timeStamp, timeStampSize, fmt, timeInfo); #endif - return std::string(timeStamp); + return std::string(timeStamp, timeStampSize-1); } std::string fileNameTag(const std::vector<std::string> &tags) { @@ -13431,6 +16839,17 @@ namespace Catch { return it->substr(1); return std::string(); } + + // Formats the duration in seconds to 3 decimal places. + // This is done because some genius defined Maven Surefire schema + // in a way that only accepts 3 decimal places, and tools like + // Jenkins use that schema for validation JUnit reporter output. + std::string formatDuration( double seconds ) { + ReusableStringStream rss; + rss << std::fixed << std::setprecision( 3 ) << seconds; + return rss.str(); + } + } // anonymous namespace JunitReporter::JunitReporter( ReporterConfig const& _config ) @@ -13490,6 +16909,7 @@ namespace Catch { void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; xml.writeAttribute( "name", stats.groupInfo.name ); xml.writeAttribute( "errors", unexpectedExceptions ); @@ -13499,15 +16919,30 @@ namespace Catch { if( m_config->showDurations() == ShowDurations::Never ) xml.writeAttribute( "time", "" ); else - xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "time", formatDuration( suiteTime ) ); xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + // Write properties if there are any + if (m_config->hasTestFilters() || m_config->rngSeed() != 0) { + auto properties = xml.scopedElement("properties"); + if (m_config->hasTestFilters()) { + xml.scopedElement("property") + .writeAttribute("name", "filters") + .writeAttribute("value", serializeFilters(m_config->getTestsOrTags())); + } + if (m_config->rngSeed() != 0) { + xml.scopedElement("property") + .writeAttribute("name", "random-seed") + .writeAttribute("value", m_config->rngSeed()); + } + } + // Write test cases for( auto const& child : groupNode.children ) writeTestCase( *child ); - xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); - xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), XmlFormatting::Newline ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), XmlFormatting::Newline ); } void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { @@ -13529,12 +16964,13 @@ namespace Catch { if ( !m_config->name().empty() ) className = m_config->name() + "." + className; - writeSection( className, "", rootSection ); + writeSection( className, "", rootSection, stats.testInfo.okToFail() ); } - void JunitReporter::writeSection( std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode ) { + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail) { std::string name = trim( sectionNode.stats.sectionInfo.name ); if( !rootName.empty() ) name = rootName + '/' + name; @@ -13551,20 +16987,30 @@ namespace Catch { xml.writeAttribute( "classname", className ); xml.writeAttribute( "name", name ); } - xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + xml.writeAttribute( "time", formatDuration( sectionNode.stats.durationInSeconds ) ); + // This is not ideal, but it should be enough to mimic gtest's + // junit output. + // Ideally the JUnit reporter would also handle `skipTest` + // events and write those out appropriately. + xml.writeAttribute( "status", "run" ); + + if (sectionNode.stats.assertions.failedButOk) { + xml.scopedElement("skipped") + .writeAttribute("message", "TEST_CASE tagged with !mayfail"); + } writeAssertions( sectionNode ); if( !sectionNode.stdOut.empty() ) - xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), XmlFormatting::Newline ); if( !sectionNode.stdErr.empty() ) - xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), XmlFormatting::Newline ); } for( auto const& childNode : sectionNode.childSections ) if( className.empty() ) - writeSection( name, "", *childNode ); + writeSection( name, "", *childNode, testOkToFail ); else - writeSection( className, name, *childNode ); + writeSection( className, name, *childNode, testOkToFail ); } void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { @@ -13582,11 +17028,7 @@ namespace Catch { elementName = "error"; break; case ResultWas::ExplicitFailure: - elementName = "failure"; - break; case ResultWas::ExpressionFailed: - elementName = "failure"; - break; case ResultWas::DidntThrowException: elementName = "failure"; break; @@ -13604,10 +17046,25 @@ namespace Catch { XmlWriter::ScopedElement e = xml.scopedElement( elementName ); - xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "message", result.getExpression() ); xml.writeAttribute( "type", result.getTestMacroName() ); ReusableStringStream rss; + if (stats.totals.assertions.total() > 0) { + rss << "FAILED" << ":\n"; + if (result.hasExpression()) { + rss << " "; + rss << result.getExpressionInMacro(); + rss << '\n'; + } + if (result.hasExpandedExpression()) { + rss << "with expansion:\n"; + rss << Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } else { + rss << '\n'; + } + if( !result.getMessage().empty() ) rss << result.getMessage() << '\n'; for( auto const& msg : stats.infoMessages ) @@ -13615,7 +17072,7 @@ namespace Catch { rss << msg.message << '\n'; rss << "at " << result.getSourceInfo(); - xml.writeText( rss.str(), false ); + xml.writeText( rss.str(), XmlFormatting::Newline ); } } @@ -13659,19 +17116,41 @@ namespace Catch { m_reporter->noMatchingTestCases( spec ); } + void ListeningReporter::reportInvalidArguments(std::string const&arg){ + for ( auto const& listener : m_listeners ) { + listener->reportInvalidArguments( arg ); + } + m_reporter->reportInvalidArguments( arg ); + } + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void ListeningReporter::benchmarkPreparing( std::string const& name ) { + for (auto const& listener : m_listeners) { + listener->benchmarkPreparing(name); + } + m_reporter->benchmarkPreparing(name); + } void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { for ( auto const& listener : m_listeners ) { listener->benchmarkStarting( benchmarkInfo ); } m_reporter->benchmarkStarting( benchmarkInfo ); } - void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { + void ListeningReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) { for ( auto const& listener : m_listeners ) { listener->benchmarkEnded( benchmarkStats ); } m_reporter->benchmarkEnded( benchmarkStats ); } + void ListeningReporter::benchmarkFailed( std::string const& error ) { + for (auto const& listener : m_listeners) { + listener->benchmarkFailed(error); + } + m_reporter->benchmarkFailed(error); + } +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { for ( auto const& listener : m_listeners ) { listener->testRunStarting( testRunInfo ); @@ -13802,6 +17281,8 @@ namespace Catch { m_xml.startElement( "Catch" ); if( !m_config->name().empty() ) m_xml.writeAttribute( "name", m_config->name() ); + if (m_config->testSpec().hasFilters()) + m_xml.writeAttribute( "filters", serializeFilters( m_config->getTestsOrTags() ) ); if( m_config->rngSeed() != 0 ) m_xml.scopedElement( "Randomness" ) .writeAttribute( "seed", m_config->rngSeed() ); @@ -13937,9 +17418,9 @@ namespace Catch { e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); if( !testCaseStats.stdOut.empty() ) - m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), XmlFormatting::Newline ); if( !testCaseStats.stdErr.empty() ) - m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), XmlFormatting::Newline ); m_xml.endElement(); } @@ -13951,6 +17432,10 @@ namespace Catch { .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.scopedElement( "OverallResultsCases") + .writeAttribute( "successes", testGroupStats.totals.testCases.passed ) + .writeAttribute( "failures", testGroupStats.totals.testCases.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.testCases.failedButOk ); m_xml.endElement(); } @@ -13960,9 +17445,58 @@ namespace Catch { .writeAttribute( "successes", testRunStats.totals.assertions.passed ) .writeAttribute( "failures", testRunStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.scopedElement( "OverallResultsCases") + .writeAttribute( "successes", testRunStats.totals.testCases.passed ) + .writeAttribute( "failures", testRunStats.totals.testCases.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.testCases.failedButOk ); + m_xml.endElement(); + } + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void XmlReporter::benchmarkPreparing(std::string const& name) { + m_xml.startElement("BenchmarkResults") + .writeAttribute("name", name); + } + + void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { + m_xml.writeAttribute("samples", info.samples) + .writeAttribute("resamples", info.resamples) + .writeAttribute("iterations", info.iterations) + .writeAttribute("clockResolution", info.clockResolution) + .writeAttribute("estimatedDuration", info.estimatedDuration) + .writeComment("All values in nano seconds"); + } + + void XmlReporter::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { + m_xml.startElement("mean") + .writeAttribute("value", benchmarkStats.mean.point.count()) + .writeAttribute("lowerBound", benchmarkStats.mean.lower_bound.count()) + .writeAttribute("upperBound", benchmarkStats.mean.upper_bound.count()) + .writeAttribute("ci", benchmarkStats.mean.confidence_interval); + m_xml.endElement(); + m_xml.startElement("standardDeviation") + .writeAttribute("value", benchmarkStats.standardDeviation.point.count()) + .writeAttribute("lowerBound", benchmarkStats.standardDeviation.lower_bound.count()) + .writeAttribute("upperBound", benchmarkStats.standardDeviation.upper_bound.count()) + .writeAttribute("ci", benchmarkStats.standardDeviation.confidence_interval); + m_xml.endElement(); + m_xml.startElement("outliers") + .writeAttribute("variance", benchmarkStats.outlierVariance) + .writeAttribute("lowMild", benchmarkStats.outliers.low_mild) + .writeAttribute("lowSevere", benchmarkStats.outliers.low_severe) + .writeAttribute("highMild", benchmarkStats.outliers.high_mild) + .writeAttribute("highSevere", benchmarkStats.outliers.high_severe); + m_xml.endElement(); m_xml.endElement(); } + void XmlReporter::benchmarkFailed(std::string const &error) { + m_xml.scopedElement("failed"). + writeAttribute("message", error); + m_xml.endElement(); + } +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + CATCH_REGISTER_REPORTER( "xml", XmlReporter ) } // end namespace Catch @@ -13988,7 +17522,7 @@ namespace Catch { #ifndef __OBJC__ -#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) // Standard C/C++ Win32 Unicode wmain entry point extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { #else @@ -14036,7 +17570,7 @@ int main (int argc, char * const argv[]) { #define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) #define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) -#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ ) +#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) #if !defined(CATCH_CONFIG_DISABLE_MATCHERS) @@ -14050,7 +17584,7 @@ int main (int argc, char * const argv[]) { #define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) -#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ ) +#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) #define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) #if !defined(CATCH_CONFIG_DISABLE_MATCHERS) @@ -14065,6 +17599,7 @@ int main (int argc, char * const argv[]) { #endif // CATCH_CONFIG_DISABLE_MATCHERS #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg ) #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE",__VA_ARGS__ ) @@ -14082,10 +17617,22 @@ int main (int argc, char * const argv[]) { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) #define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) #else #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) ) #define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) #endif #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) @@ -14106,6 +17653,13 @@ int main (int argc, char * const argv[]) { #define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) #define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +#define CATCH_BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define CATCH_BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), name) +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else @@ -14141,6 +17695,7 @@ int main (int argc, char * const argv[]) { #endif // CATCH_CONFIG_DISABLE_MATCHERS #define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg ) #define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE",__VA_ARGS__ ) @@ -14157,10 +17712,26 @@ int main (int argc, char * const argv[]) { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) #define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) +#define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__) +#define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) #else #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) +#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) ) #define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) +#define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE( __VA_ARGS__ ) ) +#define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) #endif #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) @@ -14186,6 +17757,13 @@ int main (int argc, char * const argv[]) { #define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) #define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +#define BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), name) +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + using Catch::Detail::Approx; #else // CATCH_CONFIG_DISABLE @@ -14225,12 +17803,13 @@ using Catch::Detail::Approx; #define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) #endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_INFO( msg ) (void)(0) -#define CATCH_WARN( msg ) (void)(0) -#define CATCH_CAPTURE( msg ) (void)(0) +#define CATCH_INFO( msg ) (void)(0) +#define CATCH_UNSCOPED_INFO( msg ) (void)(0) +#define CATCH_WARN( msg ) (void)(0) +#define CATCH_CAPTURE( msg ) (void)(0) -#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) #define CATCH_SECTION( ... ) @@ -14239,19 +17818,31 @@ using Catch::Detail::Approx; #define CATCH_FAIL_CHECK( ... ) (void)(0) #define CATCH_SUCCEED( ... ) (void)(0) -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) -#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) #else -#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) ) -#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) ) +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) ) +#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) #endif // "BDD-style" convenience wrappers -#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), className ) #define CATCH_GIVEN( desc ) #define CATCH_AND_GIVEN( desc ) #define CATCH_WHEN( desc ) @@ -14297,11 +17888,12 @@ using Catch::Detail::Approx; #endif // CATCH_CONFIG_DISABLE_MATCHERS #define INFO( msg ) (void)(0) +#define UNSCOPED_INFO( msg ) (void)(0) #define WARN( msg ) (void)(0) #define CAPTURE( msg ) (void)(0) -#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) #define METHOD_AS_TEST_CASE( method, ... ) #define REGISTER_TEST_CASE( Function, ... ) (void)(0) #define SECTION( ... ) @@ -14309,14 +17901,26 @@ using Catch::Detail::Approx; #define FAIL( ... ) (void)(0) #define FAIL_CHECK( ... ) (void)(0) #define SUCCEED( ... ) (void)(0) -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ )) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) -#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) +#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__) +#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) #else -#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) ) -#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) ) +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) ) +#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) ) +#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) #endif #define STATIC_REQUIRE( ... ) (void)(0) @@ -14327,8 +17931,8 @@ using Catch::Detail::Approx; #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) // "BDD-style" convenience wrappers -#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) -#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ) ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), className ) #define GIVEN( desc ) #define AND_GIVEN( desc ) @@ -14359,4 +17963,3 @@ using Catch::Detail::Approx; // end catch_reenable_warnings.h // end catch.hpp #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED - diff --git a/src/teakra/src/interpreter.h b/src/teakra/src/interpreter.h index 72724ee..aac4916 100644 --- a/src/teakra/src/interpreter.h +++ b/src/teakra/src/interpreter.h @@ -1,4 +1,5 @@ #pragma once +#include <utility> #include <atomic> #include <stdexcept> #include <tuple> diff --git a/src/teakra/src/teakra.cpp b/src/teakra/src/teakra.cpp index 76bc79f..7b4c028 100644 --- a/src/teakra/src/teakra.cpp +++ b/src/teakra/src/teakra.cpp @@ -50,7 +50,7 @@ struct Teakra::Impl { } void Reset() { - shared_memory.raw.fill(0); + shared_memory.raw.fill(0); // BAD!!!! miu.Reset(); apbp_from_cpu.Reset(); apbp_from_dsp.Reset(); diff --git a/src/types.h b/src/types.h index b723566..47c1b9e 100644 --- a/src/types.h +++ b/src/types.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. diff --git a/src/version.h b/src/version.h index 0bebb99..f387820 100644 --- a/src/version.h +++ b/src/version.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2022 melonDS team This file is part of melonDS. @@ -19,7 +19,7 @@ #ifndef VERSION_H #define VERSION_H -#define MELONDS_URL "http://melonds.kuribo64.net/" +#define MELONDS_URL "https://melonds.kuribo64.net/" #endif // VERSION_H |