forked from dolphin-emu/dolphin
bba tcp implementation
This commit is contained in:
@@ -100,7 +100,8 @@ add_library(core
|
|||||||
HW/EXI/EXI_DeviceAD16.cpp
|
HW/EXI/EXI_DeviceAD16.cpp
|
||||||
HW/EXI/EXI_DeviceAGP.cpp
|
HW/EXI/EXI_DeviceAGP.cpp
|
||||||
HW/EXI/EXI_DeviceDummy.cpp
|
HW/EXI/EXI_DeviceDummy.cpp
|
||||||
HW/EXI/EXI_DeviceEthernet.cpp
|
HW/EXI/EXI_DeviceEthernetBase.cpp
|
||||||
|
HW/EXI/EXI_DeviceEthernetTCP.cpp
|
||||||
HW/EXI/EXI_DeviceGecko.cpp
|
HW/EXI/EXI_DeviceGecko.cpp
|
||||||
HW/EXI/EXI_DeviceIPL.cpp
|
HW/EXI/EXI_DeviceIPL.cpp
|
||||||
HW/EXI/EXI_DeviceMemoryCard.cpp
|
HW/EXI/EXI_DeviceMemoryCard.cpp
|
||||||
@@ -330,7 +331,7 @@ endif()
|
|||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(core PRIVATE
|
target_sources(core PRIVATE
|
||||||
HW/EXI/BBA-TAP/TAP_Win32.cpp
|
HW/EXI/EXI_DeviceEthernetTAP_Win32.cpp
|
||||||
HW/WiimoteReal/IOWin.cpp
|
HW/WiimoteReal/IOWin.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(core PUBLIC
|
target_link_libraries(core PUBLIC
|
||||||
@@ -340,12 +341,12 @@ if(WIN32)
|
|||||||
)
|
)
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
target_sources(core PRIVATE
|
target_sources(core PRIVATE
|
||||||
HW/EXI/BBA-TAP/TAP_Apple.cpp
|
HW/EXI/EXI_DeviceEthernetTAP_Apple.cpp
|
||||||
HW/WiimoteReal/IOdarwin.mm
|
HW/WiimoteReal/IOdarwin.mm
|
||||||
)
|
)
|
||||||
target_link_libraries(core PUBLIC ${IOB_LIBRARY})
|
target_link_libraries(core PUBLIC ${IOB_LIBRARY})
|
||||||
elseif(UNIX)
|
elseif(UNIX)
|
||||||
target_sources(core PRIVATE HW/EXI/BBA-TAP/TAP_Unix.cpp)
|
target_sources(core PRIVATE HW/EXI/EXI_DeviceEthernetTAP_Unix.cpp)
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
target_sources(core PRIVATE HW/WiimoteReal/IOAndroid.cpp)
|
target_sources(core PRIVATE HW/WiimoteReal/IOAndroid.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
@@ -235,6 +235,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_SERVER", m_bba_server);
|
||||||
|
core->Set("BBA_PORT", m_bba_port);
|
||||||
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
|
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
|
||||||
{
|
{
|
||||||
core->Set(StringFromFormat("SIDevice%i", i), m_SIDevice[i]);
|
core->Set(StringFromFormat("SIDevice%i", i), m_SIDevice[i]);
|
||||||
@@ -527,6 +529,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_SERVER", &m_bba_server);
|
||||||
|
core->Get("BBA_PORT", &m_bba_port);
|
||||||
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
|
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
|
||||||
{
|
{
|
||||||
core->Get(StringFromFormat("SIDevice%i", i), (u32*)&m_SIDevice[i],
|
core->Get(StringFromFormat("SIDevice%i", i), (u32*)&m_SIDevice[i],
|
||||||
|
@@ -227,6 +227,8 @@ struct SConfig
|
|||||||
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_server;
|
||||||
|
u16 m_bba_port;
|
||||||
|
|
||||||
// interface language
|
// interface language
|
||||||
std::string m_InterfaceLanguage;
|
std::string m_InterfaceLanguage;
|
||||||
|
@@ -132,14 +132,15 @@
|
|||||||
<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\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" />
|
||||||
<ClCompile Include="HW\EXI\EXI_DeviceAD16.cpp" />
|
<ClCompile Include="HW\EXI\EXI_DeviceAD16.cpp" />
|
||||||
<ClCompile Include="HW\EXI\EXI_DeviceAGP.cpp" />
|
<ClCompile Include="HW\EXI\EXI_DeviceAGP.cpp" />
|
||||||
<ClCompile Include="HW\EXI\EXI_DeviceDummy.cpp" />
|
<ClCompile Include="HW\EXI\EXI_DeviceDummy.cpp" />
|
||||||
<ClCompile Include="HW\EXI\EXI_DeviceEthernet.cpp" />
|
<ClCompile Include="HW\EXI\EXI_DeviceEthernetBase.cpp" />
|
||||||
|
<ClCompile Include="HW\EXI\EXI_DeviceEthernetTAP_Win32.cpp" />
|
||||||
|
<ClCompile Include="HW\EXI\EXI_DeviceEthernetTCP.cpp" />
|
||||||
<ClCompile Include="HW\EXI\EXI_DeviceGecko.cpp" />
|
<ClCompile Include="HW\EXI\EXI_DeviceGecko.cpp" />
|
||||||
<ClCompile Include="HW\EXI\EXI_DeviceIPL.cpp" />
|
<ClCompile Include="HW\EXI\EXI_DeviceIPL.cpp" />
|
||||||
<ClCompile Include="HW\EXI\EXI_DeviceMemoryCard.cpp" />
|
<ClCompile Include="HW\EXI\EXI_DeviceMemoryCard.cpp" />
|
||||||
@@ -396,14 +397,16 @@
|
|||||||
<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\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" />
|
||||||
<ClInclude Include="HW\EXI\EXI_DeviceAD16.h" />
|
<ClInclude Include="HW\EXI\EXI_DeviceAD16.h" />
|
||||||
<ClInclude Include="HW\EXI\EXI_DeviceAGP.h" />
|
<ClInclude Include="HW\EXI\EXI_DeviceAGP.h" />
|
||||||
<ClInclude Include="HW\EXI\EXI_DeviceDummy.h" />
|
<ClInclude Include="HW\EXI\EXI_DeviceDummy.h" />
|
||||||
<ClInclude Include="HW\EXI\EXI_DeviceEthernet.h" />
|
<ClInclude Include="HW\EXI\EXI_DeviceEthernetBase.h" />
|
||||||
|
<ClInclude Include="HW\EXI\EXI_DeviceEthernetTAP.h" />
|
||||||
|
<ClInclude Include="HW\EXI\EXI_DeviceEthernetTAP_Win32.h" />
|
||||||
|
<ClInclude Include="HW\EXI\EXI_DeviceEthernetTCP.h" />
|
||||||
<ClInclude Include="HW\EXI\EXI_DeviceGecko.h" />
|
<ClInclude Include="HW\EXI\EXI_DeviceGecko.h" />
|
||||||
<ClInclude Include="HW\EXI\EXI_DeviceIPL.h" />
|
<ClInclude Include="HW\EXI\EXI_DeviceIPL.h" />
|
||||||
<ClInclude Include="HW\EXI\EXI_DeviceMemoryCard.h" />
|
<ClInclude Include="HW\EXI\EXI_DeviceMemoryCard.h" />
|
||||||
|
@@ -441,7 +441,16 @@
|
|||||||
<ClCompile Include="HW\EXI\EXI_DeviceDummy.cpp">
|
<ClCompile Include="HW\EXI\EXI_DeviceDummy.cpp">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="HW\EXI\EXI_DeviceEthernet.cpp">
|
<ClCompile Include="HW\EXI\EXI_DeviceEthernetBase.cpp">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="HW\EXI\EXI_DeviceEthernetTAP.cpp">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="HW\EXI\EXI_DeviceEthernetTAP_Win32.cpp">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="HW\EXI\EXI_DeviceEthernetTCP.cpp">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="HW\EXI\EXI_DeviceGecko.cpp">
|
<ClCompile Include="HW\EXI\EXI_DeviceGecko.cpp">
|
||||||
@@ -456,9 +465,6 @@
|
|||||||
<ClCompile Include="HW\EXI\EXI_DeviceMic.cpp">
|
<ClCompile Include="HW\EXI\EXI_DeviceMic.cpp">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="HW\EXI\BBA-TAP\TAP_Win32.cpp">
|
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="HW\Sram.cpp">
|
<ClCompile Include="HW\Sram.cpp">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@@ -1143,7 +1149,16 @@
|
|||||||
<ClInclude Include="HW\EXI\EXI_DeviceDummy.h">
|
<ClInclude Include="HW\EXI\EXI_DeviceDummy.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="HW\EXI\EXI_DeviceEthernet.h">
|
<ClInclude Include="HW\EXI\EXI_DeviceEthernetBase.h">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="HW\EXI\EXI_DeviceEthernetTAP.h">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="HW\EXI\EXI_DeviceEthernetTAP_Win32.h">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="HW\EXI\EXI_DeviceEthernetTCP.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="HW\EXI\EXI_DeviceGecko.h">
|
<ClInclude Include="HW\EXI\EXI_DeviceGecko.h">
|
||||||
@@ -1158,9 +1173,6 @@
|
|||||||
<ClInclude Include="HW\EXI\EXI_DeviceMic.h">
|
<ClInclude Include="HW\EXI\EXI_DeviceMic.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="HW\EXI\BBA-TAP\TAP_Win32.h">
|
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="HW\Sram.h">
|
<ClInclude Include="HW\Sram.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@@ -10,11 +10,12 @@
|
|||||||
#include "Core/HW/EXI/EXI_DeviceAD16.h"
|
#include "Core/HW/EXI/EXI_DeviceAD16.h"
|
||||||
#include "Core/HW/EXI/EXI_DeviceAGP.h"
|
#include "Core/HW/EXI/EXI_DeviceAGP.h"
|
||||||
#include "Core/HW/EXI/EXI_DeviceDummy.h"
|
#include "Core/HW/EXI/EXI_DeviceDummy.h"
|
||||||
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
#include "Core/HW/EXI/EXI_DeviceEthernetTAP.h"
|
||||||
#include "Core/HW/EXI/EXI_DeviceGecko.h"
|
#include "Core/HW/EXI/EXI_DeviceGecko.h"
|
||||||
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
||||||
#include "Core/HW/EXI/EXI_DeviceMemoryCard.h"
|
#include "Core/HW/EXI/EXI_DeviceMemoryCard.h"
|
||||||
#include "Core/HW/EXI/EXI_DeviceMic.h"
|
#include "Core/HW/EXI/EXI_DeviceMic.h"
|
||||||
|
#include "Core/HW/EXI/EXI_DeviceEthernetTCP.h"
|
||||||
#include "Core/HW/Memmap.h"
|
#include "Core/HW/Memmap.h"
|
||||||
|
|
||||||
namespace ExpansionInterface
|
namespace ExpansionInterface
|
||||||
@@ -131,8 +132,8 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(const TEXIDevices device_type, cons
|
|||||||
result = std::make_unique<CEXIMic>(channel_num);
|
result = std::make_unique<CEXIMic>(channel_num);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EXIDEVICE_ETH:
|
case EXIDEVICE_ETH_TAP:
|
||||||
result = std::make_unique<CEXIETHERNET>();
|
result = std::make_unique<CEXIEthernetTAP>();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EXIDEVICE_GECKO:
|
case EXIDEVICE_GECKO:
|
||||||
@@ -143,6 +144,10 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(const TEXIDevices device_type, cons
|
|||||||
result = std::make_unique<CEXIAgp>(channel_num);
|
result = std::make_unique<CEXIAgp>(channel_num);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case EXIDEVICE_ETH_TCP:
|
||||||
|
result = std::make_unique<CEXIEthernetTCP>();
|
||||||
|
break;
|
||||||
|
|
||||||
case EXIDEVICE_AM_BASEBOARD:
|
case EXIDEVICE_AM_BASEBOARD:
|
||||||
case EXIDEVICE_NONE:
|
case EXIDEVICE_NONE:
|
||||||
default:
|
default:
|
||||||
|
@@ -18,7 +18,7 @@ enum TEXIDevices : int
|
|||||||
EXIDEVICE_MASKROM,
|
EXIDEVICE_MASKROM,
|
||||||
EXIDEVICE_AD16,
|
EXIDEVICE_AD16,
|
||||||
EXIDEVICE_MIC,
|
EXIDEVICE_MIC,
|
||||||
EXIDEVICE_ETH,
|
EXIDEVICE_ETH_TAP,
|
||||||
// Was used for Triforce in the past, but the implementation is no longer in Dolphin.
|
// Was used for Triforce in the past, but the implementation is no longer in Dolphin.
|
||||||
// It's kept here so that values below will stay constant.
|
// It's kept here so that values below will stay constant.
|
||||||
EXIDEVICE_AM_BASEBOARD,
|
EXIDEVICE_AM_BASEBOARD,
|
||||||
@@ -27,6 +27,7 @@ enum TEXIDevices : int
|
|||||||
// Converted to EXIDEVICE_MEMORYCARD internally.
|
// Converted to EXIDEVICE_MEMORYCARD internally.
|
||||||
EXIDEVICE_MEMORYCARDFOLDER,
|
EXIDEVICE_MEMORYCARDFOLDER,
|
||||||
EXIDEVICE_AGP,
|
EXIDEVICE_AGP,
|
||||||
|
EXIDEVICE_ETH_TCP,
|
||||||
EXIDEVICE_NONE = 0xFF
|
EXIDEVICE_NONE = 0xFF
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -2,11 +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/EXI_DeviceEthernet.h"
|
#include "Core/HW/EXI/EXI_DeviceEthernetBase.h"
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
@@ -22,40 +18,43 @@ namespace ExpansionInterface
|
|||||||
// XXX: The BBA stores multi-byte elements as little endian.
|
// XXX: The BBA stores multi-byte elements as little endian.
|
||||||
// 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.
|
||||||
|
CEXIEthernetBase::CEXIEthernetBase()
|
||||||
CEXIETHERNET::CEXIETHERNET()
|
|
||||||
{
|
{
|
||||||
tx_fifo = std::make_unique<u8[]>(BBA_TXFIFO_SIZE);
|
m_tx_fifo = std::make_unique<u8[]>(BBA_TXFIFO_SIZE);
|
||||||
mBbaMem = std::make_unique<u8[]>(BBA_MEM_SIZE);
|
m_bba_mem = std::make_unique<u8[]>(BBA_MEM_SIZE);
|
||||||
mRecvBuffer = std::make_unique<u8[]>(BBA_RECV_SIZE);
|
m_recv_buffer = std::make_unique<u8[]>(BBA_RECV_SIZE);
|
||||||
|
|
||||||
MXHardReset();
|
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;
|
const std::string& mac_addr_setting = SConfig::GetInstance().m_bba_mac;
|
||||||
std::optional<Common::MACAddress> mac_addr = Common::StringToMacAddress(mac_addr_setting);
|
Common::MACAddress mac_addr;
|
||||||
|
|
||||||
if (!mac_addr)
|
if (mac_addr_setting.empty())
|
||||||
{
|
|
||||||
mac_addr = Common::GenerateMacAddress(Common::MACConsumer::BBA);
|
mac_addr = Common::GenerateMacAddress(Common::MACConsumer::BBA);
|
||||||
mac_addr_setting = Common::MacAddressToString(mac_addr.value());
|
else
|
||||||
SConfig::GetInstance().SaveSettings();
|
{
|
||||||
|
std::optional<Common::MACAddress> parsed = Common::StringToMacAddress(mac_addr_setting);
|
||||||
|
|
||||||
|
if (!parsed)
|
||||||
|
{
|
||||||
|
mac_addr = Common::GenerateMacAddress(Common::MACConsumer::BBA);
|
||||||
|
ERROR_LOG(SP1, "Invalid mac address (%s), generated a temporary one (%s)",
|
||||||
|
mac_addr_setting.c_str(), Common::MacAddressToString(mac_addr).c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mac_addr = parsed.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& mac = mac_addr.value();
|
|
||||||
memcpy(&mBbaMem[BBA_NAFR_PAR0], mac.data(), mac.size());
|
memcpy(&m_bba_mem[BBA_NAFR_PAR0], mac_addr.data(), mac_addr.size());
|
||||||
|
|
||||||
// HACK: .. fully established 100BASE-T link
|
// HACK: .. fully established 100BASE-T link
|
||||||
mBbaMem[BBA_NWAYS] = NWAYS_LS100 | NWAYS_LPNWAY | NWAYS_100TXF | NWAYS_ANCLPT;
|
m_bba_mem[BBA_NWAYS] = NWAYS_LS100 | NWAYS_LPNWAY | NWAYS_100TXF | NWAYS_ANCLPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
CEXIETHERNET::~CEXIETHERNET()
|
void CEXIEthernetBase::SetCS(int cs)
|
||||||
{
|
|
||||||
Deactivate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CEXIETHERNET::SetCS(int cs)
|
|
||||||
{
|
{
|
||||||
if (cs)
|
if (cs)
|
||||||
{
|
{
|
||||||
@@ -64,17 +63,17 @@ void CEXIETHERNET::SetCS(int cs)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::IsPresent() const
|
bool CEXIEthernetBase::IsPresent() const
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::IsInterruptSet()
|
bool CEXIEthernetBase::IsInterruptSet()
|
||||||
{
|
{
|
||||||
return !!(exi_status.interrupt & exi_status.interrupt_mask);
|
return !!(exi_status.interrupt & exi_status.interrupt_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::ImmWrite(u32 data, u32 size)
|
void CEXIEthernetBase::ImmWrite(u32 data, u32 size)
|
||||||
{
|
{
|
||||||
data >>= (4 - size) * 8;
|
data >>= (4 - size) * 8;
|
||||||
|
|
||||||
@@ -125,7 +124,7 @@ void CEXIETHERNET::ImmWrite(u32 data, u32 size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 CEXIETHERNET::ImmRead(u32 size)
|
u32 CEXIEthernetBase::ImmRead(u32 size)
|
||||||
{
|
{
|
||||||
u32 ret = 0;
|
u32 ret = 0;
|
||||||
|
|
||||||
@@ -155,7 +154,7 @@ u32 CEXIETHERNET::ImmRead(u32 size)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = size - 1; i >= 0; i--)
|
for (int i = size - 1; i >= 0; i--)
|
||||||
ret |= mBbaMem[transfer.address++] << (i * 8);
|
ret |= m_bba_mem[transfer.address++] << (i * 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_LOG(SP1, "imm r%i: %0*x", size, size * 2, ret);
|
DEBUG_LOG(SP1, "imm r%i: %0*x", size, size * 2, ret);
|
||||||
@@ -165,7 +164,7 @@ u32 CEXIETHERNET::ImmRead(u32 size)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::DMAWrite(u32 addr, u32 size)
|
void CEXIEthernetBase::DMAWrite(u32 addr, u32 size)
|
||||||
{
|
{
|
||||||
DEBUG_LOG(SP1, "DMA write: %08x %x", addr, size);
|
DEBUG_LOG(SP1, "DMA write: %08x %x", addr, size);
|
||||||
|
|
||||||
@@ -182,32 +181,32 @@ void CEXIETHERNET::DMAWrite(u32 addr, u32 size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::DMARead(u32 addr, u32 size)
|
void CEXIEthernetBase::DMARead(u32 addr, u32 size)
|
||||||
{
|
{
|
||||||
DEBUG_LOG(SP1, "DMA read: %08x %x", addr, size);
|
DEBUG_LOG(SP1, "DMA read: %08x %x", addr, size);
|
||||||
|
|
||||||
Memory::CopyToEmu(addr, &mBbaMem[transfer.address], size);
|
Memory::CopyToEmu(addr, &m_bba_mem[transfer.address], size);
|
||||||
|
|
||||||
transfer.address += size;
|
transfer.address += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::DoState(PointerWrap& p)
|
void CEXIEthernetBase::DoState(PointerWrap& p)
|
||||||
{
|
{
|
||||||
p.DoArray(tx_fifo.get(), BBA_TXFIFO_SIZE);
|
p.DoArray(m_tx_fifo.get(), BBA_TXFIFO_SIZE);
|
||||||
p.DoArray(mBbaMem.get(), BBA_MEM_SIZE);
|
p.DoArray(m_bba_mem.get(), BBA_MEM_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::IsMXCommand(u32 const data)
|
bool CEXIEthernetBase::IsMXCommand(u32 const data)
|
||||||
{
|
{
|
||||||
return !!(data & (1 << 31));
|
return !!(data & (1 << 31));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::IsWriteCommand(u32 const data)
|
bool CEXIEthernetBase::IsWriteCommand(u32 const data)
|
||||||
{
|
{
|
||||||
return IsMXCommand(data) ? !!(data & (1 << 30)) : !!(data & (1 << 14));
|
return IsMXCommand(data) ? !!(data & (1 << 30)) : !!(data & (1 << 14));
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* CEXIETHERNET::GetRegisterName() const
|
const char* CEXIEthernetBase::GetRegisterName() const
|
||||||
{
|
{
|
||||||
#define STR_RETURN(x) \
|
#define STR_RETURN(x) \
|
||||||
case x: \
|
case x: \
|
||||||
@@ -285,16 +284,16 @@ const char* CEXIETHERNET::GetRegisterName() const
|
|||||||
#undef STR_RETURN
|
#undef STR_RETURN
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::MXHardReset()
|
void CEXIEthernetBase::MXHardReset()
|
||||||
{
|
{
|
||||||
memset(mBbaMem.get(), 0, BBA_MEM_SIZE);
|
memset(m_bba_mem.get(), 0, BBA_MEM_SIZE);
|
||||||
|
|
||||||
mBbaMem[BBA_NCRB] = NCRB_PR;
|
m_bba_mem[BBA_NCRB] = NCRB_PR;
|
||||||
mBbaMem[BBA_NWAYC] = NWAYC_LTE | NWAYC_ANE;
|
m_bba_mem[BBA_NWAYC] = NWAYC_LTE | NWAYC_ANE;
|
||||||
mBbaMem[BBA_MISC] = MISC1_TPF | MISC1_TPH | MISC1_TXF | MISC1_TXH;
|
m_bba_mem[BBA_MISC] = MISC1_TPF | MISC1_TPH | MISC1_TXF | MISC1_TXH;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::MXCommandHandler(u32 data, u32 size)
|
void CEXIEthernetBase::MXCommandHandler(u32 data, u32 size)
|
||||||
{
|
{
|
||||||
switch (transfer.address)
|
switch (transfer.address)
|
||||||
{
|
{
|
||||||
@@ -306,7 +305,7 @@ void CEXIETHERNET::MXCommandHandler(u32 data, u32 size)
|
|||||||
Activate();
|
Activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((mBbaMem[BBA_NCRA] & NCRA_SR) ^ (data & NCRA_SR))
|
if ((m_bba_mem[BBA_NCRA] & NCRA_SR) ^ (data & NCRA_SR))
|
||||||
{
|
{
|
||||||
DEBUG_LOG(SP1, "%s rx", (data & NCRA_SR) ? "start" : "stop");
|
DEBUG_LOG(SP1, "%s rx", (data & NCRA_SR) ? "start" : "stop");
|
||||||
|
|
||||||
@@ -317,7 +316,7 @@ void CEXIETHERNET::MXCommandHandler(u32 data, u32 size)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only start transfer if there isn't one currently running
|
// Only start transfer if there isn't one currently running
|
||||||
if (!(mBbaMem[BBA_NCRA] & (NCRA_ST0 | NCRA_ST1)))
|
if (!(m_bba_mem[BBA_NCRA] & (NCRA_ST0 | NCRA_ST1)))
|
||||||
{
|
{
|
||||||
// Technically transfer DMA status is kept in TXDMA - not implemented
|
// Technically transfer DMA status is kept in TXDMA - not implemented
|
||||||
|
|
||||||
@@ -362,20 +361,20 @@ void CEXIETHERNET::MXCommandHandler(u32 data, u32 size)
|
|||||||
default:
|
default:
|
||||||
for (int i = size - 1; i >= 0; i--)
|
for (int i = size - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
mBbaMem[transfer.address++] = (data >> (i * 8)) & 0xff;
|
m_bba_mem[transfer.address++] = (data >> (i * 8)) & 0xff;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::DirectFIFOWrite(const u8* data, u32 size)
|
void CEXIEthernetBase::DirectFIFOWrite(const u8* data, u32 size)
|
||||||
{
|
{
|
||||||
// In direct mode, the hardware handles creating the state required by the
|
// In direct mode, the hardware handles creating the state required by the
|
||||||
// GMAC instead of finagling with packet descriptors and such
|
// GMAC instead of finagling with packet descriptors and such
|
||||||
|
|
||||||
u16* tx_fifo_count = (u16*)&mBbaMem[BBA_TXFIFOCNT];
|
u16* tx_fifo_count = (u16*)&m_bba_mem[BBA_TXFIFOCNT];
|
||||||
|
|
||||||
memcpy(tx_fifo.get() + *tx_fifo_count, data, size);
|
memcpy(m_tx_fifo.get() + *tx_fifo_count, data, size);
|
||||||
|
|
||||||
*tx_fifo_count += size;
|
*tx_fifo_count += size;
|
||||||
// TODO: not sure this mask is correct.
|
// TODO: not sure this mask is correct.
|
||||||
@@ -384,33 +383,33 @@ void CEXIETHERNET::DirectFIFOWrite(const u8* data, u32 size)
|
|||||||
*tx_fifo_count &= (1 << 12) - 1;
|
*tx_fifo_count &= (1 << 12) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::SendFromDirectFIFO()
|
void CEXIEthernetBase::SendFromDirectFIFO()
|
||||||
{
|
{
|
||||||
SendFrame(tx_fifo.get(), *(u16*)&mBbaMem[BBA_TXFIFOCNT]);
|
SendFrame(m_tx_fifo.get(), *(u16*)&m_bba_mem[BBA_TXFIFOCNT]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::SendFromPacketBuffer()
|
void CEXIEthernetBase::SendFromPacketBuffer()
|
||||||
{
|
{
|
||||||
ERROR_LOG(SP1, "tx packet buffer not implemented.");
|
ERROR_LOG(SP1, "tx packet buffer not implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::SendComplete()
|
void CEXIEthernetBase::SendComplete()
|
||||||
{
|
{
|
||||||
mBbaMem[BBA_NCRA] &= ~(NCRA_ST0 | NCRA_ST1);
|
m_bba_mem[BBA_NCRA] &= ~(NCRA_ST0 | NCRA_ST1);
|
||||||
*(u16*)&mBbaMem[BBA_TXFIFOCNT] = 0;
|
*(u16*)&m_bba_mem[BBA_TXFIFOCNT] = 0;
|
||||||
|
|
||||||
if (mBbaMem[BBA_IMR] & INT_T)
|
if (m_bba_mem[BBA_IMR] & INT_T)
|
||||||
{
|
{
|
||||||
mBbaMem[BBA_IR] |= INT_T;
|
m_bba_mem[BBA_IR] |= INT_T;
|
||||||
|
|
||||||
exi_status.interrupt |= exi_status.TRANSFER;
|
exi_status.interrupt |= exi_status.TRANSFER;
|
||||||
ExpansionInterface::ScheduleUpdateInterrupts(CoreTiming::FromThread::CPU, 0);
|
ExpansionInterface::ScheduleUpdateInterrupts(CoreTiming::FromThread::CPU, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
mBbaMem[BBA_LTPS] = 0;
|
m_bba_mem[BBA_LTPS] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline u8 CEXIETHERNET::HashIndex(const u8* dest_eth_addr)
|
inline u8 CEXIEthernetBase::HashIndex(const u8* dest_eth_addr)
|
||||||
{
|
{
|
||||||
// Calculate CRC
|
// Calculate CRC
|
||||||
u32 crc = 0xffffffff;
|
u32 crc = 0xffffffff;
|
||||||
@@ -432,25 +431,25 @@ inline u8 CEXIETHERNET::HashIndex(const u8* dest_eth_addr)
|
|||||||
return crc >> 26;
|
return crc >> 26;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool CEXIETHERNET::RecvMACFilter()
|
inline bool CEXIEthernetBase::RecvMACFilter()
|
||||||
{
|
{
|
||||||
static u8 const broadcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
static u8 const broadcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
||||||
|
|
||||||
// Accept all destination addrs?
|
// Accept all destination addrs?
|
||||||
if (mBbaMem[BBA_NCRB] & NCRB_PR)
|
if (m_bba_mem[BBA_NCRB] & NCRB_PR)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Unicast?
|
// Unicast?
|
||||||
if ((mRecvBuffer[0] & 0x01) == 0)
|
if ((m_recv_buffer[0] & 0x01) == 0)
|
||||||
{
|
{
|
||||||
return memcmp(mRecvBuffer.get(), &mBbaMem[BBA_NAFR_PAR0], 6) == 0;
|
return memcmp(m_recv_buffer.get(), &m_bba_mem[BBA_NAFR_PAR0], 6) == 0;
|
||||||
}
|
}
|
||||||
else if (memcmp(mRecvBuffer.get(), broadcast, 6) == 0)
|
else if (memcmp(m_recv_buffer.get(), broadcast, 6) == 0)
|
||||||
{
|
{
|
||||||
// Accept broadcast?
|
// Accept broadcast?
|
||||||
return !!(mBbaMem[BBA_NCRB] & NCRB_AB);
|
return !!(m_bba_mem[BBA_NCRB] & NCRB_AB);
|
||||||
}
|
}
|
||||||
else if (mBbaMem[BBA_NCRB] & NCRB_PM)
|
else if (m_bba_mem[BBA_NCRB] & NCRB_PM)
|
||||||
{
|
{
|
||||||
// Accept all multicast
|
// Accept all multicast
|
||||||
return true;
|
return true;
|
||||||
@@ -458,14 +457,14 @@ inline bool CEXIETHERNET::RecvMACFilter()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Lookup the dest eth address in the hashmap
|
// Lookup the dest eth address in the hashmap
|
||||||
u16 index = HashIndex(mRecvBuffer.get());
|
u16 index = HashIndex(m_recv_buffer.get());
|
||||||
return !!(mBbaMem[BBA_NAFR_MAR0 + index / 8] & (1 << (index % 8)));
|
return !!(m_bba_mem[BBA_NAFR_MAR0 + index / 8] & (1 << (index % 8)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void CEXIETHERNET::inc_rwp()
|
inline void CEXIEthernetBase::inc_rwp()
|
||||||
{
|
{
|
||||||
u16* rwp = (u16*)&mBbaMem[BBA_RWP];
|
u16* rwp = (u16*)&m_bba_mem[BBA_RWP];
|
||||||
|
|
||||||
if (*rwp + 1 == page_ptr(BBA_RHBP))
|
if (*rwp + 1 == page_ptr(BBA_RHBP))
|
||||||
*rwp = page_ptr(BBA_BP);
|
*rwp = page_ptr(BBA_BP);
|
||||||
@@ -475,7 +474,7 @@ inline void CEXIETHERNET::inc_rwp()
|
|||||||
|
|
||||||
// This function is on the critical path for receiving data.
|
// This function is on the critical path for receiving data.
|
||||||
// Be very careful about calling into the logger and other slow things
|
// Be very careful about calling into the logger and other slow things
|
||||||
bool CEXIETHERNET::RecvHandlePacket()
|
bool CEXIEthernetBase::RecvHandlePacket()
|
||||||
{
|
{
|
||||||
u8* write_ptr;
|
u8* write_ptr;
|
||||||
u8* end_ptr;
|
u8* end_ptr;
|
||||||
@@ -488,23 +487,23 @@ bool CEXIETHERNET::RecvHandlePacket()
|
|||||||
goto wait_for_next;
|
goto wait_for_next;
|
||||||
|
|
||||||
#ifdef BBA_TRACK_PAGE_PTRS
|
#ifdef BBA_TRACK_PAGE_PTRS
|
||||||
INFO_LOG(SP1, "RecvHandlePacket %x\n%s", mRecvBufferLength,
|
INFO_LOG(SP1, "RecvHandlePacket %x\n%s", m_recv_buffer_length,
|
||||||
ArrayToString(mRecvBuffer, mRecvBufferLength, 0x100).c_str());
|
ArrayToString(m_recv_buffer, m_recv_buffer_length, 0x100).c_str());
|
||||||
|
|
||||||
INFO_LOG(SP1, "%x %x %x %x", page_ptr(BBA_BP), page_ptr(BBA_RRP), page_ptr(BBA_RWP),
|
INFO_LOG(SP1, "%x %x %x %x", page_ptr(BBA_BP), page_ptr(BBA_RRP), page_ptr(BBA_RWP),
|
||||||
page_ptr(BBA_RHBP));
|
page_ptr(BBA_RHBP));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
write_ptr = ptr_from_page_ptr(BBA_RWP);
|
write_ptr = PtrFromPagePtr(BBA_RWP);
|
||||||
end_ptr = ptr_from_page_ptr(BBA_RHBP);
|
end_ptr = PtrFromPagePtr(BBA_RHBP);
|
||||||
read_ptr = ptr_from_page_ptr(BBA_RRP);
|
read_ptr = PtrFromPagePtr(BBA_RRP);
|
||||||
|
|
||||||
descriptor = (Descriptor*)write_ptr;
|
descriptor = (Descriptor*)write_ptr;
|
||||||
write_ptr += 4;
|
write_ptr += 4;
|
||||||
|
|
||||||
for (u32 i = 0, off = 4; i < mRecvBufferLength; ++i, ++off)
|
for (u32 i = 0, off = 4; i < m_recv_buffer_length; ++i, ++off)
|
||||||
{
|
{
|
||||||
*write_ptr++ = mRecvBuffer[i];
|
*write_ptr++ = m_recv_buffer[i];
|
||||||
|
|
||||||
if (off == 0xff)
|
if (off == 0xff)
|
||||||
{
|
{
|
||||||
@@ -513,7 +512,7 @@ bool CEXIETHERNET::RecvHandlePacket()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (write_ptr == end_ptr)
|
if (write_ptr == end_ptr)
|
||||||
write_ptr = ptr_from_page_ptr(BBA_BP);
|
write_ptr = PtrFromPagePtr(BBA_BP);
|
||||||
|
|
||||||
if (write_ptr == read_ptr)
|
if (write_ptr == read_ptr)
|
||||||
{
|
{
|
||||||
@@ -529,13 +528,13 @@ bool CEXIETHERNET::RecvHandlePacket()
|
|||||||
inc MPC instead of receiving packets
|
inc MPC instead of receiving packets
|
||||||
*/
|
*/
|
||||||
status |= DESC_FO | DESC_BF;
|
status |= DESC_FO | DESC_BF;
|
||||||
mBbaMem[BBA_IR] |= mBbaMem[BBA_IMR] & INT_RBF;
|
m_bba_mem[BBA_IR] |= m_bba_mem[BBA_IMR] & INT_RBF;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Align up to next page
|
// Align up to next page
|
||||||
if ((mRecvBufferLength + 4) % 256)
|
if ((m_recv_buffer_length + 4) % 256)
|
||||||
inc_rwp();
|
inc_rwp();
|
||||||
|
|
||||||
#ifdef BBA_TRACK_PAGE_PTRS
|
#ifdef BBA_TRACK_PAGE_PTRS
|
||||||
@@ -544,14 +543,14 @@ bool CEXIETHERNET::RecvHandlePacket()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Is the current frame multicast?
|
// Is the current frame multicast?
|
||||||
if (mRecvBuffer[0] & 0x01)
|
if (m_recv_buffer[0] & 0x01)
|
||||||
status |= DESC_MF;
|
status |= DESC_MF;
|
||||||
|
|
||||||
if (status & DESC_BF)
|
if (status & DESC_BF)
|
||||||
{
|
{
|
||||||
if (mBbaMem[BBA_MISC2] & MISC2_AUTORCVR)
|
if (m_bba_mem[BBA_MISC2] & MISC2_AUTORCVR)
|
||||||
{
|
{
|
||||||
*(u16*)&mBbaMem[BBA_RWP] = rwp_initial;
|
*(u16*)&m_bba_mem[BBA_RWP] = rwp_initial;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -559,14 +558,14 @@ bool CEXIETHERNET::RecvHandlePacket()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptor->set(*(u16*)&mBbaMem[BBA_RWP], 4 + mRecvBufferLength, status);
|
descriptor->set(*(u16*)&m_bba_mem[BBA_RWP], 4 + m_recv_buffer_length, status);
|
||||||
|
|
||||||
mBbaMem[BBA_LRPS] = status;
|
m_bba_mem[BBA_LRPS] = status;
|
||||||
|
|
||||||
// Raise interrupt
|
// Raise interrupt
|
||||||
if (mBbaMem[BBA_IMR] & INT_R)
|
if (m_bba_mem[BBA_IMR] & INT_R)
|
||||||
{
|
{
|
||||||
mBbaMem[BBA_IR] |= INT_R;
|
m_bba_mem[BBA_IR] |= INT_R;
|
||||||
|
|
||||||
exi_status.interrupt |= exi_status.TRANSFER;
|
exi_status.interrupt |= exi_status.TRANSFER;
|
||||||
ExpansionInterface::ScheduleUpdateInterrupts(CoreTiming::FromThread::NON_CPU, 0);
|
ExpansionInterface::ScheduleUpdateInterrupts(CoreTiming::FromThread::NON_CPU, 0);
|
||||||
@@ -578,9 +577,9 @@ bool CEXIETHERNET::RecvHandlePacket()
|
|||||||
}
|
}
|
||||||
|
|
||||||
wait_for_next:
|
wait_for_next:
|
||||||
if (mBbaMem[BBA_NCRA] & NCRA_SR)
|
if (m_bba_mem[BBA_NCRA] & NCRA_SR)
|
||||||
RecvStart();
|
RecvStart();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} // namespace ExpansionInterface
|
}
|
@@ -4,15 +4,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <Windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "Common/Flag.h"
|
|
||||||
#include "Core/HW/EXI/EXI_Device.h"
|
#include "Core/HW/EXI/EXI_Device.h"
|
||||||
|
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
@@ -194,13 +185,11 @@ enum RecvStatus
|
|||||||
DESC_RERR = 0x80
|
DESC_RERR = 0x80
|
||||||
};
|
};
|
||||||
|
|
||||||
#define BBA_RECV_SIZE 0x800
|
class CEXIEthernetBase : public IEXIDevice
|
||||||
|
|
||||||
class CEXIETHERNET : public IEXIDevice
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CEXIETHERNET();
|
CEXIEthernetBase();
|
||||||
virtual ~CEXIETHERNET();
|
|
||||||
void SetCS(int cs) override;
|
void SetCS(int cs) override;
|
||||||
bool IsPresent() const override;
|
bool IsPresent() const override;
|
||||||
bool IsInterruptSet() override;
|
bool IsInterruptSet() override;
|
||||||
@@ -210,7 +199,42 @@ public:
|
|||||||
void DMARead(u32 addr, u32 size) override;
|
void DMARead(u32 addr, u32 size) override;
|
||||||
void DoState(PointerWrap& p) override;
|
void DoState(PointerWrap& p) override;
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
|
virtual bool Activate() = 0;
|
||||||
|
virtual bool IsActivated() const = 0;
|
||||||
|
virtual bool SendFrame(const u8* frame, u32 size) = 0;
|
||||||
|
virtual bool RecvInit() = 0;
|
||||||
|
virtual void RecvStart() = 0;
|
||||||
|
virtual void RecvStop() = 0;
|
||||||
|
|
||||||
|
inline u16 page_ptr(int const index) const
|
||||||
|
{
|
||||||
|
return ((u16)m_bba_mem[index + 1] << 8) | m_bba_mem[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u8* PtrFromPagePtr(int const index) const { return &m_bba_mem[page_ptr(index) << 8]; }
|
||||||
|
bool IsMXCommand(u32 const data);
|
||||||
|
bool IsWriteCommand(u32 const data);
|
||||||
|
const char* GetRegisterName() const;
|
||||||
|
void MXHardReset();
|
||||||
|
void MXCommandHandler(u32 data, u32 size);
|
||||||
|
void DirectFIFOWrite(const u8* data, u32 size);
|
||||||
|
void SendFromDirectFIFO();
|
||||||
|
void SendFromPacketBuffer();
|
||||||
|
void SendComplete();
|
||||||
|
u8 HashIndex(const u8* dest_eth_addr);
|
||||||
|
bool RecvMACFilter();
|
||||||
|
void inc_rwp();
|
||||||
|
bool RecvHandlePacket();
|
||||||
|
|
||||||
|
std::unique_ptr<u8[]> m_bba_mem;
|
||||||
|
std::unique_ptr<u8[]> m_tx_fifo;
|
||||||
|
|
||||||
|
std::unique_ptr<u8[]> m_recv_buffer;
|
||||||
|
u32 m_recv_buffer_length = 0;
|
||||||
|
|
||||||
|
static constexpr std::size_t BBA_RECV_SIZE = 0x800;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
enum
|
enum
|
||||||
@@ -273,58 +297,5 @@ private:
|
|||||||
word |= next_page & 0xfff;
|
word |= next_page & 0xfff;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline u16 page_ptr(int const index) const
|
|
||||||
{
|
|
||||||
return ((u16)mBbaMem[index + 1] << 8) | mBbaMem[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline u8* ptr_from_page_ptr(int const index) const { return &mBbaMem[page_ptr(index) << 8]; }
|
|
||||||
bool IsMXCommand(u32 const data);
|
|
||||||
bool IsWriteCommand(u32 const data);
|
|
||||||
const char* GetRegisterName() const;
|
|
||||||
void MXHardReset();
|
|
||||||
void MXCommandHandler(u32 data, u32 size);
|
|
||||||
void DirectFIFOWrite(const u8* data, u32 size);
|
|
||||||
void SendFromDirectFIFO();
|
|
||||||
void SendFromPacketBuffer();
|
|
||||||
void SendComplete();
|
|
||||||
u8 HashIndex(const u8* dest_eth_addr);
|
|
||||||
bool RecvMACFilter();
|
|
||||||
void inc_rwp();
|
|
||||||
bool RecvHandlePacket();
|
|
||||||
|
|
||||||
std::unique_ptr<u8[]> mBbaMem;
|
|
||||||
std::unique_ptr<u8[]> tx_fifo;
|
|
||||||
|
|
||||||
// TAP interface
|
|
||||||
static void ReadThreadHandler(CEXIETHERNET* self);
|
|
||||||
bool Activate();
|
|
||||||
void Deactivate();
|
|
||||||
bool IsActivated();
|
|
||||||
bool SendFrame(const u8* frame, u32 size);
|
|
||||||
bool RecvInit();
|
|
||||||
void RecvStart();
|
|
||||||
void RecvStop();
|
|
||||||
|
|
||||||
std::unique_ptr<u8[]> mRecvBuffer;
|
|
||||||
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
|
52
Source/Core/Core/HW/EXI/EXI_DeviceEthernetTAP.h
Normal file
52
Source/Core/Core/HW/EXI/EXI_DeviceEthernetTAP.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2008 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <vector>
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Common/Flag.h"
|
||||||
|
#include "EXI_DeviceEthernetBase.h"
|
||||||
|
|
||||||
|
namespace ExpansionInterface
|
||||||
|
{
|
||||||
|
class CEXIEthernetTAP : public CEXIEthernetBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~CEXIEthernetTAP() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool Activate() override;
|
||||||
|
bool IsActivated() const override;
|
||||||
|
bool SendFrame(const u8* frame, u32 size) override;
|
||||||
|
bool RecvInit() override;
|
||||||
|
void RecvStart() override;
|
||||||
|
void RecvStop() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void ReadThreadHandler(CEXIEthernetTAP* self);
|
||||||
|
|
||||||
|
#ifdef _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 m_read_thread;
|
||||||
|
Common::Flag m_read_enabled;
|
||||||
|
Common::Flag m_read_thread_shutdown;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
} // namespace ExpansionInterface
|
@@ -7,12 +7,22 @@
|
|||||||
|
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Core/HW/EXI/EXI_Device.h"
|
#include "Core/HW/EXI/EXI_DeviceEthernetTAP.h"
|
||||||
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
|
||||||
|
|
||||||
namespace ExpansionInterface
|
namespace ExpansionInterface
|
||||||
{
|
{
|
||||||
bool CEXIETHERNET::Activate()
|
CEXIEthernetTAP::~CEXIEthernetTAP()
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
fd = -1;
|
||||||
|
|
||||||
|
m_read_enabled.Clear();
|
||||||
|
m_read_thread_shutdown.Set();
|
||||||
|
if (m_read_thread.joinable())
|
||||||
|
m_read_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIEthernetTAP::Activate()
|
||||||
{
|
{
|
||||||
if (IsActivated())
|
if (IsActivated())
|
||||||
return true;
|
return true;
|
||||||
@@ -30,23 +40,12 @@ bool CEXIETHERNET::Activate()
|
|||||||
return RecvInit();
|
return RecvInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::Deactivate()
|
bool CEXIEthernetTAP::IsActivated() const
|
||||||
{
|
|
||||||
close(fd);
|
|
||||||
fd = -1;
|
|
||||||
|
|
||||||
readEnabled.Clear();
|
|
||||||
readThreadShutdown.Set();
|
|
||||||
if (readThread.joinable())
|
|
||||||
readThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CEXIETHERNET::IsActivated()
|
|
||||||
{
|
{
|
||||||
return fd != -1;
|
return fd != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
bool CEXIEthernetTAP::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());
|
||||||
|
|
||||||
@@ -63,9 +62,9 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
void CEXIEthernetTAP::ReadThreadHandler(CEXIEthernetTAP* self)
|
||||||
{
|
{
|
||||||
while (!self->readThreadShutdown.IsSet())
|
while (!self->m_read_thread_shutdown.IsSet())
|
||||||
{
|
{
|
||||||
fd_set rfds;
|
fd_set rfds;
|
||||||
FD_ZERO(&rfds);
|
FD_ZERO(&rfds);
|
||||||
@@ -77,34 +76,34 @@ 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_recv_buffer.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);
|
||||||
}
|
}
|
||||||
else if (self->readEnabled.IsSet())
|
else if (self->m_read_enabled.IsSet())
|
||||||
{
|
{
|
||||||
INFO_LOG(SP1, "Read data: %s",
|
INFO_LOG(SP1, "Read data: %s",
|
||||||
ArrayToString(self->mRecvBuffer.get(), readBytes, 0x10).c_str());
|
ArrayToString(self->m_recv_buffer.get(), readBytes, 0x10).c_str());
|
||||||
self->mRecvBufferLength = readBytes;
|
self->m_recv_buffer_length = readBytes;
|
||||||
self->RecvHandlePacket();
|
self->RecvHandlePacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::RecvInit()
|
bool CEXIEthernetTAP::RecvInit()
|
||||||
{
|
{
|
||||||
readThread = std::thread(ReadThreadHandler, this);
|
m_read_thread = std::thread(ReadThreadHandler, this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStart()
|
void CEXIEthernetTAP::RecvStart()
|
||||||
{
|
{
|
||||||
readEnabled.Set();
|
m_read_enabled.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStop()
|
void CEXIEthernetTAP::RecvStop()
|
||||||
{
|
{
|
||||||
readEnabled.Clear();
|
m_read_enabled.Clear();
|
||||||
}
|
}
|
||||||
} // namespace ExpansionInterface
|
} // namespace ExpansionInterface
|
@@ -11,7 +11,7 @@
|
|||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Core/HW/EXI/EXI_Device.h"
|
#include "Core/HW/EXI/EXI_Device.h"
|
||||||
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
#include "Core/HW/EXI/EXI_DeviceEthernetTAP.h"
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@@ -28,7 +28,22 @@ 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()
|
CEXIEthernetTAP::~CEXIEthernetTAP()
|
||||||
|
{
|
||||||
|
#ifdef __linux__
|
||||||
|
close(fd);
|
||||||
|
fd = -1;
|
||||||
|
|
||||||
|
m_read_enabled.Clear();
|
||||||
|
m_read_thread_shutdown.Set();
|
||||||
|
if (m_read_thread.joinable())
|
||||||
|
m_read_thread.join();
|
||||||
|
#else
|
||||||
|
NOTIMPLEMENTED("Deactivate");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIEthernetTAP::Activate()
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
if (IsActivated())
|
if (IsActivated())
|
||||||
@@ -78,22 +93,7 @@ bool CEXIETHERNET::Activate()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::Deactivate()
|
bool CEXIEthernetTAP::IsActivated() const
|
||||||
{
|
|
||||||
#ifdef __linux__
|
|
||||||
close(fd);
|
|
||||||
fd = -1;
|
|
||||||
|
|
||||||
readEnabled.Clear();
|
|
||||||
readThreadShutdown.Set();
|
|
||||||
if (readThread.joinable())
|
|
||||||
readThread.join();
|
|
||||||
#else
|
|
||||||
NOTIMPLEMENTED("Deactivate");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CEXIETHERNET::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 CEXIEthernetTAP::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());
|
||||||
@@ -124,10 +124,39 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __linux__
|
bool CEXIEthernetTAP::RecvInit()
|
||||||
void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
|
||||||
{
|
{
|
||||||
while (!self->readThreadShutdown.IsSet())
|
#ifdef __linux__
|
||||||
|
m_read_thread = std::thread(ReadThreadHandler, this);
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
NOTIMPLEMENTED("RecvInit");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIEthernetTAP::RecvStart()
|
||||||
|
{
|
||||||
|
#ifdef __linux__
|
||||||
|
m_read_enabled.Set();
|
||||||
|
#else
|
||||||
|
NOTIMPLEMENTED("RecvStart");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIEthernetTAP::RecvStop()
|
||||||
|
{
|
||||||
|
#ifdef __linux__
|
||||||
|
m_read_enabled.Clear();
|
||||||
|
#else
|
||||||
|
NOTIMPLEMENTED("RecvStop");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
void CEXIEthernetTAP::ReadThreadHandler(CEXIEthernetTAP* self)
|
||||||
|
{
|
||||||
|
while (!self->m_read_thread_shutdown.IsSet())
|
||||||
{
|
{
|
||||||
fd_set rfds;
|
fd_set rfds;
|
||||||
FD_ZERO(&rfds);
|
FD_ZERO(&rfds);
|
||||||
@@ -139,48 +168,19 @@ 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_recv_buffer.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);
|
||||||
}
|
}
|
||||||
else if (self->readEnabled.IsSet())
|
else if (self->m_read_enabled.IsSet())
|
||||||
{
|
{
|
||||||
DEBUG_LOG(SP1, "Read data: %s",
|
DEBUG_LOG(SP1, "Read data: %s",
|
||||||
ArrayToString(self->mRecvBuffer.get(), readBytes, 0x10).c_str());
|
ArrayToString(self->m_recv_buffer.get(), readBytes, 0x10).c_str());
|
||||||
self->mRecvBufferLength = readBytes;
|
self->m_recv_buffer_length = readBytes;
|
||||||
self->RecvHandlePacket();
|
self->RecvHandlePacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool CEXIETHERNET::RecvInit()
|
|
||||||
{
|
|
||||||
#ifdef __linux__
|
|
||||||
readThread = std::thread(ReadThreadHandler, this);
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
NOTIMPLEMENTED("RecvInit");
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStart()
|
|
||||||
{
|
|
||||||
#ifdef __linux__
|
|
||||||
readEnabled.Set();
|
|
||||||
#else
|
|
||||||
NOTIMPLEMENTED("RecvStart");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStop()
|
|
||||||
{
|
|
||||||
#ifdef __linux__
|
|
||||||
readEnabled.Clear();
|
|
||||||
#else
|
|
||||||
NOTIMPLEMENTED("RecvStop");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
} // namespace ExpansionInterface
|
} // namespace ExpansionInterface
|
@@ -2,13 +2,13 @@
|
|||||||
// 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/EXI_DeviceEthernetTAP.h"
|
||||||
|
#include "Core/HW/EXI/EXI_DeviceEthernetTAP_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"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Core/HW/EXI/EXI_Device.h"
|
|
||||||
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
|
||||||
|
|
||||||
namespace Win32TAPHelper
|
namespace Win32TAPHelper
|
||||||
{
|
{
|
||||||
@@ -167,7 +167,32 @@ bool OpenTAP(HANDLE& adapter, const std::basic_string<TCHAR>& device_guid)
|
|||||||
|
|
||||||
namespace ExpansionInterface
|
namespace ExpansionInterface
|
||||||
{
|
{
|
||||||
bool CEXIETHERNET::Activate()
|
CEXIEthernetTAP::~CEXIEthernetTAP()
|
||||||
|
{
|
||||||
|
if (!IsActivated())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Signal read thread to exit.
|
||||||
|
m_read_enabled.Clear();
|
||||||
|
m_read_thread_shutdown.Set();
|
||||||
|
|
||||||
|
// Cancel any outstanding requests from both this thread (writes), and the read thread.
|
||||||
|
CancelIoEx(mHAdapter, nullptr);
|
||||||
|
|
||||||
|
// Wait for read thread to exit.
|
||||||
|
if (m_read_thread.joinable())
|
||||||
|
m_read_thread.join();
|
||||||
|
|
||||||
|
// Clean-up handles
|
||||||
|
CloseHandle(mReadOverlapped.hEvent);
|
||||||
|
CloseHandle(mWriteOverlapped.hEvent);
|
||||||
|
CloseHandle(mHAdapter);
|
||||||
|
mHAdapter = INVALID_HANDLE_VALUE;
|
||||||
|
memset(&mReadOverlapped, 0, sizeof(mReadOverlapped));
|
||||||
|
memset(&mWriteOverlapped, 0, sizeof(mWriteOverlapped));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIEthernetTAP::Activate()
|
||||||
{
|
{
|
||||||
if (IsActivated())
|
if (IsActivated())
|
||||||
return true;
|
return true;
|
||||||
@@ -233,44 +258,19 @@ bool CEXIETHERNET::Activate()
|
|||||||
return RecvInit();
|
return RecvInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::Deactivate()
|
bool CEXIEthernetTAP::IsActivated() const
|
||||||
{
|
|
||||||
if (!IsActivated())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Signal read thread to exit.
|
|
||||||
readEnabled.Clear();
|
|
||||||
readThreadShutdown.Set();
|
|
||||||
|
|
||||||
// Cancel any outstanding requests from both this thread (writes), and the read thread.
|
|
||||||
CancelIoEx(mHAdapter, nullptr);
|
|
||||||
|
|
||||||
// Wait for read thread to exit.
|
|
||||||
if (readThread.joinable())
|
|
||||||
readThread.join();
|
|
||||||
|
|
||||||
// Clean-up handles
|
|
||||||
CloseHandle(mReadOverlapped.hEvent);
|
|
||||||
CloseHandle(mWriteOverlapped.hEvent);
|
|
||||||
CloseHandle(mHAdapter);
|
|
||||||
mHAdapter = INVALID_HANDLE_VALUE;
|
|
||||||
memset(&mReadOverlapped, 0, sizeof(mReadOverlapped));
|
|
||||||
memset(&mWriteOverlapped, 0, sizeof(mWriteOverlapped));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CEXIETHERNET::IsActivated()
|
|
||||||
{
|
{
|
||||||
return mHAdapter != INVALID_HANDLE_VALUE;
|
return mHAdapter != INVALID_HANDLE_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self)
|
void CEXIEthernetTAP::ReadThreadHandler(CEXIEthernetTAP* self)
|
||||||
{
|
{
|
||||||
while (!self->readThreadShutdown.IsSet())
|
while (!self->m_read_thread_shutdown.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_recv_buffer.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_recv_buffer.get(), transferred, 0x10).c_str());
|
||||||
if (self->readEnabled.IsSet())
|
if (self->m_read_enabled.IsSet())
|
||||||
{
|
{
|
||||||
self->mRecvBufferLength = transferred;
|
self->m_recv_buffer_length = transferred;
|
||||||
self->RecvHandlePacket();
|
self->RecvHandlePacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
bool CEXIEthernetTAP::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());
|
||||||
|
|
||||||
@@ -349,19 +349,19 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::RecvInit()
|
bool CEXIEthernetTAP::RecvInit()
|
||||||
{
|
{
|
||||||
readThread = std::thread(ReadThreadHandler, this);
|
m_read_thread = std::thread(ReadThreadHandler, this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStart()
|
void CEXIEthernetTAP::RecvStart()
|
||||||
{
|
{
|
||||||
readEnabled.Set();
|
m_read_enabled.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIETHERNET::RecvStop()
|
void CEXIEthernetTAP::RecvStop()
|
||||||
{
|
{
|
||||||
readEnabled.Clear();
|
m_read_enabled.Clear();
|
||||||
}
|
}
|
||||||
} // namespace ExpansionInterface
|
} // namespace ExpansionInterface
|
188
Source/Core/Core/HW/EXI/EXI_DeviceEthernetTCP.cpp
Normal file
188
Source/Core/Core/HW/EXI/EXI_DeviceEthernetTCP.cpp
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
// Copyright 2008 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "Core/HW/EXI/EXI_DeviceEthernetTCP.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <SFML/Network.hpp>
|
||||||
|
|
||||||
|
#include "Common/ChunkFile.h"
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
|
#include "Common/Network.h"
|
||||||
|
#include "Common/StringUtil.h"
|
||||||
|
#include "Core/ConfigManager.h"
|
||||||
|
#include "Core/CoreTiming.h"
|
||||||
|
#include "Core/HW/EXI/EXI.h"
|
||||||
|
#include "Core/HW/Memmap.h"
|
||||||
|
|
||||||
|
namespace ExpansionInterface
|
||||||
|
{
|
||||||
|
CEXIEthernetTCP::~CEXIEthernetTCP()
|
||||||
|
{
|
||||||
|
DEBUG_LOG(SP1, "Ending receive thread...");
|
||||||
|
m_read_enabled.Clear();
|
||||||
|
m_read_thread_shutdown.Set();
|
||||||
|
if (m_read_thread.joinable())
|
||||||
|
m_read_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIEthernetTCP::Activate()
|
||||||
|
{
|
||||||
|
if (IsActivated())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
DEBUG_LOG(SP1, "Connecting...");
|
||||||
|
|
||||||
|
m_socket = std::make_unique<sf::TcpSocket>();
|
||||||
|
const sf::Socket::Status status = m_socket->connect(SConfig::GetInstance().m_bba_server, SConfig::GetInstance().m_bba_port);
|
||||||
|
if (status != sf::Socket::Done)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "Could not connect: %i", status);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO_LOG(SP1, "Connected!");
|
||||||
|
|
||||||
|
return RecvInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIEthernetTCP::IsActivated() const
|
||||||
|
{
|
||||||
|
return m_socket != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIEthernetTCP::SendFrame(const u8* frame, u32 size)
|
||||||
|
{
|
||||||
|
INFO_LOG(SP1, "SendFrame %i", size);
|
||||||
|
|
||||||
|
std::unique_ptr<u8[]> packet = std::make_unique<u8[]>(sizeof(int) + size);
|
||||||
|
|
||||||
|
// prepend size
|
||||||
|
for (std::size_t i = 0; i < sizeof(int); i++)
|
||||||
|
packet[i] = (size >> (i * 8)) & 0xFF;
|
||||||
|
|
||||||
|
memcpy(packet.get() + sizeof(int), frame, size);
|
||||||
|
|
||||||
|
const sf::Socket::Status status = m_socket->send(packet.get(), sizeof(int) + size);
|
||||||
|
|
||||||
|
if (status != sf::Socket::Done)
|
||||||
|
{
|
||||||
|
WARN_LOG(SP1, "Sending failed %i", status);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendComplete();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIEthernetTCP::RecvInit()
|
||||||
|
{
|
||||||
|
m_read_thread = std::thread(ReadThreadHandler, this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIEthernetTCP::RecvStart()
|
||||||
|
{
|
||||||
|
m_read_enabled.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIEthernetTCP::RecvStop()
|
||||||
|
{
|
||||||
|
m_read_enabled.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIEthernetTCP::ReadThreadHandler(CEXIEthernetTCP* self)
|
||||||
|
{
|
||||||
|
sf::SocketSelector selector;
|
||||||
|
selector.add(*self->m_socket);
|
||||||
|
|
||||||
|
// are we currently waiting for size (4 bytes) or payload?
|
||||||
|
enum { StateSize, StatePayload } state = StateSize;
|
||||||
|
|
||||||
|
// buffer to store size temporarily
|
||||||
|
u8 sizeBuffer[sizeof(int)];
|
||||||
|
|
||||||
|
// how much of size or payload have we already received?
|
||||||
|
std::size_t offset = 0;
|
||||||
|
|
||||||
|
// payload size
|
||||||
|
int size;
|
||||||
|
|
||||||
|
while (!self->m_read_thread_shutdown.IsSet())
|
||||||
|
{
|
||||||
|
// blocks until socket has data to read or 100ms passed
|
||||||
|
selector.wait(sf::milliseconds(100));
|
||||||
|
|
||||||
|
std::size_t received;
|
||||||
|
sf::Socket::Status status;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case StateSize:
|
||||||
|
// try to read remaining bytes for size
|
||||||
|
status = self->m_socket->receive(&sizeBuffer + offset, sizeof(int) - offset, received);
|
||||||
|
if (status != sf::Socket::Done)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "Receiving failed %i", status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_LOG(SP1, "Received %lu bytes for size", received);
|
||||||
|
|
||||||
|
// Have we got all bytes for size?
|
||||||
|
offset += received;
|
||||||
|
if(offset < sizeof(int))
|
||||||
|
continue;
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
// convert char array to size int
|
||||||
|
size = 0;
|
||||||
|
for(int i = 0; i < sizeof(int); i++)
|
||||||
|
size |= sizeBuffer[i] << (i * 8);
|
||||||
|
|
||||||
|
DEBUG_LOG(SP1, "Finished size %i", size);
|
||||||
|
|
||||||
|
if (size > BBA_RECV_SIZE)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "Received frame bigger than internal buffer!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = StatePayload;
|
||||||
|
case StatePayload:
|
||||||
|
// try to read remaining bytes for payload
|
||||||
|
status = self->m_socket->receive(self->m_recv_buffer.get() + offset, size - offset, received);
|
||||||
|
if (status != sf::Socket::Done)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "Receiving failed %i", status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_LOG(SP1, "Receiving %lu bytes for payload", received);
|
||||||
|
|
||||||
|
// Have we got all bytes for payload?
|
||||||
|
offset += received;
|
||||||
|
if(offset < size)
|
||||||
|
continue;
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
INFO_LOG(SP1, "Received payload %i", size);
|
||||||
|
|
||||||
|
if (self->m_read_enabled.IsSet())
|
||||||
|
{
|
||||||
|
self->m_recv_buffer_length = size;
|
||||||
|
self->RecvHandlePacket();
|
||||||
|
}
|
||||||
|
|
||||||
|
state = StateSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_LOG(SP1, "Receive thread ended!");
|
||||||
|
}
|
||||||
|
} // namespace ExpansionInterface
|
38
Source/Core/Core/HW/EXI/EXI_DeviceEthernetTCP.h
Normal file
38
Source/Core/Core/HW/EXI/EXI_DeviceEthernetTCP.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2008 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "Common/Flag.h"
|
||||||
|
#include "EXI_DeviceEthernetBase.h"
|
||||||
|
|
||||||
|
namespace sf { class TcpSocket; }
|
||||||
|
|
||||||
|
namespace ExpansionInterface
|
||||||
|
{
|
||||||
|
class CEXIEthernetTCP : public CEXIEthernetBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~CEXIEthernetTCP() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool Activate() override;
|
||||||
|
bool IsActivated() const override;
|
||||||
|
bool SendFrame(const u8* frame, u32 size) override;
|
||||||
|
bool RecvInit() override;
|
||||||
|
void RecvStart() override;
|
||||||
|
void RecvStop() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void ReadThreadHandler(CEXIEthernetTCP* self);
|
||||||
|
|
||||||
|
std::unique_ptr<sf::TcpSocket> m_socket;
|
||||||
|
|
||||||
|
std::thread m_read_thread;
|
||||||
|
Common::Flag m_read_enabled;
|
||||||
|
Common::Flag m_read_thread_shutdown;
|
||||||
|
};
|
||||||
|
} // namespace ExpansionInterface
|
73
Source/Core/DolphinQt/BBAClient.cpp
Normal file
73
Source/Core/DolphinQt/BBAClient.cpp
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "DolphinQt/BBAClient.h"
|
||||||
|
|
||||||
|
#include <QTcpSocket>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QDataStream>
|
||||||
|
|
||||||
|
#include "DolphinQt/BBAServer.h"
|
||||||
|
#include "DolphinQt/BBADebug.h"
|
||||||
|
|
||||||
|
BBAClient::BBAClient(QTcpSocket &socket, BBAServer &server) :
|
||||||
|
QObject(&server),
|
||||||
|
m_socket(socket),
|
||||||
|
m_server(server),
|
||||||
|
m_state(SocketState::Size)
|
||||||
|
{
|
||||||
|
m_socket.setParent(this);
|
||||||
|
connect(&m_socket, &QIODevice::readyRead, this, &BBAClient::ReadyRead);
|
||||||
|
connect(&m_socket, &QAbstractSocket::disconnected, this, [this](){
|
||||||
|
LogInfo() << m_socket.peerAddress() << m_socket.peerPort() << "disconnected";
|
||||||
|
deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
LogInfo() << m_socket.peerAddress() << m_socket.peerPort() << "connected";
|
||||||
|
}
|
||||||
|
|
||||||
|
BBADebug BBAClient::LogInfo()
|
||||||
|
{
|
||||||
|
return m_server.LogInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAClient::SendMessage(const QByteArray &buffer)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
QDataStream dataStream(&m_socket);
|
||||||
|
dataStream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
dataStream << buffer.size();
|
||||||
|
}
|
||||||
|
m_socket.write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAClient::ReadyRead()
|
||||||
|
{
|
||||||
|
m_buffer.append(m_socket.readAll());
|
||||||
|
|
||||||
|
while (m_buffer.size())
|
||||||
|
{
|
||||||
|
switch(m_state)
|
||||||
|
{
|
||||||
|
case SocketState::Size:
|
||||||
|
if (m_buffer.size() < sizeof(int))
|
||||||
|
return;
|
||||||
|
{
|
||||||
|
QDataStream dataStream(&m_buffer, QIODevice::ReadOnly);
|
||||||
|
dataStream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
dataStream >> m_size;
|
||||||
|
}
|
||||||
|
m_buffer.remove(0, sizeof(int));
|
||||||
|
m_state = SocketState::Payload;
|
||||||
|
Q_FALLTHROUGH();
|
||||||
|
case SocketState::Payload:
|
||||||
|
if (m_buffer.size() < m_size)
|
||||||
|
return;
|
||||||
|
const auto payload = m_buffer.left(m_size);
|
||||||
|
Q_EMIT ReceivedMessage(payload);
|
||||||
|
m_buffer.remove(0, m_size);
|
||||||
|
m_state = SocketState::Size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
Source/Core/DolphinQt/BBAClient.h
Normal file
44
Source/Core/DolphinQt/BBAClient.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class QTcpSocket;
|
||||||
|
|
||||||
|
class BBAServer;
|
||||||
|
class BBADebug;
|
||||||
|
|
||||||
|
class BBAClient : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BBAClient(QTcpSocket &socket, BBAServer &server);
|
||||||
|
|
||||||
|
BBADebug LogInfo();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void ReceivedMessage(const QByteArray &buffer);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void SendMessage(const QByteArray &buffer);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void ReadyRead();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class SocketState {
|
||||||
|
Size,
|
||||||
|
Payload
|
||||||
|
};
|
||||||
|
|
||||||
|
QTcpSocket &m_socket;
|
||||||
|
BBAServer &m_server;
|
||||||
|
|
||||||
|
QByteArray m_buffer;
|
||||||
|
SocketState m_state;
|
||||||
|
int m_size;
|
||||||
|
};
|
20
Source/Core/DolphinQt/BBADebug.cpp
Normal file
20
Source/Core/DolphinQt/BBADebug.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "DolphinQt/BBADebug.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
#include "DolphinQt/BBAServer.h"
|
||||||
|
|
||||||
|
BBADebug::BBADebug(BBAServer &server) :
|
||||||
|
QDebug(&m_log_line),
|
||||||
|
m_server(server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
BBADebug::~BBADebug()
|
||||||
|
{
|
||||||
|
m_server.Information(QDateTime::currentDateTime(), m_log_line);
|
||||||
|
}
|
20
Source/Core/DolphinQt/BBADebug.h
Normal file
20
Source/Core/DolphinQt/BBADebug.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
class BBAServer;
|
||||||
|
|
||||||
|
class BBADebug : public QDebug
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit BBADebug(BBAServer &server);
|
||||||
|
~BBADebug();
|
||||||
|
|
||||||
|
private:
|
||||||
|
BBAServer &m_server;
|
||||||
|
QString m_log_line;
|
||||||
|
};
|
143
Source/Core/DolphinQt/BBAServer.cpp
Normal file
143
Source/Core/DolphinQt/BBAServer.cpp
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "DolphinQt/BBAServer.h"
|
||||||
|
|
||||||
|
#include <QTcpServer>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QNetworkProxy>
|
||||||
|
#include <QTimerEvent>
|
||||||
|
|
||||||
|
#include "DolphinQt/BBAClient.h"
|
||||||
|
#include "DolphinQt/BBADebug.h"
|
||||||
|
|
||||||
|
BBAServer::BBAServer(QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_server(*new QTcpServer(this))
|
||||||
|
{
|
||||||
|
connect(&m_server, &QTcpServer::newConnection, this, &BBAServer::NewConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BBAServer::Listen(const QHostAddress &address, quint16 port)
|
||||||
|
{
|
||||||
|
const auto result = m_server.listen(address, port);
|
||||||
|
if (result)
|
||||||
|
LogInfo() << "Started listening on" << address << port;
|
||||||
|
else
|
||||||
|
LogInfo() << "Could not start listening on" << address << port << "because" << m_server.errorString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BBAServer::Listen(const QString &address, quint16 port)
|
||||||
|
{
|
||||||
|
return Listen(address.isEmpty() ? QHostAddress::Any : QHostAddress(address), port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAServer::Close()
|
||||||
|
{
|
||||||
|
LogInfo() << "Stopped listening";
|
||||||
|
return m_server.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BBAServer::IsListening() const
|
||||||
|
{
|
||||||
|
return m_server.isListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAServer::SetMaxPendingConnections(int num_connections)
|
||||||
|
{
|
||||||
|
m_server.setMaxPendingConnections(num_connections);
|
||||||
|
}
|
||||||
|
|
||||||
|
int BBAServer::MaxPendingConnections() const
|
||||||
|
{
|
||||||
|
return m_server.maxPendingConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
quint16 BBAServer::ServerPort() const
|
||||||
|
{
|
||||||
|
return m_server.serverPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHostAddress BBAServer::ServerAddress() const
|
||||||
|
{
|
||||||
|
return m_server.serverAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
//QAbstractSocket::SocketError BbaServer::ServerError() const
|
||||||
|
//{
|
||||||
|
// return m_server.serverError();
|
||||||
|
//}
|
||||||
|
|
||||||
|
QString BBAServer::ErrorString() const
|
||||||
|
{
|
||||||
|
return m_server.errorString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAServer::PauseAccepting()
|
||||||
|
{
|
||||||
|
m_server.pauseAccepting();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAServer::ResumeAccepting()
|
||||||
|
{
|
||||||
|
m_server.resumeAccepting();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef QT_NO_NETWORKPROXY
|
||||||
|
void BBAServer::SetProxy(const QNetworkProxy &networkProxy)
|
||||||
|
{
|
||||||
|
m_server.setProxy(networkProxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkProxy BBAServer::Proxy() const
|
||||||
|
{
|
||||||
|
return m_server.proxy();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BBADebug BBAServer::LogInfo()
|
||||||
|
{
|
||||||
|
return BBADebug(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAServer::timerEvent(QTimerEvent *ev)
|
||||||
|
{
|
||||||
|
if (ev->timerId() == m_timer_id)
|
||||||
|
{
|
||||||
|
if (m_counter <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LogInfo() << m_counter << "packets transmitted";
|
||||||
|
m_counter = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QObject::timerEvent(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAServer::NewConnection()
|
||||||
|
{
|
||||||
|
auto* const socket = m_server.nextPendingConnection();
|
||||||
|
if (!socket)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto client = new BBAClient(*socket, *this);
|
||||||
|
for(auto otherClient : m_clients)
|
||||||
|
{
|
||||||
|
connect(client, &BBAClient::ReceivedMessage, otherClient, &BBAClient::SendMessage);
|
||||||
|
connect(otherClient, &BBAClient::ReceivedMessage, client, &BBAClient::SendMessage);
|
||||||
|
}
|
||||||
|
if (m_timer_id == -1)
|
||||||
|
m_timer_id = startTimer(1000);
|
||||||
|
connect(client, &BBAClient::ReceivedMessage, this, [&counter=m_counter](){ counter++; });
|
||||||
|
connect(client, &QObject::destroyed, this, [client,this](){
|
||||||
|
m_clients.remove(client);
|
||||||
|
if (m_clients.empty() && m_timer_id != -1)
|
||||||
|
killTimer(m_timer_id);
|
||||||
|
m_timer_id = -1;
|
||||||
|
});
|
||||||
|
m_clients.insert(client);
|
||||||
|
}
|
64
Source/Core/DolphinQt/BBAServer.h
Normal file
64
Source/Core/DolphinQt/BBAServer.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
class QHostAddress;
|
||||||
|
class QNetworkProxy;
|
||||||
|
class QTcpServer;
|
||||||
|
|
||||||
|
class BBAClient;
|
||||||
|
class BBADebug;
|
||||||
|
|
||||||
|
class BBAServer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BBAServer(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool Listen(const QHostAddress &address, quint16 port = 0);
|
||||||
|
bool Listen(const QString &address, quint16 port = 0);
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
bool IsListening() const;
|
||||||
|
|
||||||
|
void SetMaxPendingConnections(int num_connections);
|
||||||
|
int MaxPendingConnections() const;
|
||||||
|
|
||||||
|
quint16 ServerPort() const;
|
||||||
|
QHostAddress ServerAddress() const;
|
||||||
|
|
||||||
|
//QAbstractSocket::SocketError ServerError() const; // can't be forward declared
|
||||||
|
QString ErrorString() const;
|
||||||
|
|
||||||
|
void PauseAccepting();
|
||||||
|
void ResumeAccepting();
|
||||||
|
|
||||||
|
#ifndef QT_NO_NETWORKPROXY
|
||||||
|
void SetProxy(const QNetworkProxy &networkProxy);
|
||||||
|
QNetworkProxy Proxy() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BBADebug LogInfo();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void Information(const QDateTime ×tamp, const QString &message);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void timerEvent(QTimerEvent *ev) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void NewConnection();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSet<BBAClient*> m_clients;
|
||||||
|
QTcpServer &m_server;
|
||||||
|
|
||||||
|
int m_counter = 0;
|
||||||
|
int m_timer_id = -1;
|
||||||
|
};
|
102
Source/Core/DolphinQt/BBAServerWindow.cpp
Normal file
102
Source/Core/DolphinQt/BBAServerWindow.cpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "DolphinQt/BBAServerWindow.h"
|
||||||
|
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QPlainTextEdit>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QTime>
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
BBAServerWindow::BBAServerWindow(QWidget *parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
m_server(this)
|
||||||
|
{
|
||||||
|
auto vbox_layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto hbox_layout = new QHBoxLayout;
|
||||||
|
|
||||||
|
auto host_addr_label = new QLabel(tr("Host address:"), this);
|
||||||
|
hbox_layout->addWidget(host_addr_label);
|
||||||
|
|
||||||
|
m_host_addr = new QLineEdit(this);
|
||||||
|
host_addr_label->setBuddy(m_host_addr);
|
||||||
|
m_host_addr->setPlaceholderText(tr("Leave empty for Any"));
|
||||||
|
hbox_layout->addWidget(m_host_addr);
|
||||||
|
|
||||||
|
auto port_label = new QLabel(tr("Port:"), this);
|
||||||
|
hbox_layout->addWidget(port_label);
|
||||||
|
|
||||||
|
m_port = new QSpinBox(this);
|
||||||
|
port_label->setBuddy(m_port);
|
||||||
|
m_port->setRange(std::numeric_limits<quint16>::min(), std::numeric_limits<quint16>::max());
|
||||||
|
m_port->setValue(50000);
|
||||||
|
hbox_layout->addWidget(m_port);
|
||||||
|
|
||||||
|
m_toggle = new QPushButton(this);
|
||||||
|
connect(m_toggle, &QAbstractButton::pressed, this, &BBAServerWindow::Toggle);
|
||||||
|
hbox_layout->addWidget(m_toggle);
|
||||||
|
|
||||||
|
vbox_layout->addLayout(hbox_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_log_output = new QPlainTextEdit(this);
|
||||||
|
m_log_output->setLineWrapMode(QPlainTextEdit::NoWrap);
|
||||||
|
m_log_output->setReadOnly(true);
|
||||||
|
vbox_layout->addWidget(m_log_output, 1);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto button_box = new QDialogButtonBox(QDialogButtonBox::Close, this);
|
||||||
|
connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
vbox_layout->addWidget(button_box, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLayout(vbox_layout);
|
||||||
|
|
||||||
|
connect(&m_server, &BBAServer::Information, this, &BBAServerWindow::LogOutput);
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAServerWindow::Toggle()
|
||||||
|
{
|
||||||
|
if (m_server.IsListening())
|
||||||
|
{
|
||||||
|
m_server.Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!m_server.Listen(m_host_addr->text(), m_port->value()))
|
||||||
|
QMessageBox::warning(this, tr("Could not start listening!"), tr("Could not start listening:\n\n%0").arg(m_server.ErrorString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAServerWindow::LogOutput(const QDateTime ×tamp, const QString &log_line)
|
||||||
|
{
|
||||||
|
m_log_output->appendPlainText(QStringLiteral("%0: %1").arg(timestamp.toString(QStringLiteral("HH:mm:ss.zzz")), log_line));
|
||||||
|
auto scrollBar = m_log_output->verticalScrollBar();
|
||||||
|
scrollBar->setValue(scrollBar->maximum());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAServerWindow::Update()
|
||||||
|
{
|
||||||
|
const auto is_listening = m_server.IsListening();
|
||||||
|
|
||||||
|
m_host_addr->setEnabled(!is_listening);
|
||||||
|
m_port->setEnabled(!is_listening);
|
||||||
|
|
||||||
|
m_toggle->setText(is_listening ? tr("Stop") : tr("Start"));
|
||||||
|
}
|
35
Source/Core/DolphinQt/BBAServerWindow.h
Normal file
35
Source/Core/DolphinQt/BBAServerWindow.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
#include "DolphinQt/BBAServer.h"
|
||||||
|
|
||||||
|
class QLineEdit;
|
||||||
|
class QSpinBox;
|
||||||
|
class QPlainTextEdit;
|
||||||
|
|
||||||
|
class BBAServerWindow : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit BBAServerWindow(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void Toggle();
|
||||||
|
void LogOutput(const QDateTime ×tamp, const QString &log_line);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
BBAServer m_server;
|
||||||
|
|
||||||
|
QLineEdit *m_host_addr;
|
||||||
|
QSpinBox *m_port;
|
||||||
|
QPushButton *m_toggle;
|
||||||
|
|
||||||
|
QPlainTextEdit *m_log_output;
|
||||||
|
};
|
21
Source/Core/DolphinQt/BBAServer_main.cpp
Normal file
21
Source/Core/DolphinQt/BBAServer_main.cpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "DolphinQt/BBAServer.h"
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
|
BBAServer server(&app);
|
||||||
|
|
||||||
|
QObject::connect(&server, &BBAServer::Information, [](const QDateTime ×tamp, const QString &logLine){
|
||||||
|
Q_UNUSED(timestamp)
|
||||||
|
qInfo().noquote() << logLine;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!server.Listen(QString(), 50000))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
find_package(Qt5 5.9 REQUIRED COMPONENTS Gui Widgets)
|
find_package(Qt5 5.9 REQUIRED COMPONENTS Core Network Gui Widgets)
|
||||||
|
|
||||||
set_property(TARGET Qt5::Core PROPERTY INTERFACE_COMPILE_FEATURES "")
|
set_property(TARGET Qt5::Core PROPERTY INTERFACE_COMPILE_FEATURES "")
|
||||||
message(STATUS "Found Qt version ${Qt5Core_VERSION}")
|
message(STATUS "Found Qt version ${Qt5Core_VERSION}")
|
||||||
@@ -7,6 +7,7 @@ set(CMAKE_AUTOMOC ON)
|
|||||||
|
|
||||||
add_executable(dolphin-emu
|
add_executable(dolphin-emu
|
||||||
AboutDialog.cpp
|
AboutDialog.cpp
|
||||||
|
BBAServerWindow.cpp
|
||||||
CheatsManager.cpp
|
CheatsManager.cpp
|
||||||
DiscordHandler.cpp
|
DiscordHandler.cpp
|
||||||
DiscordJoinRequestDialog.cpp
|
DiscordJoinRequestDialog.cpp
|
||||||
@@ -27,6 +28,7 @@ add_executable(dolphin-emu
|
|||||||
WiiUpdate.h
|
WiiUpdate.h
|
||||||
Config/CheatCodeEditor.cpp
|
Config/CheatCodeEditor.cpp
|
||||||
Config/ARCodeWidget.cpp
|
Config/ARCodeWidget.cpp
|
||||||
|
Config/BBAConfigWidget.cpp
|
||||||
Config/CheatWarningWidget.cpp
|
Config/CheatWarningWidget.cpp
|
||||||
Config/ControllersWindow.cpp
|
Config/ControllersWindow.cpp
|
||||||
Config/FilesystemWidget.cpp
|
Config/FilesystemWidget.cpp
|
||||||
@@ -146,6 +148,7 @@ PRIVATE
|
|||||||
Qt5::Widgets
|
Qt5::Widgets
|
||||||
uicommon
|
uicommon
|
||||||
imgui
|
imgui
|
||||||
|
bbaserverlib
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
@@ -238,3 +241,11 @@ endif()
|
|||||||
if(USE_DISCORD_PRESENCE)
|
if(USE_DISCORD_PRESENCE)
|
||||||
target_compile_definitions(dolphin-emu PRIVATE -DUSE_DISCORD_PRESENCE)
|
target_compile_definitions(dolphin-emu PRIVATE -DUSE_DISCORD_PRESENCE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_executable(bbaserver BBAServer_main.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(bbaserver Qt5::Core Qt5::Network bbaserverlib)
|
||||||
|
|
||||||
|
add_library(bbaserverlib BBAClient.cpp BBADebug.cpp BBAServer.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(bbaserverlib PUBLIC Qt5::Core PRIVATE Qt5::Network)
|
||||||
|
134
Source/Core/DolphinQt/Config/BBAConfigWidget.cpp
Normal file
134
Source/Core/DolphinQt/Config/BBAConfigWidget.cpp
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "DolphinQt/Config/BBAConfigWidget.h"
|
||||||
|
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include "Common/Network.h"
|
||||||
|
|
||||||
|
BBAConfigWidget::BBAConfigWidget(bool showServer, QWidget* parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
m_server(nullptr),
|
||||||
|
m_port(nullptr)
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("Broadband Adapter Configuration"));
|
||||||
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
|
|
||||||
|
auto vbox_layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto form_layout = new QFormLayout;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto hbox_layout = new QHBoxLayout;
|
||||||
|
|
||||||
|
m_mac_addr = new QLineEdit(this);
|
||||||
|
m_mac_addr->setPlaceholderText(tr("Leave empty for random"));
|
||||||
|
connect(m_mac_addr, &QLineEdit::textChanged, this, &BBAConfigWidget::TextChanged);
|
||||||
|
hbox_layout->addWidget(m_mac_addr);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto button = new QToolButton(this);
|
||||||
|
button->setText(tr("Randomize"));
|
||||||
|
connect(button, &QAbstractButton::pressed, this, &BBAConfigWidget::GenerateMac);
|
||||||
|
hbox_layout->addWidget(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
form_layout->addRow(tr("MAC address:"), hbox_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showServer)
|
||||||
|
{
|
||||||
|
auto hboxLayout = new QHBoxLayout;
|
||||||
|
|
||||||
|
m_server = new QLineEdit(this);
|
||||||
|
hboxLayout->addWidget(m_server);
|
||||||
|
|
||||||
|
auto portLabel = new QLabel(tr("Port:"), this);
|
||||||
|
hboxLayout->addWidget(portLabel);
|
||||||
|
|
||||||
|
m_port = new QSpinBox(this);
|
||||||
|
portLabel->setBuddy(m_port);
|
||||||
|
m_port->setRange(std::numeric_limits<quint16>::min(), std::numeric_limits<quint16>::max());
|
||||||
|
hboxLayout->addWidget(m_port);
|
||||||
|
|
||||||
|
form_layout->addRow(tr("Server:"), hboxLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
vbox_layout->addLayout(form_layout, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close, this);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &BBAConfigWidget::Submit);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
vbox_layout->addWidget(buttonBox, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLayout(vbox_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BBAConfigWidget::MacAddr() const
|
||||||
|
{
|
||||||
|
return m_mac_addr->text();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAConfigWidget::SetMacAddr(const QString& mac_addr)
|
||||||
|
{
|
||||||
|
m_mac_addr->setText(mac_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BBAConfigWidget::Server() const
|
||||||
|
{
|
||||||
|
return m_server ? m_server->text() : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAConfigWidget::SetServer(const QString& server)
|
||||||
|
{
|
||||||
|
m_server->setText(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
quint16 BBAConfigWidget::Port() const
|
||||||
|
{
|
||||||
|
return m_port ? m_port->value() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAConfigWidget::SetPort(quint16 port)
|
||||||
|
{
|
||||||
|
m_port->setValue(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAConfigWidget::Submit()
|
||||||
|
{
|
||||||
|
if(!MacAddr().isEmpty() && !Common::StringToMacAddress(MacAddr().toStdString()))
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, tr("Invalid MAC address!"), tr("Invalid MAC address!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAConfigWidget::GenerateMac()
|
||||||
|
{
|
||||||
|
m_mac_addr->setText(QString::fromStdString(Common::MacAddressToString(Common::GenerateMacAddress(Common::MACConsumer::BBA))));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BBAConfigWidget::TextChanged(const QString& text)
|
||||||
|
{
|
||||||
|
QString inputMask;
|
||||||
|
if(!text.isEmpty() && text != QStringLiteral(":::::"))
|
||||||
|
inputMask = QStringLiteral("HH:HH:HH:HH:HH:HH;_");
|
||||||
|
if (m_mac_addr->inputMask() != inputMask)
|
||||||
|
m_mac_addr->setInputMask(inputMask);
|
||||||
|
}
|
36
Source/Core/DolphinQt/Config/BBAConfigWidget.h
Normal file
36
Source/Core/DolphinQt/Config/BBAConfigWidget.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2019 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
class QLineEdit;
|
||||||
|
class QSpinBox;
|
||||||
|
|
||||||
|
class BBAConfigWidget : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BBAConfigWidget(bool showServer, QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
QString MacAddr() const;
|
||||||
|
void SetMacAddr(const QString& mac_addr);
|
||||||
|
|
||||||
|
QString Server() const;
|
||||||
|
void SetServer(const QString& server);
|
||||||
|
|
||||||
|
quint16 Port() const;
|
||||||
|
void SetPort(quint16 port);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void Submit();
|
||||||
|
void GenerateMac();
|
||||||
|
void TextChanged(const QString& text);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLineEdit *m_mac_addr;
|
||||||
|
QLineEdit *m_server;
|
||||||
|
QSpinBox *m_port;
|
||||||
|
};
|
@@ -57,9 +57,11 @@
|
|||||||
<!--NOTE: When adding moc'd files, you must list outputs in the ClCompile ItemGroup too!-->
|
<!--NOTE: When adding moc'd files, you must list outputs in the ClCompile ItemGroup too!-->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtMoc Include="AboutDialog.h" />
|
<QtMoc Include="AboutDialog.h" />
|
||||||
|
<QtMoc Include="BBAServerWindow.h" />
|
||||||
<QtMoc Include="CheatsManager.h" />
|
<QtMoc Include="CheatsManager.h" />
|
||||||
<QtMoc Include="Config\CheatCodeEditor.h" />
|
<QtMoc Include="Config\CheatCodeEditor.h" />
|
||||||
<QtMoc Include="Config\ARCodeWidget.h" />
|
<QtMoc Include="Config\ARCodeWidget.h" />
|
||||||
|
<QtMoc Include="Config\BBAConfigWidget.h" />
|
||||||
<QtMoc Include="Config\CheatWarningWidget.h" />
|
<QtMoc Include="Config\CheatWarningWidget.h" />
|
||||||
<QtMoc Include="Config\ControllersWindow.h" />
|
<QtMoc Include="Config\ControllersWindow.h" />
|
||||||
<QtMoc Include="Config\FilesystemWidget.h" />
|
<QtMoc Include="Config\FilesystemWidget.h" />
|
||||||
@@ -172,6 +174,8 @@
|
|||||||
<ClCompile Include="$(QtMocOutPrefix)AdvancedWidget.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)AdvancedWidget.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)AspectRatioWidget.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)AspectRatioWidget.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)AudioPane.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)AudioPane.cpp" />
|
||||||
|
<ClCompile Include="$(QtMocOutPrefix)BBAConfigWidget.cpp" />
|
||||||
|
<ClCompile Include="$(QtMocOutPrefix)BBAServerWindow.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)BlockUserInputFilter.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)BlockUserInputFilter.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)BreakpointWidget.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)BreakpointWidget.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)CheatCodeEditor.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)CheatCodeEditor.cpp" />
|
||||||
@@ -273,9 +277,11 @@
|
|||||||
<ClCompile Include="$(QtMocOutPrefix)WiimoteEmuMotionControl.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)WiimoteEmuMotionControl.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)WindowActivationEventFilter.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)WindowActivationEventFilter.cpp" />
|
||||||
<ClCompile Include="AboutDialog.cpp" />
|
<ClCompile Include="AboutDialog.cpp" />
|
||||||
|
<ClCompile Include="BBAServerWindow.cpp" />
|
||||||
<ClCompile Include="CheatsManager.cpp" />
|
<ClCompile Include="CheatsManager.cpp" />
|
||||||
<ClCompile Include="Config\CheatCodeEditor.cpp" />
|
<ClCompile Include="Config\CheatCodeEditor.cpp" />
|
||||||
<ClCompile Include="Config\ARCodeWidget.cpp" />
|
<ClCompile Include="Config\ARCodeWidget.cpp" />
|
||||||
|
<ClCompile Include="Config\BBAConfigWidget.cpp" />
|
||||||
<ClCompile Include="Config\CheatWarningWidget.cpp" />
|
<ClCompile Include="Config\CheatWarningWidget.cpp" />
|
||||||
<ClCompile Include="Config\ControllersWindow.cpp" />
|
<ClCompile Include="Config\ControllersWindow.cpp" />
|
||||||
<ClCompile Include="Config\FilesystemWidget.cpp" />
|
<ClCompile Include="Config\FilesystemWidget.cpp" />
|
||||||
|
@@ -59,6 +59,7 @@
|
|||||||
#include "DiscIO/NANDImporter.h"
|
#include "DiscIO/NANDImporter.h"
|
||||||
|
|
||||||
#include "DolphinQt/AboutDialog.h"
|
#include "DolphinQt/AboutDialog.h"
|
||||||
|
#include "DolphinQt/BBAServerWindow.h"
|
||||||
#include "DolphinQt/CheatsManager.h"
|
#include "DolphinQt/CheatsManager.h"
|
||||||
#include "DolphinQt/Config/ControllersWindow.h"
|
#include "DolphinQt/Config/ControllersWindow.h"
|
||||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||||
@@ -438,6 +439,9 @@ void MainWindow::ConnectMenuBar()
|
|||||||
connect(m_menu_bar, &MenuBar::BootWiiSystemMenu, this, &MainWindow::BootWiiSystemMenu);
|
connect(m_menu_bar, &MenuBar::BootWiiSystemMenu, this, &MainWindow::BootWiiSystemMenu);
|
||||||
connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog);
|
connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog);
|
||||||
connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer);
|
connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer);
|
||||||
|
#ifndef _WIN32
|
||||||
|
connect(m_menu_bar, &MenuBar::StartBbaServer, this, &MainWindow::StartBbaServer);
|
||||||
|
#endif
|
||||||
connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
|
connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
|
||||||
|
|
||||||
// Movie
|
// Movie
|
||||||
@@ -1127,6 +1131,18 @@ void MainWindow::ShowFIFOPlayer()
|
|||||||
m_fifo_window->activateWindow();
|
m_fifo_window->activateWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
void MainWindow::StartBbaServer()
|
||||||
|
{
|
||||||
|
if (!m_bba_server_window)
|
||||||
|
m_bba_server_window = new BBAServerWindow(this);
|
||||||
|
|
||||||
|
m_bba_server_window->show();
|
||||||
|
m_bba_server_window->raise();
|
||||||
|
m_bba_server_window->activateWindow();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void MainWindow::StateLoad()
|
void MainWindow::StateLoad()
|
||||||
{
|
{
|
||||||
QString path = QFileDialog::getOpenFileName(this, tr("Select a File"), QDir::currentPath(),
|
QString path = QFileDialog::getOpenFileName(this, tr("Select a File"), QDir::currentPath(),
|
||||||
|
@@ -23,6 +23,7 @@ class ControllersWindow;
|
|||||||
class DiscordHandler;
|
class DiscordHandler;
|
||||||
class DragEnterEvent;
|
class DragEnterEvent;
|
||||||
class FIFOPlayerWindow;
|
class FIFOPlayerWindow;
|
||||||
|
class BBAServerWindow;
|
||||||
class GameList;
|
class GameList;
|
||||||
class GCTASInputWindow;
|
class GCTASInputWindow;
|
||||||
class GraphicsWindow;
|
class GraphicsWindow;
|
||||||
@@ -148,6 +149,9 @@ private:
|
|||||||
void ShowHotkeyDialog();
|
void ShowHotkeyDialog();
|
||||||
void ShowNetPlaySetupDialog();
|
void ShowNetPlaySetupDialog();
|
||||||
void ShowFIFOPlayer();
|
void ShowFIFOPlayer();
|
||||||
|
#ifndef _WIN32
|
||||||
|
void StartBbaServer();
|
||||||
|
#endif
|
||||||
void ShowMemcardManager();
|
void ShowMemcardManager();
|
||||||
void ShowResourcePackManager();
|
void ShowResourcePackManager();
|
||||||
void ShowCheatsManager();
|
void ShowCheatsManager();
|
||||||
@@ -204,6 +208,9 @@ private:
|
|||||||
SettingsWindow* m_settings_window = nullptr;
|
SettingsWindow* m_settings_window = nullptr;
|
||||||
GraphicsWindow* m_graphics_window = nullptr;
|
GraphicsWindow* m_graphics_window = nullptr;
|
||||||
FIFOPlayerWindow* m_fifo_window = nullptr;
|
FIFOPlayerWindow* m_fifo_window = nullptr;
|
||||||
|
#ifndef _WIN32
|
||||||
|
BBAServerWindow* m_bba_server_window = nullptr;
|
||||||
|
#endif
|
||||||
MappingWindow* m_hotkey_window = nullptr;
|
MappingWindow* m_hotkey_window = nullptr;
|
||||||
|
|
||||||
HotkeyScheduler* m_hotkey_scheduler;
|
HotkeyScheduler* m_hotkey_scheduler;
|
||||||
|
@@ -240,7 +240,9 @@ void MenuBar::AddToolsMenu()
|
|||||||
|
|
||||||
tools_menu->addAction(tr("Start &NetPlay..."), this, &MenuBar::StartNetPlay);
|
tools_menu->addAction(tr("Start &NetPlay..."), this, &MenuBar::StartNetPlay);
|
||||||
tools_menu->addAction(tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer);
|
tools_menu->addAction(tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer);
|
||||||
|
#ifndef _WIN32
|
||||||
|
tools_menu->addAction(tr("&Broadband Adapter Server"), this, &MenuBar::StartBbaServer);
|
||||||
|
#endif
|
||||||
tools_menu->addSeparator();
|
tools_menu->addSeparator();
|
||||||
|
|
||||||
// Label will be set by a NANDRefresh later
|
// Label will be set by a NANDRefresh later
|
||||||
|
@@ -62,6 +62,7 @@ signals:
|
|||||||
void FrameAdvance();
|
void FrameAdvance();
|
||||||
void Screenshot();
|
void Screenshot();
|
||||||
void StartNetPlay();
|
void StartNetPlay();
|
||||||
|
void StartBbaServer();
|
||||||
void StateLoad();
|
void StateLoad();
|
||||||
void StateSave();
|
void StateSave();
|
||||||
void StateLoadSlot();
|
void StateLoadSlot();
|
||||||
|
@@ -28,6 +28,7 @@
|
|||||||
#include "Core/HW/EXI/EXI.h"
|
#include "Core/HW/EXI/EXI.h"
|
||||||
#include "Core/HW/GCMemcard/GCMemcard.h"
|
#include "Core/HW/GCMemcard/GCMemcard.h"
|
||||||
|
|
||||||
|
#include "DolphinQt/Config/BBAConfigWidget.h"
|
||||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||||
|
|
||||||
enum
|
enum
|
||||||
@@ -104,7 +105,8 @@ 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_TAP),
|
||||||
|
std::make_pair(tr("Broadband Adapter (TCP)"), ExpansionInterface::EXIDEVICE_ETH_TCP)})
|
||||||
{
|
{
|
||||||
m_slot_combos[2]->addItem(entry.first, entry.second);
|
m_slot_combos[2]->addItem(entry.first, entry.second);
|
||||||
}
|
}
|
||||||
@@ -160,7 +162,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_TAP ||
|
||||||
|
value == ExpansionInterface::EXIDEVICE_ETH_TCP);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +175,9 @@ void GameCubePane::OnConfigPressed(int slot)
|
|||||||
QString filter;
|
QString filter;
|
||||||
bool memcard = false;
|
bool memcard = false;
|
||||||
|
|
||||||
switch (m_slot_combos[slot]->currentData().toInt())
|
const auto currentData = m_slot_combos[slot]->currentData().toInt();
|
||||||
|
|
||||||
|
switch (currentData)
|
||||||
{
|
{
|
||||||
case ExpansionInterface::EXIDEVICE_MEMORYCARD:
|
case ExpansionInterface::EXIDEVICE_MEMORYCARD:
|
||||||
filter = tr("GameCube Memory Cards (*.raw *.gcp)");
|
filter = tr("GameCube Memory Cards (*.raw *.gcp)");
|
||||||
@@ -184,14 +189,26 @@ void GameCubePane::OnConfigPressed(int slot)
|
|||||||
case ExpansionInterface::EXIDEVICE_MIC:
|
case ExpansionInterface::EXIDEVICE_MIC:
|
||||||
MappingWindow(this, MappingWindow::Type::MAPPING_GC_MICROPHONE, slot).exec();
|
MappingWindow(this, MappingWindow::Type::MAPPING_GC_MICROPHONE, slot).exec();
|
||||||
return;
|
return;
|
||||||
case ExpansionInterface::EXIDEVICE_ETH:
|
case ExpansionInterface::EXIDEVICE_ETH_TAP:
|
||||||
|
case ExpansionInterface::EXIDEVICE_ETH_TCP:
|
||||||
{
|
{
|
||||||
bool ok;
|
const auto isTcp = currentData == ExpansionInterface::EXIDEVICE_ETH_TCP;
|
||||||
const auto new_mac = QInputDialog::getText(
|
BBAConfigWidget dialog(isTcp, this);
|
||||||
this, tr("Broadband Adapter MAC address"), tr("Enter new Broadband Adapter MAC address:"),
|
dialog.SetMacAddr(QString::fromStdString(SConfig::GetInstance().m_bba_mac));
|
||||||
QLineEdit::Normal, QString::fromStdString(SConfig::GetInstance().m_bba_mac), &ok);
|
if (isTcp)
|
||||||
if (ok)
|
{
|
||||||
SConfig::GetInstance().m_bba_mac = new_mac.toStdString();
|
dialog.SetServer(QString::fromStdString(SConfig::GetInstance().m_bba_server));
|
||||||
|
dialog.SetPort(SConfig::GetInstance().m_bba_port);
|
||||||
|
}
|
||||||
|
if(dialog.exec() == QDialog::Accepted)
|
||||||
|
{
|
||||||
|
SConfig::GetInstance().m_bba_mac = dialog.MacAddr().toStdString();
|
||||||
|
if (isTcp)
|
||||||
|
{
|
||||||
|
SConfig::GetInstance().m_bba_server = dialog.Server().toStdString();
|
||||||
|
SConfig::GetInstance().m_bba_port = dialog.Port();
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -200,7 +217,7 @@ void GameCubePane::OnConfigPressed(int slot)
|
|||||||
|
|
||||||
QString filename = QFileDialog::getSaveFileName(
|
QString filename = QFileDialog::getSaveFileName(
|
||||||
this, tr("Choose a file to open"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
|
this, tr("Choose a file to open"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
|
||||||
filter, 0, QFileDialog::DontConfirmOverwrite);
|
filter, nullptr, QFileDialog::DontConfirmOverwrite);
|
||||||
|
|
||||||
if (filename.isEmpty())
|
if (filename.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
Reference in New Issue
Block a user