diff --git a/.gitattributes b/.gitattributes index 2896f4aa..8b206540 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,7 +12,7 @@ src/xxhash/** linguist-vendored # A handful of custom files embedded in the vendored dependencies ## Ad-hoc CMakeLists.txt for melonDS -!src/net/libslirp/src/CMakeLists.txt -linguist-vendored +src/net/libslirp/src/CMakeLists.txt -linguist-vendored ## glib stub -!src/net/libslirp/src/glib/** -linguist-vendored +src/net/libslirp/src/glib/** -linguist-vendored diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 4178157d..9a5830e0 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - ci/vcpkg-update pull_request: branches: - master @@ -27,7 +28,7 @@ jobs: - name: Set up vcpkg uses: lukka/run-vcpkg@v11 with: - vcpkgGitCommitId: 53bef8994c541b6561884a8395ea35715ece75db + vcpkgGitCommitId: 1de2026f28ead93ff1773e6e680387643e914ea1 - name: Build uses: lukka/run-cmake@v10 with: diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index a4d84a1c..5470e7b8 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -4,40 +4,35 @@ on: push: branches: - master + - ci/vcpkg-update pull_request: branches: - master -env: - BUILD_TYPE: Release - jobs: build: - runs-on: windows-latest - defaults: run: shell: msys2 {0} steps: - - uses: actions/checkout@v1 - - uses: msys2/setup-msys2@v2 + - name: Check out sources + uses: actions/checkout@v3 + - name: Set up MSYS2 + uses: msys2/setup-msys2@v2 with: - msystem: MINGW64 - update: true - - - name: Install dependencies - run: pacman -Sq --noconfirm git pkgconf mingw-w64-x86_64-{cmake,SDL2,qt5-static,libarchive,toolchain} - + msystem: ucrt64 + update: true + pacboy: gcc:p cmake:p ninja:p make:p + - name: Set up vcpkg + uses: lukka/run-vcpkg@v11 + with: + vcpkgGitCommitId: 1de2026f28ead93ff1773e6e680387643e914ea1 - name: Configure - working-directory: ${{runner.workspace}} - run: cmake -B build $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_STATIC=ON -DCMAKE_PREFIX_PATH=C:/tools/msys64/mingw64/qt5-static - - - name: Make - working-directory: ${{runner.workspace}}/build - run: cmake --build . - - - uses: actions/upload-artifact@v1 + run: cmake --preset=release-mingw-x86_64 + - name: Build + run: cmake --build --preset=release-mingw-x86_64 + - uses: actions/upload-artifact@v4 with: name: melonDS-windows-x86_64 - path: ${{runner.workspace}}\build\melonDS.exe + path: .\build\release-mingw-x86_64\melonDS.exe diff --git a/CMakePresets.json b/CMakePresets.json index e14eda24..2144417b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -20,6 +20,23 @@ } } }, + { + "name": "release-mingw-x86_64", + "inherits": "release-vcpkg", + "displayName": "Windows MinGW release (x86_64)", + "binaryDir": "${sourceDir}/build/release-mingw-x86_64", + "generator": "Ninja", + "cacheVariables": { + "USE_QT6": { + "type": "BOOL", + "value": "ON" + }, + "BUILD_STATIC": { + "type": "BOOL", + "value": "ON" + } + } + }, { "name": "release-mac-x86_64", "inherits": "release-vcpkg", @@ -44,6 +61,10 @@ "name": "release-vcpkg", "configurePreset": "release-vcpkg" }, + { + "name": "release-mingw-x86_64", + "configurePreset": "release-mingw-x86_64" + }, { "name": "release-mac-x86_64", "configurePreset": "release-mac-x86_64" @@ -85,4 +106,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/cmake/ConfigureVcpkg.cmake b/cmake/ConfigureVcpkg.cmake index c9f3e92f..fa6e2b00 100644 --- a/cmake/ConfigureVcpkg.cmake +++ b/cmake/ConfigureVcpkg.cmake @@ -4,10 +4,12 @@ set(_DEFAULT_VCPKG_ROOT "${CMAKE_SOURCE_DIR}/vcpkg") set(VCPKG_ROOT "${_DEFAULT_VCPKG_ROOT}" CACHE STRING "The path to the vcpkg repository") if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}") - file(LOCK "${_DEFAULT_VCPKG_ROOT}" DIRECTORY GUARD FILE) + if (APPLE) # this doesn't work on non-macOS + file(LOCK "${_DEFAULT_VCPKG_ROOT}" DIRECTORY GUARD FILE) + endif() FetchContent_Declare(vcpkg GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git" - GIT_TAG 2024.01.12 + GIT_TAG 2024.07.12 SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg") FetchContent_MakeAvailable(vcpkg) endif() @@ -16,6 +18,18 @@ set(VCPKG_OVERLAY_TRIPLETS "${CMAKE_SOURCE_DIR}/cmake/overlay-triplets") option(USE_RECOMMENDED_TRIPLETS "Use the recommended triplets that are used for official builds" ON) +# Duplicated here because it needs to be set before project() +if (NOT WIN32) + option(USE_QT6 "Build using Qt 6 instead of 5" ON) +else() + option(USE_QT6 "Build using Qt 6 instead of 5" OFF) +endif() + +if (NOT USE_QT6) + list(APPEND VCPKG_MANIFEST_FEATURES qt5) + set(VCPKG_MANIFEST_NO_DEFAULT_FEATURES ON) +endif() + if (CMAKE_OSX_ARCHITECTURES MATCHES ";") message(FATAL_ERROR "macOS universal builds are not supported. Build them individually and combine afterwards instead.") endif() @@ -47,7 +61,7 @@ if (USE_RECOMMENDED_TRIPLETS) elseif(WIN32) # TODO Windows arm64 if possible set(_CAN_TARGET_AS_HOST ON) - set(_WANTED_TRIPLET x64-mingw-static) + set(_WANTED_TRIPLET x64-mingw-static-release) endif() # Don't override it if the user set something else diff --git a/cmake/overlay-triplets/x64-mingw-static-release.cmake b/cmake/overlay-triplets/x64-mingw-static-release.cmake new file mode 100644 index 00000000..19c2aeb0 --- /dev/null +++ b/cmake/overlay-triplets/x64-mingw-static-release.cmake @@ -0,0 +1,7 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_ENV_PASSTHROUGH PATH) +set(VCPKG_BUILD_TYPE release) + +set(VCPKG_CMAKE_SYSTEM_NAME MinGW) diff --git a/src/ARCodeFile.cpp b/src/ARCodeFile.cpp index 2b37e859..a98f5e50 100644 --- a/src/ARCodeFile.cpp +++ b/src/ARCodeFile.cpp @@ -33,17 +33,26 @@ ARCodeFile::ARCodeFile(const std::string& filename) { Filename = filename; - Error = false; - - Categories.clear(); - if (!Load()) Error = true; } -ARCodeFile::~ARCodeFile() +std::vector ARCodeFile::GetCodes() const noexcept { - Categories.clear(); + if (Error) + return {}; + + std::vector codes; + + for (const ARCodeCat& cat : Categories) + { + for (const ARCode& code : cat.Codes) + { + codes.push_back(code); + } + } + + return codes; } bool ARCodeFile::Load() diff --git a/src/ARCodeFile.h b/src/ARCodeFile.h index 2cdf1f05..04f9e4f4 100644 --- a/src/ARCodeFile.h +++ b/src/ARCodeFile.h @@ -48,14 +48,16 @@ class ARCodeFile { public: ARCodeFile(const std::string& filename); - ~ARCodeFile(); + ~ARCodeFile() noexcept = default; - bool Error; + [[nodiscard]] std::vector GetCodes() const noexcept; + + bool Error = false; bool Load(); bool Save(); - ARCodeCatList Categories; + ARCodeCatList Categories {}; private: std::string Filename; diff --git a/src/AREngine.cpp b/src/AREngine.cpp index 879c620c..bdda5863 100644 --- a/src/AREngine.cpp +++ b/src/AREngine.cpp @@ -31,7 +31,6 @@ using Platform::LogLevel; AREngine::AREngine(melonDS::NDS& nds) : NDS(nds) { - CodeFile = nullptr; } #define case16(x) \ @@ -388,19 +387,12 @@ void AREngine::RunCheat(const ARCode& arcode) void AREngine::RunCheats() { - if (!CodeFile) return; + if (Cheats.empty()) return; - for (ARCodeCatList::iterator i = CodeFile->Categories.begin(); i != CodeFile->Categories.end(); i++) + for (const ARCode& code : Cheats) { - ARCodeCat& cat = *i; - - for (ARCodeList::iterator j = cat.Codes.begin(); j != cat.Codes.end(); j++) - { - ARCode& code = *j; - - if (code.Enabled) - RunCheat(code); - } + if (code.Enabled) + RunCheat(code); } } } diff --git a/src/AREngine.h b/src/AREngine.h index cdffac07..e73fc98e 100644 --- a/src/AREngine.h +++ b/src/AREngine.h @@ -19,6 +19,7 @@ #ifndef ARENGINE_H #define ARENGINE_H +#include #include "ARCodeFile.h" namespace melonDS @@ -29,14 +30,13 @@ class AREngine public: AREngine(melonDS::NDS& nds); - ARCodeFile* GetCodeFile() { return CodeFile; } - void SetCodeFile(ARCodeFile* file) { CodeFile = file; } - + std::vector Cheats {}; +private: + friend class ARM; void RunCheats(); void RunCheat(const ARCode& arcode); -private: + melonDS::NDS& NDS; - ARCodeFile* CodeFile; // AR code file - frontend is responsible for managing this }; } diff --git a/src/ARM.cpp b/src/ARM.cpp index a88c73aa..3452d361 100644 --- a/src/ARM.cpp +++ b/src/ARM.cpp @@ -582,9 +582,11 @@ void ARM::CheckGdbIncoming() GdbCheckA(); } +template void ARMv5::Execute() { - GdbCheckB(); + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckB(); if (Halted) { @@ -607,231 +609,123 @@ void ARMv5::Execute() while (NDS.ARM9Timestamp < NDS.ARM9Target) { - if (CPSR & 0x20) // THUMB + if constexpr (mode == CPUExecuteMode::JIT) { - GdbCheckC(); + u32 instrAddr = R[15] - ((CPSR&0x20)?2:4); - // prefetch - R[15] += 2; - CurInstr = NextInstr[0]; - NextInstr[0] = NextInstr[1]; - if (R[15] & 0x2) { NextInstr[1] >>= 16; CodeCycles = 0; } - else NextInstr[1] = CodeRead32(R[15], false); - - // actually execute - u32 icode = (CurInstr >> 6) & 0x3FF; - ARMInterpreter::THUMBInstrTable[icode](this); - } - else - { - GdbCheckC(); - - // prefetch - R[15] += 4; - CurInstr = NextInstr[0]; - NextInstr[0] = NextInstr[1]; - NextInstr[1] = CodeRead32(R[15], false); - - // actually execute - if (CheckCondition(CurInstr >> 28)) - { - u32 icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0); - ARMInterpreter::ARMInstrTable[icode](this); - } - else if ((CurInstr & 0xFE000000) == 0xFA000000) - { - ARMInterpreter::A_BLX_IMM(this); - } - else - AddCycles_C(); - } - - // TODO optimize this shit!!! - if (Halted) - { - if (Halted == 1 && NDS.ARM9Timestamp < NDS.ARM9Target) + if ((instrAddr < FastBlockLookupStart || instrAddr >= (FastBlockLookupStart + FastBlockLookupSize)) + && !NDS.JIT.SetupExecutableRegion(0, instrAddr, FastBlockLookup, FastBlockLookupStart, FastBlockLookupSize)) { NDS.ARM9Timestamp = NDS.ARM9Target; + Log(LogLevel::Error, "ARMv5 PC in non executable region %08X\n", R[15]); + return; } - break; - } - /*if (NDS::IF[0] & NDS::IE[0]) - { - if (NDS::IME[0] & 0x1) - TriggerIRQ(); - }*/ - if (IRQ) TriggerIRQ(); - NDS.ARM9Timestamp += Cycles; - Cycles = 0; - } + JitBlockEntry block = NDS.JIT.LookUpBlock(0, FastBlockLookup, + instrAddr - FastBlockLookupStart, instrAddr); + if (block) + ARM_Dispatch(this, block); + else + NDS.JIT.CompileBlock(this); - if (Halted == 2) - Halted = 0; -} - -#ifdef JIT_ENABLED -void ARMv5::ExecuteJIT() -{ - if (Halted) - { - if (Halted == 2) - { - Halted = 0; - } - else if (NDS.HaltInterrupted(0)) - { - Halted = 0; - if (NDS.IME[0] & 0x1) - TriggerIRQ(); - } - else - { - NDS.ARM9Timestamp = NDS.ARM9Target; - return; - } - } - - while (NDS.ARM9Timestamp < NDS.ARM9Target) - { - u32 instrAddr = R[15] - ((CPSR&0x20)?2:4); - - if ((instrAddr < FastBlockLookupStart || instrAddr >= (FastBlockLookupStart + FastBlockLookupSize)) - && !NDS.JIT.SetupExecutableRegion(0, instrAddr, FastBlockLookup, FastBlockLookupStart, FastBlockLookupSize)) - { - NDS.ARM9Timestamp = NDS.ARM9Target; - Log(LogLevel::Error, "ARMv5 PC in non executable region %08X\n", R[15]); - return; - } - - JitBlockEntry block = NDS.JIT.LookUpBlock(0, FastBlockLookup, - instrAddr - FastBlockLookupStart, instrAddr); - if (block) - ARM_Dispatch(this, block); - else - NDS.JIT.CompileBlock(this); - - if (StopExecution) - { - // this order is crucial otherwise idle loops waiting for an IRQ won't function - if (IRQ) - TriggerIRQ(); - - if (Halted || IdleLoop) + if (StopExecution) { - if ((Halted == 1 || IdleLoop) && NDS.ARM9Timestamp < NDS.ARM9Target) + // this order is crucial otherwise idle loops waiting for an IRQ won't function + if (IRQ) + TriggerIRQ(); + + if (Halted || IdleLoop) { - Cycles = 0; - NDS.ARM9Timestamp = NDS.ARM9Target; + if ((Halted == 1 || IdleLoop) && NDS.ARM9Timestamp < NDS.ARM9Target) + { + Cycles = 0; + NDS.ARM9Timestamp = NDS.ARM9Target; + } + IdleLoop = 0; + break; } - IdleLoop = 0; - break; } } - - NDS.ARM9Timestamp += Cycles; - Cycles = 0; - } - - if (Halted == 2) - Halted = 0; -} -#endif - -void ARMv4::Execute() -{ - GdbCheckB(); - - if (Halted) - { - if (Halted == 2) - { - Halted = 0; - } - else if (NDS.HaltInterrupted(1)) - { - Halted = 0; - if (NDS.IME[1] & 0x1) - TriggerIRQ(); - } else { - NDS.ARM7Timestamp = NDS.ARM7Target; - return; - } - } - - while (NDS.ARM7Timestamp < NDS.ARM7Target) - { - if (CPSR & 0x20) // THUMB - { - GdbCheckC(); - - // prefetch - R[15] += 2; - CurInstr = NextInstr[0]; - NextInstr[0] = NextInstr[1]; - NextInstr[1] = CodeRead16(R[15]); - - // actually execute - u32 icode = (CurInstr >> 6); - ARMInterpreter::THUMBInstrTable[icode](this); - } - else - { - GdbCheckC(); - - // prefetch - R[15] += 4; - CurInstr = NextInstr[0]; - NextInstr[0] = NextInstr[1]; - NextInstr[1] = CodeRead32(R[15]); - - // actually execute - if (CheckCondition(CurInstr >> 28)) + if (CPSR & 0x20) // THUMB { - u32 icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0); - ARMInterpreter::ARMInstrTable[icode](this); + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckC(); + + // prefetch + R[15] += 2; + CurInstr = NextInstr[0]; + NextInstr[0] = NextInstr[1]; + if (R[15] & 0x2) { NextInstr[1] >>= 16; CodeCycles = 0; } + else NextInstr[1] = CodeRead32(R[15], false); + + // actually execute + u32 icode = (CurInstr >> 6) & 0x3FF; + ARMInterpreter::THUMBInstrTable[icode](this); } else - AddCycles_C(); - } - - // TODO optimize this shit!!! - if (Halted) - { - if (Halted == 1 && NDS.ARM7Timestamp < NDS.ARM7Target) { - NDS.ARM7Timestamp = NDS.ARM7Target; - } - break; - } - /*if (NDS::IF[1] & NDS::IE[1]) - { - if (NDS::IME[1] & 0x1) - TriggerIRQ(); - }*/ - if (IRQ) TriggerIRQ(); + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckC(); - NDS.ARM7Timestamp += Cycles; + // prefetch + R[15] += 4; + CurInstr = NextInstr[0]; + NextInstr[0] = NextInstr[1]; + NextInstr[1] = CodeRead32(R[15], false); + + // actually execute + if (CheckCondition(CurInstr >> 28)) + { + u32 icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0); + ARMInterpreter::ARMInstrTable[icode](this); + } + else if ((CurInstr & 0xFE000000) == 0xFA000000) + { + ARMInterpreter::A_BLX_IMM(this); + } + else + AddCycles_C(); + } + + // TODO optimize this shit!!! + if (Halted) + { + if (Halted == 1 && NDS.ARM9Timestamp < NDS.ARM9Target) + { + NDS.ARM9Timestamp = NDS.ARM9Target; + } + break; + } + /*if (NDS::IF[0] & NDS::IE[0]) + { + if (NDS::IME[0] & 0x1) + TriggerIRQ(); + }*/ + if (IRQ) TriggerIRQ(); + + } + + NDS.ARM9Timestamp += Cycles; Cycles = 0; } if (Halted == 2) Halted = 0; - - if (Halted == 4) - { - assert(NDS.ConsoleType == 1); - auto& dsi = dynamic_cast(NDS); - dsi.SoftReset(); - Halted = 2; - } } - +template void ARMv5::Execute(); +template void ARMv5::Execute(); #ifdef JIT_ENABLED -void ARMv4::ExecuteJIT() +template void ARMv5::Execute(); +#endif + +template +void ARMv4::Execute() { + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckB(); + if (Halted) { if (Halted == 2) @@ -853,38 +747,95 @@ void ARMv4::ExecuteJIT() while (NDS.ARM7Timestamp < NDS.ARM7Target) { - u32 instrAddr = R[15] - ((CPSR&0x20)?2:4); - - if ((instrAddr < FastBlockLookupStart || instrAddr >= (FastBlockLookupStart + FastBlockLookupSize)) - && !NDS.JIT.SetupExecutableRegion(1, instrAddr, FastBlockLookup, FastBlockLookupStart, FastBlockLookupSize)) + if constexpr (mode == CPUExecuteMode::JIT) { - NDS.ARM7Timestamp = NDS.ARM7Target; - Log(LogLevel::Error, "ARMv4 PC in non executable region %08X\n", R[15]); - return; - } + u32 instrAddr = R[15] - ((CPSR&0x20)?2:4); - JitBlockEntry block = NDS.JIT.LookUpBlock(1, FastBlockLookup, - instrAddr - FastBlockLookupStart, instrAddr); - if (block) - ARM_Dispatch(this, block); - else - NDS.JIT.CompileBlock(this); - - if (StopExecution) - { - if (IRQ) - TriggerIRQ(); - - if (Halted || IdleLoop) + if ((instrAddr < FastBlockLookupStart || instrAddr >= (FastBlockLookupStart + FastBlockLookupSize)) + && !NDS.JIT.SetupExecutableRegion(1, instrAddr, FastBlockLookup, FastBlockLookupStart, FastBlockLookupSize)) { - if ((Halted == 1 || IdleLoop) && NDS.ARM7Timestamp < NDS.ARM7Target) + NDS.ARM7Timestamp = NDS.ARM7Target; + Log(LogLevel::Error, "ARMv4 PC in non executable region %08X\n", R[15]); + return; + } + + JitBlockEntry block = NDS.JIT.LookUpBlock(1, FastBlockLookup, + instrAddr - FastBlockLookupStart, instrAddr); + if (block) + ARM_Dispatch(this, block); + else + NDS.JIT.CompileBlock(this); + + if (StopExecution) + { + if (IRQ) + TriggerIRQ(); + + if (Halted || IdleLoop) + { + if ((Halted == 1 || IdleLoop) && NDS.ARM7Timestamp < NDS.ARM7Target) + { + Cycles = 0; + NDS.ARM7Timestamp = NDS.ARM7Target; + } + IdleLoop = 0; + break; + } + } + } + else + { + if (CPSR & 0x20) // THUMB + { + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckC(); + + // prefetch + R[15] += 2; + CurInstr = NextInstr[0]; + NextInstr[0] = NextInstr[1]; + NextInstr[1] = CodeRead16(R[15]); + + // actually execute + u32 icode = (CurInstr >> 6); + ARMInterpreter::THUMBInstrTable[icode](this); + } + else + { + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckC(); + + // prefetch + R[15] += 4; + CurInstr = NextInstr[0]; + NextInstr[0] = NextInstr[1]; + NextInstr[1] = CodeRead32(R[15]); + + // actually execute + if (CheckCondition(CurInstr >> 28)) + { + u32 icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0); + ARMInterpreter::ARMInstrTable[icode](this); + } + else + AddCycles_C(); + } + + // TODO optimize this shit!!! + if (Halted) + { + if (Halted == 1 && NDS.ARM7Timestamp < NDS.ARM7Target) { - Cycles = 0; NDS.ARM7Timestamp = NDS.ARM7Target; } - IdleLoop = 0; break; } + /*if (NDS::IF[1] & NDS::IE[1]) + { + if (NDS::IME[1] & 0x1) + TriggerIRQ(); + }*/ + if (IRQ) TriggerIRQ(); } NDS.ARM7Timestamp += Cycles; @@ -902,6 +853,11 @@ void ARMv4::ExecuteJIT() Halted = 2; } } + +template void ARMv4::Execute(); +template void ARMv4::Execute(); +#ifdef JIT_ENABLED +template void ARMv4::Execute(); #endif void ARMv5::FillPipeline() diff --git a/src/ARM.h b/src/ARM.h index f2a1d1d7..b652e74d 100644 --- a/src/ARM.h +++ b/src/ARM.h @@ -43,6 +43,15 @@ enum RWFlags_ForceUser = (1<<21), }; +enum class CPUExecuteMode : u32 +{ + Interpreter, + InterpreterGDB, +#ifdef JIT_ENABLED + JIT +#endif +}; + struct GDBArgs; class ARMJIT; class GPU; @@ -75,10 +84,6 @@ public: } void NocashPrint(u32 addr) noexcept; - virtual void Execute() = 0; -#ifdef JIT_ENABLED - virtual void ExecuteJIT() = 0; -#endif bool CheckCondition(u32 code) const { @@ -241,10 +246,8 @@ public: void PrefetchAbort(); void DataAbort(); - void Execute() override; -#ifdef JIT_ENABLED - void ExecuteJIT() override; -#endif + template + void Execute(); // all code accesses are forced nonseq 32bit u32 CodeRead32(u32 addr, bool branch); @@ -383,10 +386,8 @@ public: void JumpTo(u32 addr, bool restorecpsr = false) override; - void Execute() override; -#ifdef JIT_ENABLED - void ExecuteJIT() override; -#endif + template + void Execute(); u16 CodeRead16(u32 addr) { diff --git a/src/ARMInterpreter.cpp b/src/ARMInterpreter.cpp index 6fb86ef5..f5bf7713 100644 --- a/src/ARMInterpreter.cpp +++ b/src/ARMInterpreter.cpp @@ -38,7 +38,7 @@ void A_UNK(ARM* cpu) { Log(LogLevel::Warn, "undefined ARM%d instruction %08X @ %08X\n", cpu->Num?7:9, cpu->CurInstr, cpu->R[15]-8); #ifdef GDBSTUB_ENABLED - cpu->GdbStub.Enter(true, Gdb::TgtStatus::FaultInsn, cpu->R[15]-8); + cpu->GdbStub.Enter(cpu->GdbStub.IsConnected(), Gdb::TgtStatus::FaultInsn, cpu->R[15]-8); #endif //for (int i = 0; i < 16; i++) printf("R%d: %08X\n", i, cpu->R[i]); //NDS::Halt(); @@ -56,7 +56,7 @@ void T_UNK(ARM* cpu) { Log(LogLevel::Warn, "undefined THUMB%d instruction %04X @ %08X\n", cpu->Num?7:9, cpu->CurInstr, cpu->R[15]-4); #ifdef GDBSTUB_ENABLED - cpu->GdbStub.Enter(true, Gdb::TgtStatus::FaultInsn, cpu->R[15]-4); + cpu->GdbStub.Enter(cpu->GdbStub.IsConnected(), Gdb::TgtStatus::FaultInsn, cpu->R[15]-4); #endif //NDS::Halt(); u32 oldcpsr = cpu->CPSR; diff --git a/src/DSi_AES.h b/src/DSi_AES.h index badeb192..f3b79868 100644 --- a/src/DSi_AES.h +++ b/src/DSi_AES.h @@ -28,7 +28,7 @@ namespace melonDS { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wattributes" -#if defined(__GNUC__) && (__GNUC__ >= 11) // gcc 11.* +#if defined(__GNUC__) && (__GNUC__ >= 11) && defined(__SIZEOF_INT128__) // gcc 11.* // NOTE: Yes, the compiler does *not* recognize this code pattern, so it is indeed an optimization. __attribute((always_inline)) static void Bswap128(void* Dst, const void* Src) { diff --git a/src/DSi_I2C.h b/src/DSi_I2C.h index a05c7aed..3102ffeb 100644 --- a/src/DSi_I2C.h +++ b/src/DSi_I2C.h @@ -87,6 +87,7 @@ public: void DoSavestate(Savestate* file) override; u8 GetBootFlag() const; + void SetBootFlag(u8 boot) noexcept { Registers[0x70] = boot; } bool GetBatteryCharging() const; void SetBatteryCharging(bool charging); diff --git a/src/GBACart.cpp b/src/GBACart.cpp index 40e90436..1518b6cf 100644 --- a/src/GBACart.cpp +++ b/src/GBACart.cpp @@ -681,6 +681,44 @@ void CartRAMExpansion::ROMWrite(u32 addr, u16 val) } } +CartRumblePak::CartRumblePak(void* userdata) : + CartCommon(RumblePak), + UserData(userdata) +{ +} + +CartRumblePak::~CartRumblePak() = default; + +void CartRumblePak::Reset() +{ + RumbleState = 0; +} + +void CartRumblePak::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + file->Var16(&RumbleState); +} + +u16 CartRumblePak::ROMRead(u32 addr) const +{ + // A1 is pulled low on a real Rumble Pak, so return the + // necessary detection value here, + // and let the existing open bus implementation take care of the rest + return 0xFFFD; +} + +void CartRumblePak::ROMWrite(u32 addr, u16 val) +{ + addr &= 0x01FFFFFF; + if (RumbleState != val) + { + Platform::Addon_RumbleStop(UserData); + RumbleState = val; + Platform::Addon_RumbleStart(16, UserData); + } +} + GBACartSlot::GBACartSlot(melonDS::NDS& nds, std::unique_ptr&& cart) noexcept : NDS(nds), Cart(std::move(cart)) { } @@ -821,13 +859,16 @@ void GBACartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept } } -void GBACartSlot::LoadAddon(int type) noexcept +void GBACartSlot::LoadAddon(void* userdata, int type) noexcept { switch (type) { case GBAAddon_RAMExpansion: Cart = std::make_unique(); break; + case GBAAddon_RumblePak: + Cart = std::make_unique(userdata); + break; default: Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type); diff --git a/src/GBACart.h b/src/GBACart.h index 4eb0faad..f6fb95dd 100644 --- a/src/GBACart.h +++ b/src/GBACart.h @@ -32,6 +32,7 @@ enum CartType Game = 0x101, GameSolarSensor = 0x102, RAMExpansion = 0x201, + RumblePak = 0x202, }; // CartCommon -- base code shared by all cart types @@ -189,6 +190,25 @@ private: u16 RAMEnable = 0; }; +// CartRumblePak -- DS Rumble Pak (used in various NDS games) +class CartRumblePak : public CartCommon +{ +public: + CartRumblePak(void* userdata); + ~CartRumblePak() override; + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + u16 ROMRead(u32 addr) const override; + void ROMWrite(u32 addr, u16 val) override; + +private: + void* UserData; + u16 RumbleState = 0; +}; + // possible inputs for GBA carts that might accept user input enum { @@ -219,7 +239,7 @@ public: [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); } [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); } - void LoadAddon(int type) noexcept; + void LoadAddon(void* userdata, int type) noexcept; /// @return The cart that was in the cart slot if any, /// or \c nullptr if the cart slot was empty. diff --git a/src/GPU2D.cpp b/src/GPU2D.cpp index 4ad0853a..e76e85c1 100644 --- a/src/GPU2D.cpp +++ b/src/GPU2D.cpp @@ -387,6 +387,14 @@ void Unit::Write16(u32 addr, u16 val) if (!Num) GPU.GPU3D.SetRenderXPos(val); break; + case 0x064: + CaptureCnt = (CaptureCnt & 0xFFFF0000) | (val & 0xEF3F1F1F); + return; + + case 0x066: + CaptureCnt = (CaptureCnt & 0xFFFF) | ((val << 16) & 0xEF3F1F1F); + return; + case 0x068: DispFIFO[DispFIFOWritePtr] = val; return; diff --git a/src/GPU3D.cpp b/src/GPU3D.cpp index 8da5527a..865b00a1 100644 --- a/src/GPU3D.cpp +++ b/src/GPU3D.cpp @@ -192,7 +192,7 @@ void GPU3D::Reset() noexcept CmdStallQueue.Clear(); - ZeroDotWLimit = 0; // CHECKME + ZeroDotWLimit = 0xFFFFFF; GXStat = 0; @@ -1279,7 +1279,7 @@ void GPU3D::SubmitPolygon() noexcept { Vertex* vtx = poly->Vertices[i]; - if (vtx->FinalPosition[1] < ytop || (vtx->FinalPosition[1] == ytop && vtx->FinalPosition[0] < xtop)) + if (vtx->FinalPosition[1] < ytop) { xtop = vtx->FinalPosition[0]; ytop = vtx->FinalPosition[1]; diff --git a/src/GPU3D.h b/src/GPU3D.h index 15b31096..a12d4bc0 100644 --- a/src/GPU3D.h +++ b/src/GPU3D.h @@ -197,7 +197,7 @@ public: FIFO CmdStallQueue {}; - u32 ZeroDotWLimit = 0; + u32 ZeroDotWLimit = 0xFFFFFF; u32 GXStat = 0; diff --git a/src/GPU3D_Compute.h b/src/GPU3D_Compute.h index f3f184de..751737b7 100644 --- a/src/GPU3D_Compute.h +++ b/src/GPU3D_Compute.h @@ -59,7 +59,7 @@ public: void Blit(const GPU& gpu) override; void Stop(const GPU& gpu) override; - bool NeedsShaderCompile() { return ShaderStepIdx != 33; } + bool NeedsShaderCompile() override { return ShaderStepIdx != 33; } void ShaderCompileStep(int& current, int& count) override; private: ComputeRenderer(GLCompositor&& compositor); diff --git a/src/GPU3D_Soft.cpp b/src/GPU3D_Soft.cpp index e2526069..1221ed59 100644 --- a/src/GPU3D_Soft.cpp +++ b/src/GPU3D_Soft.cpp @@ -242,13 +242,20 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s { vramaddr += ((t & 0x3FC) * (width>>2)) + (s & 0x3FC); vramaddr += (t & 0x3); + vramaddr &= 0x7FFFF; // address used for all calcs wraps around after slot 3 u32 slot1addr = 0x20000 + ((vramaddr & 0x1FFFC) >> 1); if (vramaddr >= 0x40000) slot1addr += 0x10000; - u8 val = ReadVRAM_Texture(vramaddr, gpu); - val >>= (2 * (s & 0x3)); + u8 val; + if (vramaddr >= 0x20000 && vramaddr < 0x40000) // reading slot 1 for texels should always read 0 + val = 0; + else + { + val = ReadVRAM_Texture(vramaddr, gpu); + val >>= (2 * (s & 0x3)); + } u16 palinfo = ReadVRAM_Texture(slot1addr, gpu); u32 paloffset = (palinfo & 0x3FFF) << 2; diff --git a/src/NDS.cpp b/src/NDS.cpp index 7ef6602c..1023d3c0 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -107,6 +107,9 @@ NDS::NDS(NDSArgs&& args, int type, void* userdata) noexcept : AREngine(*this), ARM9(*this, args.GDB, args.JIT.has_value()), ARM7(*this, args.GDB, args.JIT.has_value()), +#ifdef GDBSTUB_ENABLED + EnableGDBStub(args.GDB.has_value()), +#endif #ifdef JIT_ENABLED EnableJIT(args.JIT.has_value()), #endif @@ -751,7 +754,7 @@ void NDS::SetGBASave(const u8* savedata, u32 savelen) void NDS::LoadGBAAddon(int type) { - GBACartSlot.LoadAddon(type); + GBACartSlot.LoadAddon(UserData, type); } void NDS::LoadBIOS() @@ -886,7 +889,7 @@ void NDS::RunSystemSleep(u64 timestamp) } } -template +template u32 NDS::RunFrame() { FrameStartTimestamp = SysTimestamp; @@ -927,8 +930,11 @@ u32 NDS::RunFrame() } else { - ARM9.CheckGdbIncoming(); - ARM7.CheckGdbIncoming(); + if (cpuMode == CPUExecuteMode::InterpreterGDB) + { + ARM9.CheckGdbIncoming(); + ARM7.CheckGdbIncoming(); + } if (!(CPUStop & CPUStop_Wakeup)) { @@ -963,12 +969,7 @@ u32 NDS::RunFrame() } else { -#ifdef JIT_ENABLED - if (EnableJIT) - ARM9.ExecuteJIT(); - else -#endif - ARM9.Execute(); + ARM9.Execute(); } RunTimers(0); @@ -995,12 +996,7 @@ u32 NDS::RunFrame() } else { -#ifdef JIT_ENABLED - if (EnableJIT) - ARM7.ExecuteJIT(); - else -#endif - ARM7.Execute(); + ARM7.Execute(); } RunTimers(1); @@ -1045,10 +1041,18 @@ u32 NDS::RunFrame() { #ifdef JIT_ENABLED if (EnableJIT) - return RunFrame(); + return RunFrame(); else #endif - return RunFrame(); +#ifdef GDBSTUB_ENABLED + if (EnableGDBStub) + { + return RunFrame(); + } else +#endif + { + return RunFrame(); + } } void NDS::Reschedule(u64 target) @@ -1463,7 +1467,7 @@ u64 NDS::GetSysClockCycles(int num) return ret; } -void NDS::NocashPrint(u32 ncpu, u32 addr) +void NDS::NocashPrint(u32 ncpu, u32 addr, bool appendNewline) { // addr: debug string @@ -1541,7 +1545,7 @@ void NDS::NocashPrint(u32 ncpu, u32 addr) } output[ptr] = '\0'; - Log(LogLevel::Debug, "%s\n", output); + Log(LogLevel::Debug, appendNewline ? "%s\n" : "%s", output); } void NDS::MonitorARM9Jump(u32 addr) @@ -2946,6 +2950,8 @@ u16 NDS::ARM9IORead16(u32 addr) case 0x04000208: return IME[0]; case 0x04000210: return IE[0] & 0xFFFF; case 0x04000212: return IE[0] >> 16; + case 0x04000214: return IF[0] & 0xFFFF; + case 0x04000216: return IF[0] >> 16; case 0x04000240: return GPU.VRAMCNT[0] | (GPU.VRAMCNT[1] << 8); case 0x04000242: return GPU.VRAMCNT[2] | (GPU.VRAMCNT[3] << 8); @@ -3257,6 +3263,9 @@ void NDS::ARM9IOWrite16(u32 addr, u16 val) case 0x04000060: GPU.GPU3D.Write16(addr, val); return; + case 0x04000064: + case 0x04000066: GPU.GPU2D_A.Write16(addr, val); return; + case 0x04000068: case 0x0400006A: GPU.GPU2D_A.Write16(addr, val); return; @@ -3385,6 +3394,8 @@ void NDS::ARM9IOWrite16(u32 addr, u16 val) case 0x04000210: IE[0] = (IE[0] & 0xFFFF0000) | val; UpdateIRQ(0); return; case 0x04000212: IE[0] = (IE[0] & 0x0000FFFF) | (val << 16); UpdateIRQ(0); return; // TODO: what happens when writing to IF this way?? + case 0x04000214: IF[0] &= ~val; GPU.GPU3D.CheckFIFOIRQ(); UpdateIRQ(0); return; + case 0x04000216: IF[0] &= ~(val<<16); GPU.GPU3D.CheckFIFOIRQ(); UpdateIRQ(0); return; case 0x04000240: GPU.MapVRAM_AB(0, val & 0xFF); @@ -3609,10 +3620,8 @@ void NDS::ARM9IOWrite32(u32 addr, u32 val) case 0x04FFFA14: case 0x04FFFA18: { - bool appendLF = 0x04FFFA18 == addr; - NocashPrint(0, val); - if(appendLF) - Log(LogLevel::Debug, "\n"); + NocashPrint(0, val, 0x04FFFA18 == addr); + return; } diff --git a/src/NDS.h b/src/NDS.h index c965cde0..b2bfb385 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -210,6 +210,7 @@ enum enum { GBAAddon_RAMExpansion = 1, + GBAAddon_RumblePak = 2, }; class SPU; @@ -227,6 +228,9 @@ private: #ifdef JIT_ENABLED bool EnableJIT; #endif +#ifdef GDBSTUB_ENABLED + bool EnableGDBStub = false; +#endif public: // TODO: Encapsulate the rest of these members void* UserData; @@ -422,7 +426,7 @@ public: // TODO: Encapsulate the rest of these members u32 GetPC(u32 cpu) const; u64 GetSysClockCycles(int num); - void NocashPrint(u32 cpu, u32 addr); + void NocashPrint(u32 cpu, u32 addr, bool appendNewline = true); void MonitorARM9Jump(u32 addr); @@ -521,8 +525,9 @@ private: void SetWifiWaitCnt(u16 val); void SetGBASlotTimings(); void EnterSleepMode(); - template + template u32 RunFrame(); + public: NDS(NDSArgs&& args, void* userdata = nullptr) noexcept : NDS(std::move(args), 0, userdata) {} NDS() noexcept; diff --git a/src/OpenGLSupport.cpp b/src/OpenGLSupport.cpp index f728386f..2740e157 100644 --- a/src/OpenGLSupport.cpp +++ b/src/OpenGLSupport.cpp @@ -160,7 +160,7 @@ void SaveShaderCache() Platform::FileSeek(file, 0, Platform::FileSeekOrigin::End); - printf("new shaders %d\n", NewShaders.size()); + printf("new shaders %zu\n", NewShaders.size()); for (u64 newShader : NewShaders) { diff --git a/src/Platform.h b/src/Platform.h index 0dfc04f6..90791100 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -305,6 +305,7 @@ u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata); // packet type: Ethernet (802.3) int Net_SendPacket(u8* data, int len, void* userdata); int Net_RecvPacket(u8* data, void* userdata); +using SendPacketCallback = std::function; // interface for camera emulation @@ -316,6 +317,17 @@ void Camera_Start(int num, void* userdata); void Camera_Stop(int num, void* userdata); void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv, void* userdata); +// interface for addon inputs + +// Called by the DS Rumble Pak emulation to start the necessary +// rumble effects on the connected game controller, if available. +// @param len The duration of the controller rumble effect in milliseconds. +void Addon_RumbleStart(u32 len, void* userdata); + +// Called by the DS Rumble Pak emulation to stop any necessary +// rumble effects on the connected game controller, if available. +void Addon_RumbleStop(void* userdata); + struct DynamicLibrary; /** diff --git a/src/PlatformOGL.h b/src/PlatformOGL.h index bc047807..120e8690 100644 --- a/src/PlatformOGL.h +++ b/src/PlatformOGL.h @@ -1,9 +1,16 @@ #ifndef PLATFORMOGL_H #define PLATFORMOGL_H -// if you don't wanna use glad for your platform -// add your header here! +// If you don't wanna use glad for your platform, +// define MELONDS_GL_HEADER to the path of some other header +// that pulls in the necessary OpenGL declarations. +// Make sure to include quotes or angle brackets as needed, +// and that all targets get the same MELONDS_GL_HEADER definition. -#include "frontend/glad/glad.h" +#ifndef MELONDS_GL_HEADER +#define MELONDS_GL_HEADER "\"frontend/glad/glad.h\"" +#endif + +#include MELONDS_GL_HEADER #endif diff --git a/src/debug/GdbStub.h b/src/debug/GdbStub.h index 99b88158..6461a354 100644 --- a/src/debug/GdbStub.h +++ b/src/debug/GdbStub.h @@ -152,6 +152,7 @@ public: int RespFmt(const char* fmt, ...); int RespStr(const char* str); + inline bool IsConnected() { return ConnFd > 0; } private: void Disconnect(); diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 32ffff9e..6a4634c7 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -161,12 +161,19 @@ endif() if (BUILD_STATIC) qt_import_plugins(melonDS INCLUDE Qt::QSvgPlugin) + if (WIN32 AND USE_QT6) + qt_import_plugins(melonDS INCLUDE Qt::QModernWindowsStylePlugin) + endif() target_link_options(melonDS PRIVATE -static) 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_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../../net") +target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../../net/libslirp/src") +get_target_property(SLIRP_BINARY_DIR slirp BINARY_DIR) +target_include_directories(melonDS PUBLIC "${SLIRP_BINARY_DIR}") # for libslirp-version.h if (USE_QT6) target_include_directories(melonDS PUBLIC ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) else() @@ -245,3 +252,11 @@ if (UNIX AND NOT APPLE) INTERPROCEDURAL_OPTIMIZATION_RELEASE OFF) endif() endif() + +if (ENABLE_OGLRENDERER) + set(MELONDS_GL_HEADER \"frontend/glad/glad.h\" CACHE STRING "Path to a header that contains OpenGL function and type declarations.") + + target_compile_definitions(melonDS PUBLIC OGLRENDERER_ENABLED) + target_compile_definitions(melonDS PUBLIC MELONDS_GL_HEADER=${MELONDS_GL_HEADER}) + target_compile_definitions(core PUBLIC MELONDS_GL_HEADER=${MELONDS_GL_HEADER}) +endif() diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 98749175..998a03ec 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "toml/toml.hpp" @@ -741,7 +742,7 @@ bool Load() try { - RootTable = toml::parse(cfgpath); + RootTable = toml::parse(std::filesystem::u8path(cfgpath)); } catch (toml::syntax_error& err) { @@ -758,7 +759,7 @@ void Save() return; std::ofstream file; - file.open(cfgpath, std::ofstream::out | std::ofstream::trunc); + file.open(std::filesystem::u8path(cfgpath), std::ofstream::out | std::ofstream::trunc); file << RootTable; file.close(); } diff --git a/src/frontend/qt_sdl/EmuInstance.cpp b/src/frontend/qt_sdl/EmuInstance.cpp index ef59901d..62481ac7 100644 --- a/src/frontend/qt_sdl/EmuInstance.cpp +++ b/src/frontend/qt_sdl/EmuInstance.cpp @@ -62,6 +62,8 @@ using namespace melonDS::Platform; MainWindow* topWindow = nullptr; const string kWifiSettingsPath = "wfcsettings.bin"; +extern LocalMP localMp; +extern Net net; EmuInstance::EmuInstance(int inst) : deleting(false), @@ -98,7 +100,7 @@ EmuInstance::EmuInstance(int inst) : deleting(false), audioInit(); inputInit(); - Net::RegisterInstance(instanceID); + net.RegisterInstance(instanceID); emuThread = new EmuThread(this); @@ -119,13 +121,13 @@ EmuInstance::~EmuInstance() deleting = true; deleteAllWindows(); - LocalMP::End(instanceID); + localMp.End(instanceID); emuThread->emuExit(); emuThread->wait(); delete emuThread; - Net::UnregisterInstance(instanceID); + net.UnregisterInstance(instanceID); audioDeInit(); inputDeInit(); @@ -558,7 +560,7 @@ std::string EmuInstance::getEffectiveFirmwareSavePath() { if (!globalCfg.GetBool("Emu.ExternalBIOSEnable")) { - return kWifiSettingsPath; + return GetLocalFilePath(kWifiSettingsPath); } if (consoleType == 1) { @@ -592,7 +594,7 @@ bool EmuInstance::savestateExists(int slot) bool EmuInstance::loadState(const std::string& filename) { - FILE* file = fopen(filename.c_str(), "rb"); + Platform::FileHandle* file = Platform::OpenFile(filename, Platform::FileMode::Read); if (file == nullptr) { // If we couldn't open the state file... Platform::Log(Platform::LogLevel::Error, "Failed to open state file \"%s\"\n", filename.c_str()); @@ -603,38 +605,31 @@ bool EmuInstance::loadState(const std::string& filename) if (backup->Error) { // If we couldn't allocate memory for the backup... Platform::Log(Platform::LogLevel::Error, "Failed to allocate memory for state backup\n"); - fclose(file); + Platform::CloseFile(file); return false; } if (!nds->DoSavestate(backup.get()) || backup->Error) { // Back up the emulator's state. If that failed... Platform::Log(Platform::LogLevel::Error, "Failed to back up state, aborting load (from \"%s\")\n", filename.c_str()); - fclose(file); + Platform::CloseFile(file); return false; } // We'll store the backup once we're sure that the state was loaded. // Now that we know the file and backup are both good, let's load the new state. // Get the size of the file that we opened - if (fseek(file, 0, SEEK_END) != 0) - { - Platform::Log(Platform::LogLevel::Error, "Failed to seek to end of state file \"%s\"\n", filename.c_str()); - fclose(file); - return false; - } - size_t size = ftell(file); - rewind(file); // reset the filebuf's position + size_t size = Platform::FileLength(file); // Allocate exactly as much memory as we need for the savestate std::vector buffer(size); - if (fread(buffer.data(), size, 1, file) == 0) + if (Platform::FileRead(buffer.data(), size, 1, file) == 0) { // Read the state file into the buffer. If that failed... Platform::Log(Platform::LogLevel::Error, "Failed to read %u-byte state file \"%s\"\n", size, filename.c_str()); - fclose(file); + Platform::CloseFile(file); return false; } - fclose(file); // done with the file now + Platform::CloseFile(file); // done with the file now // Get ready to load the state from the buffer into the emulator std::unique_ptr state = std::make_unique(buffer.data(), size, false); @@ -666,7 +661,7 @@ bool EmuInstance::loadState(const std::string& filename) bool EmuInstance::saveState(const std::string& filename) { - FILE* file = fopen(filename.c_str(), "wb"); + Platform::FileHandle* file = Platform::OpenFile(filename, Platform::FileMode::Write); if (file == nullptr) { // If the file couldn't be opened... @@ -676,7 +671,7 @@ bool EmuInstance::saveState(const std::string& filename) Savestate state; if (state.Error) { // If there was an error creating the state (and allocating its memory)... - fclose(file); + Platform::CloseFile(file); return false; } @@ -685,22 +680,22 @@ bool EmuInstance::saveState(const std::string& filename) if (state.Error) { - fclose(file); + Platform::CloseFile(file); return false; } - if (fwrite(state.Buffer(), state.Length(), 1, file) == 0) + if (Platform::FileWrite(state.Buffer(), state.Length(), 1, file) == 0) { // Write the Savestate buffer to the file. If that fails... Platform::Log(Platform::Error, "Failed to write %d-byte savestate to %s\n", state.Length(), filename.c_str() ); - fclose(file); + Platform::CloseFile(file); return false; } - fclose(file); + Platform::CloseFile(file); if (globalCfg.GetBool("Savestate.RelocSRAM") && ndsSave) { @@ -733,12 +728,8 @@ void EmuInstance::undoStateLoad() void EmuInstance::unloadCheats() { - if (cheatFile) - { - delete cheatFile; - cheatFile = nullptr; - nds->AREngine.SetCodeFile(nullptr); - } + cheatFile = nullptr; // cleaned up by unique_ptr + nds->AREngine.Cheats.clear(); } void EmuInstance::loadCheats() @@ -748,9 +739,16 @@ void EmuInstance::loadCheats() std::string filename = getAssetPath(false, globalCfg.GetString("CheatFilePath"), ".mch"); // TODO: check for error (malformed cheat file, ...) - cheatFile = new ARCodeFile(filename); + cheatFile = std::make_unique(filename); - nds->AREngine.SetCodeFile(cheatsOn ? cheatFile : nullptr); + if (cheatsOn) + { + nds->AREngine.Cheats = cheatFile->GetCodes(); + } + else + { + nds->AREngine.Cheats.clear(); + } } std::unique_ptr EmuInstance::loadARM9BIOS() noexcept @@ -1060,12 +1058,14 @@ void EmuInstance::enableCheats(bool enable) { cheatsOn = enable; if (cheatFile) - nds->AREngine.SetCodeFile(cheatsOn ? cheatFile : nullptr); + nds->AREngine.Cheats = cheatFile->GetCodes(); + else + nds->AREngine.Cheats.clear(); } ARCodeFile* EmuInstance::getCheatFile() { - return cheatFile; + return cheatFile.get(); } void EmuInstance::setBatteryLevels() @@ -1150,7 +1150,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB }; auto jitargs = jitopt.GetBool("Enable") ? std::make_optional(_jitargs) : std::nullopt; #else - optional jitargs = std::nullopt; + std::optional jitargs = std::nullopt; #endif #ifdef GDBSTUB_ENABLED @@ -1301,7 +1301,7 @@ void EmuInstance::reset() } else { - newsave = kWifiSettingsPath + instanceFileSuffix(); + newsave = GetLocalFilePath(kWifiSettingsPath + instanceFileSuffix()); } if (oldsave != newsave) diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h index 72adccab..39c187c2 100644 --- a/src/frontend/qt_sdl/EmuInstance.h +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -129,6 +129,8 @@ public: void inputInit(); void inputDeInit(); void inputLoadConfig(); + void inputRumbleStart(melonDS::u32 len_ms); + void inputRumbleStop(); void setJoystick(int id); int getJoystickID() { return joystickID; } @@ -258,7 +260,7 @@ private: bool savestateLoaded; std::string previousSaveFile; - melonDS::ARCodeFile* cheatFile; + std::unique_ptr cheatFile; bool cheatsOn; SDL_AudioDeviceID audioDevice; @@ -295,6 +297,9 @@ private: int joystickID; SDL_Joystick* joystick; + SDL_GameController* controller; + bool hasRumble = false; + bool isRumbling = false; melonDS::u32 keyInputMask, joyInputMask; melonDS::u32 keyHotkeyMask, joyHotkeyMask; diff --git a/src/frontend/qt_sdl/EmuInstanceInput.cpp b/src/frontend/qt_sdl/EmuInstanceInput.cpp index ccc9fe79..ddaca8f0 100644 --- a/src/frontend/qt_sdl/EmuInstanceInput.cpp +++ b/src/frontend/qt_sdl/EmuInstanceInput.cpp @@ -72,6 +72,9 @@ void EmuInstance::inputInit() lastHotkeyMask = 0; joystick = nullptr; + controller = nullptr; + hasRumble = false; + isRumbling = false; inputLoadConfig(); } @@ -100,6 +103,24 @@ void EmuInstance::inputLoadConfig() setJoystick(localCfg.GetInt("JoystickID")); } +void EmuInstance::inputRumbleStart(melonDS::u32 len_ms) +{ + if (controller && hasRumble && !isRumbling) + { + SDL_GameControllerRumble(controller, 0xFFFF, 0xFFFF, len_ms); + isRumbling = true; + } +} + +void EmuInstance::inputRumbleStop() +{ + if (controller && hasRumble && isRumbling) + { + SDL_GameControllerRumble(controller, 0, 0, 0); + isRumbling = false; + } +} + void EmuInstance::setJoystick(int id) { @@ -109,12 +130,16 @@ void EmuInstance::setJoystick(int id) void EmuInstance::openJoystick() { + if (controller) SDL_GameControllerClose(controller); + if (joystick) SDL_JoystickClose(joystick); int num = SDL_NumJoysticks(); if (num < 1) { + controller = nullptr; joystick = nullptr; + hasRumble = false; return; } @@ -122,10 +147,30 @@ void EmuInstance::openJoystick() joystickID = 0; joystick = SDL_JoystickOpen(joystickID); + + if (SDL_IsGameController(joystickID)) + { + controller = SDL_GameControllerOpen(joystickID); + } + + if (controller) + { + if (SDL_GameControllerHasRumble(controller)) + { + hasRumble = true; + } + } } void EmuInstance::closeJoystick() { + if (controller) + { + SDL_GameControllerClose(controller); + controller = nullptr; + hasRumble = false; + } + if (joystick) { SDL_JoystickClose(joystick); diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp index a2510c2d..5d5ecd01 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp @@ -72,8 +72,6 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent ui->txtMAC->setText(firmcfg.GetQString("MAC")); - on_overrideFirmwareBox_toggled(); - int inst = emuInstance->getInstanceID(); if (inst > 0) ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); @@ -225,10 +223,3 @@ void FirmwareSettingsDialog::on_cbxBirthdayMonth_currentIndexChanged(int idx) } } } - -void FirmwareSettingsDialog::on_overrideFirmwareBox_toggled() -{ - 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 d80d370b..2053ab99 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.h +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.h @@ -125,7 +125,6 @@ private slots: void done(int r); void on_cbxBirthdayMonth_currentIndexChanged(int idx); - void on_overrideFirmwareBox_toggled(); private: bool verifyMAC(); diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 605d80f9..217f1b45 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -47,7 +47,8 @@ #endif // __WIN32__ extern CameraManager* camManager[2]; - +extern melonDS::LocalMP localMp; +extern melonDS::Net net; namespace melonDS::Platform { @@ -457,69 +458,69 @@ void WriteDateTime(int year, int month, int day, int hour, int minute, int secon void MP_Begin(void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - LocalMP::Begin(inst); + localMp.Begin(inst); } void MP_End(void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - LocalMP::End(inst); + localMp.End(inst); } int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - return LocalMP::SendPacket(inst, data, len, timestamp); + return localMp.SendPacket(inst, data, len, timestamp); } int MP_RecvPacket(u8* data, u64* timestamp, void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - return LocalMP::RecvPacket(inst, data, timestamp); + return localMp.RecvPacket(inst, data, timestamp); } int MP_SendCmd(u8* data, int len, u64 timestamp, void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - return LocalMP::SendCmd(inst, data, len, timestamp); + return localMp.SendCmd(inst, data, len, timestamp); } int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid, void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - return LocalMP::SendReply(inst, data, len, timestamp, aid); + return localMp.SendReply(inst, data, len, timestamp, aid); } int MP_SendAck(u8* data, int len, u64 timestamp, void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - return LocalMP::SendAck(inst, data, len, timestamp); + return localMp.SendAck(inst, data, len, timestamp); } int MP_RecvHostPacket(u8* data, u64* timestamp, void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - return LocalMP::RecvHostPacket(inst, data, timestamp); + return localMp.RecvHostPacket(inst, data, timestamp); } u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - return LocalMP::RecvReplies(inst, data, timestamp, aidmask); + return localMp.RecvReplies(inst, data, timestamp, aidmask); } int Net_SendPacket(u8* data, int len, void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - Net::SendPacket(data, len, inst); + net.SendPacket(data, len, inst); return 0; } int Net_RecvPacket(u8* data, void* userdata) { int inst = ((EmuInstance*)userdata)->getInstanceID(); - return Net::RecvPacket(data, inst); + return net.RecvPacket(data, inst); } @@ -538,6 +539,16 @@ void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv, v return camManager[num]->captureFrame(frame, width, height, yuv); } +void Addon_RumbleStart(u32 len, void* userdata) +{ + ((EmuInstance*)userdata)->inputRumbleStart(len); +} + +void Addon_RumbleStop(void* userdata) +{ + ((EmuInstance*)userdata)->inputRumbleStop(); +} + DynamicLibrary* DynamicLibrary_Load(const char* lib) { return (DynamicLibrary*) SDL_LoadObject(lib); diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp index 29e0663a..425d99b5 100644 --- a/src/frontend/qt_sdl/Screen.cpp +++ b/src/frontend/qt_sdl/Screen.cpp @@ -276,8 +276,13 @@ void ScreenPanel::tabletEvent(QTabletEvent* event) case QEvent::TabletPress: case QEvent::TabletMove: { +#if QT_VERSION_MAJOR == 6 + int x = event->position().x(); + int y = event->position().y(); +#else int x = event->x(); int y = event->y(); +#endif if (layout.GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) { @@ -302,8 +307,10 @@ void ScreenPanel::tabletEvent(QTabletEvent* event) void ScreenPanel::touchEvent(QTouchEvent* event) { +#if QT_VERSION_MAJOR == 6 if (event->device()->type() == QInputDevice::DeviceType::TouchPad) return; +#endif event->accept(); @@ -311,9 +318,15 @@ void ScreenPanel::touchEvent(QTouchEvent* event) { case QEvent::TouchBegin: case QEvent::TouchUpdate: +#if QT_VERSION_MAJOR == 6 if (event->points().length() > 0) { QPointF lastPosition = event->points().first().lastPosition(); +#else + if (event->touchPoints().length() > 0) + { + QPointF lastPosition = event->touchPoints().first().lastPos(); +#endif int x = (int)lastPosition.x(); int y = (int)lastPosition.y(); @@ -1096,7 +1109,7 @@ std::optional ScreenPanelGL::getWindowInfo() else { //qCritical() << "Unknown PNI platform " << platform_name; - Platform::Log(LogLevel::Error, "Unknown PNI platform %s\n", platform_name.toStdString().c_str()); + Platform::Log(Platform::LogLevel::Error, "Unknown PNI platform %s\n", platform_name.toStdString().c_str()); return std::nullopt; } #endif diff --git a/src/frontend/qt_sdl/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp index 908ae70f..30f29d24 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.cpp +++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp @@ -303,7 +303,7 @@ void TitleManagerDialog::onImportTitleData() if (file.isEmpty()) return; - FILE* f = fopen(file.toStdString().c_str(), "rb"); + Platform::FileHandle* f = Platform::OpenFile(file.toStdString(), Platform::Read); if (!f) { QMessageBox::critical(this, @@ -312,9 +312,8 @@ void TitleManagerDialog::onImportTitleData() return; } - fseek(f, 0, SEEK_END); - u64 len = ftell(f); - fclose(f); + u64 len = Platform::FileLength(f); + Platform::CloseFile(f); if (len != wantedsize) { diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp index 183fd9e7..c3f988b1 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp +++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp @@ -37,11 +37,14 @@ #define PCAP_NAME "libpcap" #endif +extern std::optional pcap; +extern melonDS::Net net; WifiSettingsDialog* WifiSettingsDialog::currentDlg = nullptr; bool WifiSettingsDialog::needsReset = false; +void NetInit(); WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::WifiSettingsDialog) { @@ -51,8 +54,12 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne emuInstance = ((MainWindow*)parent)->getEmuInstance(); auto& cfg = emuInstance->getGlobalConfig(); - Net::DeInit(); - haspcap = Net_PCap::InitAdapterList(); + if (!pcap) + pcap = melonDS::LibPCap::New(); + + haspcap = pcap.has_value(); + if (pcap) + adapters = pcap->GetAdapters(); ui->rbDirectMode->setText("Direct mode (requires " PCAP_NAME " and ethernet connection)"); @@ -60,13 +67,13 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->lblAdapterIP->setText("(none)"); int sel = 0; - for (int i = 0; i < Net_PCap::NumAdapters; i++) + for (int i = 0; i < adapters.size(); i++) { - Net_PCap::AdapterData* adapter = &Net_PCap::Adapters[i]; + melonDS::AdapterData& adapter = adapters[i]; - ui->cbxDirectAdapter->addItem(QString(adapter->FriendlyName)); + ui->cbxDirectAdapter->addItem(QString(adapter.FriendlyName)); - if (!strncmp(adapter->DeviceName, cfg.GetString("LAN.Device").c_str(), 128)) + if (!strncmp(adapter.DeviceName, cfg.GetString("LAN.Device").c_str(), 128)) sel = i; } ui->cbxDirectAdapter->setCurrentIndex(sel); @@ -96,24 +103,23 @@ void WifiSettingsDialog::done(int r) cfg.SetBool("LAN.DirectMode", ui->rbDirectMode->isChecked()); int sel = ui->cbxDirectAdapter->currentIndex(); - if (sel < 0 || sel >= Net_PCap::NumAdapters) sel = 0; - if (Net_PCap::NumAdapters < 1) + if (sel < 0 || sel >= adapters.size()) sel = 0; + if (adapters.empty()) { cfg.SetString("LAN.Device", ""); } else { - cfg.SetString("LAN.Device", Net_PCap::Adapters[sel].DeviceName); + cfg.SetString("LAN.Device", adapters[sel].DeviceName); } Config::Save(); } - Net_PCap::DeInit(); Config::Table cfg = Config::GetGlobalTable(); - bool direct = cfg.GetBool("LAN.DirectMode"); std::string devicename = cfg.GetString("LAN.Device"); - Net::Init(direct, devicename.c_str()); + + NetInit(); QDialog::done(r); @@ -134,10 +140,9 @@ void WifiSettingsDialog::on_cbxDirectAdapter_currentIndexChanged(int sel) { if (!haspcap) return; - if (sel < 0 || sel >= Net_PCap::NumAdapters) return; - if (Net_PCap::NumAdapters < 1) return; + if (sel < 0 || sel >= adapters.size() || adapters.empty()) return; - Net_PCap::AdapterData* adapter = &Net_PCap::Adapters[sel]; + melonDS::AdapterData* adapter = &adapters[sel]; char tmp[64]; sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.h b/src/frontend/qt_sdl/WifiSettingsDialog.h index 8f2e4a1d..d78b0f65 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.h +++ b/src/frontend/qt_sdl/WifiSettingsDialog.h @@ -20,6 +20,8 @@ #define WIFISETTINGSDIALOG_H #include +#include +#include "Net_PCap.h" namespace Ui { class WifiSettingsDialog; } class WifiSettingsDialog; @@ -68,6 +70,7 @@ private: bool haspcap; void updateAdapterControls(); + std::vector adapters; }; #endif // WIFISETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index b43ef0ad..e2a6fa03 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -88,6 +88,7 @@ using namespace melonDS; extern CameraManager* camManager[2]; extern bool camStarted[2]; +extern LocalMP localMp; QString NdsRomMimeType = "application/x-nintendo-ds-rom"; @@ -313,6 +314,10 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : actInsertGBAAddon[0] = submenu->addAction("Memory expansion"); actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion)); connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); + + actInsertGBAAddon[1] = submenu->addAction("Rumble Pak"); + actInsertGBAAddon[1]->setData(QVariant(GBAAddon_RumblePak)); + connect(actInsertGBAAddon[1], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); } actEjectGBACart = menu->addAction("Eject cart"); @@ -1881,7 +1886,7 @@ void MainWindow::onMPSettingsFinished(int res) { emuInstance->mpAudioMode = globalCfg.GetInt("MP.AudioMode"); emuInstance->audioMute(); - LocalMP::SetRecvTimeout(globalCfg.GetInt("MP.RecvTimeout")); + localMp.SetRecvTimeout(globalCfg.GetInt("MP.RecvTimeout")); emuThread->emuUnpause(); } diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h index 5286a23f..67a16ae5 100644 --- a/src/frontend/qt_sdl/Window.h +++ b/src/frontend/qt_sdl/Window.h @@ -265,7 +265,7 @@ public: QAction* actEjectCart; QAction* actCurrentGBACart; QAction* actInsertGBACart; - QAction* actInsertGBAAddon[1]; + QAction* actInsertGBAAddon[2]; QAction* actEjectGBACart; QAction* actImportSavefile; QAction* actSaveState[9]; diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 64aa664d..17959c5e 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -79,6 +79,9 @@ #include "CLI.h" +#include "Net_PCap.h" +#include "Net_Slirp.h" + using namespace melonDS; QString* systemThemeName; @@ -92,6 +95,38 @@ EmuInstance* emuInstances[kMaxEmuInstances]; CameraManager* camManager[2]; bool camStarted[2]; +LocalMP localMp; +std::optional pcap; +Net net; + +void NetInit() +{ + Config::Table cfg = Config::GetGlobalTable(); + if (cfg.GetBool("LAN.DirectMode")) + { + if (!pcap) + pcap = LibPCap::New(); + + if (pcap) + { + std::string devicename = cfg.GetString("LAN.Device"); + std::unique_ptr netPcap = pcap->Open(devicename, [](const u8* data, int len) { + net.RXEnqueue(data, len); + }); + + if (netPcap) + { + net.SetDriver(std::move(netPcap)); + } + } + } + else + { + net.SetDriver(std::make_unique([](const u8* data, int len) { + net.RXEnqueue(data, len); + })); + } +} bool createEmuInstance() @@ -286,13 +321,8 @@ int main(int argc, char** argv) } } - LocalMP::Init(); - { - Config::Table cfg = Config::GetGlobalTable(); - bool direct = cfg.GetBool("LAN.DirectMode"); - std::string devicename = cfg.GetString("LAN.Device"); - Net::Init(direct, devicename.c_str()); - } + // localMp is initialized at this point + NetInit(); createEmuInstance(); @@ -329,9 +359,6 @@ int main(int argc, char** argv) // but with this we make extra sure they are all deleted deleteAllEmuInstances(); - LocalMP::DeInit(); - Net::DeInit(); - delete camManager[0]; delete camManager[1]; @@ -347,6 +374,12 @@ int main(int argc, char** argv) int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdshow) { + if (AttachConsole(ATTACH_PARENT_PROCESS) && GetStdHandle(STD_OUTPUT_HANDLE)) + { + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + } + int ret = main(__argc, __argv); printf("\n\n>"); diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt index f1a9eaf7..082c00fb 100644 --- a/src/net/CMakeLists.txt +++ b/src/net/CMakeLists.txt @@ -16,5 +16,6 @@ if (USE_SYSTEM_LIBSLIRP) target_link_libraries(net-utils PRIVATE PkgConfig::Slirp) else() add_subdirectory(libslirp EXCLUDE_FROM_ALL) + target_include_directories(net-utils SYSTEM PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/libslirp/glib") target_link_libraries(net-utils PRIVATE slirp) endif() diff --git a/src/net/LocalMP.cpp b/src/net/LocalMP.cpp index 89f98c8c..0f6889ac 100644 --- a/src/net/LocalMP.cpp +++ b/src/net/LocalMP.cpp @@ -28,99 +28,31 @@ using namespace melonDS::Platform; using Platform::Log; using Platform::LogLevel; -namespace LocalMP +namespace melonDS { -struct MPStatusData +LocalMP::LocalMP() noexcept : + MPQueueLock(Mutex_Create()) { - u16 ConnectedBitmask; // bitmask of which instances are ready to send/receive packets - u32 PacketWriteOffset; - u32 ReplyWriteOffset; - u16 MPHostinst; // 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; -}; - -const u32 kPacketQueueSize = 0x10000; -const u32 kReplyQueueSize = 0x10000; -const u32 kMaxFrameSize = 0x948; - -Mutex* MPQueueLock; -MPStatusData MPStatus; -u8 MPPacketQueue[kPacketQueueSize]; -u8 MPReplyQueue[kReplyQueueSize]; -u32 PacketReadOffset[16]; -u32 ReplyReadOffset[16]; - -int RecvTimeout; - -int LastHostID; - - -Semaphore* SemPool[32]; - -void SemPoolInit() -{ - for (int i = 0; i < 32; i++) - { - SemPool[i] = Semaphore_Create(); - } -} - -bool SemPost(int num) -{ - Semaphore_Post(SemPool[num]); - return true; -} - -bool SemWait(int num, int timeout) -{ - return Semaphore_TryWait(SemPool[num], timeout); -} - -void SemReset(int num) -{ - Semaphore_Reset(SemPool[num]); -} - - -bool Init() -{ - MPQueueLock = Mutex_Create(); - Mutex_Lock(MPQueueLock); - memset(MPPacketQueue, 0, kPacketQueueSize); memset(MPReplyQueue, 0, kReplyQueueSize); memset(&MPStatus, 0, sizeof(MPStatus)); memset(PacketReadOffset, 0, sizeof(PacketReadOffset)); memset(ReplyReadOffset, 0, sizeof(ReplyReadOffset)); - Mutex_Unlock(MPQueueLock); - // 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(); - - LastHostID = -1; + for (int i = 0; i < 32; i++) + { + SemPool[i] = Semaphore_Create(); + } Log(LogLevel::Info, "MP comm init OK\n"); - - RecvTimeout = 25; - - return true; } -void DeInit() +LocalMP::~LocalMP() noexcept { for (int i = 0; i < 32; i++) { @@ -131,30 +63,25 @@ void DeInit() Mutex_Free(MPQueueLock); } -void SetRecvTimeout(int timeout) -{ - RecvTimeout = timeout; -} - -void Begin(int inst) +void LocalMP::Begin(int inst) { Mutex_Lock(MPQueueLock); PacketReadOffset[inst] = MPStatus.PacketWriteOffset; ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset; - SemReset(inst); - SemReset(16+inst); + Semaphore_Reset(SemPool[inst]); + Semaphore_Reset(SemPool[16 + inst]); MPStatus.ConnectedBitmask |= (1 << inst); Mutex_Unlock(MPQueueLock); } -void End(int inst) +void LocalMP::End(int inst) { Mutex_Lock(MPQueueLock); MPStatus.ConnectedBitmask &= ~(1 << inst); Mutex_Unlock(MPQueueLock); } -void FIFORead(int inst, int fifo, void* buf, int len) +void LocalMP::FIFORead(int inst, int fifo, void* buf, int len) noexcept { u8* data; @@ -189,7 +116,7 @@ void FIFORead(int inst, int fifo, void* buf, int len) else ReplyReadOffset[inst] = offset; } -void FIFOWrite(int inst, int fifo, void* buf, int len) +void LocalMP::FIFOWrite(int inst, int fifo, void* buf, int len) noexcept { u8* data; @@ -224,7 +151,7 @@ void FIFOWrite(int inst, int fifo, void* buf, int len) else MPStatus.ReplyWriteOffset = offset; } -int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp) +int LocalMP::SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp) noexcept { if (len > kMaxFrameSize) { @@ -258,7 +185,7 @@ int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp) MPStatus.MPHostinst = inst; MPStatus.MPReplyBitmask = 0; ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset; - SemReset(16 + inst); + Semaphore_Reset(SemPool[16 + inst]); } else if (type == 2) { @@ -269,39 +196,39 @@ int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp) if (type == 2) { - SemPost(16 + MPStatus.MPHostinst); + Semaphore_Post(SemPool[16 + MPStatus.MPHostinst]); } else { for (int i = 0; i < 16; i++) { if (mask & (1< #include #include "Net.h" -#include "Net_PCap.h" -#include "Net_Slirp.h" #include "PacketDispatcher.h" #include "Platform.h" -using namespace melonDS; - -namespace Net +namespace melonDS { using Platform::Log; using Platform::LogLevel; -bool Inited = false; -bool DirectMode; - -PacketDispatcher Dispatcher; - - -bool Init(bool direct, const char* devicename) -{ - if (Inited) DeInit(); - - Dispatcher.clear(); - - DirectMode = direct; - - bool ret = false; - if (DirectMode) - ret = Net_PCap::Init(devicename); - else - ret = Net_Slirp::Init(); - - Inited = ret; - return ret; -} - -void DeInit() -{ - if (!Inited) return; - - if (DirectMode) - Net_PCap::DeInit(); - else - Net_Slirp::DeInit(); - - Inited = false; -} - - -void RegisterInstance(int inst) +void Net::RegisterInstance(int inst) { Dispatcher.registerInstance(inst); } -void UnregisterInstance(int inst) +void Net::UnregisterInstance(int inst) { Dispatcher.unregisterInstance(inst); } -void RXEnqueue(const void* buf, int len) +void Net::RXEnqueue(const void* buf, int len) { Dispatcher.sendPacket(nullptr, 0, buf, len, 16, 0xFFFF); } -int SendPacket(u8* data, int len, int inst) +int Net::SendPacket(u8* data, int len, int inst) { - if (DirectMode) - return Net_PCap::SendPacket(data, len); - else - return Net_Slirp::SendPacket(data, len); + if (!Driver) + return 0; + + return Driver->SendPacket(data, len); } -int RecvPacket(u8* data, int inst) +int Net::RecvPacket(u8* data, int inst) { - if (DirectMode) - Net_PCap::RecvCheck(); - else - Net_Slirp::RecvCheck(); + if (!Driver) + return 0; + + Driver->RecvCheck(); int ret = 0; if (!Dispatcher.recvPacket(nullptr, nullptr, data, &ret, inst)) diff --git a/src/net/Net.h b/src/net/Net.h index 31418831..4229468c 100644 --- a/src/net/Net.h +++ b/src/net/Net.h @@ -19,26 +19,42 @@ #ifndef NET_H #define NET_H +#include + #include "types.h" +#include "PacketDispatcher.h" +#include "NetDriver.h" -namespace Net +namespace melonDS { -using namespace melonDS; -/// -/// @param direct Whether to use direct or indirect mode -/// @param devicename The name of the network device to use; ignored if direct is false -/// @return true if initialization succeeded -bool Init(bool direct, const char* devicename); -void DeInit(); +class Net +{ +public: + Net() noexcept = default; + Net(const Net&) = delete; + Net& operator=(const Net&) = delete; + // Not movable because of callbacks that point to this object + Net(Net&& other) = delete; + Net& operator=(Net&& other) = delete; + ~Net() noexcept = default; -void RegisterInstance(int inst); -void UnregisterInstance(int inst); + void RegisterInstance(int inst); + void UnregisterInstance(int inst); -void RXEnqueue(const void* buf, int len); + void RXEnqueue(const void* buf, int len); -int SendPacket(u8* data, int len, int inst); -int RecvPacket(u8* data, int inst); + int SendPacket(u8* data, int len, int inst); + int RecvPacket(u8* data, int inst); + + void SetDriver(std::unique_ptr&& driver) noexcept { Driver = std::move(driver); } + [[nodiscard]] std::unique_ptr& GetDriver() noexcept { return Driver; } + [[nodiscard]] const std::unique_ptr& GetDriver() const noexcept { return Driver; } + +private: + PacketDispatcher Dispatcher {}; + std::unique_ptr Driver = nullptr; +}; } diff --git a/src/net/NetDriver.h b/src/net/NetDriver.h new file mode 100644 index 00000000..2575fdea --- /dev/null +++ b/src/net/NetDriver.h @@ -0,0 +1,35 @@ +/* + Copyright 2016-2024 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 MELONDS_NETDRIVER_H +#define MELONDS_NETDRIVER_H + +#include "types.h" + +namespace melonDS +{ +class NetDriver +{ +public: + virtual ~NetDriver() = default; + virtual int SendPacket(u8* data, int len) noexcept = 0; + virtual void RecvCheck() noexcept = 0; +}; +} + +#endif //MELONDS_NETDRIVER_H diff --git a/src/net/Net_PCap.cpp b/src/net/Net_PCap.cpp index f6ce59e6..e92ad06d 100644 --- a/src/net/Net_PCap.cpp +++ b/src/net/Net_PCap.cpp @@ -45,23 +45,7 @@ using Platform::LogLevel; #define PCAP_OPENFLAG_PROMISCUOUS 1 #endif - -#define DECL_PCAP_FUNC(ret, name, args, args2) \ - typedef ret (*type_##name) args; \ - type_##name ptr_##name = nullptr; \ - ret name args { return ptr_##name args2; } - -DECL_PCAP_FUNC(int, pcap_findalldevs, (pcap_if_t** alldevs, char* errbuf), (alldevs,errbuf)) -DECL_PCAP_FUNC(void, pcap_freealldevs, (pcap_if_t* alldevs), (alldevs)) -DECL_PCAP_FUNC(pcap_t*, pcap_open_live, (const char* src, int snaplen, int flags, int readtimeout, char* errbuf), (src,snaplen,flags,readtimeout,errbuf)) -DECL_PCAP_FUNC(void, pcap_close, (pcap_t* dev), (dev)) -DECL_PCAP_FUNC(int, pcap_setnonblock, (pcap_t* dev, int nonblock, char* errbuf), (dev,nonblock,errbuf)) -DECL_PCAP_FUNC(int, pcap_sendpacket, (pcap_t* dev, const u_char* data, int len), (dev,data,len)) -DECL_PCAP_FUNC(int, pcap_dispatch, (pcap_t* dev, int num, pcap_handler callback, u_char* data), (dev,num,callback,data)) -DECL_PCAP_FUNC(const u_char*, pcap_next, (pcap_t* dev, struct pcap_pkthdr* hdr), (dev,hdr)) - - -namespace Net_PCap +namespace melonDS { const char* PCapLibNames[] = @@ -80,96 +64,152 @@ const char* PCapLibNames[] = nullptr }; -AdapterData* Adapters = nullptr; -int NumAdapters = 0; - -Platform::DynamicLibrary* PCapLib = nullptr; -pcap_t* PCapAdapter = nullptr; -AdapterData* PCapAdapterData; - - -#define LOAD_PCAP_FUNC(sym) \ - ptr_##sym = (type_##sym)DynamicLibrary_LoadFunction(lib, #sym); \ - if (!ptr_##sym) return false; - -bool TryLoadPCap(Platform::DynamicLibrary *lib) +std::optional LibPCap::New() noexcept { - LOAD_PCAP_FUNC(pcap_findalldevs) - LOAD_PCAP_FUNC(pcap_freealldevs) - LOAD_PCAP_FUNC(pcap_open_live) - LOAD_PCAP_FUNC(pcap_close) - LOAD_PCAP_FUNC(pcap_setnonblock) - LOAD_PCAP_FUNC(pcap_sendpacket) - LOAD_PCAP_FUNC(pcap_dispatch) - LOAD_PCAP_FUNC(pcap_next) + for (int i = 0; PCapLibNames[i]; i++) + { + Platform::DynamicLibrary* lib = Platform::DynamicLibrary_Load(PCapLibNames[i]); + if (!lib) continue; + + LibPCap pcap; + // Use a custom deleter to clean up the DLL automatically + // (in this case, the deleter is the DynamicLibrary_Unload function) + pcap.PCapLib = std::shared_ptr(lib, Platform::DynamicLibrary_Unload); + + if (!TryLoadPCap(pcap, lib)) + { + Platform::DynamicLibrary_Unload(lib); + continue; + } + + Log(LogLevel::Info, "PCap: lib %s, init successful\n", PCapLibNames[i]); + return pcap; + } + + Log(LogLevel::Error, "PCap: init failed\n"); + return std::nullopt; +} + +LibPCap::LibPCap(LibPCap&& other) noexcept +{ + PCapLib = std::move(other.PCapLib); + findalldevs = other.findalldevs; + freealldevs = other.freealldevs; + open_live = other.open_live; + close = other.close; + setnonblock = other.setnonblock; + sendpacket = other.sendpacket; + dispatch = other.dispatch; + next = other.next; + + other.PCapLib = nullptr; + other.findalldevs = nullptr; + other.freealldevs = nullptr; + other.open_live = nullptr; + other.close = nullptr; + other.setnonblock = nullptr; + other.sendpacket = nullptr; + other.dispatch = nullptr; + other.next = nullptr; +} + +LibPCap& LibPCap::operator=(LibPCap&& other) noexcept +{ + if (this != &other) + { + PCapLib = nullptr; + // Unloads the DLL due to the custom deleter + + PCapLib = std::move(other.PCapLib); + findalldevs = other.findalldevs; + freealldevs = other.freealldevs; + open_live = other.open_live; + close = other.close; + setnonblock = other.setnonblock; + sendpacket = other.sendpacket; + dispatch = other.dispatch; + next = other.next; + + other.PCapLib = nullptr; + other.findalldevs = nullptr; + other.freealldevs = nullptr; + other.open_live = nullptr; + other.close = nullptr; + other.setnonblock = nullptr; + other.sendpacket = nullptr; + other.dispatch = nullptr; + other.next = nullptr; + } + + return *this; +} + +bool LibPCap::TryLoadPCap(LibPCap& pcap, Platform::DynamicLibrary *lib) noexcept +{ + pcap.findalldevs = (pcap_findalldevs_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_findalldevs"); + if (!pcap.findalldevs) return false; + + pcap.freealldevs = (pcap_freealldevs_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_freealldevs"); + if (!pcap.freealldevs) return false; + + pcap.open_live = (pcap_open_live_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_open_live"); + if (!pcap.open_live) return false; + + pcap.close = (pcap_close_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_close"); + if (!pcap.close) return false; + + pcap.setnonblock = (pcap_setnonblock_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_setnonblock"); + if (!pcap.setnonblock) return false; + + pcap.sendpacket = (pcap_sendpacket_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_sendpacket"); + if (!pcap.sendpacket) return false; + + pcap.dispatch = (pcap_dispatch_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_dispatch"); + if (!pcap.dispatch) return false; + + pcap.next = (pcap_next_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_next"); + if (!pcap.next) return false; return true; } -bool InitAdapterList() +std::vector LibPCap::GetAdapters() const noexcept { - NumAdapters = 0; - // TODO: how to deal with cases where an adapter is unplugged or changes config?? - if (!PCapLib) + if (!IsValid()) { - PCapLib = nullptr; - PCapAdapter = nullptr; - - for (int i = 0; PCapLibNames[i]; i++) - { - Platform::DynamicLibrary* lib = Platform::DynamicLibrary_Load(PCapLibNames[i]); - if (!lib) continue; - - if (!TryLoadPCap(lib)) - { - Platform::DynamicLibrary_Unload(lib); - continue; - } - - Log(LogLevel::Info, "PCap: lib %s, init successful\n", PCapLibNames[i]); - PCapLib = lib; - break; - } - - if (PCapLib == nullptr) - { - Log(LogLevel::Error, "PCap: init failed\n"); - return false; - } + Log(LogLevel::Error, "PCap: instance not initialized\n"); + return {}; } char errbuf[PCAP_ERRBUF_SIZE]; - int ret; - pcap_if_t* alldevs; - ret = pcap_findalldevs(&alldevs, errbuf); - if (ret < 0 || alldevs == nullptr) - { - Log(LogLevel::Warn, "PCap: no devices available\n"); - return false; + pcap_if_t* alldevs = nullptr; + if (int ret = findalldevs(&alldevs, errbuf); ret < 0) + { // If there was an error... + errbuf[PCAP_ERRBUF_SIZE - 1] = '\0'; + Log(LogLevel::Error, "PCap: Error %d finding devices: %s\n", ret, errbuf); } - pcap_if_t* dev = alldevs; - while (dev) { NumAdapters++; dev = dev->next; } + if (alldevs == nullptr) + { // If no devices were found... + Log(LogLevel::Warn, "PCap: no devices available\n"); + return {}; + } - Adapters = new AdapterData[NumAdapters]; - memset(Adapters, 0, sizeof(AdapterData)*NumAdapters); - - AdapterData* adata = &Adapters[0]; - dev = alldevs; - while (dev) + std::vector adapters; + for (pcap_if_t* dev = alldevs; dev != nullptr; dev = dev->next) { - strncpy(adata->DeviceName, dev->name, 127); - adata->DeviceName[127] = '\0'; + adapters.emplace_back(); // Add a new (empty) adapter to the list + AdapterData& adata = adapters.back(); + strncpy(adata.DeviceName, dev->name, 127); + adata.DeviceName[127] = '\0'; + adata.Flags = dev->flags; #ifndef __WIN32__ - strncpy(adata->FriendlyName, adata->DeviceName, 127); - adata->FriendlyName[127] = '\0'; + strncpy(adata.FriendlyName, adata.DeviceName, 127); + adata.FriendlyName[127] = '\0'; #endif // __WIN32__ - - dev = dev->next; - adata++; } #ifdef __WIN32__ @@ -186,33 +226,33 @@ bool InitAdapterList() if (uret != ERROR_SUCCESS) { Log(LogLevel::Error, "GetAdaptersAddresses() shat itself: %08X\n", uret); - return false; + freealldevs(alldevs); + return {}; } - for (int i = 0; i < NumAdapters; i++) + for (AdapterData& adata : adapters) { - adata = &Adapters[i]; IP_ADAPTER_ADDRESSES* addr = buf; while (addr) { - if (strcmp(addr->AdapterName, &adata->DeviceName[12])) + if (strcmp(addr->AdapterName, &adata.DeviceName[12])) { addr = addr->Next; continue; } - WideCharToMultiByte(CP_UTF8, 0, addr->FriendlyName, 127, adata->FriendlyName, 127, nullptr, nullptr); - adata->FriendlyName[127] = '\0'; + WideCharToMultiByte(CP_UTF8, 0, addr->FriendlyName, 127, adata.FriendlyName, 127, nullptr, nullptr); + adata.FriendlyName[127] = '\0'; - WideCharToMultiByte(CP_UTF8, 0, addr->Description, 127, adata->Description, 127, nullptr, nullptr); - adata->Description[127] = '\0'; + WideCharToMultiByte(CP_UTF8, 0, addr->Description, 127, adata.Description, 127, nullptr, nullptr); + adata.Description[127] = '\0'; if (addr->PhysicalAddressLength != 6) { Log(LogLevel::Warn, "weird MAC addr length %d for %s\n", addr->PhysicalAddressLength, addr->AdapterName); } else - memcpy(adata->MAC, addr->PhysicalAddress, 6); + memcpy(adata.MAC, addr->PhysicalAddress, 6); IP_ADAPTER_UNICAST_ADDRESS* ipaddr = addr->FirstUnicastAddress; while (ipaddr) @@ -221,7 +261,7 @@ bool InitAdapterList() if (sa->sa_family == AF_INET) { struct in_addr sa4 = ((sockaddr_in*)sa)->sin_addr; - memcpy(adata->IP_v4, &sa4, 4); + memcpy(adata.IP_v4, &sa4, 4); } ipaddr = ipaddr->Next; @@ -239,16 +279,15 @@ bool InitAdapterList() if (getifaddrs(&addrs) != 0) { Log(LogLevel::Error, "getifaddrs() shat itself :(\n"); - return false; + return {}; } - for (int i = 0; i < NumAdapters; i++) + for (const AdapterData& adata : adapters) { - adata = &Adapters[i]; struct ifaddrs* curaddr = addrs; while (curaddr) { - if (strcmp(curaddr->ifa_name, adata->DeviceName)) + if (strcmp(curaddr->ifa_name, adata.DeviceName)) { curaddr = curaddr->ifa_next; continue; @@ -265,7 +304,7 @@ bool InitAdapterList() if (af == AF_INET) { struct sockaddr_in* sa = (sockaddr_in*)curaddr->ifa_addr; - memcpy(adata->IP_v4, &sa->sin_addr, 4); + memcpy((void*)adata.IP_v4, &sa->sin_addr, 4); } #ifdef __linux__ else if (af == AF_PACKET) @@ -274,7 +313,7 @@ bool InitAdapterList() if (sa->sll_halen != 6) Log(LogLevel::Warn, "weird MAC length %d for %s\n", sa->sll_halen, curaddr->ifa_name); else - memcpy(adata->MAC, sa->sll_addr, 6); + memcpy((void*)adata.MAC, sa->sll_addr, 6); } #else else if (af == AF_LINK) @@ -283,7 +322,7 @@ bool InitAdapterList() if (sa->sdl_alen != 6) Log(LogLevel::Warn, "weird MAC length %d for %s\n", sa->sdl_alen, curaddr->ifa_name); else - memcpy(adata->MAC, LLADDR(sa), 6); + memcpy((void*)adata.MAC, LLADDR(sa), 6); } #endif curaddr = curaddr->ifa_next; @@ -294,67 +333,110 @@ bool InitAdapterList() #endif // __WIN32__ - pcap_freealldevs(alldevs); - return true; + freealldevs(alldevs); + return adapters; } -bool Init(std::string_view devicename) +std::unique_ptr LibPCap::Open(const AdapterData& device, const Platform::SendPacketCallback& handler) const noexcept { - if (!PCapLib) PCapAdapter = nullptr; - if (PCapAdapter) pcap_close(PCapAdapter); - - InitAdapterList(); - - // open pcap device - PCapAdapterData = &Adapters[0]; - for (int i = 0; i < NumAdapters; i++) - { - if (!strncmp(Adapters[i].DeviceName, devicename.data(), 128)) - PCapAdapterData = &Adapters[i]; - } + return Open(device.DeviceName, handler); +} +std::unique_ptr LibPCap::Open(std::string_view devicename, const Platform::SendPacketCallback& handler) const noexcept +{ char errbuf[PCAP_ERRBUF_SIZE]; - PCapAdapter = pcap_open_live(PCapAdapterData->DeviceName, 2048, PCAP_OPENFLAG_PROMISCUOUS, 1, errbuf); - if (!PCapAdapter) + pcap_t* adapter = open_live(devicename.data(), 2048, PCAP_OPENFLAG_PROMISCUOUS, 1, errbuf); + if (!adapter) { - Log(LogLevel::Error, "PCap: failed to open adapter %s\n", errbuf); - return false; + errbuf[PCAP_ERRBUF_SIZE - 1] = '\0'; + Log(LogLevel::Error, "PCap: failed to open adapter: %s\n", errbuf); + return nullptr; } - if (pcap_setnonblock(PCapAdapter, 1, errbuf) < 0) + if (int err = setnonblock(adapter, 1, errbuf); err < 0) { - Log(LogLevel::Error, "PCap: failed to set nonblocking mode\n"); - pcap_close(PCapAdapter); PCapAdapter = nullptr; - return false; + errbuf[PCAP_ERRBUF_SIZE - 1] = '\0'; + Log(LogLevel::Error, "PCap: failed to set nonblocking mode with %d: %s\n", err, errbuf); + close(adapter); + return nullptr; } - return true; + std::unique_ptr pcap = std::make_unique(); + pcap->PCapAdapter = adapter; + pcap->Callback = handler; + pcap->PCapLib = PCapLib; + pcap->close = close; + pcap->sendpacket = sendpacket; + pcap->dispatch = dispatch; + + return pcap; } -void DeInit() +Net_PCap::Net_PCap(Net_PCap&& other) noexcept { - if (PCapLib) + PCapAdapter = other.PCapAdapter; + PCapLib = std::move(other.PCapLib); + close = other.close; + sendpacket = other.sendpacket; + dispatch = other.dispatch; + Callback = std::move(other.Callback); + + other.PCapAdapter = nullptr; + other.close = nullptr; + other.PCapLib = nullptr; + other.sendpacket = nullptr; + other.dispatch = nullptr; + other.Callback = nullptr; +} + +Net_PCap& Net_PCap::operator=(Net_PCap&& other) noexcept +{ + if (this != &other) { - if (PCapAdapter) + if (close && PCapAdapter) { - pcap_close(PCapAdapter); + close(PCapAdapter); PCapAdapter = nullptr; } - Platform::DynamicLibrary_Unload(PCapLib); - PCapLib = nullptr; + PCapAdapter = other.PCapAdapter; + PCapLib = std::move(other.PCapLib); + close = other.close; + sendpacket = other.sendpacket; + dispatch = other.dispatch; + Callback = std::move(other.Callback); + + other.PCapAdapter = nullptr; + other.close = nullptr; + other.PCapLib = nullptr; + other.sendpacket = nullptr; + other.dispatch = nullptr; + other.Callback = nullptr; } + + return *this; } - -void RXCallback(u_char* userdata, const struct pcap_pkthdr* header, const u_char* data) +Net_PCap::~Net_PCap() noexcept { - Net::RXEnqueue(data, header->len); + if (close && PCapAdapter) + { + close(PCapAdapter); + PCapAdapter = nullptr; + } + // PCapLib will be freed at this point (shared_ptr + custom deleter) } -int SendPacket(u8* data, int len) +void Net_PCap::RXCallback(u_char* userdata, const struct pcap_pkthdr* header, const u_char* data) noexcept { - if (PCapAdapter == nullptr) + Net_PCap& self = *reinterpret_cast(userdata); + if (self.Callback) + self.Callback(data, header->len); +} + +int Net_PCap::SendPacket(u8* data, int len) noexcept +{ + if (PCapAdapter == nullptr || data == nullptr) return 0; if (len > 2048) @@ -363,17 +445,17 @@ int SendPacket(u8* data, int len) return 0; } - pcap_sendpacket(PCapAdapter, data, len); + sendpacket(PCapAdapter, data, len); // TODO: check success return len; } -void RecvCheck() +void Net_PCap::RecvCheck() noexcept { - if (PCapAdapter == nullptr) + if (PCapAdapter == nullptr || dispatch == nullptr) return; - pcap_dispatch(PCapAdapter, 1, RXCallback, nullptr); + dispatch(PCapAdapter, 1, RXCallback, reinterpret_cast(this)); } } diff --git a/src/net/Net_PCap.h b/src/net/Net_PCap.h index bf2ad882..cd2ab90b 100644 --- a/src/net/Net_PCap.h +++ b/src/net/Net_PCap.h @@ -19,13 +19,20 @@ #ifndef NET_PCAP_H #define NET_PCAP_H +#include +#include +#include #include +#include +#include + #include "types.h" +#include "Platform.h" +#include "NetDriver.h" -namespace Net_PCap + +namespace melonDS { - -using namespace melonDS; struct AdapterData { char DeviceName[128]; @@ -34,19 +41,95 @@ struct AdapterData u8 MAC[6]; u8 IP_v4[4]; + + /// The flags on the pcap_if_t that was used to populate this struct + u32 Flags; }; +typedef int (*pcap_findalldevs_t)(pcap_if_t** alldevs, char* errbuf); +typedef void (*pcap_freealldevs_t)(pcap_if_t* alldevs); +typedef pcap_t* (*pcap_open_live_t)(const char* src, int snaplen, int flags, int readtimeout, char* errbuf); +typedef void (*pcap_close_t)(pcap_t* dev); +typedef int (*pcap_setnonblock_t)(pcap_t* dev, int nonblock, char* errbuf); +typedef int (*pcap_sendpacket_t)(pcap_t* dev, const u_char* data, int len); +typedef int (*pcap_dispatch_t)(pcap_t* dev, int num, pcap_handler callback, u_char* data); +typedef const u_char* (*pcap_next_t)(pcap_t* dev, struct pcap_pkthdr* hdr); -extern AdapterData* Adapters; -extern int NumAdapters; +class Net_PCap; +class LibPCap +{ +public: + static std::optional New() noexcept; + LibPCap(const LibPCap&) = delete; + LibPCap& operator=(const LibPCap&) = delete; + LibPCap(LibPCap&&) noexcept; + LibPCap& operator=(LibPCap&&) noexcept; + ~LibPCap() noexcept = default; -bool InitAdapterList(); -bool Init(std::string_view devicename); -void DeInit(); + [[nodiscard]] std::unique_ptr Open(std::string_view devicename, const Platform::SendPacketCallback& handler) const noexcept; + [[nodiscard]] std::unique_ptr Open(const AdapterData& device, const Platform::SendPacketCallback& handler) const noexcept; -int SendPacket(u8* data, int len); -void RecvCheck(); + // so that Net_PCap objects can safely outlive LibPCap + // (because the actual DLL will be kept loaded until no shared_ptrs remain) + std::shared_ptr PCapLib = nullptr; + pcap_findalldevs_t findalldevs = nullptr; + pcap_freealldevs_t freealldevs = nullptr; + pcap_open_live_t open_live = nullptr; + pcap_close_t close = nullptr; + pcap_setnonblock_t setnonblock = nullptr; + pcap_sendpacket_t sendpacket = nullptr; + pcap_dispatch_t dispatch = nullptr; + pcap_next_t next = nullptr; + + [[nodiscard]] bool IsValid() const noexcept + { + return + PCapLib != nullptr && + findalldevs != nullptr && + freealldevs != nullptr && + open_live != nullptr && + close != nullptr && + setnonblock != nullptr && + sendpacket != nullptr && + dispatch != nullptr && + next != nullptr + ; + } + + /// @return List of all network interfaces available at the time of the call + [[nodiscard]] std::vector GetAdapters() const noexcept; +private: + static bool TryLoadPCap(LibPCap& pcap, Platform::DynamicLibrary *lib) noexcept; + LibPCap() noexcept = default; +}; + +class Net_PCap : public NetDriver +{ +public: + Net_PCap() noexcept = default; + ~Net_PCap() noexcept override; + Net_PCap(const Net_PCap&) = delete; + Net_PCap& operator=(const Net_PCap&) = delete; + Net_PCap(Net_PCap&& other) noexcept; + Net_PCap& operator=(Net_PCap&& other) noexcept; + + int SendPacket(u8* data, int len) noexcept override; + void RecvCheck() noexcept override; +private: + friend class LibPCap; + static void RXCallback(u_char* userdata, const pcap_pkthdr* header, const u_char* data) noexcept; + + pcap_t* PCapAdapter = nullptr; + Platform::SendPacketCallback Callback; + + // To avoid undefined behavior in case the original LibPCap object is destroyed + // before this interface is cleaned up + std::shared_ptr PCapLib = nullptr; + pcap_close_t close = nullptr; + pcap_sendpacket_t sendpacket = nullptr; + pcap_dispatch_t dispatch = nullptr; +}; } diff --git a/src/net/Net_Slirp.cpp b/src/net/Net_Slirp.cpp index 7ec7a046..fef4d19c 100644 --- a/src/net/Net_Slirp.cpp +++ b/src/net/Net_Slirp.cpp @@ -19,11 +19,17 @@ #include #include #include "Net.h" +#include "Net_Slirp.h" #include "FIFO.h" #include "Platform.h" #include +// "register" is indirectly used by slirp.h but isn't allowed in C++17, this is a workaround +#define register +// Needed for Slirp's definition so we can adjust the opaque pointer in the move constructor +#include + #ifdef __WIN32__ #include #else @@ -33,9 +39,7 @@ #include #endif -using namespace melonDS; - -namespace Net_Slirp +namespace melonDS { using Platform::Log; @@ -48,13 +52,6 @@ const u32 kClientIP = kSubnet | 0x10; const u8 kServerMAC[6] = {0x00, 0xAB, 0x33, 0x28, 0x99, 0x44}; -FIFO> 2)> RXBuffer; - -u32 IPv4ID; - -Slirp* Ctx = nullptr; - - #ifdef __WIN32__ #define poll WSAPoll @@ -77,7 +74,7 @@ int clock_gettime(int, struct timespec *spec) #endif // __WIN32__ -ssize_t SlirpCbSendPacket(const void* buf, size_t len, void* opaque) +ssize_t Net_Slirp::SlirpCbSendPacket(const void* buf, size_t len, void* opaque) noexcept { if (len > 2048) { @@ -87,7 +84,11 @@ ssize_t SlirpCbSendPacket(const void* buf, size_t len, void* opaque) Log(LogLevel::Debug, "slirp: response packet of %zu bytes, type %04X\n", len, ntohs(((u16*)buf)[6])); - Net::RXEnqueue(buf, len); + Net_Slirp& self = *static_cast(opaque); + if (self.Callback) + { + self.Callback((const u8*)buf, len); + } return len; } @@ -132,7 +133,7 @@ void SlirpCbNotify(void* opaque) Log(LogLevel::Debug, "Slirp: notify???\n"); } -SlirpCb cb = +const SlirpCb Net_Slirp::cb = { .send_packet = SlirpCbSendPacket, .guest_error = SlirpCbGuestError, @@ -145,11 +146,9 @@ SlirpCb cb = .notify = SlirpCbNotify }; -bool Init() +Net_Slirp::Net_Slirp(const Platform::SendPacketCallback& callback) noexcept : Callback(callback) { - IPv4ID = 0; - - SlirpConfig cfg; + SlirpConfig cfg {}; memset(&cfg, 0, sizeof(cfg)); cfg.version = 1; @@ -161,12 +160,67 @@ bool Init() *(u32*)&cfg.vdhcp_start = htonl(kClientIP); *(u32*)&cfg.vnameserver = htonl(kDNSIP); - Ctx = slirp_new(&cfg, &cb, nullptr); - - return true; + Ctx = slirp_new(&cfg, &cb, this); } -void DeInit() + +Net_Slirp::Net_Slirp(Net_Slirp&& other) noexcept +{ + RXBuffer = other.RXBuffer; + IPv4ID = other.IPv4ID; + Ctx = other.Ctx; + PollListSize = other.PollListSize; + Callback = std::move(other.Callback); + memcpy(PollList, other.PollList, sizeof(PollList)); + + other.RXBuffer = {}; + other.IPv4ID = 0; + other.Ctx = nullptr; + other.PollListSize = 0; + other.Callback = nullptr; + memset(other.PollList, 0, sizeof(other.PollList)); + + if (Ctx) + { + Ctx->opaque = this; + // Gotta ensure that the context doesn't try to pass around a dead object + } +} + +Net_Slirp& Net_Slirp::operator=(Net_Slirp&& other) noexcept +{ + if (this != &other) + { + if (Ctx) + { + slirp_cleanup(Ctx); + } + + RXBuffer = other.RXBuffer; + IPv4ID = other.IPv4ID; + Ctx = other.Ctx; + PollListSize = other.PollListSize; + Callback = std::move(other.Callback); + memcpy(PollList, other.PollList, sizeof(PollList)); + + other.RXBuffer = {}; + other.IPv4ID = 0; + other.Ctx = nullptr; + other.PollListSize = 0; + other.Callback = nullptr; + memset(other.PollList, 0, sizeof(other.PollList)); + + if (Ctx) + { + Ctx->opaque = this; + // Gotta ensure that the context doesn't try to pass around a dead object + } + } + + return *this; +} + +Net_Slirp::~Net_Slirp() noexcept { if (Ctx) { @@ -215,7 +269,7 @@ void FinishUDPFrame(u8* data, int len) *(u16*)&udpheader[6] = htons(tmp); } -void HandleDNSFrame(u8* data, int len) +void Net_Slirp::HandleDNSFrame(u8* data, int len) noexcept { u8* ipheader = &data[0xE]; u8* udpheader = &data[0x22]; @@ -368,10 +422,11 @@ void HandleDNSFrame(u8* data, int len) if (framelen & 1) { *out++ = 0; framelen++; } FinishUDPFrame(resp, framelen); - Net::RXEnqueue(resp, framelen); + if (Callback) + Callback(resp, framelen); } -int SendPacket(u8* data, int len) +int Net_Slirp::SendPacket(u8* data, int len) noexcept { if (!Ctx) return 0; @@ -401,19 +456,17 @@ int SendPacket(u8* data, int len) return len; } -const int PollListMax = 64; -struct pollfd PollList[PollListMax]; -int PollListSize; - -int SlirpCbAddPoll(int fd, int events, void* opaque) +int Net_Slirp::SlirpCbAddPoll(int fd, int events, void* opaque) noexcept { - if (PollListSize >= PollListMax) + Net_Slirp& self = *static_cast(opaque); + + if (self.PollListSize >= PollListMax) { Log(LogLevel::Error, "slirp: POLL LIST FULL\n"); return -1; } - int idx = PollListSize++; + int idx = self.PollListSize++; u16 evt = 0; @@ -427,18 +480,20 @@ int SlirpCbAddPoll(int fd, int events, void* opaque) if (events & SLIRP_POLL_HUP) evt |= POLLHUP; #endif // !__WIN32__ - PollList[idx].fd = fd; - PollList[idx].events = evt; + self.PollList[idx].fd = fd; + self.PollList[idx].events = evt; return idx; } -int SlirpCbGetREvents(int idx, void* opaque) +int Net_Slirp::SlirpCbGetREvents(int idx, void* opaque) noexcept { - if (idx < 0 || idx >= PollListSize) + Net_Slirp& self = *static_cast(opaque); + + if (idx < 0 || idx >= self.PollListSize) return 0; - u16 evt = PollList[idx].revents; + u16 evt = self.PollList[idx].revents; int ret = 0; if (evt & POLLIN) ret |= SLIRP_POLL_IN; @@ -450,7 +505,7 @@ int SlirpCbGetREvents(int idx, void* opaque) return ret; } -void RecvCheck() +void Net_Slirp::RecvCheck() noexcept { if (!Ctx) return; @@ -458,9 +513,9 @@ void RecvCheck() { u32 timeout = 0; PollListSize = 0; - slirp_pollfds_fill(Ctx, &timeout, SlirpCbAddPoll, nullptr); + slirp_pollfds_fill(Ctx, &timeout, SlirpCbAddPoll, this); int res = poll(PollList, PollListSize, timeout); - slirp_pollfds_poll(Ctx, res<0, SlirpCbGetREvents, nullptr); + slirp_pollfds_poll(Ctx, res<0, SlirpCbGetREvents, this); } } diff --git a/src/net/Net_Slirp.h b/src/net/Net_Slirp.h index 7446adf4..5f9b6587 100644 --- a/src/net/Net_Slirp.h +++ b/src/net/Net_Slirp.h @@ -20,17 +20,48 @@ #define NET_SLIRP_H #include "types.h" +#include "FIFO.h" +#include "Platform.h" +#include "NetDriver.h" -namespace Net_Slirp +#include + +#ifdef __WIN32__ + #include +#else + #include +#endif + +struct Slirp; + +namespace melonDS { -using namespace melonDS; +class Net_Slirp : public NetDriver +{ +public: + explicit Net_Slirp(const Platform::SendPacketCallback& callback) noexcept; + Net_Slirp(const Net_Slirp&) = delete; + Net_Slirp& operator=(const Net_Slirp&) = delete; + Net_Slirp(Net_Slirp&& other) noexcept; + Net_Slirp& operator=(Net_Slirp&& other) noexcept; + ~Net_Slirp() noexcept override; -bool Init(); -void DeInit(); - -int SendPacket(u8* data, int len); -void RecvCheck(); + int SendPacket(u8* data, int len) noexcept override; + void RecvCheck() noexcept override; +private: + static constexpr int PollListMax = 64; + static const SlirpCb cb; + static int SlirpCbGetREvents(int idx, void* opaque) noexcept; + static int SlirpCbAddPoll(int fd, int events, void* opaque) noexcept; + static ssize_t SlirpCbSendPacket(const void* buf, size_t len, void* opaque) noexcept; + void HandleDNSFrame(u8* data, int len) noexcept; + Platform::SendPacketCallback Callback; + pollfd PollList[PollListMax] {}; + int PollListSize = 0; + FIFO> 2)> RXBuffer {}; + u32 IPv4ID = 0; + Slirp* Ctx = nullptr; +}; } - #endif // NET_SLIRP_H diff --git a/src/net/PacketDispatcher.cpp b/src/net/PacketDispatcher.cpp index 61deed81..c9e0d274 100644 --- a/src/net/PacketDispatcher.cpp +++ b/src/net/PacketDispatcher.cpp @@ -34,7 +34,6 @@ const u32 kPacketMagic = 0x4B504C4D; PacketDispatcher::PacketDispatcher() : mutex(Platform::Mutex_Create()) { instanceMask = 0; - memset(packetQueues, 0, sizeof(packetQueues)); } PacketDispatcher::~PacketDispatcher() @@ -48,7 +47,7 @@ void PacketDispatcher::registerInstance(int inst) Mutex_Lock(mutex); instanceMask |= (1 << inst); - packetQueues[inst] = new PacketQueue(); + packetQueues[inst] = std::make_unique(); Mutex_Unlock(mutex); } @@ -58,7 +57,7 @@ void PacketDispatcher::unregisterInstance(int inst) Mutex_Lock(mutex); instanceMask &= ~(1 << inst); - delete packetQueues[inst]; + packetQueues[inst] = nullptr; Mutex_Unlock(mutex); } @@ -72,8 +71,7 @@ void PacketDispatcher::clear() if (!(instanceMask & (1 << i))) continue; - PacketQueue* queue = packetQueues[i]; - queue->Clear(); + packetQueues[i]->Clear(); } Mutex_Unlock(mutex); } @@ -105,7 +103,7 @@ void PacketDispatcher::sendPacket(const void* header, int headerlen, const void* if (!(recv_mask & (1 << i))) continue; - PacketQueue* queue = packetQueues[i]; + PacketQueue* queue = packetQueues[i].get(); // if we run out of space: discard old packets while (!queue->CanFit(totallen)) @@ -128,7 +126,7 @@ bool PacketDispatcher::recvPacket(void *header, int *headerlen, void *data, int if (receiver < 0 || receiver > 15) return false; Mutex_Lock(mutex); - PacketQueue* queue = packetQueues[receiver]; + PacketQueue* queue = packetQueues[receiver].get(); PacketHeader phdr; if (!queue->Read(&phdr, sizeof(phdr))) diff --git a/src/net/PacketDispatcher.h b/src/net/PacketDispatcher.h index 92df9941..d7f37c9a 100644 --- a/src/net/PacketDispatcher.h +++ b/src/net/PacketDispatcher.h @@ -19,6 +19,8 @@ #ifndef PACKETDISPATCHER_H #define PACKETDISPATCHER_H +#include +#include #include "Platform.h" #include "types.h" #include "FIFO.h" @@ -42,7 +44,7 @@ public: private: melonDS::Platform::Mutex* mutex; melonDS::u16 instanceMask; - PacketQueue* packetQueues[16]; + std::array, 16> packetQueues {}; }; #endif // PACKETDISPATCHER_H diff --git a/vcpkg.json b/vcpkg.json index fafa0868..ab89176e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,26 +1,46 @@ { + "default-features": ["qt6"], "dependencies": [ "sdl2", - { - "name": "libarchive", - "default-features": false, - "features": ["bzip2", "crypto", "lz4", "zstd"] + "libarchive", + "zstd" + ], + "features": { + "qt6": { + "description": "Use Qt 6 for the frontend.", + "dependencies": [ + { + "name": "qtbase", + "default-features": false, + "features": ["gui", "png", "thread", "widgets", "opengl", "zstd"] + }, + { + "name": "qtbase", + "host": true, + "default-features": false + }, + { + "name": "qtmultimedia", + "default-features": false + }, + "qtsvg" + ] }, - "zstd", - { - "name": "qtbase", - "default-features": false, - "features": ["gui", "png", "thread", "widgets", "opengl", "zstd"] - }, - { - "name": "qtbase", - "host": true, - "default-features": false - }, - { - "name": "qtmultimedia", - "default-features": false - }, - "qtsvg" - ] + "qt5": { + "description": "Use Qt 5 for the frontend.", + "dependencies": [ + { + "name": "qt5-base", + "default-features": false + }, + { + "name": "qt5-base", + "host": true, + "default-features": false + }, + "qt5-multimedia", + "qt5-svg" + ] + } + } }