Merge remote-tracking branch 'origin/master' into season3
# Conflicts: # src/frontend/qt_sdl/EmuInstance.cpp
This commit is contained in:
commit
9dbb4babfa
|
@ -12,7 +12,7 @@ src/xxhash/** linguist-vendored
|
||||||
# A handful of custom files embedded in the vendored dependencies
|
# A handful of custom files embedded in the vendored dependencies
|
||||||
|
|
||||||
## Ad-hoc CMakeLists.txt for melonDS
|
## Ad-hoc CMakeLists.txt for melonDS
|
||||||
!src/net/libslirp/src/CMakeLists.txt -linguist-vendored
|
src/net/libslirp/src/CMakeLists.txt -linguist-vendored
|
||||||
|
|
||||||
## glib stub
|
## glib stub
|
||||||
!src/net/libslirp/src/glib/** -linguist-vendored
|
src/net/libslirp/src/glib/** -linguist-vendored
|
||||||
|
|
|
@ -4,6 +4,7 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- ci/vcpkg-update
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
@ -27,7 +28,7 @@ jobs:
|
||||||
- name: Set up vcpkg
|
- name: Set up vcpkg
|
||||||
uses: lukka/run-vcpkg@v11
|
uses: lukka/run-vcpkg@v11
|
||||||
with:
|
with:
|
||||||
vcpkgGitCommitId: 53bef8994c541b6561884a8395ea35715ece75db
|
vcpkgGitCommitId: 1de2026f28ead93ff1773e6e680387643e914ea1
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: lukka/run-cmake@v10
|
uses: lukka/run-cmake@v10
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -4,40 +4,35 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- ci/vcpkg-update
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
env:
|
|
||||||
BUILD_TYPE: Release
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- name: Check out sources
|
||||||
- uses: msys2/setup-msys2@v2
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up MSYS2
|
||||||
|
uses: msys2/setup-msys2@v2
|
||||||
with:
|
with:
|
||||||
msystem: MINGW64
|
msystem: ucrt64
|
||||||
update: true
|
update: true
|
||||||
|
pacboy: gcc:p cmake:p ninja:p make:p
|
||||||
- name: Install dependencies
|
- name: Set up vcpkg
|
||||||
run: pacman -Sq --noconfirm git pkgconf mingw-w64-x86_64-{cmake,SDL2,qt5-static,libarchive,toolchain}
|
uses: lukka/run-vcpkg@v11
|
||||||
|
with:
|
||||||
|
vcpkgGitCommitId: 1de2026f28ead93ff1773e6e680387643e914ea1
|
||||||
- name: Configure
|
- name: Configure
|
||||||
working-directory: ${{runner.workspace}}
|
run: cmake --preset=release-mingw-x86_64
|
||||||
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: Build
|
||||||
|
run: cmake --build --preset=release-mingw-x86_64
|
||||||
- name: Make
|
- uses: actions/upload-artifact@v4
|
||||||
working-directory: ${{runner.workspace}}/build
|
|
||||||
run: cmake --build .
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v1
|
|
||||||
with:
|
with:
|
||||||
name: melonDS-windows-x86_64
|
name: melonDS-windows-x86_64
|
||||||
path: ${{runner.workspace}}\build\melonDS.exe
|
path: .\build\release-mingw-x86_64\melonDS.exe
|
||||||
|
|
|
@ -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",
|
"name": "release-mac-x86_64",
|
||||||
"inherits": "release-vcpkg",
|
"inherits": "release-vcpkg",
|
||||||
|
@ -44,6 +61,10 @@
|
||||||
"name": "release-vcpkg",
|
"name": "release-vcpkg",
|
||||||
"configurePreset": "release-vcpkg"
|
"configurePreset": "release-vcpkg"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "release-mingw-x86_64",
|
||||||
|
"configurePreset": "release-mingw-x86_64"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "release-mac-x86_64",
|
"name": "release-mac-x86_64",
|
||||||
"configurePreset": "release-mac-x86_64"
|
"configurePreset": "release-mac-x86_64"
|
||||||
|
|
|
@ -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")
|
set(VCPKG_ROOT "${_DEFAULT_VCPKG_ROOT}" CACHE STRING "The path to the vcpkg repository")
|
||||||
|
|
||||||
if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}")
|
if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}")
|
||||||
|
if (APPLE) # this doesn't work on non-macOS
|
||||||
file(LOCK "${_DEFAULT_VCPKG_ROOT}" DIRECTORY GUARD FILE)
|
file(LOCK "${_DEFAULT_VCPKG_ROOT}" DIRECTORY GUARD FILE)
|
||||||
|
endif()
|
||||||
FetchContent_Declare(vcpkg
|
FetchContent_Declare(vcpkg
|
||||||
GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git"
|
GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git"
|
||||||
GIT_TAG 2024.01.12
|
GIT_TAG 2024.07.12
|
||||||
SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg")
|
SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg")
|
||||||
FetchContent_MakeAvailable(vcpkg)
|
FetchContent_MakeAvailable(vcpkg)
|
||||||
endif()
|
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)
|
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 ";")
|
if (CMAKE_OSX_ARCHITECTURES MATCHES ";")
|
||||||
message(FATAL_ERROR "macOS universal builds are not supported. Build them individually and combine afterwards instead.")
|
message(FATAL_ERROR "macOS universal builds are not supported. Build them individually and combine afterwards instead.")
|
||||||
endif()
|
endif()
|
||||||
|
@ -47,7 +61,7 @@ if (USE_RECOMMENDED_TRIPLETS)
|
||||||
elseif(WIN32)
|
elseif(WIN32)
|
||||||
# TODO Windows arm64 if possible
|
# TODO Windows arm64 if possible
|
||||||
set(_CAN_TARGET_AS_HOST ON)
|
set(_CAN_TARGET_AS_HOST ON)
|
||||||
set(_WANTED_TRIPLET x64-mingw-static)
|
set(_WANTED_TRIPLET x64-mingw-static-release)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Don't override it if the user set something else
|
# Don't override it if the user set something else
|
||||||
|
|
|
@ -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)
|
|
@ -33,17 +33,26 @@ ARCodeFile::ARCodeFile(const std::string& filename)
|
||||||
{
|
{
|
||||||
Filename = filename;
|
Filename = filename;
|
||||||
|
|
||||||
Error = false;
|
|
||||||
|
|
||||||
Categories.clear();
|
|
||||||
|
|
||||||
if (!Load())
|
if (!Load())
|
||||||
Error = true;
|
Error = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ARCodeFile::~ARCodeFile()
|
std::vector<ARCode> ARCodeFile::GetCodes() const noexcept
|
||||||
{
|
{
|
||||||
Categories.clear();
|
if (Error)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<ARCode> codes;
|
||||||
|
|
||||||
|
for (const ARCodeCat& cat : Categories)
|
||||||
|
{
|
||||||
|
for (const ARCode& code : cat.Codes)
|
||||||
|
{
|
||||||
|
codes.push_back(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return codes;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ARCodeFile::Load()
|
bool ARCodeFile::Load()
|
||||||
|
|
|
@ -48,14 +48,16 @@ class ARCodeFile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ARCodeFile(const std::string& filename);
|
ARCodeFile(const std::string& filename);
|
||||||
~ARCodeFile();
|
~ARCodeFile() noexcept = default;
|
||||||
|
|
||||||
bool Error;
|
[[nodiscard]] std::vector<ARCode> GetCodes() const noexcept;
|
||||||
|
|
||||||
|
bool Error = false;
|
||||||
|
|
||||||
bool Load();
|
bool Load();
|
||||||
bool Save();
|
bool Save();
|
||||||
|
|
||||||
ARCodeCatList Categories;
|
ARCodeCatList Categories {};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string Filename;
|
std::string Filename;
|
||||||
|
|
|
@ -31,7 +31,6 @@ using Platform::LogLevel;
|
||||||
|
|
||||||
AREngine::AREngine(melonDS::NDS& nds) : NDS(nds)
|
AREngine::AREngine(melonDS::NDS& nds) : NDS(nds)
|
||||||
{
|
{
|
||||||
CodeFile = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define case16(x) \
|
#define case16(x) \
|
||||||
|
@ -388,19 +387,12 @@ void AREngine::RunCheat(const ARCode& arcode)
|
||||||
|
|
||||||
void AREngine::RunCheats()
|
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)
|
if (code.Enabled)
|
||||||
RunCheat(code);
|
RunCheat(code);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#ifndef ARENGINE_H
|
#ifndef ARENGINE_H
|
||||||
#define ARENGINE_H
|
#define ARENGINE_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
#include "ARCodeFile.h"
|
#include "ARCodeFile.h"
|
||||||
|
|
||||||
namespace melonDS
|
namespace melonDS
|
||||||
|
@ -29,14 +30,13 @@ class AREngine
|
||||||
public:
|
public:
|
||||||
AREngine(melonDS::NDS& nds);
|
AREngine(melonDS::NDS& nds);
|
||||||
|
|
||||||
ARCodeFile* GetCodeFile() { return CodeFile; }
|
std::vector<ARCode> Cheats {};
|
||||||
void SetCodeFile(ARCodeFile* file) { CodeFile = file; }
|
private:
|
||||||
|
friend class ARM;
|
||||||
void RunCheats();
|
void RunCheats();
|
||||||
void RunCheat(const ARCode& arcode);
|
void RunCheat(const ARCode& arcode);
|
||||||
private:
|
|
||||||
melonDS::NDS& NDS;
|
melonDS::NDS& NDS;
|
||||||
ARCodeFile* CodeFile; // AR code file - frontend is responsible for managing this
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
266
src/ARM.cpp
266
src/ARM.cpp
|
@ -582,8 +582,10 @@ void ARM::CheckGdbIncoming()
|
||||||
GdbCheckA();
|
GdbCheckA();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <CPUExecuteMode mode>
|
||||||
void ARMv5::Execute()
|
void ARMv5::Execute()
|
||||||
{
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::InterpreterGDB)
|
||||||
GdbCheckB();
|
GdbCheckB();
|
||||||
|
|
||||||
if (Halted)
|
if (Halted)
|
||||||
|
@ -606,9 +608,49 @@ void ARMv5::Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
while (NDS.ARM9Timestamp < NDS.ARM9Target)
|
while (NDS.ARM9Timestamp < NDS.ARM9Target)
|
||||||
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::JIT)
|
||||||
|
{
|
||||||
|
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 ((Halted == 1 || IdleLoop) && NDS.ARM9Timestamp < NDS.ARM9Target)
|
||||||
|
{
|
||||||
|
Cycles = 0;
|
||||||
|
NDS.ARM9Timestamp = NDS.ARM9Target;
|
||||||
|
}
|
||||||
|
IdleLoop = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (CPSR & 0x20) // THUMB
|
if (CPSR & 0x20) // THUMB
|
||||||
{
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::InterpreterGDB)
|
||||||
GdbCheckC();
|
GdbCheckC();
|
||||||
|
|
||||||
// prefetch
|
// prefetch
|
||||||
|
@ -624,6 +666,7 @@ void ARMv5::Execute()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::InterpreterGDB)
|
||||||
GdbCheckC();
|
GdbCheckC();
|
||||||
|
|
||||||
// prefetch
|
// prefetch
|
||||||
|
@ -662,6 +705,8 @@ void ARMv5::Execute()
|
||||||
}*/
|
}*/
|
||||||
if (IRQ) TriggerIRQ();
|
if (IRQ) TriggerIRQ();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
NDS.ARM9Timestamp += Cycles;
|
NDS.ARM9Timestamp += Cycles;
|
||||||
Cycles = 0;
|
Cycles = 0;
|
||||||
}
|
}
|
||||||
|
@ -669,77 +714,16 @@ void ARMv5::Execute()
|
||||||
if (Halted == 2)
|
if (Halted == 2)
|
||||||
Halted = 0;
|
Halted = 0;
|
||||||
}
|
}
|
||||||
|
template void ARMv5::Execute<CPUExecuteMode::Interpreter>();
|
||||||
|
template void ARMv5::Execute<CPUExecuteMode::InterpreterGDB>();
|
||||||
#ifdef JIT_ENABLED
|
#ifdef JIT_ENABLED
|
||||||
void ARMv5::ExecuteJIT()
|
template void ARMv5::Execute<CPUExecuteMode::JIT>();
|
||||||
{
|
|
||||||
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 ((Halted == 1 || IdleLoop) && NDS.ARM9Timestamp < NDS.ARM9Target)
|
|
||||||
{
|
|
||||||
Cycles = 0;
|
|
||||||
NDS.ARM9Timestamp = NDS.ARM9Target;
|
|
||||||
}
|
|
||||||
IdleLoop = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NDS.ARM9Timestamp += Cycles;
|
|
||||||
Cycles = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Halted == 2)
|
|
||||||
Halted = 0;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
template <CPUExecuteMode mode>
|
||||||
void ARMv4::Execute()
|
void ARMv4::Execute()
|
||||||
{
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::InterpreterGDB)
|
||||||
GdbCheckB();
|
GdbCheckB();
|
||||||
|
|
||||||
if (Halted)
|
if (Halted)
|
||||||
|
@ -763,95 +747,7 @@ void ARMv4::Execute()
|
||||||
|
|
||||||
while (NDS.ARM7Timestamp < NDS.ARM7Target)
|
while (NDS.ARM7Timestamp < NDS.ARM7Target)
|
||||||
{
|
{
|
||||||
if (CPSR & 0x20) // THUMB
|
if constexpr (mode == CPUExecuteMode::JIT)
|
||||||
{
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
NDS.ARM7Timestamp = NDS.ARM7Target;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/*if (NDS::IF[1] & NDS::IE[1])
|
|
||||||
{
|
|
||||||
if (NDS::IME[1] & 0x1)
|
|
||||||
TriggerIRQ();
|
|
||||||
}*/
|
|
||||||
if (IRQ) TriggerIRQ();
|
|
||||||
|
|
||||||
NDS.ARM7Timestamp += Cycles;
|
|
||||||
Cycles = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Halted == 2)
|
|
||||||
Halted = 0;
|
|
||||||
|
|
||||||
if (Halted == 4)
|
|
||||||
{
|
|
||||||
assert(NDS.ConsoleType == 1);
|
|
||||||
auto& dsi = dynamic_cast<melonDS::DSi&>(NDS);
|
|
||||||
dsi.SoftReset();
|
|
||||||
Halted = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef JIT_ENABLED
|
|
||||||
void ARMv4::ExecuteJIT()
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
u32 instrAddr = R[15] - ((CPSR&0x20)?2:4);
|
u32 instrAddr = R[15] - ((CPSR&0x20)?2:4);
|
||||||
|
|
||||||
|
@ -886,6 +782,61 @@ void ARMv4::ExecuteJIT()
|
||||||
break;
|
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)
|
||||||
|
{
|
||||||
|
NDS.ARM7Timestamp = NDS.ARM7Target;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/*if (NDS::IF[1] & NDS::IE[1])
|
||||||
|
{
|
||||||
|
if (NDS::IME[1] & 0x1)
|
||||||
|
TriggerIRQ();
|
||||||
|
}*/
|
||||||
|
if (IRQ) TriggerIRQ();
|
||||||
|
}
|
||||||
|
|
||||||
NDS.ARM7Timestamp += Cycles;
|
NDS.ARM7Timestamp += Cycles;
|
||||||
Cycles = 0;
|
Cycles = 0;
|
||||||
|
@ -902,6 +853,11 @@ void ARMv4::ExecuteJIT()
|
||||||
Halted = 2;
|
Halted = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template void ARMv4::Execute<CPUExecuteMode::Interpreter>();
|
||||||
|
template void ARMv4::Execute<CPUExecuteMode::InterpreterGDB>();
|
||||||
|
#ifdef JIT_ENABLED
|
||||||
|
template void ARMv4::Execute<CPUExecuteMode::JIT>();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void ARMv5::FillPipeline()
|
void ARMv5::FillPipeline()
|
||||||
|
|
25
src/ARM.h
25
src/ARM.h
|
@ -43,6 +43,15 @@ enum
|
||||||
RWFlags_ForceUser = (1<<21),
|
RWFlags_ForceUser = (1<<21),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class CPUExecuteMode : u32
|
||||||
|
{
|
||||||
|
Interpreter,
|
||||||
|
InterpreterGDB,
|
||||||
|
#ifdef JIT_ENABLED
|
||||||
|
JIT
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
struct GDBArgs;
|
struct GDBArgs;
|
||||||
class ARMJIT;
|
class ARMJIT;
|
||||||
class GPU;
|
class GPU;
|
||||||
|
@ -75,10 +84,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void NocashPrint(u32 addr) noexcept;
|
void NocashPrint(u32 addr) noexcept;
|
||||||
virtual void Execute() = 0;
|
|
||||||
#ifdef JIT_ENABLED
|
|
||||||
virtual void ExecuteJIT() = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool CheckCondition(u32 code) const
|
bool CheckCondition(u32 code) const
|
||||||
{
|
{
|
||||||
|
@ -241,10 +246,8 @@ public:
|
||||||
void PrefetchAbort();
|
void PrefetchAbort();
|
||||||
void DataAbort();
|
void DataAbort();
|
||||||
|
|
||||||
void Execute() override;
|
template <CPUExecuteMode mode>
|
||||||
#ifdef JIT_ENABLED
|
void Execute();
|
||||||
void ExecuteJIT() override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// all code accesses are forced nonseq 32bit
|
// all code accesses are forced nonseq 32bit
|
||||||
u32 CodeRead32(u32 addr, bool branch);
|
u32 CodeRead32(u32 addr, bool branch);
|
||||||
|
@ -383,10 +386,8 @@ public:
|
||||||
|
|
||||||
void JumpTo(u32 addr, bool restorecpsr = false) override;
|
void JumpTo(u32 addr, bool restorecpsr = false) override;
|
||||||
|
|
||||||
void Execute() override;
|
template <CPUExecuteMode mode>
|
||||||
#ifdef JIT_ENABLED
|
void Execute();
|
||||||
void ExecuteJIT() override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
u16 CodeRead16(u32 addr)
|
u16 CodeRead16(u32 addr)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
Log(LogLevel::Warn, "undefined ARM%d instruction %08X @ %08X\n", cpu->Num?7:9, cpu->CurInstr, cpu->R[15]-8);
|
||||||
#ifdef GDBSTUB_ENABLED
|
#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
|
#endif
|
||||||
//for (int i = 0; i < 16; i++) printf("R%d: %08X\n", i, cpu->R[i]);
|
//for (int i = 0; i < 16; i++) printf("R%d: %08X\n", i, cpu->R[i]);
|
||||||
//NDS::Halt();
|
//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);
|
Log(LogLevel::Warn, "undefined THUMB%d instruction %04X @ %08X\n", cpu->Num?7:9, cpu->CurInstr, cpu->R[15]-4);
|
||||||
#ifdef GDBSTUB_ENABLED
|
#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
|
#endif
|
||||||
//NDS::Halt();
|
//NDS::Halt();
|
||||||
u32 oldcpsr = cpu->CPSR;
|
u32 oldcpsr = cpu->CPSR;
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace melonDS
|
||||||
{
|
{
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wattributes"
|
#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.
|
// 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)
|
__attribute((always_inline)) static void Bswap128(void* Dst, const void* Src)
|
||||||
{
|
{
|
||||||
|
|
|
@ -87,6 +87,7 @@ public:
|
||||||
void DoSavestate(Savestate* file) override;
|
void DoSavestate(Savestate* file) override;
|
||||||
|
|
||||||
u8 GetBootFlag() const;
|
u8 GetBootFlag() const;
|
||||||
|
void SetBootFlag(u8 boot) noexcept { Registers[0x70] = boot; }
|
||||||
|
|
||||||
bool GetBatteryCharging() const;
|
bool GetBatteryCharging() const;
|
||||||
void SetBatteryCharging(bool charging);
|
void SetBatteryCharging(bool charging);
|
||||||
|
|
|
@ -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<CartCommon>&& cart) noexcept : NDS(nds), Cart(std::move(cart))
|
GBACartSlot::GBACartSlot(melonDS::NDS& nds, std::unique_ptr<CartCommon>&& 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)
|
switch (type)
|
||||||
{
|
{
|
||||||
case GBAAddon_RAMExpansion:
|
case GBAAddon_RAMExpansion:
|
||||||
Cart = std::make_unique<CartRAMExpansion>();
|
Cart = std::make_unique<CartRAMExpansion>();
|
||||||
break;
|
break;
|
||||||
|
case GBAAddon_RumblePak:
|
||||||
|
Cart = std::make_unique<CartRumblePak>(userdata);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
|
Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
|
||||||
|
|
|
@ -32,6 +32,7 @@ enum CartType
|
||||||
Game = 0x101,
|
Game = 0x101,
|
||||||
GameSolarSensor = 0x102,
|
GameSolarSensor = 0x102,
|
||||||
RAMExpansion = 0x201,
|
RAMExpansion = 0x201,
|
||||||
|
RumblePak = 0x202,
|
||||||
};
|
};
|
||||||
|
|
||||||
// CartCommon -- base code shared by all cart types
|
// CartCommon -- base code shared by all cart types
|
||||||
|
@ -189,6 +190,25 @@ private:
|
||||||
u16 RAMEnable = 0;
|
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
|
// possible inputs for GBA carts that might accept user input
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
|
@ -219,7 +239,7 @@ public:
|
||||||
[[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); }
|
[[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); }
|
||||||
[[nodiscard]] const CartCommon* GetCart() const 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,
|
/// @return The cart that was in the cart slot if any,
|
||||||
/// or \c nullptr if the cart slot was empty.
|
/// or \c nullptr if the cart slot was empty.
|
||||||
|
|
|
@ -387,6 +387,14 @@ void Unit::Write16(u32 addr, u16 val)
|
||||||
if (!Num) GPU.GPU3D.SetRenderXPos(val);
|
if (!Num) GPU.GPU3D.SetRenderXPos(val);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 0x064:
|
||||||
|
CaptureCnt = (CaptureCnt & 0xFFFF0000) | (val & 0xEF3F1F1F);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 0x066:
|
||||||
|
CaptureCnt = (CaptureCnt & 0xFFFF) | ((val << 16) & 0xEF3F1F1F);
|
||||||
|
return;
|
||||||
|
|
||||||
case 0x068:
|
case 0x068:
|
||||||
DispFIFO[DispFIFOWritePtr] = val;
|
DispFIFO[DispFIFOWritePtr] = val;
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -192,7 +192,7 @@ void GPU3D::Reset() noexcept
|
||||||
|
|
||||||
CmdStallQueue.Clear();
|
CmdStallQueue.Clear();
|
||||||
|
|
||||||
ZeroDotWLimit = 0; // CHECKME
|
ZeroDotWLimit = 0xFFFFFF;
|
||||||
|
|
||||||
GXStat = 0;
|
GXStat = 0;
|
||||||
|
|
||||||
|
@ -1279,7 +1279,7 @@ void GPU3D::SubmitPolygon() noexcept
|
||||||
{
|
{
|
||||||
Vertex* vtx = poly->Vertices[i];
|
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];
|
xtop = vtx->FinalPosition[0];
|
||||||
ytop = vtx->FinalPosition[1];
|
ytop = vtx->FinalPosition[1];
|
||||||
|
|
|
@ -197,7 +197,7 @@ public:
|
||||||
|
|
||||||
FIFO<CmdFIFOEntry, 64> CmdStallQueue {};
|
FIFO<CmdFIFOEntry, 64> CmdStallQueue {};
|
||||||
|
|
||||||
u32 ZeroDotWLimit = 0;
|
u32 ZeroDotWLimit = 0xFFFFFF;
|
||||||
|
|
||||||
u32 GXStat = 0;
|
u32 GXStat = 0;
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ public:
|
||||||
void Blit(const GPU& gpu) override;
|
void Blit(const GPU& gpu) override;
|
||||||
void Stop(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;
|
void ShaderCompileStep(int& current, int& count) override;
|
||||||
private:
|
private:
|
||||||
ComputeRenderer(GLCompositor&& compositor);
|
ComputeRenderer(GLCompositor&& compositor);
|
||||||
|
|
|
@ -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 & 0x3FC) * (width>>2)) + (s & 0x3FC);
|
||||||
vramaddr += (t & 0x3);
|
vramaddr += (t & 0x3);
|
||||||
|
vramaddr &= 0x7FFFF; // address used for all calcs wraps around after slot 3
|
||||||
|
|
||||||
u32 slot1addr = 0x20000 + ((vramaddr & 0x1FFFC) >> 1);
|
u32 slot1addr = 0x20000 + ((vramaddr & 0x1FFFC) >> 1);
|
||||||
if (vramaddr >= 0x40000)
|
if (vramaddr >= 0x40000)
|
||||||
slot1addr += 0x10000;
|
slot1addr += 0x10000;
|
||||||
|
|
||||||
u8 val = ReadVRAM_Texture<u8>(vramaddr, gpu);
|
u8 val;
|
||||||
|
if (vramaddr >= 0x20000 && vramaddr < 0x40000) // reading slot 1 for texels should always read 0
|
||||||
|
val = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
val = ReadVRAM_Texture<u8>(vramaddr, gpu);
|
||||||
val >>= (2 * (s & 0x3));
|
val >>= (2 * (s & 0x3));
|
||||||
|
}
|
||||||
|
|
||||||
u16 palinfo = ReadVRAM_Texture<u16>(slot1addr, gpu);
|
u16 palinfo = ReadVRAM_Texture<u16>(slot1addr, gpu);
|
||||||
u32 paloffset = (palinfo & 0x3FFF) << 2;
|
u32 paloffset = (palinfo & 0x3FFF) << 2;
|
||||||
|
|
53
src/NDS.cpp
53
src/NDS.cpp
|
@ -107,6 +107,9 @@ NDS::NDS(NDSArgs&& args, int type, void* userdata) noexcept :
|
||||||
AREngine(*this),
|
AREngine(*this),
|
||||||
ARM9(*this, args.GDB, args.JIT.has_value()),
|
ARM9(*this, args.GDB, args.JIT.has_value()),
|
||||||
ARM7(*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
|
#ifdef JIT_ENABLED
|
||||||
EnableJIT(args.JIT.has_value()),
|
EnableJIT(args.JIT.has_value()),
|
||||||
#endif
|
#endif
|
||||||
|
@ -751,7 +754,7 @@ void NDS::SetGBASave(const u8* savedata, u32 savelen)
|
||||||
|
|
||||||
void NDS::LoadGBAAddon(int type)
|
void NDS::LoadGBAAddon(int type)
|
||||||
{
|
{
|
||||||
GBACartSlot.LoadAddon(type);
|
GBACartSlot.LoadAddon(UserData, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NDS::LoadBIOS()
|
void NDS::LoadBIOS()
|
||||||
|
@ -886,7 +889,7 @@ void NDS::RunSystemSleep(u64 timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool EnableJIT>
|
template <CPUExecuteMode cpuMode>
|
||||||
u32 NDS::RunFrame()
|
u32 NDS::RunFrame()
|
||||||
{
|
{
|
||||||
FrameStartTimestamp = SysTimestamp;
|
FrameStartTimestamp = SysTimestamp;
|
||||||
|
@ -926,9 +929,12 @@ u32 NDS::RunFrame()
|
||||||
GPU.BlankFrame();
|
GPU.BlankFrame();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (cpuMode == CPUExecuteMode::InterpreterGDB)
|
||||||
{
|
{
|
||||||
ARM9.CheckGdbIncoming();
|
ARM9.CheckGdbIncoming();
|
||||||
ARM7.CheckGdbIncoming();
|
ARM7.CheckGdbIncoming();
|
||||||
|
}
|
||||||
|
|
||||||
if (!(CPUStop & CPUStop_Wakeup))
|
if (!(CPUStop & CPUStop_Wakeup))
|
||||||
{
|
{
|
||||||
|
@ -963,12 +969,7 @@ u32 NDS::RunFrame()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef JIT_ENABLED
|
ARM9.Execute<cpuMode>();
|
||||||
if (EnableJIT)
|
|
||||||
ARM9.ExecuteJIT();
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
ARM9.Execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RunTimers(0);
|
RunTimers(0);
|
||||||
|
@ -995,12 +996,7 @@ u32 NDS::RunFrame()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef JIT_ENABLED
|
ARM7.Execute<cpuMode>();
|
||||||
if (EnableJIT)
|
|
||||||
ARM7.ExecuteJIT();
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
ARM7.Execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RunTimers(1);
|
RunTimers(1);
|
||||||
|
@ -1045,10 +1041,18 @@ u32 NDS::RunFrame()
|
||||||
{
|
{
|
||||||
#ifdef JIT_ENABLED
|
#ifdef JIT_ENABLED
|
||||||
if (EnableJIT)
|
if (EnableJIT)
|
||||||
return RunFrame<true>();
|
return RunFrame<CPUExecuteMode::JIT>();
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
return RunFrame<false>();
|
#ifdef GDBSTUB_ENABLED
|
||||||
|
if (EnableGDBStub)
|
||||||
|
{
|
||||||
|
return RunFrame<CPUExecuteMode::InterpreterGDB>();
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
return RunFrame<CPUExecuteMode::Interpreter>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NDS::Reschedule(u64 target)
|
void NDS::Reschedule(u64 target)
|
||||||
|
@ -1463,7 +1467,7 @@ u64 NDS::GetSysClockCycles(int num)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NDS::NocashPrint(u32 ncpu, u32 addr)
|
void NDS::NocashPrint(u32 ncpu, u32 addr, bool appendNewline)
|
||||||
{
|
{
|
||||||
// addr: debug string
|
// addr: debug string
|
||||||
|
|
||||||
|
@ -1541,7 +1545,7 @@ void NDS::NocashPrint(u32 ncpu, u32 addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
output[ptr] = '\0';
|
output[ptr] = '\0';
|
||||||
Log(LogLevel::Debug, "%s\n", output);
|
Log(LogLevel::Debug, appendNewline ? "%s\n" : "%s", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NDS::MonitorARM9Jump(u32 addr)
|
void NDS::MonitorARM9Jump(u32 addr)
|
||||||
|
@ -2946,6 +2950,8 @@ u16 NDS::ARM9IORead16(u32 addr)
|
||||||
case 0x04000208: return IME[0];
|
case 0x04000208: return IME[0];
|
||||||
case 0x04000210: return IE[0] & 0xFFFF;
|
case 0x04000210: return IE[0] & 0xFFFF;
|
||||||
case 0x04000212: return IE[0] >> 16;
|
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 0x04000240: return GPU.VRAMCNT[0] | (GPU.VRAMCNT[1] << 8);
|
||||||
case 0x04000242: return GPU.VRAMCNT[2] | (GPU.VRAMCNT[3] << 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 0x04000060: GPU.GPU3D.Write16(addr, val); return;
|
||||||
|
|
||||||
|
case 0x04000064:
|
||||||
|
case 0x04000066: GPU.GPU2D_A.Write16(addr, val); return;
|
||||||
|
|
||||||
case 0x04000068:
|
case 0x04000068:
|
||||||
case 0x0400006A: GPU.GPU2D_A.Write16(addr, val); return;
|
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 0x04000210: IE[0] = (IE[0] & 0xFFFF0000) | val; UpdateIRQ(0); return;
|
||||||
case 0x04000212: IE[0] = (IE[0] & 0x0000FFFF) | (val << 16); UpdateIRQ(0); return;
|
case 0x04000212: IE[0] = (IE[0] & 0x0000FFFF) | (val << 16); UpdateIRQ(0); return;
|
||||||
// TODO: what happens when writing to IF this way??
|
// 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:
|
case 0x04000240:
|
||||||
GPU.MapVRAM_AB(0, val & 0xFF);
|
GPU.MapVRAM_AB(0, val & 0xFF);
|
||||||
|
@ -3609,10 +3620,8 @@ void NDS::ARM9IOWrite32(u32 addr, u32 val)
|
||||||
case 0x04FFFA14:
|
case 0x04FFFA14:
|
||||||
case 0x04FFFA18:
|
case 0x04FFFA18:
|
||||||
{
|
{
|
||||||
bool appendLF = 0x04FFFA18 == addr;
|
NocashPrint(0, val, 0x04FFFA18 == addr);
|
||||||
NocashPrint(0, val);
|
|
||||||
if(appendLF)
|
|
||||||
Log(LogLevel::Debug, "\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -210,6 +210,7 @@ enum
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
GBAAddon_RAMExpansion = 1,
|
GBAAddon_RAMExpansion = 1,
|
||||||
|
GBAAddon_RumblePak = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SPU;
|
class SPU;
|
||||||
|
@ -227,6 +228,9 @@ private:
|
||||||
#ifdef JIT_ENABLED
|
#ifdef JIT_ENABLED
|
||||||
bool EnableJIT;
|
bool EnableJIT;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef GDBSTUB_ENABLED
|
||||||
|
bool EnableGDBStub = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
public: // TODO: Encapsulate the rest of these members
|
public: // TODO: Encapsulate the rest of these members
|
||||||
void* UserData;
|
void* UserData;
|
||||||
|
@ -422,7 +426,7 @@ public: // TODO: Encapsulate the rest of these members
|
||||||
|
|
||||||
u32 GetPC(u32 cpu) const;
|
u32 GetPC(u32 cpu) const;
|
||||||
u64 GetSysClockCycles(int num);
|
u64 GetSysClockCycles(int num);
|
||||||
void NocashPrint(u32 cpu, u32 addr);
|
void NocashPrint(u32 cpu, u32 addr, bool appendNewline = true);
|
||||||
|
|
||||||
void MonitorARM9Jump(u32 addr);
|
void MonitorARM9Jump(u32 addr);
|
||||||
|
|
||||||
|
@ -521,8 +525,9 @@ private:
|
||||||
void SetWifiWaitCnt(u16 val);
|
void SetWifiWaitCnt(u16 val);
|
||||||
void SetGBASlotTimings();
|
void SetGBASlotTimings();
|
||||||
void EnterSleepMode();
|
void EnterSleepMode();
|
||||||
template <bool EnableJIT>
|
template <CPUExecuteMode cpuMode>
|
||||||
u32 RunFrame();
|
u32 RunFrame();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NDS(NDSArgs&& args, void* userdata = nullptr) noexcept : NDS(std::move(args), 0, userdata) {}
|
NDS(NDSArgs&& args, void* userdata = nullptr) noexcept : NDS(std::move(args), 0, userdata) {}
|
||||||
NDS() noexcept;
|
NDS() noexcept;
|
||||||
|
|
|
@ -160,7 +160,7 @@ void SaveShaderCache()
|
||||||
|
|
||||||
Platform::FileSeek(file, 0, Platform::FileSeekOrigin::End);
|
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)
|
for (u64 newShader : NewShaders)
|
||||||
{
|
{
|
||||||
|
|
|
@ -305,6 +305,7 @@ u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata);
|
||||||
// packet type: Ethernet (802.3)
|
// packet type: Ethernet (802.3)
|
||||||
int Net_SendPacket(u8* data, int len, void* userdata);
|
int Net_SendPacket(u8* data, int len, void* userdata);
|
||||||
int Net_RecvPacket(u8* data, void* userdata);
|
int Net_RecvPacket(u8* data, void* userdata);
|
||||||
|
using SendPacketCallback = std::function<void(const u8* data, int len)>;
|
||||||
|
|
||||||
|
|
||||||
// interface for camera emulation
|
// interface for camera emulation
|
||||||
|
@ -316,6 +317,17 @@ void Camera_Start(int num, void* userdata);
|
||||||
void Camera_Stop(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);
|
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;
|
struct DynamicLibrary;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
#ifndef PLATFORMOGL_H
|
#ifndef PLATFORMOGL_H
|
||||||
#define PLATFORMOGL_H
|
#define PLATFORMOGL_H
|
||||||
|
|
||||||
// if you don't wanna use glad for your platform
|
// If you don't wanna use glad for your platform,
|
||||||
// add your header here!
|
// 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
|
#endif
|
||||||
|
|
|
@ -152,6 +152,7 @@ public:
|
||||||
int RespFmt(const char* fmt, ...);
|
int RespFmt(const char* fmt, ...);
|
||||||
|
|
||||||
int RespStr(const char* str);
|
int RespStr(const char* str);
|
||||||
|
inline bool IsConnected() { return ConnFd > 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Disconnect();
|
void Disconnect();
|
||||||
|
|
|
@ -161,12 +161,19 @@ endif()
|
||||||
|
|
||||||
if (BUILD_STATIC)
|
if (BUILD_STATIC)
|
||||||
qt_import_plugins(melonDS INCLUDE Qt::QSvgPlugin)
|
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)
|
target_link_options(melonDS PRIVATE -static)
|
||||||
endif()
|
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}/..")
|
||||||
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)
|
if (USE_QT6)
|
||||||
target_include_directories(melonDS PUBLIC ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
target_include_directories(melonDS PUBLIC ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
else()
|
else()
|
||||||
|
@ -245,3 +252,11 @@ if (UNIX AND NOT APPLE)
|
||||||
INTERPROCEDURAL_OPTIMIZATION_RELEASE OFF)
|
INTERPROCEDURAL_OPTIMIZATION_RELEASE OFF)
|
||||||
endif()
|
endif()
|
||||||
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()
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include "toml/toml.hpp"
|
#include "toml/toml.hpp"
|
||||||
|
|
||||||
|
@ -741,7 +742,7 @@ bool Load()
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RootTable = toml::parse(cfgpath);
|
RootTable = toml::parse(std::filesystem::u8path(cfgpath));
|
||||||
}
|
}
|
||||||
catch (toml::syntax_error& err)
|
catch (toml::syntax_error& err)
|
||||||
{
|
{
|
||||||
|
@ -758,7 +759,7 @@ void Save()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::ofstream file;
|
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 << RootTable;
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,8 @@ using namespace melonDS::Platform;
|
||||||
MainWindow* topWindow = nullptr;
|
MainWindow* topWindow = nullptr;
|
||||||
|
|
||||||
const string kWifiSettingsPath = "wfcsettings.bin";
|
const string kWifiSettingsPath = "wfcsettings.bin";
|
||||||
|
extern LocalMP localMp;
|
||||||
|
extern Net net;
|
||||||
|
|
||||||
|
|
||||||
EmuInstance::EmuInstance(int inst) : deleting(false),
|
EmuInstance::EmuInstance(int inst) : deleting(false),
|
||||||
|
@ -98,7 +100,7 @@ EmuInstance::EmuInstance(int inst) : deleting(false),
|
||||||
audioInit();
|
audioInit();
|
||||||
inputInit();
|
inputInit();
|
||||||
|
|
||||||
Net::RegisterInstance(instanceID);
|
net.RegisterInstance(instanceID);
|
||||||
|
|
||||||
emuThread = new EmuThread(this);
|
emuThread = new EmuThread(this);
|
||||||
|
|
||||||
|
@ -119,13 +121,13 @@ EmuInstance::~EmuInstance()
|
||||||
deleting = true;
|
deleting = true;
|
||||||
deleteAllWindows();
|
deleteAllWindows();
|
||||||
|
|
||||||
LocalMP::End(instanceID);
|
localMp.End(instanceID);
|
||||||
|
|
||||||
emuThread->emuExit();
|
emuThread->emuExit();
|
||||||
emuThread->wait();
|
emuThread->wait();
|
||||||
delete emuThread;
|
delete emuThread;
|
||||||
|
|
||||||
Net::UnregisterInstance(instanceID);
|
net.UnregisterInstance(instanceID);
|
||||||
|
|
||||||
audioDeInit();
|
audioDeInit();
|
||||||
inputDeInit();
|
inputDeInit();
|
||||||
|
@ -558,7 +560,7 @@ std::string EmuInstance::getEffectiveFirmwareSavePath()
|
||||||
{
|
{
|
||||||
if (!globalCfg.GetBool("Emu.ExternalBIOSEnable"))
|
if (!globalCfg.GetBool("Emu.ExternalBIOSEnable"))
|
||||||
{
|
{
|
||||||
return kWifiSettingsPath;
|
return GetLocalFilePath(kWifiSettingsPath);
|
||||||
}
|
}
|
||||||
if (consoleType == 1)
|
if (consoleType == 1)
|
||||||
{
|
{
|
||||||
|
@ -592,7 +594,7 @@ bool EmuInstance::savestateExists(int slot)
|
||||||
|
|
||||||
bool EmuInstance::loadState(const std::string& filename)
|
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 (file == nullptr)
|
||||||
{ // If we couldn't open the state file...
|
{ // If we couldn't open the state file...
|
||||||
Platform::Log(Platform::LogLevel::Error, "Failed to open state file \"%s\"\n", filename.c_str());
|
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 (backup->Error)
|
||||||
{ // If we couldn't allocate memory for the backup...
|
{ // If we couldn't allocate memory for the backup...
|
||||||
Platform::Log(Platform::LogLevel::Error, "Failed to allocate memory for state backup\n");
|
Platform::Log(Platform::LogLevel::Error, "Failed to allocate memory for state backup\n");
|
||||||
fclose(file);
|
Platform::CloseFile(file);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nds->DoSavestate(backup.get()) || backup->Error)
|
if (!nds->DoSavestate(backup.get()) || backup->Error)
|
||||||
{ // Back up the emulator's state. If that failed...
|
{ // 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());
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
// We'll store the backup once we're sure that the state was loaded.
|
// 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.
|
// 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
|
// Get the size of the file that we opened
|
||||||
if (fseek(file, 0, SEEK_END) != 0)
|
size_t size = Platform::FileLength(file);
|
||||||
{
|
|
||||||
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
|
|
||||||
|
|
||||||
// Allocate exactly as much memory as we need for the savestate
|
// Allocate exactly as much memory as we need for the savestate
|
||||||
std::vector<u8> buffer(size);
|
std::vector<u8> 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...
|
{ // 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());
|
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;
|
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
|
// Get ready to load the state from the buffer into the emulator
|
||||||
std::unique_ptr<Savestate> state = std::make_unique<Savestate>(buffer.data(), size, false);
|
std::unique_ptr<Savestate> state = std::make_unique<Savestate>(buffer.data(), size, false);
|
||||||
|
@ -666,7 +661,7 @@ bool EmuInstance::loadState(const std::string& filename)
|
||||||
|
|
||||||
bool EmuInstance::saveState(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 (file == nullptr)
|
||||||
{ // If the file couldn't be opened...
|
{ // If the file couldn't be opened...
|
||||||
|
@ -676,7 +671,7 @@ bool EmuInstance::saveState(const std::string& filename)
|
||||||
Savestate state;
|
Savestate state;
|
||||||
if (state.Error)
|
if (state.Error)
|
||||||
{ // If there was an error creating the state (and allocating its memory)...
|
{ // If there was an error creating the state (and allocating its memory)...
|
||||||
fclose(file);
|
Platform::CloseFile(file);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -685,22 +680,22 @@ bool EmuInstance::saveState(const std::string& filename)
|
||||||
|
|
||||||
if (state.Error)
|
if (state.Error)
|
||||||
{
|
{
|
||||||
fclose(file);
|
Platform::CloseFile(file);
|
||||||
return false;
|
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...
|
{ // Write the Savestate buffer to the file. If that fails...
|
||||||
Platform::Log(Platform::Error,
|
Platform::Log(Platform::Error,
|
||||||
"Failed to write %d-byte savestate to %s\n",
|
"Failed to write %d-byte savestate to %s\n",
|
||||||
state.Length(),
|
state.Length(),
|
||||||
filename.c_str()
|
filename.c_str()
|
||||||
);
|
);
|
||||||
fclose(file);
|
Platform::CloseFile(file);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(file);
|
Platform::CloseFile(file);
|
||||||
|
|
||||||
if (globalCfg.GetBool("Savestate.RelocSRAM") && ndsSave)
|
if (globalCfg.GetBool("Savestate.RelocSRAM") && ndsSave)
|
||||||
{
|
{
|
||||||
|
@ -733,12 +728,8 @@ void EmuInstance::undoStateLoad()
|
||||||
|
|
||||||
void EmuInstance::unloadCheats()
|
void EmuInstance::unloadCheats()
|
||||||
{
|
{
|
||||||
if (cheatFile)
|
cheatFile = nullptr; // cleaned up by unique_ptr
|
||||||
{
|
nds->AREngine.Cheats.clear();
|
||||||
delete cheatFile;
|
|
||||||
cheatFile = nullptr;
|
|
||||||
nds->AREngine.SetCodeFile(nullptr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuInstance::loadCheats()
|
void EmuInstance::loadCheats()
|
||||||
|
@ -748,9 +739,16 @@ void EmuInstance::loadCheats()
|
||||||
std::string filename = getAssetPath(false, globalCfg.GetString("CheatFilePath"), ".mch");
|
std::string filename = getAssetPath(false, globalCfg.GetString("CheatFilePath"), ".mch");
|
||||||
|
|
||||||
// TODO: check for error (malformed cheat file, ...)
|
// TODO: check for error (malformed cheat file, ...)
|
||||||
cheatFile = new ARCodeFile(filename);
|
cheatFile = std::make_unique<ARCodeFile>(filename);
|
||||||
|
|
||||||
nds->AREngine.SetCodeFile(cheatsOn ? cheatFile : nullptr);
|
if (cheatsOn)
|
||||||
|
{
|
||||||
|
nds->AREngine.Cheats = cheatFile->GetCodes();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nds->AREngine.Cheats.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ARM9BIOSImage> EmuInstance::loadARM9BIOS() noexcept
|
std::unique_ptr<ARM9BIOSImage> EmuInstance::loadARM9BIOS() noexcept
|
||||||
|
@ -1060,12 +1058,14 @@ void EmuInstance::enableCheats(bool enable)
|
||||||
{
|
{
|
||||||
cheatsOn = enable;
|
cheatsOn = enable;
|
||||||
if (cheatFile)
|
if (cheatFile)
|
||||||
nds->AREngine.SetCodeFile(cheatsOn ? cheatFile : nullptr);
|
nds->AREngine.Cheats = cheatFile->GetCodes();
|
||||||
|
else
|
||||||
|
nds->AREngine.Cheats.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
ARCodeFile* EmuInstance::getCheatFile()
|
ARCodeFile* EmuInstance::getCheatFile()
|
||||||
{
|
{
|
||||||
return cheatFile;
|
return cheatFile.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuInstance::setBatteryLevels()
|
void EmuInstance::setBatteryLevels()
|
||||||
|
@ -1150,7 +1150,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
|
||||||
};
|
};
|
||||||
auto jitargs = jitopt.GetBool("Enable") ? std::make_optional(_jitargs) : std::nullopt;
|
auto jitargs = jitopt.GetBool("Enable") ? std::make_optional(_jitargs) : std::nullopt;
|
||||||
#else
|
#else
|
||||||
optional<JITArgs> jitargs = std::nullopt;
|
std::optional<JITArgs> jitargs = std::nullopt;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef GDBSTUB_ENABLED
|
#ifdef GDBSTUB_ENABLED
|
||||||
|
@ -1301,7 +1301,7 @@ void EmuInstance::reset()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
newsave = kWifiSettingsPath + instanceFileSuffix();
|
newsave = GetLocalFilePath(kWifiSettingsPath + instanceFileSuffix());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldsave != newsave)
|
if (oldsave != newsave)
|
||||||
|
|
|
@ -129,6 +129,8 @@ public:
|
||||||
void inputInit();
|
void inputInit();
|
||||||
void inputDeInit();
|
void inputDeInit();
|
||||||
void inputLoadConfig();
|
void inputLoadConfig();
|
||||||
|
void inputRumbleStart(melonDS::u32 len_ms);
|
||||||
|
void inputRumbleStop();
|
||||||
|
|
||||||
void setJoystick(int id);
|
void setJoystick(int id);
|
||||||
int getJoystickID() { return joystickID; }
|
int getJoystickID() { return joystickID; }
|
||||||
|
@ -258,7 +260,7 @@ private:
|
||||||
bool savestateLoaded;
|
bool savestateLoaded;
|
||||||
std::string previousSaveFile;
|
std::string previousSaveFile;
|
||||||
|
|
||||||
melonDS::ARCodeFile* cheatFile;
|
std::unique_ptr<melonDS::ARCodeFile> cheatFile;
|
||||||
bool cheatsOn;
|
bool cheatsOn;
|
||||||
|
|
||||||
SDL_AudioDeviceID audioDevice;
|
SDL_AudioDeviceID audioDevice;
|
||||||
|
@ -295,6 +297,9 @@ private:
|
||||||
|
|
||||||
int joystickID;
|
int joystickID;
|
||||||
SDL_Joystick* joystick;
|
SDL_Joystick* joystick;
|
||||||
|
SDL_GameController* controller;
|
||||||
|
bool hasRumble = false;
|
||||||
|
bool isRumbling = false;
|
||||||
|
|
||||||
melonDS::u32 keyInputMask, joyInputMask;
|
melonDS::u32 keyInputMask, joyInputMask;
|
||||||
melonDS::u32 keyHotkeyMask, joyHotkeyMask;
|
melonDS::u32 keyHotkeyMask, joyHotkeyMask;
|
||||||
|
|
|
@ -72,6 +72,9 @@ void EmuInstance::inputInit()
|
||||||
lastHotkeyMask = 0;
|
lastHotkeyMask = 0;
|
||||||
|
|
||||||
joystick = nullptr;
|
joystick = nullptr;
|
||||||
|
controller = nullptr;
|
||||||
|
hasRumble = false;
|
||||||
|
isRumbling = false;
|
||||||
inputLoadConfig();
|
inputLoadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +103,24 @@ void EmuInstance::inputLoadConfig()
|
||||||
setJoystick(localCfg.GetInt("JoystickID"));
|
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)
|
void EmuInstance::setJoystick(int id)
|
||||||
{
|
{
|
||||||
|
@ -109,12 +130,16 @@ void EmuInstance::setJoystick(int id)
|
||||||
|
|
||||||
void EmuInstance::openJoystick()
|
void EmuInstance::openJoystick()
|
||||||
{
|
{
|
||||||
|
if (controller) SDL_GameControllerClose(controller);
|
||||||
|
|
||||||
if (joystick) SDL_JoystickClose(joystick);
|
if (joystick) SDL_JoystickClose(joystick);
|
||||||
|
|
||||||
int num = SDL_NumJoysticks();
|
int num = SDL_NumJoysticks();
|
||||||
if (num < 1)
|
if (num < 1)
|
||||||
{
|
{
|
||||||
|
controller = nullptr;
|
||||||
joystick = nullptr;
|
joystick = nullptr;
|
||||||
|
hasRumble = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,10 +147,30 @@ void EmuInstance::openJoystick()
|
||||||
joystickID = 0;
|
joystickID = 0;
|
||||||
|
|
||||||
joystick = SDL_JoystickOpen(joystickID);
|
joystick = SDL_JoystickOpen(joystickID);
|
||||||
|
|
||||||
|
if (SDL_IsGameController(joystickID))
|
||||||
|
{
|
||||||
|
controller = SDL_GameControllerOpen(joystickID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller)
|
||||||
|
{
|
||||||
|
if (SDL_GameControllerHasRumble(controller))
|
||||||
|
{
|
||||||
|
hasRumble = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuInstance::closeJoystick()
|
void EmuInstance::closeJoystick()
|
||||||
{
|
{
|
||||||
|
if (controller)
|
||||||
|
{
|
||||||
|
SDL_GameControllerClose(controller);
|
||||||
|
controller = nullptr;
|
||||||
|
hasRumble = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (joystick)
|
if (joystick)
|
||||||
{
|
{
|
||||||
SDL_JoystickClose(joystick);
|
SDL_JoystickClose(joystick);
|
||||||
|
|
|
@ -72,8 +72,6 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent
|
||||||
|
|
||||||
ui->txtMAC->setText(firmcfg.GetQString("MAC"));
|
ui->txtMAC->setText(firmcfg.GetQString("MAC"));
|
||||||
|
|
||||||
on_overrideFirmwareBox_toggled();
|
|
||||||
|
|
||||||
int inst = emuInstance->getInstanceID();
|
int inst = emuInstance->getInstanceID();
|
||||||
if (inst > 0)
|
if (inst > 0)
|
||||||
ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1));
|
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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -125,7 +125,6 @@ private slots:
|
||||||
void done(int r);
|
void done(int r);
|
||||||
|
|
||||||
void on_cbxBirthdayMonth_currentIndexChanged(int idx);
|
void on_cbxBirthdayMonth_currentIndexChanged(int idx);
|
||||||
void on_overrideFirmwareBox_toggled();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool verifyMAC();
|
bool verifyMAC();
|
||||||
|
|
|
@ -47,7 +47,8 @@
|
||||||
#endif // __WIN32__
|
#endif // __WIN32__
|
||||||
|
|
||||||
extern CameraManager* camManager[2];
|
extern CameraManager* camManager[2];
|
||||||
|
extern melonDS::LocalMP localMp;
|
||||||
|
extern melonDS::Net net;
|
||||||
|
|
||||||
namespace melonDS::Platform
|
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)
|
void MP_Begin(void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
LocalMP::Begin(inst);
|
localMp.Begin(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP_End(void* userdata)
|
void MP_End(void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
LocalMP::End(inst);
|
localMp.End(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata)
|
int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
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 MP_RecvPacket(u8* data, u64* timestamp, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
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 MP_SendCmd(u8* data, int len, u64 timestamp, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
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 MP_SendReply(u8* data, int len, u64 timestamp, u16 aid, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
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 MP_SendAck(u8* data, int len, u64 timestamp, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
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 MP_RecvHostPacket(u8* data, u64* timestamp, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
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)
|
u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
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 Net_SendPacket(u8* data, int len, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
Net::SendPacket(data, len, inst);
|
net.SendPacket(data, len, inst);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Net_RecvPacket(u8* data, void* userdata)
|
int Net_RecvPacket(u8* data, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
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);
|
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)
|
DynamicLibrary* DynamicLibrary_Load(const char* lib)
|
||||||
{
|
{
|
||||||
return (DynamicLibrary*) SDL_LoadObject(lib);
|
return (DynamicLibrary*) SDL_LoadObject(lib);
|
||||||
|
|
|
@ -276,8 +276,13 @@ void ScreenPanel::tabletEvent(QTabletEvent* event)
|
||||||
case QEvent::TabletPress:
|
case QEvent::TabletPress:
|
||||||
case QEvent::TabletMove:
|
case QEvent::TabletMove:
|
||||||
{
|
{
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
|
int x = event->position().x();
|
||||||
|
int y = event->position().y();
|
||||||
|
#else
|
||||||
int x = event->x();
|
int x = event->x();
|
||||||
int y = event->y();
|
int y = event->y();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (layout.GetTouchCoords(x, y, event->type()==QEvent::TabletMove))
|
if (layout.GetTouchCoords(x, y, event->type()==QEvent::TabletMove))
|
||||||
{
|
{
|
||||||
|
@ -302,8 +307,10 @@ void ScreenPanel::tabletEvent(QTabletEvent* event)
|
||||||
|
|
||||||
void ScreenPanel::touchEvent(QTouchEvent* event)
|
void ScreenPanel::touchEvent(QTouchEvent* event)
|
||||||
{
|
{
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
if (event->device()->type() == QInputDevice::DeviceType::TouchPad)
|
if (event->device()->type() == QInputDevice::DeviceType::TouchPad)
|
||||||
return;
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
event->accept();
|
event->accept();
|
||||||
|
|
||||||
|
@ -311,9 +318,15 @@ void ScreenPanel::touchEvent(QTouchEvent* event)
|
||||||
{
|
{
|
||||||
case QEvent::TouchBegin:
|
case QEvent::TouchBegin:
|
||||||
case QEvent::TouchUpdate:
|
case QEvent::TouchUpdate:
|
||||||
|
#if QT_VERSION_MAJOR == 6
|
||||||
if (event->points().length() > 0)
|
if (event->points().length() > 0)
|
||||||
{
|
{
|
||||||
QPointF lastPosition = event->points().first().lastPosition();
|
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 x = (int)lastPosition.x();
|
||||||
int y = (int)lastPosition.y();
|
int y = (int)lastPosition.y();
|
||||||
|
|
||||||
|
@ -1096,7 +1109,7 @@ std::optional<WindowInfo> ScreenPanelGL::getWindowInfo()
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//qCritical() << "Unknown PNI platform " << platform_name;
|
//qCritical() << "Unknown PNI platform " << platform_name;
|
||||||
Platform::Log(LogLevel::Error, "Unknown PNI platform %s\n", platform_name.toStdString().c_str());
|
Platform::Log(Platform::LogLevel::Error, "Unknown PNI platform %s\n", platform_name.toStdString().c_str());
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -303,7 +303,7 @@ void TitleManagerDialog::onImportTitleData()
|
||||||
|
|
||||||
if (file.isEmpty()) return;
|
if (file.isEmpty()) return;
|
||||||
|
|
||||||
FILE* f = fopen(file.toStdString().c_str(), "rb");
|
Platform::FileHandle* f = Platform::OpenFile(file.toStdString(), Platform::Read);
|
||||||
if (!f)
|
if (!f)
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this,
|
QMessageBox::critical(this,
|
||||||
|
@ -312,9 +312,8 @@ void TitleManagerDialog::onImportTitleData()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fseek(f, 0, SEEK_END);
|
u64 len = Platform::FileLength(f);
|
||||||
u64 len = ftell(f);
|
Platform::CloseFile(f);
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
if (len != wantedsize)
|
if (len != wantedsize)
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,11 +37,14 @@
|
||||||
#define PCAP_NAME "libpcap"
|
#define PCAP_NAME "libpcap"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
extern std::optional<melonDS::LibPCap> pcap;
|
||||||
|
extern melonDS::Net net;
|
||||||
|
|
||||||
WifiSettingsDialog* WifiSettingsDialog::currentDlg = nullptr;
|
WifiSettingsDialog* WifiSettingsDialog::currentDlg = nullptr;
|
||||||
|
|
||||||
bool WifiSettingsDialog::needsReset = false;
|
bool WifiSettingsDialog::needsReset = false;
|
||||||
|
|
||||||
|
void NetInit();
|
||||||
|
|
||||||
WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::WifiSettingsDialog)
|
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();
|
emuInstance = ((MainWindow*)parent)->getEmuInstance();
|
||||||
auto& cfg = emuInstance->getGlobalConfig();
|
auto& cfg = emuInstance->getGlobalConfig();
|
||||||
|
|
||||||
Net::DeInit();
|
if (!pcap)
|
||||||
haspcap = Net_PCap::InitAdapterList();
|
pcap = melonDS::LibPCap::New();
|
||||||
|
|
||||||
|
haspcap = pcap.has_value();
|
||||||
|
if (pcap)
|
||||||
|
adapters = pcap->GetAdapters();
|
||||||
|
|
||||||
ui->rbDirectMode->setText("Direct mode (requires " PCAP_NAME " and ethernet connection)");
|
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)");
|
ui->lblAdapterIP->setText("(none)");
|
||||||
|
|
||||||
int sel = 0;
|
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;
|
sel = i;
|
||||||
}
|
}
|
||||||
ui->cbxDirectAdapter->setCurrentIndex(sel);
|
ui->cbxDirectAdapter->setCurrentIndex(sel);
|
||||||
|
@ -96,24 +103,23 @@ void WifiSettingsDialog::done(int r)
|
||||||
cfg.SetBool("LAN.DirectMode", ui->rbDirectMode->isChecked());
|
cfg.SetBool("LAN.DirectMode", ui->rbDirectMode->isChecked());
|
||||||
|
|
||||||
int sel = ui->cbxDirectAdapter->currentIndex();
|
int sel = ui->cbxDirectAdapter->currentIndex();
|
||||||
if (sel < 0 || sel >= Net_PCap::NumAdapters) sel = 0;
|
if (sel < 0 || sel >= adapters.size()) sel = 0;
|
||||||
if (Net_PCap::NumAdapters < 1)
|
if (adapters.empty())
|
||||||
{
|
{
|
||||||
cfg.SetString("LAN.Device", "");
|
cfg.SetString("LAN.Device", "");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cfg.SetString("LAN.Device", Net_PCap::Adapters[sel].DeviceName);
|
cfg.SetString("LAN.Device", adapters[sel].DeviceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Config::Save();
|
Config::Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Net_PCap::DeInit();
|
|
||||||
Config::Table cfg = Config::GetGlobalTable();
|
Config::Table cfg = Config::GetGlobalTable();
|
||||||
bool direct = cfg.GetBool("LAN.DirectMode");
|
|
||||||
std::string devicename = cfg.GetString("LAN.Device");
|
std::string devicename = cfg.GetString("LAN.Device");
|
||||||
Net::Init(direct, devicename.c_str());
|
|
||||||
|
NetInit();
|
||||||
|
|
||||||
QDialog::done(r);
|
QDialog::done(r);
|
||||||
|
|
||||||
|
@ -134,10 +140,9 @@ void WifiSettingsDialog::on_cbxDirectAdapter_currentIndexChanged(int sel)
|
||||||
{
|
{
|
||||||
if (!haspcap) return;
|
if (!haspcap) return;
|
||||||
|
|
||||||
if (sel < 0 || sel >= Net_PCap::NumAdapters) return;
|
if (sel < 0 || sel >= adapters.size() || adapters.empty()) return;
|
||||||
if (Net_PCap::NumAdapters < 1) return;
|
|
||||||
|
|
||||||
Net_PCap::AdapterData* adapter = &Net_PCap::Adapters[sel];
|
melonDS::AdapterData* adapter = &adapters[sel];
|
||||||
char tmp[64];
|
char tmp[64];
|
||||||
|
|
||||||
sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X",
|
sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
#define WIFISETTINGSDIALOG_H
|
#define WIFISETTINGSDIALOG_H
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <vector>
|
||||||
|
#include "Net_PCap.h"
|
||||||
|
|
||||||
namespace Ui { class WifiSettingsDialog; }
|
namespace Ui { class WifiSettingsDialog; }
|
||||||
class WifiSettingsDialog;
|
class WifiSettingsDialog;
|
||||||
|
@ -68,6 +70,7 @@ private:
|
||||||
bool haspcap;
|
bool haspcap;
|
||||||
|
|
||||||
void updateAdapterControls();
|
void updateAdapterControls();
|
||||||
|
std::vector<melonDS::AdapterData> adapters;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WIFISETTINGSDIALOG_H
|
#endif // WIFISETTINGSDIALOG_H
|
||||||
|
|
|
@ -88,6 +88,7 @@ using namespace melonDS;
|
||||||
|
|
||||||
extern CameraManager* camManager[2];
|
extern CameraManager* camManager[2];
|
||||||
extern bool camStarted[2];
|
extern bool camStarted[2];
|
||||||
|
extern LocalMP localMp;
|
||||||
|
|
||||||
|
|
||||||
QString NdsRomMimeType = "application/x-nintendo-ds-rom";
|
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] = submenu->addAction("Memory expansion");
|
||||||
actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion));
|
actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion));
|
||||||
connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon);
|
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");
|
actEjectGBACart = menu->addAction("Eject cart");
|
||||||
|
@ -1881,7 +1886,7 @@ void MainWindow::onMPSettingsFinished(int res)
|
||||||
{
|
{
|
||||||
emuInstance->mpAudioMode = globalCfg.GetInt("MP.AudioMode");
|
emuInstance->mpAudioMode = globalCfg.GetInt("MP.AudioMode");
|
||||||
emuInstance->audioMute();
|
emuInstance->audioMute();
|
||||||
LocalMP::SetRecvTimeout(globalCfg.GetInt("MP.RecvTimeout"));
|
localMp.SetRecvTimeout(globalCfg.GetInt("MP.RecvTimeout"));
|
||||||
|
|
||||||
emuThread->emuUnpause();
|
emuThread->emuUnpause();
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,7 +265,7 @@ public:
|
||||||
QAction* actEjectCart;
|
QAction* actEjectCart;
|
||||||
QAction* actCurrentGBACart;
|
QAction* actCurrentGBACart;
|
||||||
QAction* actInsertGBACart;
|
QAction* actInsertGBACart;
|
||||||
QAction* actInsertGBAAddon[1];
|
QAction* actInsertGBAAddon[2];
|
||||||
QAction* actEjectGBACart;
|
QAction* actEjectGBACart;
|
||||||
QAction* actImportSavefile;
|
QAction* actImportSavefile;
|
||||||
QAction* actSaveState[9];
|
QAction* actSaveState[9];
|
||||||
|
|
|
@ -79,6 +79,9 @@
|
||||||
|
|
||||||
#include "CLI.h"
|
#include "CLI.h"
|
||||||
|
|
||||||
|
#include "Net_PCap.h"
|
||||||
|
#include "Net_Slirp.h"
|
||||||
|
|
||||||
using namespace melonDS;
|
using namespace melonDS;
|
||||||
|
|
||||||
QString* systemThemeName;
|
QString* systemThemeName;
|
||||||
|
@ -92,6 +95,38 @@ EmuInstance* emuInstances[kMaxEmuInstances];
|
||||||
|
|
||||||
CameraManager* camManager[2];
|
CameraManager* camManager[2];
|
||||||
bool camStarted[2];
|
bool camStarted[2];
|
||||||
|
LocalMP localMp;
|
||||||
|
std::optional<LibPCap> 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<Net_PCap> 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<Net_Slirp>([](const u8* data, int len) {
|
||||||
|
net.RXEnqueue(data, len);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool createEmuInstance()
|
bool createEmuInstance()
|
||||||
|
@ -286,13 +321,8 @@ int main(int argc, char** argv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalMP::Init();
|
// localMp is initialized at this point
|
||||||
{
|
NetInit();
|
||||||
Config::Table cfg = Config::GetGlobalTable();
|
|
||||||
bool direct = cfg.GetBool("LAN.DirectMode");
|
|
||||||
std::string devicename = cfg.GetString("LAN.Device");
|
|
||||||
Net::Init(direct, devicename.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
createEmuInstance();
|
createEmuInstance();
|
||||||
|
|
||||||
|
@ -329,9 +359,6 @@ int main(int argc, char** argv)
|
||||||
// but with this we make extra sure they are all deleted
|
// but with this we make extra sure they are all deleted
|
||||||
deleteAllEmuInstances();
|
deleteAllEmuInstances();
|
||||||
|
|
||||||
LocalMP::DeInit();
|
|
||||||
Net::DeInit();
|
|
||||||
|
|
||||||
delete camManager[0];
|
delete camManager[0];
|
||||||
delete camManager[1];
|
delete camManager[1];
|
||||||
|
|
||||||
|
@ -347,6 +374,12 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdshow)
|
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);
|
int ret = main(__argc, __argv);
|
||||||
|
|
||||||
printf("\n\n>");
|
printf("\n\n>");
|
||||||
|
|
|
@ -16,5 +16,6 @@ if (USE_SYSTEM_LIBSLIRP)
|
||||||
target_link_libraries(net-utils PRIVATE PkgConfig::Slirp)
|
target_link_libraries(net-utils PRIVATE PkgConfig::Slirp)
|
||||||
else()
|
else()
|
||||||
add_subdirectory(libslirp EXCLUDE_FROM_ALL)
|
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)
|
target_link_libraries(net-utils PRIVATE slirp)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -28,99 +28,31 @@ using namespace melonDS::Platform;
|
||||||
using Platform::Log;
|
using Platform::Log;
|
||||||
using Platform::LogLevel;
|
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(MPPacketQueue, 0, kPacketQueueSize);
|
||||||
memset(MPReplyQueue, 0, kReplyQueueSize);
|
memset(MPReplyQueue, 0, kReplyQueueSize);
|
||||||
memset(&MPStatus, 0, sizeof(MPStatus));
|
memset(&MPStatus, 0, sizeof(MPStatus));
|
||||||
memset(PacketReadOffset, 0, sizeof(PacketReadOffset));
|
memset(PacketReadOffset, 0, sizeof(PacketReadOffset));
|
||||||
memset(ReplyReadOffset, 0, sizeof(ReplyReadOffset));
|
memset(ReplyReadOffset, 0, sizeof(ReplyReadOffset));
|
||||||
|
|
||||||
Mutex_Unlock(MPQueueLock);
|
|
||||||
|
|
||||||
// prepare semaphores
|
// prepare semaphores
|
||||||
// semaphores 0-15: regular frames; semaphore I is posted when instance I needs to process a new frame
|
// 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
|
// semaphores 16-31: MP replies; semaphore I is posted when instance I needs to process a new MP reply
|
||||||
|
|
||||||
SemPoolInit();
|
for (int i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
LastHostID = -1;
|
SemPool[i] = Semaphore_Create();
|
||||||
|
}
|
||||||
|
|
||||||
Log(LogLevel::Info, "MP comm init OK\n");
|
Log(LogLevel::Info, "MP comm init OK\n");
|
||||||
|
|
||||||
RecvTimeout = 25;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeInit()
|
LocalMP::~LocalMP() noexcept
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 32; i++)
|
for (int i = 0; i < 32; i++)
|
||||||
{
|
{
|
||||||
|
@ -131,30 +63,25 @@ void DeInit()
|
||||||
Mutex_Free(MPQueueLock);
|
Mutex_Free(MPQueueLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetRecvTimeout(int timeout)
|
void LocalMP::Begin(int inst)
|
||||||
{
|
|
||||||
RecvTimeout = timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Begin(int inst)
|
|
||||||
{
|
{
|
||||||
Mutex_Lock(MPQueueLock);
|
Mutex_Lock(MPQueueLock);
|
||||||
PacketReadOffset[inst] = MPStatus.PacketWriteOffset;
|
PacketReadOffset[inst] = MPStatus.PacketWriteOffset;
|
||||||
ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset;
|
ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset;
|
||||||
SemReset(inst);
|
Semaphore_Reset(SemPool[inst]);
|
||||||
SemReset(16+inst);
|
Semaphore_Reset(SemPool[16 + inst]);
|
||||||
MPStatus.ConnectedBitmask |= (1 << inst);
|
MPStatus.ConnectedBitmask |= (1 << inst);
|
||||||
Mutex_Unlock(MPQueueLock);
|
Mutex_Unlock(MPQueueLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void End(int inst)
|
void LocalMP::End(int inst)
|
||||||
{
|
{
|
||||||
Mutex_Lock(MPQueueLock);
|
Mutex_Lock(MPQueueLock);
|
||||||
MPStatus.ConnectedBitmask &= ~(1 << inst);
|
MPStatus.ConnectedBitmask &= ~(1 << inst);
|
||||||
Mutex_Unlock(MPQueueLock);
|
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;
|
u8* data;
|
||||||
|
|
||||||
|
@ -189,7 +116,7 @@ void FIFORead(int inst, int fifo, void* buf, int len)
|
||||||
else ReplyReadOffset[inst] = offset;
|
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;
|
u8* data;
|
||||||
|
|
||||||
|
@ -224,7 +151,7 @@ void FIFOWrite(int inst, int fifo, void* buf, int len)
|
||||||
else MPStatus.ReplyWriteOffset = offset;
|
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)
|
if (len > kMaxFrameSize)
|
||||||
{
|
{
|
||||||
|
@ -258,7 +185,7 @@ int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp)
|
||||||
MPStatus.MPHostinst = inst;
|
MPStatus.MPHostinst = inst;
|
||||||
MPStatus.MPReplyBitmask = 0;
|
MPStatus.MPReplyBitmask = 0;
|
||||||
ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset;
|
ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset;
|
||||||
SemReset(16 + inst);
|
Semaphore_Reset(SemPool[16 + inst]);
|
||||||
}
|
}
|
||||||
else if (type == 2)
|
else if (type == 2)
|
||||||
{
|
{
|
||||||
|
@ -269,39 +196,39 @@ int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp)
|
||||||
|
|
||||||
if (type == 2)
|
if (type == 2)
|
||||||
{
|
{
|
||||||
SemPost(16 + MPStatus.MPHostinst);
|
Semaphore_Post(SemPool[16 + MPStatus.MPHostinst]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 16; i++)
|
for (int i = 0; i < 16; i++)
|
||||||
{
|
{
|
||||||
if (mask & (1<<i))
|
if (mask & (1<<i))
|
||||||
SemPost(i);
|
Semaphore_Post(SemPool[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RecvPacketGeneric(int inst, u8* packet, bool block, u64* timestamp)
|
int LocalMP::RecvPacketGeneric(int inst, u8* packet, bool block, u64* timestamp) noexcept
|
||||||
{
|
{
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (!SemWait(inst, block ? RecvTimeout : 0))
|
if (!Semaphore_TryWait(SemPool[inst], block ? RecvTimeout : 0))
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mutex_Lock(MPQueueLock);
|
Mutex_Lock(MPQueueLock);
|
||||||
|
|
||||||
MPPacketHeader pktheader;
|
MPPacketHeader pktheader = {};
|
||||||
FIFORead(inst, 0, &pktheader, sizeof(pktheader));
|
FIFORead(inst, 0, &pktheader, sizeof(pktheader));
|
||||||
|
|
||||||
if (pktheader.Magic != 0x4946494E)
|
if (pktheader.Magic != 0x4946494E)
|
||||||
{
|
{
|
||||||
Log(LogLevel::Warn, "PACKET FIFO OVERFLOW\n");
|
Log(LogLevel::Warn, "PACKET FIFO OVERFLOW\n");
|
||||||
PacketReadOffset[inst] = MPStatus.PacketWriteOffset;
|
PacketReadOffset[inst] = MPStatus.PacketWriteOffset;
|
||||||
SemReset(inst);
|
Semaphore_Reset(SemPool[inst]);
|
||||||
Mutex_Unlock(MPQueueLock);
|
Mutex_Unlock(MPQueueLock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -331,33 +258,32 @@ int RecvPacketGeneric(int inst, u8* packet, bool block, u64* timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int SendPacket(int inst, u8* packet, int len, u64 timestamp)
|
int LocalMP::SendPacket(int inst, u8* packet, int len, u64 timestamp)
|
||||||
{
|
{
|
||||||
return SendPacketGeneric(inst, 0, packet, len, timestamp);
|
return SendPacketGeneric(inst, 0, packet, len, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int RecvPacket(int inst, u8* packet, u64* timestamp)
|
int LocalMP::RecvPacket(int inst, u8* packet, u64* timestamp)
|
||||||
{
|
{
|
||||||
return RecvPacketGeneric(inst, packet, false, timestamp);
|
return RecvPacketGeneric(inst, packet, false, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int LocalMP::SendCmd(int inst, u8* packet, int len, u64 timestamp)
|
||||||
int SendCmd(int inst, u8* packet, int len, u64 timestamp)
|
|
||||||
{
|
{
|
||||||
return SendPacketGeneric(inst, 1, packet, len, timestamp);
|
return SendPacketGeneric(inst, 1, packet, len, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SendReply(int inst, u8* packet, int len, u64 timestamp, u16 aid)
|
int LocalMP::SendReply(int inst, u8* packet, int len, u64 timestamp, u16 aid)
|
||||||
{
|
{
|
||||||
return SendPacketGeneric(inst, 2 | (aid<<16), packet, len, timestamp);
|
return SendPacketGeneric(inst, 2 | (aid<<16), packet, len, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SendAck(int inst, u8* packet, int len, u64 timestamp)
|
int LocalMP::SendAck(int inst, u8* packet, int len, u64 timestamp)
|
||||||
{
|
{
|
||||||
return SendPacketGeneric(inst, 3, packet, len, timestamp);
|
return SendPacketGeneric(inst, 3, packet, len, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int RecvHostPacket(int inst, u8* packet, u64* timestamp)
|
int LocalMP::RecvHostPacket(int inst, u8* packet, u64* timestamp)
|
||||||
{
|
{
|
||||||
if (LastHostID != -1)
|
if (LastHostID != -1)
|
||||||
{
|
{
|
||||||
|
@ -372,7 +298,7 @@ int RecvHostPacket(int inst, u8* packet, u64* timestamp)
|
||||||
return RecvPacketGeneric(inst, packet, true, timestamp);
|
return RecvPacketGeneric(inst, packet, true, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
u16 RecvReplies(int inst, u8* packets, u64 timestamp, u16 aidmask)
|
u16 LocalMP::RecvReplies(int inst, u8* packets, u64 timestamp, u16 aidmask)
|
||||||
{
|
{
|
||||||
u16 ret = 0;
|
u16 ret = 0;
|
||||||
u16 myinstmask = (1 << inst);
|
u16 myinstmask = (1 << inst);
|
||||||
|
@ -386,7 +312,7 @@ u16 RecvReplies(int inst, u8* packets, u64 timestamp, u16 aidmask)
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (!SemWait(16+inst, RecvTimeout))
|
if (!Semaphore_TryWait(SemPool[16+inst], RecvTimeout))
|
||||||
{
|
{
|
||||||
// no more replies available
|
// no more replies available
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -394,14 +320,14 @@ u16 RecvReplies(int inst, u8* packets, u64 timestamp, u16 aidmask)
|
||||||
|
|
||||||
Mutex_Lock(MPQueueLock);
|
Mutex_Lock(MPQueueLock);
|
||||||
|
|
||||||
MPPacketHeader pktheader;
|
MPPacketHeader pktheader = {};
|
||||||
FIFORead(inst, 1, &pktheader, sizeof(pktheader));
|
FIFORead(inst, 1, &pktheader, sizeof(pktheader));
|
||||||
|
|
||||||
if (pktheader.Magic != 0x4946494E)
|
if (pktheader.Magic != 0x4946494E)
|
||||||
{
|
{
|
||||||
Log(LogLevel::Warn, "REPLY FIFO OVERFLOW\n");
|
Log(LogLevel::Warn, "REPLY FIFO OVERFLOW\n");
|
||||||
ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset;
|
ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset;
|
||||||
SemReset(16+inst);
|
Semaphore_Reset(SemPool[16 + inst]);
|
||||||
Mutex_Unlock(MPQueueLock);
|
Mutex_Unlock(MPQueueLock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,27 +20,72 @@
|
||||||
#define LOCALMP_H
|
#define LOCALMP_H
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
#include "Platform.h"
|
||||||
|
|
||||||
namespace LocalMP
|
namespace melonDS
|
||||||
{
|
{
|
||||||
|
struct MPStatusData
|
||||||
|
{
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
using namespace melonDS;
|
struct MPPacketHeader
|
||||||
bool Init();
|
{
|
||||||
void DeInit();
|
u32 Magic;
|
||||||
|
u32 SenderID;
|
||||||
|
u32 Type; // 0=regular 1=CMD 2=reply 3=ack
|
||||||
|
u32 Length;
|
||||||
|
u64 Timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
void SetRecvTimeout(int timeout);
|
constexpr u32 kPacketQueueSize = 0x10000;
|
||||||
|
constexpr u32 kReplyQueueSize = 0x10000;
|
||||||
|
constexpr u32 kMaxFrameSize = 0x948;
|
||||||
|
|
||||||
void Begin(int inst);
|
class LocalMP
|
||||||
void End(int inst);
|
{
|
||||||
|
public:
|
||||||
|
LocalMP() noexcept;
|
||||||
|
LocalMP(const LocalMP&) = delete;
|
||||||
|
LocalMP& operator=(const LocalMP&) = delete;
|
||||||
|
LocalMP(LocalMP&& other) = delete;
|
||||||
|
LocalMP& operator=(LocalMP&& other) = delete;
|
||||||
|
~LocalMP() noexcept;
|
||||||
|
|
||||||
int SendPacket(int inst, u8* data, int len, u64 timestamp);
|
[[nodiscard]] int GetRecvTimeout() const noexcept { return RecvTimeout; }
|
||||||
int RecvPacket(int inst, u8* data, u64* timestamp);
|
void SetRecvTimeout(int timeout) noexcept { RecvTimeout = timeout; }
|
||||||
int SendCmd(int inst, u8* data, int len, u64 timestamp);
|
|
||||||
int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid);
|
|
||||||
int SendAck(int inst, u8* data, int len, u64 timestamp);
|
|
||||||
int RecvHostPacket(int inst, u8* data, u64* timestamp);
|
|
||||||
u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask);
|
|
||||||
|
|
||||||
|
void Begin(int inst);
|
||||||
|
void End(int inst);
|
||||||
|
|
||||||
|
int SendPacket(int inst, u8* data, int len, u64 timestamp);
|
||||||
|
int RecvPacket(int inst, u8* data, u64* timestamp);
|
||||||
|
int SendCmd(int inst, u8* data, int len, u64 timestamp);
|
||||||
|
int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid);
|
||||||
|
int SendAck(int inst, u8* data, int len, u64 timestamp);
|
||||||
|
int RecvHostPacket(int inst, u8* data, u64* timestamp);
|
||||||
|
u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask);
|
||||||
|
private:
|
||||||
|
void FIFORead(int inst, int fifo, void* buf, int len) noexcept;
|
||||||
|
void FIFOWrite(int inst, int fifo, void* buf, int len) noexcept;
|
||||||
|
int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp) noexcept;
|
||||||
|
int RecvPacketGeneric(int inst, u8* packet, bool block, u64* timestamp) noexcept;
|
||||||
|
Platform::Mutex* MPQueueLock;
|
||||||
|
MPStatusData MPStatus {};
|
||||||
|
u8 MPPacketQueue[kPacketQueueSize] {};
|
||||||
|
u8 MPReplyQueue[kReplyQueueSize] {};
|
||||||
|
u32 PacketReadOffset[16] {};
|
||||||
|
u32 ReplyReadOffset[16] {};
|
||||||
|
|
||||||
|
int RecvTimeout = 25;
|
||||||
|
|
||||||
|
int LastHostID = -1;
|
||||||
|
Platform::Semaphore* SemPool[32] {};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // LOCALMP_H
|
#endif // LOCALMP_H
|
||||||
|
|
|
@ -19,87 +19,46 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "Net.h"
|
#include "Net.h"
|
||||||
#include "Net_PCap.h"
|
|
||||||
#include "Net_Slirp.h"
|
|
||||||
#include "PacketDispatcher.h"
|
#include "PacketDispatcher.h"
|
||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
|
|
||||||
using namespace melonDS;
|
namespace melonDS
|
||||||
|
|
||||||
namespace Net
|
|
||||||
{
|
{
|
||||||
|
|
||||||
using Platform::Log;
|
using Platform::Log;
|
||||||
using Platform::LogLevel;
|
using Platform::LogLevel;
|
||||||
|
|
||||||
bool Inited = false;
|
void Net::RegisterInstance(int inst)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
Dispatcher.registerInstance(inst);
|
Dispatcher.registerInstance(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnregisterInstance(int inst)
|
void Net::UnregisterInstance(int inst)
|
||||||
{
|
{
|
||||||
Dispatcher.unregisterInstance(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);
|
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)
|
if (!Driver)
|
||||||
return Net_PCap::SendPacket(data, len);
|
return 0;
|
||||||
else
|
|
||||||
return Net_Slirp::SendPacket(data, len);
|
return Driver->SendPacket(data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
int RecvPacket(u8* data, int inst)
|
int Net::RecvPacket(u8* data, int inst)
|
||||||
{
|
{
|
||||||
if (DirectMode)
|
if (!Driver)
|
||||||
Net_PCap::RecvCheck();
|
return 0;
|
||||||
else
|
|
||||||
Net_Slirp::RecvCheck();
|
Driver->RecvCheck();
|
||||||
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
if (!Dispatcher.recvPacket(nullptr, nullptr, data, &ret, inst))
|
if (!Dispatcher.recvPacket(nullptr, nullptr, data, &ret, inst))
|
||||||
|
|
|
@ -19,26 +19,42 @@
|
||||||
#ifndef NET_H
|
#ifndef NET_H
|
||||||
#define NET_H
|
#define NET_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
#include "PacketDispatcher.h"
|
||||||
|
#include "NetDriver.h"
|
||||||
|
|
||||||
namespace Net
|
namespace melonDS
|
||||||
{
|
{
|
||||||
using namespace melonDS;
|
|
||||||
|
|
||||||
///
|
class Net
|
||||||
/// @param direct Whether to use direct or indirect mode
|
{
|
||||||
/// @param devicename The name of the network device to use; ignored if direct is false
|
public:
|
||||||
/// @return true if initialization succeeded
|
Net() noexcept = default;
|
||||||
bool Init(bool direct, const char* devicename);
|
Net(const Net&) = delete;
|
||||||
void DeInit();
|
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 RegisterInstance(int inst);
|
||||||
void UnregisterInstance(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 SendPacket(u8* data, int len, int inst);
|
||||||
int RecvPacket(u8* data, int inst);
|
int RecvPacket(u8* data, int inst);
|
||||||
|
|
||||||
|
void SetDriver(std::unique_ptr<NetDriver>&& driver) noexcept { Driver = std::move(driver); }
|
||||||
|
[[nodiscard]] std::unique_ptr<NetDriver>& GetDriver() noexcept { return Driver; }
|
||||||
|
[[nodiscard]] const std::unique_ptr<NetDriver>& GetDriver() const noexcept { return Driver; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
PacketDispatcher Dispatcher {};
|
||||||
|
std::unique_ptr<NetDriver> Driver = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -45,23 +45,7 @@ using Platform::LogLevel;
|
||||||
#define PCAP_OPENFLAG_PROMISCUOUS 1
|
#define PCAP_OPENFLAG_PROMISCUOUS 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace melonDS
|
||||||
#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
|
|
||||||
{
|
{
|
||||||
|
|
||||||
const char* PCapLibNames[] =
|
const char* PCapLibNames[] =
|
||||||
|
@ -80,96 +64,152 @@ const char* PCapLibNames[] =
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
AdapterData* Adapters = nullptr;
|
std::optional<LibPCap> LibPCap::New() noexcept
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
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)
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InitAdapterList()
|
|
||||||
{
|
|
||||||
NumAdapters = 0;
|
|
||||||
|
|
||||||
// TODO: how to deal with cases where an adapter is unplugged or changes config??
|
|
||||||
if (!PCapLib)
|
|
||||||
{
|
|
||||||
PCapLib = nullptr;
|
|
||||||
PCapAdapter = nullptr;
|
|
||||||
|
|
||||||
for (int i = 0; PCapLibNames[i]; i++)
|
for (int i = 0; PCapLibNames[i]; i++)
|
||||||
{
|
{
|
||||||
Platform::DynamicLibrary* lib = Platform::DynamicLibrary_Load(PCapLibNames[i]);
|
Platform::DynamicLibrary* lib = Platform::DynamicLibrary_Load(PCapLibNames[i]);
|
||||||
if (!lib) continue;
|
if (!lib) continue;
|
||||||
|
|
||||||
if (!TryLoadPCap(lib))
|
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<Platform::DynamicLibrary>(lib, Platform::DynamicLibrary_Unload);
|
||||||
|
|
||||||
|
if (!TryLoadPCap(pcap, lib))
|
||||||
{
|
{
|
||||||
Platform::DynamicLibrary_Unload(lib);
|
Platform::DynamicLibrary_Unload(lib);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log(LogLevel::Info, "PCap: lib %s, init successful\n", PCapLibNames[i]);
|
Log(LogLevel::Info, "PCap: lib %s, init successful\n", PCapLibNames[i]);
|
||||||
PCapLib = lib;
|
return pcap;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PCapLib == nullptr)
|
|
||||||
{
|
|
||||||
Log(LogLevel::Error, "PCap: init failed\n");
|
Log(LogLevel::Error, "PCap: init failed\n");
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AdapterData> LibPCap::GetAdapters() const noexcept
|
||||||
|
{
|
||||||
|
// TODO: how to deal with cases where an adapter is unplugged or changes config??
|
||||||
|
if (!IsValid())
|
||||||
|
{
|
||||||
|
Log(LogLevel::Error, "PCap: instance not initialized\n");
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
char errbuf[PCAP_ERRBUF_SIZE];
|
char errbuf[PCAP_ERRBUF_SIZE];
|
||||||
int ret;
|
|
||||||
|
|
||||||
pcap_if_t* alldevs;
|
pcap_if_t* alldevs = nullptr;
|
||||||
ret = pcap_findalldevs(&alldevs, errbuf);
|
if (int ret = findalldevs(&alldevs, errbuf); ret < 0)
|
||||||
if (ret < 0 || alldevs == nullptr)
|
{ // If there was an error...
|
||||||
{
|
errbuf[PCAP_ERRBUF_SIZE - 1] = '\0';
|
||||||
Log(LogLevel::Warn, "PCap: no devices available\n");
|
Log(LogLevel::Error, "PCap: Error %d finding devices: %s\n", ret, errbuf);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pcap_if_t* dev = alldevs;
|
if (alldevs == nullptr)
|
||||||
while (dev) { NumAdapters++; dev = dev->next; }
|
{ // If no devices were found...
|
||||||
|
Log(LogLevel::Warn, "PCap: no devices available\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
Adapters = new AdapterData[NumAdapters];
|
std::vector<AdapterData> adapters;
|
||||||
memset(Adapters, 0, sizeof(AdapterData)*NumAdapters);
|
for (pcap_if_t* dev = alldevs; dev != nullptr; dev = dev->next)
|
||||||
|
|
||||||
AdapterData* adata = &Adapters[0];
|
|
||||||
dev = alldevs;
|
|
||||||
while (dev)
|
|
||||||
{
|
{
|
||||||
strncpy(adata->DeviceName, dev->name, 127);
|
adapters.emplace_back(); // Add a new (empty) adapter to the list
|
||||||
adata->DeviceName[127] = '\0';
|
AdapterData& adata = adapters.back();
|
||||||
|
strncpy(adata.DeviceName, dev->name, 127);
|
||||||
|
adata.DeviceName[127] = '\0';
|
||||||
|
adata.Flags = dev->flags;
|
||||||
|
|
||||||
#ifndef __WIN32__
|
#ifndef __WIN32__
|
||||||
strncpy(adata->FriendlyName, adata->DeviceName, 127);
|
strncpy(adata.FriendlyName, adata.DeviceName, 127);
|
||||||
adata->FriendlyName[127] = '\0';
|
adata.FriendlyName[127] = '\0';
|
||||||
#endif // __WIN32__
|
#endif // __WIN32__
|
||||||
|
|
||||||
dev = dev->next;
|
|
||||||
adata++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __WIN32__
|
#ifdef __WIN32__
|
||||||
|
@ -186,33 +226,33 @@ bool InitAdapterList()
|
||||||
if (uret != ERROR_SUCCESS)
|
if (uret != ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
Log(LogLevel::Error, "GetAdaptersAddresses() shat itself: %08X\n", uret);
|
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;
|
IP_ADAPTER_ADDRESSES* addr = buf;
|
||||||
while (addr)
|
while (addr)
|
||||||
{
|
{
|
||||||
if (strcmp(addr->AdapterName, &adata->DeviceName[12]))
|
if (strcmp(addr->AdapterName, &adata.DeviceName[12]))
|
||||||
{
|
{
|
||||||
addr = addr->Next;
|
addr = addr->Next;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, addr->FriendlyName, 127, adata->FriendlyName, 127, nullptr, nullptr);
|
WideCharToMultiByte(CP_UTF8, 0, addr->FriendlyName, 127, adata.FriendlyName, 127, nullptr, nullptr);
|
||||||
adata->FriendlyName[127] = '\0';
|
adata.FriendlyName[127] = '\0';
|
||||||
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, addr->Description, 127, adata->Description, 127, nullptr, nullptr);
|
WideCharToMultiByte(CP_UTF8, 0, addr->Description, 127, adata.Description, 127, nullptr, nullptr);
|
||||||
adata->Description[127] = '\0';
|
adata.Description[127] = '\0';
|
||||||
|
|
||||||
if (addr->PhysicalAddressLength != 6)
|
if (addr->PhysicalAddressLength != 6)
|
||||||
{
|
{
|
||||||
Log(LogLevel::Warn, "weird MAC addr length %d for %s\n", addr->PhysicalAddressLength, addr->AdapterName);
|
Log(LogLevel::Warn, "weird MAC addr length %d for %s\n", addr->PhysicalAddressLength, addr->AdapterName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
memcpy(adata->MAC, addr->PhysicalAddress, 6);
|
memcpy(adata.MAC, addr->PhysicalAddress, 6);
|
||||||
|
|
||||||
IP_ADAPTER_UNICAST_ADDRESS* ipaddr = addr->FirstUnicastAddress;
|
IP_ADAPTER_UNICAST_ADDRESS* ipaddr = addr->FirstUnicastAddress;
|
||||||
while (ipaddr)
|
while (ipaddr)
|
||||||
|
@ -221,7 +261,7 @@ bool InitAdapterList()
|
||||||
if (sa->sa_family == AF_INET)
|
if (sa->sa_family == AF_INET)
|
||||||
{
|
{
|
||||||
struct in_addr sa4 = ((sockaddr_in*)sa)->sin_addr;
|
struct in_addr sa4 = ((sockaddr_in*)sa)->sin_addr;
|
||||||
memcpy(adata->IP_v4, &sa4, 4);
|
memcpy(adata.IP_v4, &sa4, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
ipaddr = ipaddr->Next;
|
ipaddr = ipaddr->Next;
|
||||||
|
@ -239,16 +279,15 @@ bool InitAdapterList()
|
||||||
if (getifaddrs(&addrs) != 0)
|
if (getifaddrs(&addrs) != 0)
|
||||||
{
|
{
|
||||||
Log(LogLevel::Error, "getifaddrs() shat itself :(\n");
|
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;
|
struct ifaddrs* curaddr = addrs;
|
||||||
while (curaddr)
|
while (curaddr)
|
||||||
{
|
{
|
||||||
if (strcmp(curaddr->ifa_name, adata->DeviceName))
|
if (strcmp(curaddr->ifa_name, adata.DeviceName))
|
||||||
{
|
{
|
||||||
curaddr = curaddr->ifa_next;
|
curaddr = curaddr->ifa_next;
|
||||||
continue;
|
continue;
|
||||||
|
@ -265,7 +304,7 @@ bool InitAdapterList()
|
||||||
if (af == AF_INET)
|
if (af == AF_INET)
|
||||||
{
|
{
|
||||||
struct sockaddr_in* sa = (sockaddr_in*)curaddr->ifa_addr;
|
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__
|
#ifdef __linux__
|
||||||
else if (af == AF_PACKET)
|
else if (af == AF_PACKET)
|
||||||
|
@ -274,7 +313,7 @@ bool InitAdapterList()
|
||||||
if (sa->sll_halen != 6)
|
if (sa->sll_halen != 6)
|
||||||
Log(LogLevel::Warn, "weird MAC length %d for %s\n", sa->sll_halen, curaddr->ifa_name);
|
Log(LogLevel::Warn, "weird MAC length %d for %s\n", sa->sll_halen, curaddr->ifa_name);
|
||||||
else
|
else
|
||||||
memcpy(adata->MAC, sa->sll_addr, 6);
|
memcpy((void*)adata.MAC, sa->sll_addr, 6);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
else if (af == AF_LINK)
|
else if (af == AF_LINK)
|
||||||
|
@ -283,7 +322,7 @@ bool InitAdapterList()
|
||||||
if (sa->sdl_alen != 6)
|
if (sa->sdl_alen != 6)
|
||||||
Log(LogLevel::Warn, "weird MAC length %d for %s\n", sa->sdl_alen, curaddr->ifa_name);
|
Log(LogLevel::Warn, "weird MAC length %d for %s\n", sa->sdl_alen, curaddr->ifa_name);
|
||||||
else
|
else
|
||||||
memcpy(adata->MAC, LLADDR(sa), 6);
|
memcpy((void*)adata.MAC, LLADDR(sa), 6);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
curaddr = curaddr->ifa_next;
|
curaddr = curaddr->ifa_next;
|
||||||
|
@ -294,67 +333,110 @@ bool InitAdapterList()
|
||||||
|
|
||||||
#endif // __WIN32__
|
#endif // __WIN32__
|
||||||
|
|
||||||
pcap_freealldevs(alldevs);
|
freealldevs(alldevs);
|
||||||
return true;
|
return adapters;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Init(std::string_view devicename)
|
std::unique_ptr<Net_PCap> LibPCap::Open(const AdapterData& device, const Platform::SendPacketCallback& handler) const noexcept
|
||||||
{
|
{
|
||||||
if (!PCapLib) PCapAdapter = nullptr;
|
return Open(device.DeviceName, handler);
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
std::unique_ptr<Net_PCap> LibPCap::Open(std::string_view devicename, const Platform::SendPacketCallback& handler) const noexcept
|
||||||
|
{
|
||||||
char errbuf[PCAP_ERRBUF_SIZE];
|
char errbuf[PCAP_ERRBUF_SIZE];
|
||||||
PCapAdapter = pcap_open_live(PCapAdapterData->DeviceName, 2048, PCAP_OPENFLAG_PROMISCUOUS, 1, errbuf);
|
pcap_t* adapter = open_live(devicename.data(), 2048, PCAP_OPENFLAG_PROMISCUOUS, 1, errbuf);
|
||||||
if (!PCapAdapter)
|
if (!adapter)
|
||||||
{
|
{
|
||||||
Log(LogLevel::Error, "PCap: failed to open adapter %s\n", errbuf);
|
errbuf[PCAP_ERRBUF_SIZE - 1] = '\0';
|
||||||
return false;
|
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");
|
errbuf[PCAP_ERRBUF_SIZE - 1] = '\0';
|
||||||
pcap_close(PCapAdapter); PCapAdapter = nullptr;
|
Log(LogLevel::Error, "PCap: failed to set nonblocking mode with %d: %s\n", err, errbuf);
|
||||||
return false;
|
close(adapter);
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
std::unique_ptr<Net_PCap> pcap = std::make_unique<Net_PCap>();
|
||||||
|
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;
|
PCapAdapter = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform::DynamicLibrary_Unload(PCapLib);
|
PCapAdapter = other.PCapAdapter;
|
||||||
PCapLib = nullptr;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Net_PCap::~Net_PCap() noexcept
|
||||||
void RXCallback(u_char* userdata, const struct pcap_pkthdr* header, const u_char* data)
|
|
||||||
{
|
{
|
||||||
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<Net_PCap*>(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;
|
return 0;
|
||||||
|
|
||||||
if (len > 2048)
|
if (len > 2048)
|
||||||
|
@ -363,17 +445,17 @@ int SendPacket(u8* data, int len)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pcap_sendpacket(PCapAdapter, data, len);
|
sendpacket(PCapAdapter, data, len);
|
||||||
// TODO: check success
|
// TODO: check success
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecvCheck()
|
void Net_PCap::RecvCheck() noexcept
|
||||||
{
|
{
|
||||||
if (PCapAdapter == nullptr)
|
if (PCapAdapter == nullptr || dispatch == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
pcap_dispatch(PCapAdapter, 1, RXCallback, nullptr);
|
dispatch(PCapAdapter, 1, RXCallback, reinterpret_cast<u_char*>(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,20 @@
|
||||||
#ifndef NET_PCAP_H
|
#ifndef NET_PCAP_H
|
||||||
#define NET_PCAP_H
|
#define NET_PCAP_H
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
#include <pcap/pcap.h>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
#include "Platform.h"
|
||||||
|
#include "NetDriver.h"
|
||||||
|
|
||||||
namespace Net_PCap
|
|
||||||
|
namespace melonDS
|
||||||
{
|
{
|
||||||
|
|
||||||
using namespace melonDS;
|
|
||||||
struct AdapterData
|
struct AdapterData
|
||||||
{
|
{
|
||||||
char DeviceName[128];
|
char DeviceName[128];
|
||||||
|
@ -34,19 +41,95 @@ struct AdapterData
|
||||||
|
|
||||||
u8 MAC[6];
|
u8 MAC[6];
|
||||||
u8 IP_v4[4];
|
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;
|
class Net_PCap;
|
||||||
extern int NumAdapters;
|
|
||||||
|
|
||||||
|
class LibPCap
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::optional<LibPCap> New() noexcept;
|
||||||
|
LibPCap(const LibPCap&) = delete;
|
||||||
|
LibPCap& operator=(const LibPCap&) = delete;
|
||||||
|
LibPCap(LibPCap&&) noexcept;
|
||||||
|
LibPCap& operator=(LibPCap&&) noexcept;
|
||||||
|
~LibPCap() noexcept = default;
|
||||||
|
|
||||||
bool InitAdapterList();
|
[[nodiscard]] std::unique_ptr<Net_PCap> Open(std::string_view devicename, const Platform::SendPacketCallback& handler) const noexcept;
|
||||||
bool Init(std::string_view devicename);
|
[[nodiscard]] std::unique_ptr<Net_PCap> Open(const AdapterData& device, const Platform::SendPacketCallback& handler) const noexcept;
|
||||||
void DeInit();
|
|
||||||
|
|
||||||
int SendPacket(u8* data, int len);
|
// so that Net_PCap objects can safely outlive LibPCap
|
||||||
void RecvCheck();
|
// (because the actual DLL will be kept loaded until no shared_ptrs remain)
|
||||||
|
std::shared_ptr<Platform::DynamicLibrary> 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<AdapterData> 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<Platform::DynamicLibrary> PCapLib = nullptr;
|
||||||
|
pcap_close_t close = nullptr;
|
||||||
|
pcap_sendpacket_t sendpacket = nullptr;
|
||||||
|
pcap_dispatch_t dispatch = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,17 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "Net.h"
|
#include "Net.h"
|
||||||
|
#include "Net_Slirp.h"
|
||||||
#include "FIFO.h"
|
#include "FIFO.h"
|
||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
|
|
||||||
#include <libslirp.h>
|
#include <libslirp.h>
|
||||||
|
|
||||||
|
// "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 <slirp.h>
|
||||||
|
|
||||||
#ifdef __WIN32__
|
#ifdef __WIN32__
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#else
|
#else
|
||||||
|
@ -33,9 +39,7 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using namespace melonDS;
|
namespace melonDS
|
||||||
|
|
||||||
namespace Net_Slirp
|
|
||||||
{
|
{
|
||||||
|
|
||||||
using Platform::Log;
|
using Platform::Log;
|
||||||
|
@ -48,13 +52,6 @@ const u32 kClientIP = kSubnet | 0x10;
|
||||||
|
|
||||||
const u8 kServerMAC[6] = {0x00, 0xAB, 0x33, 0x28, 0x99, 0x44};
|
const u8 kServerMAC[6] = {0x00, 0xAB, 0x33, 0x28, 0x99, 0x44};
|
||||||
|
|
||||||
FIFO<u32, (0x8000 >> 2)> RXBuffer;
|
|
||||||
|
|
||||||
u32 IPv4ID;
|
|
||||||
|
|
||||||
Slirp* Ctx = nullptr;
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __WIN32__
|
#ifdef __WIN32__
|
||||||
|
|
||||||
#define poll WSAPoll
|
#define poll WSAPoll
|
||||||
|
@ -77,7 +74,7 @@ int clock_gettime(int, struct timespec *spec)
|
||||||
#endif // __WIN32__
|
#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)
|
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]));
|
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<Net_Slirp*>(opaque);
|
||||||
|
if (self.Callback)
|
||||||
|
{
|
||||||
|
self.Callback((const u8*)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
@ -132,7 +133,7 @@ void SlirpCbNotify(void* opaque)
|
||||||
Log(LogLevel::Debug, "Slirp: notify???\n");
|
Log(LogLevel::Debug, "Slirp: notify???\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
SlirpCb cb =
|
const SlirpCb Net_Slirp::cb =
|
||||||
{
|
{
|
||||||
.send_packet = SlirpCbSendPacket,
|
.send_packet = SlirpCbSendPacket,
|
||||||
.guest_error = SlirpCbGuestError,
|
.guest_error = SlirpCbGuestError,
|
||||||
|
@ -145,11 +146,9 @@ SlirpCb cb =
|
||||||
.notify = SlirpCbNotify
|
.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));
|
memset(&cfg, 0, sizeof(cfg));
|
||||||
cfg.version = 1;
|
cfg.version = 1;
|
||||||
|
|
||||||
|
@ -161,12 +160,67 @@ bool Init()
|
||||||
*(u32*)&cfg.vdhcp_start = htonl(kClientIP);
|
*(u32*)&cfg.vdhcp_start = htonl(kClientIP);
|
||||||
*(u32*)&cfg.vnameserver = htonl(kDNSIP);
|
*(u32*)&cfg.vnameserver = htonl(kDNSIP);
|
||||||
|
|
||||||
Ctx = slirp_new(&cfg, &cb, nullptr);
|
Ctx = slirp_new(&cfg, &cb, this);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
if (Ctx)
|
||||||
{
|
{
|
||||||
|
@ -215,7 +269,7 @@ void FinishUDPFrame(u8* data, int len)
|
||||||
*(u16*)&udpheader[6] = htons(tmp);
|
*(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* ipheader = &data[0xE];
|
||||||
u8* udpheader = &data[0x22];
|
u8* udpheader = &data[0x22];
|
||||||
|
@ -368,10 +422,11 @@ void HandleDNSFrame(u8* data, int len)
|
||||||
if (framelen & 1) { *out++ = 0; framelen++; }
|
if (framelen & 1) { *out++ = 0; framelen++; }
|
||||||
FinishUDPFrame(resp, 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;
|
if (!Ctx) return 0;
|
||||||
|
|
||||||
|
@ -401,19 +456,17 @@ int SendPacket(u8* data, int len)
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int PollListMax = 64;
|
int Net_Slirp::SlirpCbAddPoll(int fd, int events, void* opaque) noexcept
|
||||||
struct pollfd PollList[PollListMax];
|
|
||||||
int PollListSize;
|
|
||||||
|
|
||||||
int SlirpCbAddPoll(int fd, int events, void* opaque)
|
|
||||||
{
|
{
|
||||||
if (PollListSize >= PollListMax)
|
Net_Slirp& self = *static_cast<Net_Slirp*>(opaque);
|
||||||
|
|
||||||
|
if (self.PollListSize >= PollListMax)
|
||||||
{
|
{
|
||||||
Log(LogLevel::Error, "slirp: POLL LIST FULL\n");
|
Log(LogLevel::Error, "slirp: POLL LIST FULL\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int idx = PollListSize++;
|
int idx = self.PollListSize++;
|
||||||
|
|
||||||
u16 evt = 0;
|
u16 evt = 0;
|
||||||
|
|
||||||
|
@ -427,18 +480,20 @@ int SlirpCbAddPoll(int fd, int events, void* opaque)
|
||||||
if (events & SLIRP_POLL_HUP) evt |= POLLHUP;
|
if (events & SLIRP_POLL_HUP) evt |= POLLHUP;
|
||||||
#endif // !__WIN32__
|
#endif // !__WIN32__
|
||||||
|
|
||||||
PollList[idx].fd = fd;
|
self.PollList[idx].fd = fd;
|
||||||
PollList[idx].events = evt;
|
self.PollList[idx].events = evt;
|
||||||
|
|
||||||
return idx;
|
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<Net_Slirp*>(opaque);
|
||||||
|
|
||||||
|
if (idx < 0 || idx >= self.PollListSize)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
u16 evt = PollList[idx].revents;
|
u16 evt = self.PollList[idx].revents;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
if (evt & POLLIN) ret |= SLIRP_POLL_IN;
|
if (evt & POLLIN) ret |= SLIRP_POLL_IN;
|
||||||
|
@ -450,7 +505,7 @@ int SlirpCbGetREvents(int idx, void* opaque)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecvCheck()
|
void Net_Slirp::RecvCheck() noexcept
|
||||||
{
|
{
|
||||||
if (!Ctx) return;
|
if (!Ctx) return;
|
||||||
|
|
||||||
|
@ -458,9 +513,9 @@ void RecvCheck()
|
||||||
{
|
{
|
||||||
u32 timeout = 0;
|
u32 timeout = 0;
|
||||||
PollListSize = 0;
|
PollListSize = 0;
|
||||||
slirp_pollfds_fill(Ctx, &timeout, SlirpCbAddPoll, nullptr);
|
slirp_pollfds_fill(Ctx, &timeout, SlirpCbAddPoll, this);
|
||||||
int res = poll(PollList, PollListSize, timeout);
|
int res = poll(PollList, PollListSize, timeout);
|
||||||
slirp_pollfds_poll(Ctx, res<0, SlirpCbGetREvents, nullptr);
|
slirp_pollfds_poll(Ctx, res<0, SlirpCbGetREvents, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,17 +20,48 @@
|
||||||
#define NET_SLIRP_H
|
#define NET_SLIRP_H
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
#include "FIFO.h"
|
||||||
|
#include "Platform.h"
|
||||||
|
#include "NetDriver.h"
|
||||||
|
|
||||||
namespace Net_Slirp
|
#include <libslirp.h>
|
||||||
|
|
||||||
|
#ifdef __WIN32__
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <poll.h>
|
||||||
|
#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();
|
int SendPacket(u8* data, int len) noexcept override;
|
||||||
void DeInit();
|
void RecvCheck() noexcept override;
|
||||||
|
private:
|
||||||
int SendPacket(u8* data, int len);
|
static constexpr int PollListMax = 64;
|
||||||
void RecvCheck();
|
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<u32, (0x8000 >> 2)> RXBuffer {};
|
||||||
|
u32 IPv4ID = 0;
|
||||||
|
Slirp* Ctx = nullptr;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // NET_SLIRP_H
|
#endif // NET_SLIRP_H
|
||||||
|
|
|
@ -34,7 +34,6 @@ const u32 kPacketMagic = 0x4B504C4D;
|
||||||
PacketDispatcher::PacketDispatcher() : mutex(Platform::Mutex_Create())
|
PacketDispatcher::PacketDispatcher() : mutex(Platform::Mutex_Create())
|
||||||
{
|
{
|
||||||
instanceMask = 0;
|
instanceMask = 0;
|
||||||
memset(packetQueues, 0, sizeof(packetQueues));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PacketDispatcher::~PacketDispatcher()
|
PacketDispatcher::~PacketDispatcher()
|
||||||
|
@ -48,7 +47,7 @@ void PacketDispatcher::registerInstance(int inst)
|
||||||
Mutex_Lock(mutex);
|
Mutex_Lock(mutex);
|
||||||
|
|
||||||
instanceMask |= (1 << inst);
|
instanceMask |= (1 << inst);
|
||||||
packetQueues[inst] = new PacketQueue();
|
packetQueues[inst] = std::make_unique<PacketQueue>();
|
||||||
|
|
||||||
Mutex_Unlock(mutex);
|
Mutex_Unlock(mutex);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +57,7 @@ void PacketDispatcher::unregisterInstance(int inst)
|
||||||
Mutex_Lock(mutex);
|
Mutex_Lock(mutex);
|
||||||
|
|
||||||
instanceMask &= ~(1 << inst);
|
instanceMask &= ~(1 << inst);
|
||||||
delete packetQueues[inst];
|
packetQueues[inst] = nullptr;
|
||||||
|
|
||||||
Mutex_Unlock(mutex);
|
Mutex_Unlock(mutex);
|
||||||
}
|
}
|
||||||
|
@ -72,8 +71,7 @@ void PacketDispatcher::clear()
|
||||||
if (!(instanceMask & (1 << i)))
|
if (!(instanceMask & (1 << i)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
PacketQueue* queue = packetQueues[i];
|
packetQueues[i]->Clear();
|
||||||
queue->Clear();
|
|
||||||
}
|
}
|
||||||
Mutex_Unlock(mutex);
|
Mutex_Unlock(mutex);
|
||||||
}
|
}
|
||||||
|
@ -105,7 +103,7 @@ void PacketDispatcher::sendPacket(const void* header, int headerlen, const void*
|
||||||
if (!(recv_mask & (1 << i)))
|
if (!(recv_mask & (1 << i)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
PacketQueue* queue = packetQueues[i];
|
PacketQueue* queue = packetQueues[i].get();
|
||||||
|
|
||||||
// if we run out of space: discard old packets
|
// if we run out of space: discard old packets
|
||||||
while (!queue->CanFit(totallen))
|
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;
|
if (receiver < 0 || receiver > 15) return false;
|
||||||
|
|
||||||
Mutex_Lock(mutex);
|
Mutex_Lock(mutex);
|
||||||
PacketQueue* queue = packetQueues[receiver];
|
PacketQueue* queue = packetQueues[receiver].get();
|
||||||
|
|
||||||
PacketHeader phdr;
|
PacketHeader phdr;
|
||||||
if (!queue->Read(&phdr, sizeof(phdr)))
|
if (!queue->Read(&phdr, sizeof(phdr)))
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
#ifndef PACKETDISPATCHER_H
|
#ifndef PACKETDISPATCHER_H
|
||||||
#define PACKETDISPATCHER_H
|
#define PACKETDISPATCHER_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "FIFO.h"
|
#include "FIFO.h"
|
||||||
|
@ -42,7 +44,7 @@ public:
|
||||||
private:
|
private:
|
||||||
melonDS::Platform::Mutex* mutex;
|
melonDS::Platform::Mutex* mutex;
|
||||||
melonDS::u16 instanceMask;
|
melonDS::u16 instanceMask;
|
||||||
PacketQueue* packetQueues[16];
|
std::array<std::unique_ptr<PacketQueue>, 16> packetQueues {};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PACKETDISPATCHER_H
|
#endif // PACKETDISPATCHER_H
|
||||||
|
|
32
vcpkg.json
32
vcpkg.json
|
@ -1,12 +1,14 @@
|
||||||
{
|
{
|
||||||
|
"default-features": ["qt6"],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"sdl2",
|
"sdl2",
|
||||||
{
|
"libarchive",
|
||||||
"name": "libarchive",
|
"zstd"
|
||||||
"default-features": false,
|
],
|
||||||
"features": ["bzip2", "crypto", "lz4", "zstd"]
|
"features": {
|
||||||
},
|
"qt6": {
|
||||||
"zstd",
|
"description": "Use Qt 6 for the frontend.",
|
||||||
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"name": "qtbase",
|
"name": "qtbase",
|
||||||
"default-features": false,
|
"default-features": false,
|
||||||
|
@ -23,4 +25,22 @@
|
||||||
},
|
},
|
||||||
"qtsvg"
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue