Merge pull request #8853 from CrunchBite/feature-xlink-kai-bba
Feature XLink Kai BBA
This commit is contained in:
commit
961f937010
|
@ -592,8 +592,9 @@ endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(core PRIVATE
|
target_sources(core PRIVATE
|
||||||
HW/EXI/BBA-TAP/TAP_Win32.cpp
|
HW/EXI/BBA/TAP_Win32.cpp
|
||||||
HW/EXI/BBA-TAP/TAP_Win32.h
|
HW/EXI/BBA/TAP_Win32.h
|
||||||
|
HW/EXI/BBA/XLINK_KAI_BBA.cpp
|
||||||
HW/WiimoteReal/IOWin.cpp
|
HW/WiimoteReal/IOWin.cpp
|
||||||
HW/WiimoteReal/IOWin.h
|
HW/WiimoteReal/IOWin.h
|
||||||
)
|
)
|
||||||
|
@ -606,7 +607,8 @@ if(WIN32)
|
||||||
target_compile_definitions(core PRIVATE "-D_WINSOCK_DEPRECATED_NO_WARNINGS")
|
target_compile_definitions(core PRIVATE "-D_WINSOCK_DEPRECATED_NO_WARNINGS")
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
target_sources(core PRIVATE
|
target_sources(core PRIVATE
|
||||||
HW/EXI/BBA-TAP/TAP_Apple.cpp
|
HW/EXI/BBA/TAP_Apple.cpp
|
||||||
|
HW/EXI/BBA/XLINK_KAI_BBA.cpp
|
||||||
HW/WiimoteReal/IOdarwin.h
|
HW/WiimoteReal/IOdarwin.h
|
||||||
HW/WiimoteReal/IOdarwin_private.h
|
HW/WiimoteReal/IOdarwin_private.h
|
||||||
HW/WiimoteReal/IOdarwin.mm
|
HW/WiimoteReal/IOdarwin.mm
|
||||||
|
@ -614,7 +616,8 @@ elseif(APPLE)
|
||||||
target_link_libraries(core PUBLIC ${IOB_LIBRARY})
|
target_link_libraries(core PUBLIC ${IOB_LIBRARY})
|
||||||
elseif(UNIX)
|
elseif(UNIX)
|
||||||
target_sources(core PRIVATE
|
target_sources(core PRIVATE
|
||||||
HW/EXI/BBA-TAP/TAP_Unix.cpp
|
HW/EXI/BBA/TAP_Unix.cpp
|
||||||
|
HW/EXI/BBA/XLINK_KAI_BBA.cpp
|
||||||
)
|
)
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
target_sources(core PRIVATE
|
target_sources(core PRIVATE
|
||||||
|
|
|
@ -227,6 +227,8 @@ void SConfig::SaveCoreSettings(IniFile& ini)
|
||||||
core->Set("SlotB", m_EXIDevice[1]);
|
core->Set("SlotB", m_EXIDevice[1]);
|
||||||
core->Set("SerialPort1", m_EXIDevice[2]);
|
core->Set("SerialPort1", m_EXIDevice[2]);
|
||||||
core->Set("BBA_MAC", m_bba_mac);
|
core->Set("BBA_MAC", m_bba_mac);
|
||||||
|
core->Set("BBA_XLINK_IP", m_bba_xlink_ip);
|
||||||
|
core->Set("BBA_XLINK_CHAT_OSD", m_bba_xlink_chat_osd);
|
||||||
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
|
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
|
||||||
{
|
{
|
||||||
core->Set(fmt::format("SIDevice{}", i), m_SIDevice[i]);
|
core->Set(fmt::format("SIDevice{}", i), m_SIDevice[i]);
|
||||||
|
@ -495,6 +497,8 @@ void SConfig::LoadCoreSettings(IniFile& ini)
|
||||||
core->Get("SlotB", (int*)&m_EXIDevice[1], ExpansionInterface::EXIDEVICE_NONE);
|
core->Get("SlotB", (int*)&m_EXIDevice[1], ExpansionInterface::EXIDEVICE_NONE);
|
||||||
core->Get("SerialPort1", (int*)&m_EXIDevice[2], ExpansionInterface::EXIDEVICE_NONE);
|
core->Get("SerialPort1", (int*)&m_EXIDevice[2], ExpansionInterface::EXIDEVICE_NONE);
|
||||||
core->Get("BBA_MAC", &m_bba_mac);
|
core->Get("BBA_MAC", &m_bba_mac);
|
||||||
|
core->Get("BBA_XLINK_IP", &m_bba_xlink_ip, "127.0.0.1");
|
||||||
|
core->Get("BBA_XLINK_CHAT_OSD", &m_bba_xlink_chat_osd, true);
|
||||||
for (size_t i = 0; i < std::size(m_SIDevice); ++i)
|
for (size_t i = 0; i < std::size(m_SIDevice); ++i)
|
||||||
{
|
{
|
||||||
core->Get(fmt::format("SIDevice{}", i), &m_SIDevice[i],
|
core->Get(fmt::format("SIDevice{}", i), &m_SIDevice[i],
|
||||||
|
|
|
@ -226,7 +226,10 @@ struct SConfig
|
||||||
std::string m_strGbaCartB;
|
std::string m_strGbaCartB;
|
||||||
ExpansionInterface::TEXIDevices m_EXIDevice[3];
|
ExpansionInterface::TEXIDevices m_EXIDevice[3];
|
||||||
SerialInterface::SIDevices m_SIDevice[4];
|
SerialInterface::SIDevices m_SIDevice[4];
|
||||||
|
|
||||||
std::string m_bba_mac;
|
std::string m_bba_mac;
|
||||||
|
std::string m_bba_xlink_ip;
|
||||||
|
bool m_bba_xlink_chat_osd = true;
|
||||||
|
|
||||||
// interface language
|
// interface language
|
||||||
std::string m_InterfaceLanguage;
|
std::string m_InterfaceLanguage;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup Label="ProjectConfigurations">
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
<ProjectConfiguration Include="Debug|ARM64">
|
<ProjectConfiguration Include="Debug|ARM64">
|
||||||
|
@ -140,7 +140,8 @@
|
||||||
<ClCompile Include="HW\DVD\DVDMath.cpp" />
|
<ClCompile Include="HW\DVD\DVDMath.cpp" />
|
||||||
<ClCompile Include="HW\DVD\DVDThread.cpp" />
|
<ClCompile Include="HW\DVD\DVDThread.cpp" />
|
||||||
<ClCompile Include="HW\DVD\FileMonitor.cpp" />
|
<ClCompile Include="HW\DVD\FileMonitor.cpp" />
|
||||||
<ClCompile Include="HW\EXI\BBA-TAP\TAP_Win32.cpp" />
|
<ClCompile Include="HW\EXI\BBA\TAP_Win32.cpp" />
|
||||||
|
<ClCompile Include="HW\EXI\BBA\XLINK_KAI_BBA.cpp" />
|
||||||
<ClCompile Include="HW\EXI\EXI.cpp" />
|
<ClCompile Include="HW\EXI\EXI.cpp" />
|
||||||
<ClCompile Include="HW\EXI\EXI_Channel.cpp" />
|
<ClCompile Include="HW\EXI\EXI_Channel.cpp" />
|
||||||
<ClCompile Include="HW\EXI\EXI_Device.cpp" />
|
<ClCompile Include="HW\EXI\EXI_Device.cpp" />
|
||||||
|
@ -498,7 +499,7 @@
|
||||||
<ClInclude Include="HW\DVD\DVDMath.h" />
|
<ClInclude Include="HW\DVD\DVDMath.h" />
|
||||||
<ClInclude Include="HW\DVD\DVDThread.h" />
|
<ClInclude Include="HW\DVD\DVDThread.h" />
|
||||||
<ClInclude Include="HW\DVD\FileMonitor.h" />
|
<ClInclude Include="HW\DVD\FileMonitor.h" />
|
||||||
<ClInclude Include="HW\EXI\BBA-TAP\TAP_Win32.h" />
|
<ClInclude Include="HW\EXI\BBA\TAP_Win32.h" />
|
||||||
<ClInclude Include="HW\EXI\EXI.h" />
|
<ClInclude Include="HW\EXI\EXI.h" />
|
||||||
<ClInclude Include="HW\EXI\EXI_Channel.h" />
|
<ClInclude Include="HW\EXI\EXI_Channel.h" />
|
||||||
<ClInclude Include="HW\EXI\EXI_Device.h" />
|
<ClInclude Include="HW\EXI\EXI_Device.h" />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Filter Include="ActionReplay">
|
<Filter Include="ActionReplay">
|
||||||
|
@ -993,6 +993,12 @@
|
||||||
<ClCompile Include="PowerPC\Jit64\RegCache\FPURegCache.cpp">
|
<ClCompile Include="PowerPC\Jit64\RegCache\FPURegCache.cpp">
|
||||||
<Filter>PowerPC\Jit64</Filter>
|
<Filter>PowerPC\Jit64</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="HW\EXI\BBA\XLINK_KAI_BBA.cpp">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="HW\EXI\BBA\TAP_Win32.cpp">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="BootManager.h" />
|
<ClInclude Include="BootManager.h" />
|
||||||
|
@ -1743,6 +1749,9 @@
|
||||||
<ClInclude Include="PowerPC\JitArmCommon\BackPatch.h">
|
<ClInclude Include="PowerPC\JitArmCommon\BackPatch.h">
|
||||||
<Filter>PowerPC\JitArmCommon</Filter>
|
<Filter>PowerPC\JitArmCommon</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="HW\EXI\BBA\TAP_Win32.h">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Text Include="CMakeLists.txt" />
|
<Text Include="CMakeLists.txt" />
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
namespace ExpansionInterface
|
namespace ExpansionInterface
|
||||||
{
|
{
|
||||||
bool CEXIETHERNET::Activate()
|
bool CEXIETHERNET::TAPNetworkInterface::Activate()
|
||||||
{
|
{
|
||||||
if (IsActivated())
|
if (IsActivated())
|
||||||
return true;
|
return true;
|
||||||
|
@ -30,7 +30,7 @@ bool CEXIETHERNET::Activate()
|
||||||
return RecvInit();
|
return RecvInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::Deactivate()
|
void CEXIETHERNET::TAPNetworkInterface::Deactivate()
|
||||||
{
|
{
|
||||||
close(fd);
|
close(fd);
|
||||||
fd = -1;
|
fd = -1;
|
||||||
|
@ -41,12 +41,12 @@ void CEXIETHERNET::Deactivate()
|
||||||
readThread.join();
|
readThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::IsActivated()
|
bool CEXIETHERNET::TAPNetworkInterface::IsActivated()
|
||||||
{
|
{
|
||||||
return fd != -1;
|
return fd != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
bool CEXIETHERNET::TAPNetworkInterface::SendFrame(const u8* frame, u32 size)
|
||||||
{
|
{
|
||||||
INFO_LOG(SP1, "SendFrame %x\n%s", size, ArrayToString(frame, size, 0x10).c_str());
|
INFO_LOG(SP1, "SendFrame %x\n%s", size, ArrayToString(frame, size, 0x10).c_str());
|
||||||
|
|
||||||
|
@ -58,12 +58,12 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SendComplete();
|
m_eth_ref->SendComplete();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* self)
|
||||||
{
|
{
|
||||||
while (!self->readThreadShutdown.IsSet())
|
while (!self->readThreadShutdown.IsSet())
|
||||||
{
|
{
|
||||||
|
@ -77,7 +77,7 @@ void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
||||||
if (select(self->fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0)
|
if (select(self->fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int readBytes = read(self->fd, self->mRecvBuffer.get(), BBA_RECV_SIZE);
|
int readBytes = read(self->fd, self->m_eth_ref->mRecvBuffer.get(), BBA_RECV_SIZE);
|
||||||
if (readBytes < 0)
|
if (readBytes < 0)
|
||||||
{
|
{
|
||||||
ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes);
|
ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes);
|
||||||
|
@ -85,25 +85,25 @@ void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
||||||
else if (self->readEnabled.IsSet())
|
else if (self->readEnabled.IsSet())
|
||||||
{
|
{
|
||||||
INFO_LOG(SP1, "Read data: %s",
|
INFO_LOG(SP1, "Read data: %s",
|
||||||
ArrayToString(self->mRecvBuffer.get(), readBytes, 0x10).c_str());
|
ArrayToString(self->m_eth_ref->mRecvBuffer.get(), readBytes, 0x10).c_str());
|
||||||
self->mRecvBufferLength = readBytes;
|
self->m_eth_ref->mRecvBufferLength = readBytes;
|
||||||
self->RecvHandlePacket();
|
self->m_eth_ref->RecvHandlePacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::RecvInit()
|
bool CEXIETHERNET::TAPNetworkInterface::RecvInit()
|
||||||
{
|
{
|
||||||
readThread = std::thread(ReadThreadHandler, this);
|
readThread = std::thread(ReadThreadHandler, this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStart()
|
void CEXIETHERNET::TAPNetworkInterface::RecvStart()
|
||||||
{
|
{
|
||||||
readEnabled.Set();
|
readEnabled.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStop()
|
void CEXIETHERNET::TAPNetworkInterface::RecvStop()
|
||||||
{
|
{
|
||||||
readEnabled.Clear();
|
readEnabled.Clear();
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@ namespace ExpansionInterface
|
||||||
#define NOTIMPLEMENTED(Name) \
|
#define NOTIMPLEMENTED(Name) \
|
||||||
NOTICE_LOG(SP1, "CEXIETHERNET::%s not implemented for your UNIX", Name);
|
NOTICE_LOG(SP1, "CEXIETHERNET::%s not implemented for your UNIX", Name);
|
||||||
|
|
||||||
bool CEXIETHERNET::Activate()
|
bool CEXIETHERNET::TAPNetworkInterface::Activate()
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
if (IsActivated())
|
if (IsActivated())
|
||||||
|
@ -50,7 +50,7 @@ bool CEXIETHERNET::Activate()
|
||||||
const int MAX_INTERFACES = 32;
|
const int MAX_INTERFACES = 32;
|
||||||
for (int i = 0; i < MAX_INTERFACES; ++i)
|
for (int i = 0; i < MAX_INTERFACES; ++i)
|
||||||
{
|
{
|
||||||
strncpy(ifr.ifr_name, StringFromFormat("Dolphin%d", i).c_str(), IFNAMSIZ - 1);
|
strncpy(ifr.ifr_name, StringFromFormat("Dolphin%d", i).c_str(), IFNAMSIZ);
|
||||||
|
|
||||||
int err;
|
int err;
|
||||||
if ((err = ioctl(fd, TUNSETIFF, (void*)&ifr)) < 0)
|
if ((err = ioctl(fd, TUNSETIFF, (void*)&ifr)) < 0)
|
||||||
|
@ -78,7 +78,7 @@ bool CEXIETHERNET::Activate()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::Deactivate()
|
void CEXIETHERNET::TAPNetworkInterface::Deactivate()
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
close(fd);
|
close(fd);
|
||||||
|
@ -93,7 +93,7 @@ void CEXIETHERNET::Deactivate()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::IsActivated()
|
bool CEXIETHERNET::TAPNetworkInterface::IsActivated()
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
return fd != -1 ? true : false;
|
return fd != -1 ? true : false;
|
||||||
|
@ -102,7 +102,7 @@ bool CEXIETHERNET::IsActivated()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
bool CEXIETHERNET::TAPNetworkInterface::SendFrame(const u8* frame, u32 size)
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
DEBUG_LOG(SP1, "SendFrame %x\n%s", size, ArrayToString(frame, size, 0x10).c_str());
|
DEBUG_LOG(SP1, "SendFrame %x\n%s", size, ArrayToString(frame, size, 0x10).c_str());
|
||||||
|
@ -115,7 +115,7 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SendComplete();
|
m_eth_ref->SendComplete();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
@ -125,7 +125,7 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* self)
|
||||||
{
|
{
|
||||||
while (!self->readThreadShutdown.IsSet())
|
while (!self->readThreadShutdown.IsSet())
|
||||||
{
|
{
|
||||||
|
@ -139,7 +139,7 @@ void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
||||||
if (select(self->fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0)
|
if (select(self->fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int readBytes = read(self->fd, self->mRecvBuffer.get(), BBA_RECV_SIZE);
|
int readBytes = read(self->fd, self->m_eth_ref->mRecvBuffer.get(), BBA_RECV_SIZE);
|
||||||
if (readBytes < 0)
|
if (readBytes < 0)
|
||||||
{
|
{
|
||||||
ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes);
|
ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes);
|
||||||
|
@ -147,15 +147,15 @@ void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
||||||
else if (self->readEnabled.IsSet())
|
else if (self->readEnabled.IsSet())
|
||||||
{
|
{
|
||||||
DEBUG_LOG(SP1, "Read data: %s",
|
DEBUG_LOG(SP1, "Read data: %s",
|
||||||
ArrayToString(self->mRecvBuffer.get(), readBytes, 0x10).c_str());
|
ArrayToString(self->m_eth_ref->mRecvBuffer.get(), readBytes, 0x10).c_str());
|
||||||
self->mRecvBufferLength = readBytes;
|
self->m_eth_ref->mRecvBufferLength = readBytes;
|
||||||
self->RecvHandlePacket();
|
self->m_eth_ref->RecvHandlePacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool CEXIETHERNET::RecvInit()
|
bool CEXIETHERNET::TAPNetworkInterface::RecvInit()
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
readThread = std::thread(ReadThreadHandler, this);
|
readThread = std::thread(ReadThreadHandler, this);
|
||||||
|
@ -166,7 +166,7 @@ bool CEXIETHERNET::RecvInit()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStart()
|
void CEXIETHERNET::TAPNetworkInterface::RecvStart()
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
readEnabled.Set();
|
readEnabled.Set();
|
||||||
|
@ -175,7 +175,7 @@ void CEXIETHERNET::RecvStart()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStop()
|
void CEXIETHERNET::TAPNetworkInterface::RecvStop()
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
readEnabled.Clear();
|
readEnabled.Clear();
|
|
@ -2,7 +2,7 @@
|
||||||
// Licensed under GPLv2+
|
// Licensed under GPLv2+
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "Core/HW/EXI/BBA-TAP/TAP_Win32.h"
|
#include "Core/HW/EXI/BBA/TAP_Win32.h"
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/MsgHandler.h"
|
#include "Common/MsgHandler.h"
|
||||||
|
@ -167,7 +167,7 @@ bool OpenTAP(HANDLE& adapter, const std::basic_string<TCHAR>& device_guid)
|
||||||
|
|
||||||
namespace ExpansionInterface
|
namespace ExpansionInterface
|
||||||
{
|
{
|
||||||
bool CEXIETHERNET::Activate()
|
bool CEXIETHERNET::TAPNetworkInterface::Activate()
|
||||||
{
|
{
|
||||||
if (IsActivated())
|
if (IsActivated())
|
||||||
return true;
|
return true;
|
||||||
|
@ -233,7 +233,7 @@ bool CEXIETHERNET::Activate()
|
||||||
return RecvInit();
|
return RecvInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::Deactivate()
|
void CEXIETHERNET::TAPNetworkInterface::Deactivate()
|
||||||
{
|
{
|
||||||
if (!IsActivated())
|
if (!IsActivated())
|
||||||
return;
|
return;
|
||||||
|
@ -258,19 +258,19 @@ void CEXIETHERNET::Deactivate()
|
||||||
memset(&mWriteOverlapped, 0, sizeof(mWriteOverlapped));
|
memset(&mWriteOverlapped, 0, sizeof(mWriteOverlapped));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::IsActivated()
|
bool CEXIETHERNET::TAPNetworkInterface::IsActivated()
|
||||||
{
|
{
|
||||||
return mHAdapter != INVALID_HANDLE_VALUE;
|
return mHAdapter != INVALID_HANDLE_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* self)
|
||||||
{
|
{
|
||||||
while (!self->readThreadShutdown.IsSet())
|
while (!self->readThreadShutdown.IsSet())
|
||||||
{
|
{
|
||||||
DWORD transferred;
|
DWORD transferred;
|
||||||
|
|
||||||
// Read from TAP into internal buffer.
|
// Read from TAP into internal buffer.
|
||||||
if (ReadFile(self->mHAdapter, self->mRecvBuffer.get(), BBA_RECV_SIZE, &transferred,
|
if (ReadFile(self->mHAdapter, self->m_eth_ref->mRecvBuffer.get(), BBA_RECV_SIZE, &transferred,
|
||||||
&self->mReadOverlapped))
|
&self->mReadOverlapped))
|
||||||
{
|
{
|
||||||
// Returning immediately is not likely to happen, but if so, reset the event state manually.
|
// Returning immediately is not likely to happen, but if so, reset the event state manually.
|
||||||
|
@ -300,16 +300,16 @@ void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
||||||
|
|
||||||
// Copy to BBA buffer, and fire interrupt if enabled.
|
// Copy to BBA buffer, and fire interrupt if enabled.
|
||||||
DEBUG_LOG(SP1, "Received %u bytes:\n %s", transferred,
|
DEBUG_LOG(SP1, "Received %u bytes:\n %s", transferred,
|
||||||
ArrayToString(self->mRecvBuffer.get(), transferred, 0x10).c_str());
|
ArrayToString(self->m_eth_ref->mRecvBuffer.get(), transferred, 0x10).c_str());
|
||||||
if (self->readEnabled.IsSet())
|
if (self->readEnabled.IsSet())
|
||||||
{
|
{
|
||||||
self->mRecvBufferLength = transferred;
|
self->m_eth_ref->mRecvBufferLength = transferred;
|
||||||
self->RecvHandlePacket();
|
self->m_eth_ref->RecvHandlePacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
bool CEXIETHERNET::TAPNetworkInterface::SendFrame(const u8* frame, u32 size)
|
||||||
{
|
{
|
||||||
DEBUG_LOG(SP1, "SendFrame %u bytes:\n%s", size, ArrayToString(frame, size, 0x10).c_str());
|
DEBUG_LOG(SP1, "SendFrame %u bytes:\n%s", size, ArrayToString(frame, size, 0x10).c_str());
|
||||||
|
|
||||||
|
@ -345,22 +345,22 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always report the packet as being sent successfully, even though it might be a lie
|
// Always report the packet as being sent successfully, even though it might be a lie
|
||||||
SendComplete();
|
m_eth_ref->SendComplete();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::RecvInit()
|
bool CEXIETHERNET::TAPNetworkInterface::RecvInit()
|
||||||
{
|
{
|
||||||
readThread = std::thread(ReadThreadHandler, this);
|
readThread = std::thread(ReadThreadHandler, this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStart()
|
void CEXIETHERNET::TAPNetworkInterface::RecvStart()
|
||||||
{
|
{
|
||||||
readEnabled.Set();
|
readEnabled.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStop()
|
void CEXIETHERNET::TAPNetworkInterface::RecvStop()
|
||||||
{
|
{
|
||||||
readEnabled.Clear();
|
readEnabled.Clear();
|
||||||
}
|
}
|
|
@ -0,0 +1,306 @@
|
||||||
|
// Copyright 2008 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "Common/Assert.h"
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
|
#include "Common/MsgHandler.h"
|
||||||
|
#include "Common/StringUtil.h"
|
||||||
|
#include "Core/HW/EXI/EXI_Device.h"
|
||||||
|
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/OnScreenDisplay.h"
|
||||||
|
|
||||||
|
#include <SFML/Network.hpp>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// BBA implementation with UDP interface to XLink Kai PC/MAC/RaspberryPi client
|
||||||
|
// For more information please see: https://www.teamxlink.co.uk/wiki/Emulator_Integration_Protocol
|
||||||
|
// Still have questions? Please email crunchbite@teamxlink.co.uk
|
||||||
|
// When editing this file please maintain proper capitalization of "XLink Kai"
|
||||||
|
|
||||||
|
namespace ExpansionInterface
|
||||||
|
{
|
||||||
|
bool CEXIETHERNET::XLinkNetworkInterface::Activate()
|
||||||
|
{
|
||||||
|
if (IsActivated())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (m_sf_socket.bind(sf::Socket::AnyPort) != sf::Socket::Done)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "Couldn't open XLink Kai UDP socket, unable to initialize BBA");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sf_recipient_ip = m_dest_ip.c_str();
|
||||||
|
|
||||||
|
// Send connect command with unique local name
|
||||||
|
// connect;locally_unique_name;emulator_name;optional_padding
|
||||||
|
u8 buffer[255] = {};
|
||||||
|
std::string cmd =
|
||||||
|
"connect;" + m_client_identifier + ";dolphin;000000000000000000000000000000000000000000";
|
||||||
|
|
||||||
|
const auto size = u32(cmd.length());
|
||||||
|
memmove(buffer, cmd.c_str(), size);
|
||||||
|
|
||||||
|
DEBUG_LOG(SP1, "SendCommandPayload %x\n%s", size, ArrayToString(buffer, size, 0x10).c_str());
|
||||||
|
|
||||||
|
if (m_sf_socket.send(buffer, size, m_sf_recipient_ip, m_dest_port) != sf::Socket::Done)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "Activate(): failed to send connect message to XLink Kai client");
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO_LOG(SP1, "BBA initialized.");
|
||||||
|
|
||||||
|
return RecvInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIETHERNET::XLinkNetworkInterface::Deactivate()
|
||||||
|
{
|
||||||
|
// Send d; to tell XLink we want to disconnect cleanly
|
||||||
|
// disconnect;optional_locally_unique_name;optional_padding
|
||||||
|
std::string cmd =
|
||||||
|
"disconnect;" + m_client_identifier + ";0000000000000000000000000000000000000000000";
|
||||||
|
const auto size = u32(cmd.length());
|
||||||
|
u8 buffer[255] = {};
|
||||||
|
memmove(buffer, cmd.c_str(), size);
|
||||||
|
|
||||||
|
DEBUG_LOG(SP1, "SendCommandPayload %x\n%s", size, ArrayToString(buffer, size, 0x10).c_str());
|
||||||
|
|
||||||
|
if (m_sf_socket.send(buffer, size, m_sf_recipient_ip, m_dest_port) != sf::Socket::Done)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "Deactivate(): failed to send disconnect message to XLink Kai client");
|
||||||
|
}
|
||||||
|
|
||||||
|
NOTICE_LOG(SP1, "XLink Kai BBA deactivated");
|
||||||
|
|
||||||
|
m_bba_link_up = false;
|
||||||
|
|
||||||
|
// Disable socket blocking
|
||||||
|
m_sf_socket.setBlocking(false);
|
||||||
|
|
||||||
|
// Set flags for clean shutdown of the read thread
|
||||||
|
m_read_enabled.Clear();
|
||||||
|
m_read_thread_shutdown.Set();
|
||||||
|
|
||||||
|
// Detach the thread and let it die on its own (leaks ~9kb of memory)
|
||||||
|
m_read_thread.detach();
|
||||||
|
|
||||||
|
// Close the socket
|
||||||
|
m_sf_socket.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIETHERNET::XLinkNetworkInterface::IsActivated()
|
||||||
|
{
|
||||||
|
return m_sf_socket.getLocalPort() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIETHERNET::XLinkNetworkInterface::SendFrame(const u8* frame, u32 size)
|
||||||
|
{
|
||||||
|
// Is the BBA actually connected?
|
||||||
|
if (!m_bba_link_up)
|
||||||
|
{
|
||||||
|
// Has the user been told the connection failed?
|
||||||
|
if (!m_bba_failure_notified)
|
||||||
|
{
|
||||||
|
OSD::AddMessage("XLink Kai BBA not connected", 30000);
|
||||||
|
m_bba_failure_notified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true because this isnt an error dolphin needs to handle
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite buffer and add header
|
||||||
|
memmove(m_out_frame, "e;e;", 4);
|
||||||
|
memmove(m_out_frame + 4, frame, size);
|
||||||
|
size += 4;
|
||||||
|
|
||||||
|
// Only uncomment for debugging, the performance hit is too big otherwise
|
||||||
|
// INFO_LOG(SP1, "SendFrame %x\n%s", size, ArrayToString(m_out_frame, size, 0x10).c_str());
|
||||||
|
|
||||||
|
if (m_sf_socket.send(m_out_frame, size, m_sf_recipient_ip, m_dest_port) != sf::Socket::Done)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "SendFrame(): expected to write %u bytes, but failed, errno %d", size, errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_eth_ref->SendComplete();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIETHERNET::XLinkNetworkInterface::ReadThreadHandler(
|
||||||
|
CEXIETHERNET::XLinkNetworkInterface* self)
|
||||||
|
{
|
||||||
|
sf::IpAddress sender;
|
||||||
|
u16 port;
|
||||||
|
|
||||||
|
while (!self->m_read_thread_shutdown.IsSet())
|
||||||
|
{
|
||||||
|
if (!self->IsActivated())
|
||||||
|
break;
|
||||||
|
|
||||||
|
// XLink supports jumboframes but Gamecube BBA does not. We need to support jumbo frames
|
||||||
|
// *here* because XLink *could* send one
|
||||||
|
std::size_t bytes_read = 0;
|
||||||
|
if (self->m_sf_socket.receive(self->m_in_frame, std::size(self->m_in_frame), bytes_read, sender,
|
||||||
|
port) != sf::Socket::Done &&
|
||||||
|
self->m_bba_link_up)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "Failed to read from BBA, err=%zu", bytes_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure *anything* was recieved before going any further
|
||||||
|
if (bytes_read < 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Did we get an ethernet frame? Copy the first 4 bytes to check the header
|
||||||
|
char temp_check[4];
|
||||||
|
memmove(temp_check, self->m_in_frame, std::size(temp_check));
|
||||||
|
|
||||||
|
// Check for e;e; header; this indicates the received data is an ethernet frame
|
||||||
|
// e;e;raw_ethernet_frame_data
|
||||||
|
if (temp_check[2] == 'e' && temp_check[3] == ';')
|
||||||
|
{
|
||||||
|
// Is the frame larger than BBA_RECV_SIZE?
|
||||||
|
if ((bytes_read - 4) < BBA_RECV_SIZE)
|
||||||
|
{
|
||||||
|
// Copy payload into BBA buffer as an ethernet frame
|
||||||
|
memmove(self->m_eth_ref->mRecvBuffer.get(), self->m_in_frame + 4, bytes_read - 4);
|
||||||
|
|
||||||
|
// Check the frame size again after the header is removed
|
||||||
|
if (bytes_read < 1)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "Failed to read from BBA, err=%zu", bytes_read - 4);
|
||||||
|
}
|
||||||
|
else if (self->m_read_enabled.IsSet())
|
||||||
|
{
|
||||||
|
// Only uncomment for debugging, the performance hit is too big otherwise
|
||||||
|
// DEBUG_LOG(SP1, "Read data: %s", ArrayToString(self->m_eth_ref->mRecvBuffer.get(),
|
||||||
|
// u32(bytes_read - 4), 0x10).c_str());
|
||||||
|
self->m_eth_ref->mRecvBufferLength = u32(bytes_read - 4);
|
||||||
|
self->m_eth_ref->RecvHandlePacket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise we recieved control data or junk
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::string control_msg(self->m_in_frame, self->m_in_frame + bytes_read);
|
||||||
|
INFO_LOG(SP1, "Received XLink Kai control data: %s", control_msg.c_str());
|
||||||
|
|
||||||
|
// connected;identifier;
|
||||||
|
if (StringBeginsWith(control_msg, "connected"))
|
||||||
|
{
|
||||||
|
NOTICE_LOG(SP1, "XLink Kai BBA connected");
|
||||||
|
OSD::AddMessage("XLink Kai BBA connected", 4500);
|
||||||
|
|
||||||
|
self->m_bba_link_up = true;
|
||||||
|
// TODO (in EXI_DeviceEthernet.cpp) bring the BBA link up here
|
||||||
|
|
||||||
|
// Send any extra settings now
|
||||||
|
// Enable XLink chat messages in OSD if set
|
||||||
|
if (self->m_chat_osd_enabled)
|
||||||
|
{
|
||||||
|
constexpr std::string_view cmd = "setting;chat;true;";
|
||||||
|
const auto size = u32(cmd.length());
|
||||||
|
u8 buffer[255] = {};
|
||||||
|
memmove(buffer, cmd.data(), size);
|
||||||
|
|
||||||
|
DEBUG_LOG(SP1, "SendCommandPayload %x\n%s", size,
|
||||||
|
ArrayToString(buffer, size, 0x10).c_str());
|
||||||
|
|
||||||
|
if (self->m_sf_socket.send(buffer, size, self->m_sf_recipient_ip, self->m_dest_port) !=
|
||||||
|
sf::Socket::Done)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1,
|
||||||
|
"ReadThreadHandler(): failed to send setting message to XLink Kai client");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// disconnected;optional_identifier;optional_message;
|
||||||
|
else if (StringBeginsWith(control_msg, "disconnected"))
|
||||||
|
{
|
||||||
|
NOTICE_LOG(SP1, "XLink Kai BBA disconnected");
|
||||||
|
// Show OSD message for 15 seconds to make sure the user sees it
|
||||||
|
OSD::AddMessage("XLink Kai BBA disconnected", 15000);
|
||||||
|
|
||||||
|
// TODO (TODO in EXI_DeviceEthernet.cpp) bring the BBA link down here
|
||||||
|
self->m_bba_link_up = false;
|
||||||
|
|
||||||
|
// Disable socket blocking
|
||||||
|
self->m_sf_socket.setBlocking(false);
|
||||||
|
|
||||||
|
// Shut down the read thread
|
||||||
|
self->m_read_enabled.Clear();
|
||||||
|
self->m_read_thread_shutdown.Set();
|
||||||
|
|
||||||
|
// Close the socket
|
||||||
|
self->m_sf_socket.unbind();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// keepalive;
|
||||||
|
else if (StringBeginsWith(control_msg, "keepalive"))
|
||||||
|
{
|
||||||
|
DEBUG_LOG(SP1, "XLink Kai BBA keepalive");
|
||||||
|
|
||||||
|
// Only uncomment for debugging, just clogs the log otherwise
|
||||||
|
// INFO_LOG(SP1, "SendCommandPayload %x\n%s", 2, ArrayToString(m_in_frame, 2,
|
||||||
|
// 0x10).c_str());
|
||||||
|
|
||||||
|
// Reply (using the message that came in!)
|
||||||
|
if (self->m_sf_socket.send(self->m_in_frame, 10, self->m_sf_recipient_ip,
|
||||||
|
self->m_dest_port) != sf::Socket::Done)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "ReadThreadHandler(): failed to reply to XLink Kai client keepalive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// message;message_text;
|
||||||
|
else if (StringBeginsWith(control_msg, "message"))
|
||||||
|
{
|
||||||
|
std::string msg = control_msg.substr(8, control_msg.length() - 1);
|
||||||
|
|
||||||
|
NOTICE_LOG(SP1, "XLink Kai message: %s", msg.c_str());
|
||||||
|
// Show OSD message for 15 seconds to make sure the user sees it
|
||||||
|
OSD::AddMessage(std::move(msg), 15000);
|
||||||
|
}
|
||||||
|
// chat;message_text;
|
||||||
|
else if (StringBeginsWith(control_msg, "chat"))
|
||||||
|
{
|
||||||
|
std::string msg = control_msg.substr(5, control_msg.length() - 1);
|
||||||
|
|
||||||
|
NOTICE_LOG(SP1, "XLink Kai chat: %s", msg.c_str());
|
||||||
|
OSD::AddMessage(std::move(msg), 5000);
|
||||||
|
}
|
||||||
|
// directmessage;message_text;
|
||||||
|
else if (StringBeginsWith(control_msg, "directmessage"))
|
||||||
|
{
|
||||||
|
std::string msg = control_msg.substr(14, control_msg.length() - 1);
|
||||||
|
|
||||||
|
NOTICE_LOG(SP1, "XLink Kai direct message: %s", msg.c_str());
|
||||||
|
OSD::AddMessage(std::move(msg), 5000);
|
||||||
|
}
|
||||||
|
// else junk/unsupported control message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIETHERNET::XLinkNetworkInterface::RecvInit()
|
||||||
|
{
|
||||||
|
m_read_thread = std::thread(ReadThreadHandler, this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIETHERNET::XLinkNetworkInterface::RecvStart()
|
||||||
|
{
|
||||||
|
m_read_enabled.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIETHERNET::XLinkNetworkInterface::RecvStop()
|
||||||
|
{
|
||||||
|
m_read_enabled.Clear();
|
||||||
|
}
|
||||||
|
} // namespace ExpansionInterface
|
|
@ -133,7 +133,11 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(const TEXIDevices device_type, cons
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EXIDEVICE_ETH:
|
case EXIDEVICE_ETH:
|
||||||
result = std::make_unique<CEXIETHERNET>();
|
result = std::make_unique<CEXIETHERNET>(BBADeviceType::TAP);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXIDEVICE_ETHXLINK:
|
||||||
|
result = std::make_unique<CEXIETHERNET>(BBADeviceType::XLINK);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EXIDEVICE_GECKO:
|
case EXIDEVICE_GECKO:
|
||||||
|
|
|
@ -32,6 +32,7 @@ enum TEXIDevices : int
|
||||||
// Converted to EXIDEVICE_MEMORYCARD internally.
|
// Converted to EXIDEVICE_MEMORYCARD internally.
|
||||||
EXIDEVICE_MEMORYCARDFOLDER,
|
EXIDEVICE_MEMORYCARDFOLDER,
|
||||||
EXIDEVICE_AGP,
|
EXIDEVICE_AGP,
|
||||||
|
EXIDEVICE_ETHXLINK,
|
||||||
EXIDEVICE_NONE = 0xFF
|
EXIDEVICE_NONE = 0xFF
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -12,6 +13,7 @@
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/Network.h"
|
#include "Common/Network.h"
|
||||||
|
#include "Common/StringUtil.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
#include "Core/CoreTiming.h"
|
#include "Core/CoreTiming.h"
|
||||||
#include "Core/HW/EXI/EXI.h"
|
#include "Core/HW/EXI/EXI.h"
|
||||||
|
@ -23,19 +25,16 @@ namespace ExpansionInterface
|
||||||
// Multiple parts of this implementation depend on Dolphin
|
// Multiple parts of this implementation depend on Dolphin
|
||||||
// being compiled for a little endian host.
|
// being compiled for a little endian host.
|
||||||
|
|
||||||
CEXIETHERNET::CEXIETHERNET()
|
CEXIETHERNET::CEXIETHERNET(BBADeviceType type)
|
||||||
{
|
{
|
||||||
tx_fifo = std::make_unique<u8[]>(BBA_TXFIFO_SIZE);
|
|
||||||
mBbaMem = std::make_unique<u8[]>(BBA_MEM_SIZE);
|
|
||||||
mRecvBuffer = std::make_unique<u8[]>(BBA_RECV_SIZE);
|
|
||||||
|
|
||||||
MXHardReset();
|
|
||||||
|
|
||||||
// Parse MAC address from config, and generate a new one if it doesn't
|
// Parse MAC address from config, and generate a new one if it doesn't
|
||||||
// exist or can't be parsed.
|
// exist or can't be parsed.
|
||||||
std::string& mac_addr_setting = SConfig::GetInstance().m_bba_mac;
|
std::string& mac_addr_setting = SConfig::GetInstance().m_bba_mac;
|
||||||
std::optional<Common::MACAddress> mac_addr = Common::StringToMacAddress(mac_addr_setting);
|
std::optional<Common::MACAddress> mac_addr = Common::StringToMacAddress(mac_addr_setting);
|
||||||
|
|
||||||
|
std::transform(mac_addr_setting.begin(), mac_addr_setting.end(), mac_addr_setting.begin(),
|
||||||
|
[](unsigned char c) { return std::tolower(c); });
|
||||||
|
|
||||||
if (!mac_addr)
|
if (!mac_addr)
|
||||||
{
|
{
|
||||||
mac_addr = Common::GenerateMacAddress(Common::MACConsumer::BBA);
|
mac_addr = Common::GenerateMacAddress(Common::MACConsumer::BBA);
|
||||||
|
@ -43,6 +42,42 @@ CEXIETHERNET::CEXIETHERNET()
|
||||||
SConfig::GetInstance().SaveSettings();
|
SConfig::GetInstance().SaveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case BBADeviceType::TAP:
|
||||||
|
m_network_interface = std::make_unique<TAPNetworkInterface>(this);
|
||||||
|
INFO_LOG(SP1, "Created TAP physical network interface.");
|
||||||
|
break;
|
||||||
|
case BBADeviceType::XLINK:
|
||||||
|
// TODO start BBA with network link down, bring it up after "connected" response from XLink
|
||||||
|
|
||||||
|
// Perform sanity check on BBA MAC address, XLink requires the vendor OUI to be Nintendo's and
|
||||||
|
// to be one of the two used for the GameCube.
|
||||||
|
// Don't actually stop the BBA from initializing though
|
||||||
|
if (!StringBeginsWith(mac_addr_setting, "00:09:bf") &&
|
||||||
|
!StringBeginsWith(mac_addr_setting, "00:17:ab"))
|
||||||
|
{
|
||||||
|
PanicAlertT("BBA MAC address %s invalid for XLink Kai. A valid Nintendo GameCube MAC address "
|
||||||
|
"must be used. Generate a new MAC address starting with 00:09:bf or 00:17:ab.",
|
||||||
|
mac_addr_setting.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// m_client_mdentifier should be unique per connected emulator from the XLink kai client's
|
||||||
|
// perspective so lets use "dolphin<bba mac>"
|
||||||
|
m_network_interface = std::make_unique<XLinkNetworkInterface>(
|
||||||
|
this, SConfig::GetInstance().m_bba_xlink_ip, 34523,
|
||||||
|
"dolphin" + SConfig::GetInstance().m_bba_mac, SConfig::GetInstance().m_bba_xlink_chat_osd);
|
||||||
|
INFO_LOG(SP1, "Created XLink Kai BBA network interface connection to %s:34523",
|
||||||
|
SConfig::GetInstance().m_bba_xlink_ip.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx_fifo = std::make_unique<u8[]>(BBA_TXFIFO_SIZE);
|
||||||
|
mBbaMem = std::make_unique<u8[]>(BBA_MEM_SIZE);
|
||||||
|
mRecvBuffer = std::make_unique<u8[]>(BBA_RECV_SIZE);
|
||||||
|
|
||||||
|
MXHardReset();
|
||||||
|
|
||||||
const auto& mac = mac_addr.value();
|
const auto& mac = mac_addr.value();
|
||||||
memcpy(&mBbaMem[BBA_NAFR_PAR0], mac.data(), mac.size());
|
memcpy(&mBbaMem[BBA_NAFR_PAR0], mac.data(), mac.size());
|
||||||
|
|
||||||
|
@ -52,7 +87,7 @@ CEXIETHERNET::CEXIETHERNET()
|
||||||
|
|
||||||
CEXIETHERNET::~CEXIETHERNET()
|
CEXIETHERNET::~CEXIETHERNET()
|
||||||
{
|
{
|
||||||
Deactivate();
|
m_network_interface->Deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::SetCS(int cs)
|
void CEXIETHERNET::SetCS(int cs)
|
||||||
|
@ -303,7 +338,7 @@ void CEXIETHERNET::MXCommandHandler(u32 data, u32 size)
|
||||||
{
|
{
|
||||||
INFO_LOG(SP1, "Software reset");
|
INFO_LOG(SP1, "Software reset");
|
||||||
// MXSoftReset();
|
// MXSoftReset();
|
||||||
Activate();
|
m_network_interface->Activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((mBbaMem[BBA_NCRA] & NCRA_SR) ^ (data & NCRA_SR))
|
if ((mBbaMem[BBA_NCRA] & NCRA_SR) ^ (data & NCRA_SR))
|
||||||
|
@ -311,9 +346,9 @@ void CEXIETHERNET::MXCommandHandler(u32 data, u32 size)
|
||||||
DEBUG_LOG(SP1, "%s rx", (data & NCRA_SR) ? "start" : "stop");
|
DEBUG_LOG(SP1, "%s rx", (data & NCRA_SR) ? "start" : "stop");
|
||||||
|
|
||||||
if (data & NCRA_SR)
|
if (data & NCRA_SR)
|
||||||
RecvStart();
|
m_network_interface->RecvStart();
|
||||||
else
|
else
|
||||||
RecvStop();
|
m_network_interface->RecvStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only start transfer if there isn't one currently running
|
// Only start transfer if there isn't one currently running
|
||||||
|
@ -386,7 +421,7 @@ void CEXIETHERNET::DirectFIFOWrite(const u8* data, u32 size)
|
||||||
|
|
||||||
void CEXIETHERNET::SendFromDirectFIFO()
|
void CEXIETHERNET::SendFromDirectFIFO()
|
||||||
{
|
{
|
||||||
SendFrame(tx_fifo.get(), *(u16*)&mBbaMem[BBA_TXFIFOCNT]);
|
m_network_interface->SendFrame(tx_fifo.get(), *(u16*)&mBbaMem[BBA_TXFIFOCNT]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::SendFromPacketBuffer()
|
void CEXIETHERNET::SendFromPacketBuffer()
|
||||||
|
@ -579,7 +614,7 @@ bool CEXIETHERNET::RecvHandlePacket()
|
||||||
|
|
||||||
wait_for_next:
|
wait_for_next:
|
||||||
if (mBbaMem[BBA_NCRA] & NCRA_SR)
|
if (mBbaMem[BBA_NCRA] & NCRA_SR)
|
||||||
RecvStart();
|
m_network_interface->RecvStart();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <SFML/Network.hpp>
|
||||||
|
|
||||||
#include "Common/Flag.h"
|
#include "Common/Flag.h"
|
||||||
#include "Core/HW/EXI/EXI_Device.h"
|
#include "Core/HW/EXI/EXI_Device.h"
|
||||||
|
|
||||||
|
@ -196,10 +198,16 @@ enum RecvStatus
|
||||||
|
|
||||||
#define BBA_RECV_SIZE 0x800
|
#define BBA_RECV_SIZE 0x800
|
||||||
|
|
||||||
|
enum class BBADeviceType
|
||||||
|
{
|
||||||
|
TAP,
|
||||||
|
XLINK,
|
||||||
|
};
|
||||||
|
|
||||||
class CEXIETHERNET : public IEXIDevice
|
class CEXIETHERNET : public IEXIDevice
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CEXIETHERNET();
|
explicit CEXIETHERNET(BBADeviceType type);
|
||||||
virtual ~CEXIETHERNET();
|
virtual ~CEXIETHERNET();
|
||||||
void SetCS(int cs) override;
|
void SetCS(int cs) override;
|
||||||
bool IsPresent() const override;
|
bool IsPresent() const override;
|
||||||
|
@ -297,34 +305,99 @@ private:
|
||||||
std::unique_ptr<u8[]> mBbaMem;
|
std::unique_ptr<u8[]> mBbaMem;
|
||||||
std::unique_ptr<u8[]> tx_fifo;
|
std::unique_ptr<u8[]> tx_fifo;
|
||||||
|
|
||||||
// TAP interface
|
class NetworkInterface
|
||||||
static void ReadThreadHandler(CEXIETHERNET* self);
|
{
|
||||||
bool Activate();
|
protected:
|
||||||
void Deactivate();
|
CEXIETHERNET* m_eth_ref = nullptr;
|
||||||
bool IsActivated();
|
explicit NetworkInterface(CEXIETHERNET* eth_ref) : m_eth_ref{eth_ref} {}
|
||||||
bool SendFrame(const u8* frame, u32 size);
|
|
||||||
bool RecvInit();
|
public:
|
||||||
void RecvStart();
|
virtual bool Activate() { return false; }
|
||||||
void RecvStop();
|
virtual void Deactivate() {}
|
||||||
|
virtual bool IsActivated() { return false; }
|
||||||
|
virtual bool SendFrame(const u8* frame, u32 size) { return false; }
|
||||||
|
virtual bool RecvInit() { return false; }
|
||||||
|
virtual void RecvStart() {}
|
||||||
|
virtual void RecvStop() {}
|
||||||
|
|
||||||
|
virtual ~NetworkInterface() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TAPNetworkInterface : public NetworkInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit TAPNetworkInterface(CEXIETHERNET* eth_ref) : NetworkInterface(eth_ref) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool Activate() override;
|
||||||
|
void Deactivate() override;
|
||||||
|
bool IsActivated() override;
|
||||||
|
bool SendFrame(const u8* frame, u32 size) override;
|
||||||
|
bool RecvInit() override;
|
||||||
|
void RecvStart() override;
|
||||||
|
void RecvStop() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \
|
||||||
|
defined(__OpenBSD__)
|
||||||
|
std::thread readThread;
|
||||||
|
Common::Flag readEnabled;
|
||||||
|
Common::Flag readThreadShutdown;
|
||||||
|
static void ReadThreadHandler(TAPNetworkInterface* self);
|
||||||
|
#endif
|
||||||
|
#if defined(_WIN32)
|
||||||
|
HANDLE mHAdapter = INVALID_HANDLE_VALUE;
|
||||||
|
OVERLAPPED mReadOverlapped = {};
|
||||||
|
OVERLAPPED mWriteOverlapped = {};
|
||||||
|
std::vector<u8> mWriteBuffer;
|
||||||
|
bool mWritePending = false;
|
||||||
|
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||||
|
int fd = -1;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class XLinkNetworkInterface : public NetworkInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
XLinkNetworkInterface(CEXIETHERNET* eth_ref, std::string dest_ip, int dest_port,
|
||||||
|
std::string identifier, bool chat_osd_enabled)
|
||||||
|
: NetworkInterface(eth_ref), m_dest_ip(std::move(dest_ip)), m_dest_port(dest_port),
|
||||||
|
m_client_identifier(identifier), m_chat_osd_enabled(chat_osd_enabled)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool Activate() override;
|
||||||
|
void Deactivate() override;
|
||||||
|
bool IsActivated() override;
|
||||||
|
bool SendFrame(const u8* frame, u32 size) override;
|
||||||
|
bool RecvInit() override;
|
||||||
|
void RecvStart() override;
|
||||||
|
void RecvStop() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_dest_ip;
|
||||||
|
int m_dest_port;
|
||||||
|
std::string m_client_identifier;
|
||||||
|
bool m_chat_osd_enabled;
|
||||||
|
bool m_bba_link_up = false;
|
||||||
|
bool m_bba_failure_notified = false;
|
||||||
|
#if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \
|
||||||
|
defined(__OpenBSD__)
|
||||||
|
sf::UdpSocket m_sf_socket;
|
||||||
|
sf::IpAddress m_sf_recipient_ip;
|
||||||
|
char m_in_frame[9004];
|
||||||
|
char m_out_frame[9004];
|
||||||
|
std::thread m_read_thread;
|
||||||
|
Common::Flag m_read_enabled;
|
||||||
|
Common::Flag m_read_thread_shutdown;
|
||||||
|
static void ReadThreadHandler(XLinkNetworkInterface* self);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<NetworkInterface> m_network_interface;
|
||||||
|
|
||||||
std::unique_ptr<u8[]> mRecvBuffer;
|
std::unique_ptr<u8[]> mRecvBuffer;
|
||||||
u32 mRecvBufferLength = 0;
|
u32 mRecvBufferLength = 0;
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
HANDLE mHAdapter = INVALID_HANDLE_VALUE;
|
|
||||||
OVERLAPPED mReadOverlapped = {};
|
|
||||||
OVERLAPPED mWriteOverlapped = {};
|
|
||||||
std::vector<u8> mWriteBuffer;
|
|
||||||
bool mWritePending = false;
|
|
||||||
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
|
||||||
int fd = -1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \
|
|
||||||
defined(__OpenBSD__)
|
|
||||||
std::thread readThread;
|
|
||||||
Common::Flag readEnabled;
|
|
||||||
Common::Flag readThreadShutdown;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
} // namespace ExpansionInterface
|
} // namespace ExpansionInterface
|
||||||
|
|
|
@ -103,7 +103,9 @@ void GameCubePane::CreateWidgets()
|
||||||
for (const auto& entry :
|
for (const auto& entry :
|
||||||
{std::make_pair(tr("<Nothing>"), ExpansionInterface::EXIDEVICE_NONE),
|
{std::make_pair(tr("<Nothing>"), ExpansionInterface::EXIDEVICE_NONE),
|
||||||
std::make_pair(tr("Dummy"), ExpansionInterface::EXIDEVICE_DUMMY),
|
std::make_pair(tr("Dummy"), ExpansionInterface::EXIDEVICE_DUMMY),
|
||||||
std::make_pair(tr("Broadband Adapter"), ExpansionInterface::EXIDEVICE_ETH)})
|
std::make_pair(tr("Broadband Adapter (TAP)"), ExpansionInterface::EXIDEVICE_ETH),
|
||||||
|
std::make_pair(tr("Broadband Adapter (XLink Kai)"),
|
||||||
|
ExpansionInterface::EXIDEVICE_ETHXLINK)})
|
||||||
{
|
{
|
||||||
m_slot_combos[2]->addItem(entry.first, entry.second);
|
m_slot_combos[2]->addItem(entry.first, entry.second);
|
||||||
}
|
}
|
||||||
|
@ -158,7 +160,8 @@ void GameCubePane::UpdateButton(int slot)
|
||||||
value == ExpansionInterface::EXIDEVICE_AGP || value == ExpansionInterface::EXIDEVICE_MIC);
|
value == ExpansionInterface::EXIDEVICE_AGP || value == ExpansionInterface::EXIDEVICE_MIC);
|
||||||
break;
|
break;
|
||||||
case SLOT_SP1_INDEX:
|
case SLOT_SP1_INDEX:
|
||||||
has_config = (value == ExpansionInterface::EXIDEVICE_ETH);
|
has_config = (value == ExpansionInterface::EXIDEVICE_ETH ||
|
||||||
|
value == ExpansionInterface::EXIDEVICE_ETHXLINK);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,12 +189,26 @@ void GameCubePane::OnConfigPressed(int slot)
|
||||||
{
|
{
|
||||||
bool ok;
|
bool ok;
|
||||||
const auto new_mac = QInputDialog::getText(
|
const auto new_mac = QInputDialog::getText(
|
||||||
|
// i18n: MAC stands for Media Access Control. A MAC address uniquely identifies a network
|
||||||
|
// interface (physical) like a serial number. "MAC" should be kept in translations.
|
||||||
this, tr("Broadband Adapter MAC address"), tr("Enter new Broadband Adapter MAC address:"),
|
this, tr("Broadband Adapter MAC address"), tr("Enter new Broadband Adapter MAC address:"),
|
||||||
QLineEdit::Normal, QString::fromStdString(SConfig::GetInstance().m_bba_mac), &ok);
|
QLineEdit::Normal, QString::fromStdString(SConfig::GetInstance().m_bba_mac), &ok);
|
||||||
if (ok)
|
if (ok)
|
||||||
SConfig::GetInstance().m_bba_mac = new_mac.toStdString();
|
SConfig::GetInstance().m_bba_mac = new_mac.toStdString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case ExpansionInterface::EXIDEVICE_ETHXLINK:
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
const auto new_dest = QInputDialog::getText(
|
||||||
|
this, tr("Broadband Adapter (XLink Kai) Destination Address"),
|
||||||
|
tr("Enter IP address of device running the XLink Kai Client.\nFor more information see"
|
||||||
|
" https://www.teamxlink.co.uk/wiki/Dolphin"),
|
||||||
|
QLineEdit::Normal, QString::fromStdString(SConfig::GetInstance().m_bba_xlink_ip), &ok);
|
||||||
|
if (ok)
|
||||||
|
SConfig::GetInstance().m_bba_xlink_ip = new_dest.toStdString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
qFatal("unknown settings pressed");
|
qFatal("unknown settings pressed");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue