mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-03-12 06:39:14 +01:00
Merge pull request #9300 from leoetlino/ncd-wd-fixes
IOS: WD and NCD fixes
This commit is contained in:
commit
27013e8d18
@ -133,7 +133,7 @@ void DolphinAnalytics::ReportGameStart()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Keep in sync with enum class GameQuirk definition.
|
// Keep in sync with enum class GameQuirk definition.
|
||||||
constexpr std::array<const char*, 12> GAME_QUIRKS_NAMES{"icache-matters",
|
constexpr std::array<const char*, 14> GAME_QUIRKS_NAMES{"icache-matters",
|
||||||
"directly-reads-wiimote-input",
|
"directly-reads-wiimote-input",
|
||||||
"uses-DVDLowStopLaser",
|
"uses-DVDLowStopLaser",
|
||||||
"uses-DVDLowOffset",
|
"uses-DVDLowOffset",
|
||||||
@ -144,7 +144,9 @@ constexpr std::array<const char*, 12> GAME_QUIRKS_NAMES{"icache-matters",
|
|||||||
"uses-different-partition-command",
|
"uses-different-partition-command",
|
||||||
"uses-di-interrupt-command",
|
"uses-di-interrupt-command",
|
||||||
"mismatched-gpu-texgens-between-xf-and-bp",
|
"mismatched-gpu-texgens-between-xf-and-bp",
|
||||||
"mismatched-gpu-colors-between-xf-and-bp"};
|
"mismatched-gpu-colors-between-xf-and-bp",
|
||||||
|
"uses-uncommon-wd-mode",
|
||||||
|
"uses-wd-unimplemented-ioctl"};
|
||||||
static_assert(GAME_QUIRKS_NAMES.size() == static_cast<u32>(GameQuirk::COUNT),
|
static_assert(GAME_QUIRKS_NAMES.size() == static_cast<u32>(GameQuirk::COUNT),
|
||||||
"Game quirks names and enum definition are out of sync.");
|
"Game quirks names and enum definition are out of sync.");
|
||||||
|
|
||||||
|
@ -54,6 +54,13 @@ enum class GameQuirk
|
|||||||
MISMATCHED_GPU_TEXGENS_BETWEEN_XF_AND_BP,
|
MISMATCHED_GPU_TEXGENS_BETWEEN_XF_AND_BP,
|
||||||
MISMATCHED_GPU_COLORS_BETWEEN_XF_AND_BP,
|
MISMATCHED_GPU_COLORS_BETWEEN_XF_AND_BP,
|
||||||
|
|
||||||
|
// The WD module can be configured to operate in six different modes.
|
||||||
|
// In practice, only mode 1 (DS communications) and mode 3 (AOSS access point scanning)
|
||||||
|
// are used by games and the system menu respectively.
|
||||||
|
USES_UNCOMMON_WD_MODE,
|
||||||
|
|
||||||
|
USES_WD_UNIMPLEMENTED_IOCTL,
|
||||||
|
|
||||||
COUNT,
|
COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -78,7 +78,8 @@ IOCtlVRequest::IOCtlVRequest(const u32 address_) : Request(address_)
|
|||||||
|
|
||||||
const IOCtlVRequest::IOVector* IOCtlVRequest::GetVector(size_t index) const
|
const IOCtlVRequest::IOVector* IOCtlVRequest::GetVector(size_t index) const
|
||||||
{
|
{
|
||||||
ASSERT(index < (in_vectors.size() + io_vectors.size()));
|
if (index >= in_vectors.size() + io_vectors.size())
|
||||||
|
return nullptr;
|
||||||
if (index < in_vectors.size())
|
if (index < in_vectors.size())
|
||||||
return &in_vectors[index];
|
return &in_vectors[index];
|
||||||
return &io_vectors[index - in_vectors.size()];
|
return &io_vectors[index - in_vectors.size()];
|
||||||
|
@ -154,7 +154,10 @@ struct IOCtlVRequest final : Request
|
|||||||
// merging them into a single std::vector would make using the first out vector more complicated.
|
// merging them into a single std::vector would make using the first out vector more complicated.
|
||||||
std::vector<IOVector> in_vectors;
|
std::vector<IOVector> in_vectors;
|
||||||
std::vector<IOVector> io_vectors;
|
std::vector<IOVector> io_vectors;
|
||||||
|
|
||||||
|
/// Returns the specified vector or nullptr if the index is out of bounds.
|
||||||
const IOVector* GetVector(size_t index) const;
|
const IOVector* GetVector(size_t index) const;
|
||||||
|
|
||||||
explicit IOCtlVRequest(u32 address);
|
explicit IOCtlVRequest(u32 address);
|
||||||
bool HasNumberOfValidVectors(size_t in_count, size_t io_count) const;
|
bool HasNumberOfValidVectors(size_t in_count, size_t io_count) const;
|
||||||
void Dump(std::string_view description, Common::Log::LOG_TYPE type = Common::Log::IOS,
|
void Dump(std::string_view description, Common::Log::LOG_TYPE type = Common::Log::IOS,
|
||||||
|
@ -20,6 +20,12 @@ NetNCDManage::NetNCDManage(Kernel& ios, const std::string& device_name) : Device
|
|||||||
config.ReadConfig(ios.GetFS().get());
|
config.ReadConfig(ios.GetFS().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetNCDManage::DoState(PointerWrap& p)
|
||||||
|
{
|
||||||
|
Device::DoState(p);
|
||||||
|
p.Do(m_ipc_fd);
|
||||||
|
}
|
||||||
|
|
||||||
IPCCommandResult NetNCDManage::IOCtlV(const IOCtlVRequest& request)
|
IPCCommandResult NetNCDManage::IOCtlV(const IOCtlVRequest& request)
|
||||||
{
|
{
|
||||||
s32 return_value = IPC_SUCCESS;
|
s32 return_value = IPC_SUCCESS;
|
||||||
@ -29,11 +35,51 @@ IPCCommandResult NetNCDManage::IOCtlV(const IOCtlVRequest& request)
|
|||||||
switch (request.request)
|
switch (request.request)
|
||||||
{
|
{
|
||||||
case IOCTLV_NCD_LOCKWIRELESSDRIVER:
|
case IOCTLV_NCD_LOCKWIRELESSDRIVER:
|
||||||
|
if (!request.HasNumberOfValidVectors(0, 1))
|
||||||
|
return GetDefaultReply(IPC_EINVAL);
|
||||||
|
|
||||||
|
if (request.io_vectors[0].size < 2 * sizeof(u32))
|
||||||
|
return GetDefaultReply(IPC_EINVAL);
|
||||||
|
|
||||||
|
if (m_ipc_fd != 0)
|
||||||
|
{
|
||||||
|
// It is an error to lock the driver again when it is already locked.
|
||||||
|
common_result = IPC_EINVAL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// NCD writes the internal address of the request's file descriptor.
|
||||||
|
// We will just write the value of the file descriptor.
|
||||||
|
// The value will be positive so this will work fine.
|
||||||
|
m_ipc_fd = request.fd;
|
||||||
|
Memory::Write_U32(request.fd, request.io_vectors[0].address + 4);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IOCTLV_NCD_UNLOCKWIRELESSDRIVER:
|
case IOCTLV_NCD_UNLOCKWIRELESSDRIVER:
|
||||||
// Memory::Read_U32(request.in_vectors.at(0).address);
|
{
|
||||||
|
if (!request.HasNumberOfValidVectors(1, 1))
|
||||||
|
return GetDefaultReply(IPC_EINVAL);
|
||||||
|
|
||||||
|
if (request.in_vectors[0].size < sizeof(u32))
|
||||||
|
return GetDefaultReply(IPC_EINVAL);
|
||||||
|
|
||||||
|
if (request.io_vectors[0].size < sizeof(u32))
|
||||||
|
return GetDefaultReply(IPC_EINVAL);
|
||||||
|
|
||||||
|
const u32 request_handle = Memory::Read_U32(request.in_vectors[0].address);
|
||||||
|
if (m_ipc_fd == request_handle)
|
||||||
|
{
|
||||||
|
m_ipc_fd = 0;
|
||||||
|
common_result = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
common_result = -3;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case IOCTLV_NCD_GETCONFIG:
|
case IOCTLV_NCD_GETCONFIG:
|
||||||
INFO_LOG_FMT(IOS_NET, "NET_NCD_MANAGE: IOCTLV_NCD_GETCONFIG");
|
INFO_LOG_FMT(IOS_NET, "NET_NCD_MANAGE: IOCTLV_NCD_GETCONFIG");
|
||||||
|
@ -20,6 +20,8 @@ public:
|
|||||||
|
|
||||||
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
|
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
|
||||||
|
|
||||||
|
void DoState(PointerWrap& p) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
@ -34,5 +36,6 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
Net::WiiNetConfig config;
|
Net::WiiNetConfig config;
|
||||||
|
u32 m_ipc_fd = 0;
|
||||||
};
|
};
|
||||||
} // namespace IOS::HLE::Device
|
} // namespace IOS::HLE::Device
|
||||||
|
@ -4,33 +4,330 @@
|
|||||||
|
|
||||||
#include "Core/IOS/Network/WD/Command.h"
|
#include "Core/IOS/Network/WD/Command.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "Common/BitSet.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/Network.h"
|
#include "Common/Network.h"
|
||||||
#include "Common/Swap.h"
|
#include "Common/Swap.h"
|
||||||
|
#include "Core/Analytics.h"
|
||||||
#include "Core/HW/Memmap.h"
|
#include "Core/HW/Memmap.h"
|
||||||
#include "Core/IOS/Network/MACUtils.h"
|
#include "Core/IOS/Network/MACUtils.h"
|
||||||
|
|
||||||
namespace IOS::HLE::Device
|
namespace IOS::HLE::Device
|
||||||
{
|
{
|
||||||
NetWDCommand::NetWDCommand(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
|
namespace
|
||||||
{
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Channel: FEDC BA98 7654 3210
|
||||||
|
constexpr u16 LegalChannelMask = 0b0111'1111'1111'1110u;
|
||||||
|
constexpr u16 LegalNitroChannelMask = 0b0011'1111'1111'1110u;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
u16 SelectWifiChannel(u16 enabled_channels_mask, u16 current_channel)
|
||||||
|
{
|
||||||
|
const Common::BitSet<u16> enabled_channels{enabled_channels_mask & LegalChannelMask};
|
||||||
|
u16 next_channel = current_channel;
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
|
next_channel = (next_channel + 3) % 16;
|
||||||
|
if (enabled_channels[next_channel])
|
||||||
|
return next_channel;
|
||||||
|
}
|
||||||
|
// This does not make a lot of sense, but it is what WD does.
|
||||||
|
return u16(enabled_channels[next_channel]);
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 MakeNitroAllowedChannelMask(u16 enabled_channels_mask, u16 nitro_mask)
|
||||||
|
{
|
||||||
|
nitro_mask &= LegalNitroChannelMask;
|
||||||
|
// TODO: WD's version of this function has some complicated logic to determine the actual mask.
|
||||||
|
return enabled_channels_mask & nitro_mask;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
NetWDCommand::Status NetWDCommand::GetTargetStatusForMode(WD::Mode mode)
|
||||||
|
{
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case WD::Mode::DSCommunications:
|
||||||
|
return Status::ScanningForDS;
|
||||||
|
case WD::Mode::AOSSAccessPointScan:
|
||||||
|
return Status::ScanningForAOSSAccessPoint;
|
||||||
|
default:
|
||||||
|
return Status::Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NetWDCommand::NetWDCommand(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
|
||||||
|
{
|
||||||
|
// TODO: use the MPCH setting in setting.txt to determine this value.
|
||||||
|
m_nitro_enabled_channels = LegalNitroChannelMask;
|
||||||
|
|
||||||
|
// TODO: Set the version string here. This is exposed to the PPC.
|
||||||
|
m_info.mac = IOS::Net::GetMACAddress();
|
||||||
|
m_info.enabled_channels = 0xfffe;
|
||||||
|
m_info.channel = SelectWifiChannel(m_info.enabled_channels, 0);
|
||||||
|
// The country code is supposed to be null terminated as it is logged with printf in WD.
|
||||||
|
std::strncpy(m_info.country_code.data(), "US", m_info.country_code.size());
|
||||||
|
m_info.nitro_allowed_channels =
|
||||||
|
MakeNitroAllowedChannelMask(m_info.enabled_channels, m_nitro_enabled_channels);
|
||||||
|
m_info.initialised = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetWDCommand::Update()
|
||||||
|
{
|
||||||
|
Device::Update();
|
||||||
|
ProcessRecvRequests();
|
||||||
|
HandleStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetWDCommand::ProcessRecvRequests()
|
||||||
|
{
|
||||||
|
// Because we currently do not actually emulate the wireless driver, we have no frames
|
||||||
|
// and no notification data that could be used to reply to requests.
|
||||||
|
// Therefore, requests are left pending to simulate the situation where there is nothing to send.
|
||||||
|
|
||||||
|
// All pending requests must still be processed when the handle to the resource manager is closed.
|
||||||
|
const bool force_process = m_clear_all_requests.TestAndClear();
|
||||||
|
|
||||||
|
const auto process_queue = [&](std::deque<u32>& queue) {
|
||||||
|
if (!force_process)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (!queue.empty())
|
||||||
|
{
|
||||||
|
const auto request = queue.front();
|
||||||
|
s32 result;
|
||||||
|
|
||||||
|
// If the resource manager handle is closed while processing a request,
|
||||||
|
// InvalidFd is returned.
|
||||||
|
if (m_ipc_owner_fd < 0)
|
||||||
|
{
|
||||||
|
result = s32(ResultCode::InvalidFd);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Frame/notification data would be copied here.
|
||||||
|
// And result would be set to the data length or to an error code.
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO_LOG_FMT(IOS_NET, "Processed request {:08x} (result {:08x})", request, result);
|
||||||
|
m_ios.EnqueueIPCReply(Request{request}, result);
|
||||||
|
queue.pop_front();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process_queue(m_recv_notification_requests);
|
||||||
|
process_queue(m_recv_frame_requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetWDCommand::HandleStateChange()
|
||||||
|
{
|
||||||
|
const auto status = m_status;
|
||||||
|
const auto target_status = m_target_status;
|
||||||
|
|
||||||
|
if (status == target_status)
|
||||||
|
return;
|
||||||
|
|
||||||
|
INFO_LOG_FMT(IOS_NET, "{}: Handling status change ({} -> {})", __func__, status, target_status);
|
||||||
|
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case Status::Idle:
|
||||||
|
switch (target_status)
|
||||||
|
{
|
||||||
|
case Status::ScanningForAOSSAccessPoint:
|
||||||
|
// This is supposed to reset the driver first by going into another state.
|
||||||
|
// However, we can worry about that once we actually emulate WL.
|
||||||
|
m_status = Status::ScanningForAOSSAccessPoint;
|
||||||
|
break;
|
||||||
|
case Status::ScanningForDS:
|
||||||
|
// This is supposed to set a bunch of Wi-Fi driver parameters and initiate a scan.
|
||||||
|
m_status = Status::ScanningForDS;
|
||||||
|
break;
|
||||||
|
case Status::Idle:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Status::ScanningForDS:
|
||||||
|
m_status = Status::Idle;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Status::ScanningForAOSSAccessPoint:
|
||||||
|
// We are supposed to reset the driver by going into a reset state.
|
||||||
|
// However, we can worry about that once we actually emulate WL.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO_LOG_FMT(IOS_NET, "{}: done (status: {} -> {}, target was {})", __func__, status, m_status,
|
||||||
|
target_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetWDCommand::DoState(PointerWrap& p)
|
||||||
|
{
|
||||||
|
Device::DoState(p);
|
||||||
|
p.Do(m_ipc_owner_fd);
|
||||||
|
p.Do(m_mode);
|
||||||
|
p.Do(m_buffer_flags);
|
||||||
|
p.Do(m_status);
|
||||||
|
p.Do(m_target_status);
|
||||||
|
p.Do(m_nitro_enabled_channels);
|
||||||
|
p.Do(m_info);
|
||||||
|
p.Do(m_recv_frame_requests);
|
||||||
|
p.Do(m_recv_notification_requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPCCommandResult NetWDCommand::Open(const OpenRequest& request)
|
||||||
|
{
|
||||||
|
if (m_ipc_owner_fd < 0)
|
||||||
|
{
|
||||||
|
const auto flags = u32(request.flags);
|
||||||
|
const auto mode = WD::Mode(flags & 0xFFFF);
|
||||||
|
const auto buffer_flags = flags & 0x7FFF0000;
|
||||||
|
INFO_LOG_FMT(IOS_NET, "Opening with mode={} buffer_flags={:08x}", mode, buffer_flags);
|
||||||
|
|
||||||
|
// We don't support anything other than mode 1 and mode 3 at the moment.
|
||||||
|
if (mode != WD::Mode::DSCommunications && mode != WD::Mode::AOSSAccessPointScan)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "Unsupported WD operating mode: {}", mode);
|
||||||
|
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_UNCOMMON_WD_MODE);
|
||||||
|
return GetDefaultReply(s32(ResultCode::UnavailableCommand));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_target_status == Status::Idle && mode <= WD::Mode::Unknown6)
|
||||||
|
{
|
||||||
|
m_mode = mode;
|
||||||
|
m_ipc_owner_fd = request.fd;
|
||||||
|
m_buffer_flags = buffer_flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO_LOG_FMT(IOS_NET, "Opened");
|
||||||
|
return Device::Open(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPCCommandResult NetWDCommand::Close(u32 fd)
|
||||||
|
{
|
||||||
|
if (m_ipc_owner_fd < 0 || fd != u32(m_ipc_owner_fd))
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "Invalid close attempt.");
|
||||||
|
return GetDefaultReply(u32(ResultCode::InvalidFd));
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO_LOG_FMT(IOS_NET, "Closing and resetting status to Idle");
|
||||||
|
m_target_status = m_status = Status::Idle;
|
||||||
|
|
||||||
|
m_ipc_owner_fd = -1;
|
||||||
|
m_clear_all_requests.Set();
|
||||||
|
return Device::Close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPCCommandResult NetWDCommand::SetLinkState(const IOCtlVRequest& request)
|
||||||
|
{
|
||||||
|
const auto* vector = request.GetVector(0);
|
||||||
|
if (!vector || vector->address == 0)
|
||||||
|
return GetDefaultReply(u32(ResultCode::IllegalParameter));
|
||||||
|
|
||||||
|
const u32 state = Memory::Read_U32(vector->address);
|
||||||
|
INFO_LOG_FMT(IOS_NET, "WD_SetLinkState called (state={}, mode={})", state, m_mode);
|
||||||
|
|
||||||
|
if (state == 0)
|
||||||
|
{
|
||||||
|
if (!WD::IsValidMode(m_mode))
|
||||||
|
return GetDefaultReply(u32(ResultCode::UnavailableCommand));
|
||||||
|
|
||||||
|
INFO_LOG_FMT(IOS_NET, "WD_SetLinkState: setting target status to 1 (Idle)");
|
||||||
|
m_target_status = Status::Idle;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (state != 1)
|
||||||
|
return GetDefaultReply(u32(ResultCode::IllegalParameter));
|
||||||
|
|
||||||
|
if (!WD::IsValidMode(m_mode))
|
||||||
|
return GetDefaultReply(u32(ResultCode::UnavailableCommand));
|
||||||
|
|
||||||
|
const auto target_status = GetTargetStatusForMode(m_mode);
|
||||||
|
if (m_status != target_status && m_info.enabled_channels == 0)
|
||||||
|
return GetDefaultReply(u32(ResultCode::UnavailableCommand));
|
||||||
|
|
||||||
|
INFO_LOG_FMT(IOS_NET, "WD_SetLinkState: setting target status to {}", target_status);
|
||||||
|
m_target_status = target_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetDefaultReply(IPC_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPCCommandResult NetWDCommand::GetLinkState(const IOCtlVRequest& request) const
|
||||||
|
{
|
||||||
|
INFO_LOG_FMT(IOS_NET, "WD_GetLinkState called (status={}, mode={})", m_status, m_mode);
|
||||||
|
if (!WD::IsValidMode(m_mode))
|
||||||
|
return GetDefaultReply(u32(ResultCode::UnavailableCommand));
|
||||||
|
|
||||||
|
// Contrary to what the name of the ioctl suggests, this returns a boolean, not the current state.
|
||||||
|
return GetDefaultReply(u32(m_status == GetTargetStatusForMode(m_mode)));
|
||||||
|
}
|
||||||
|
|
||||||
|
IPCCommandResult NetWDCommand::Disassociate(const IOCtlVRequest& request)
|
||||||
|
{
|
||||||
|
const auto* vector = request.GetVector(0);
|
||||||
|
if (!vector || vector->address == 0)
|
||||||
|
return GetDefaultReply(u32(ResultCode::IllegalParameter));
|
||||||
|
|
||||||
|
Common::MACAddress mac;
|
||||||
|
Memory::CopyFromEmu(mac.data(), vector->address, mac.size());
|
||||||
|
|
||||||
|
INFO_LOG_FMT(IOS_NET, "WD_Disassociate: MAC {}", Common::MacAddressToString(mac));
|
||||||
|
|
||||||
|
if (m_mode != WD::Mode::DSCommunications && m_mode != WD::Mode::Unknown5 &&
|
||||||
|
m_mode != WD::Mode::Unknown6)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "WD_Disassociate: cannot disassociate in mode {}", m_mode);
|
||||||
|
return GetDefaultReply(u32(ResultCode::UnavailableCommand));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto target_status = GetTargetStatusForMode(m_mode);
|
||||||
|
if (m_status != target_status)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "WD_Disassociate: cannot disassociate in status {} (target {})",
|
||||||
|
m_status, target_status);
|
||||||
|
return GetDefaultReply(u32(ResultCode::UnavailableCommand));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check the input MAC address and only return 0x80008001 if it is unknown.
|
||||||
|
return GetDefaultReply(u32(ResultCode::IllegalParameter));
|
||||||
|
}
|
||||||
|
|
||||||
|
IPCCommandResult NetWDCommand::GetInfo(const IOCtlVRequest& request) const
|
||||||
|
{
|
||||||
|
const auto* vector = request.GetVector(0);
|
||||||
|
if (!vector || vector->address == 0)
|
||||||
|
return GetDefaultReply(u32(ResultCode::IllegalParameter));
|
||||||
|
|
||||||
|
Memory::CopyToEmu(vector->address, &m_info, sizeof(m_info));
|
||||||
|
return GetDefaultReply(IPC_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is just for debugging / playing around.
|
|
||||||
// There really is no reason to implement wd unless we can bend it such that
|
|
||||||
// we can talk to the DS.
|
|
||||||
IPCCommandResult NetWDCommand::IOCtlV(const IOCtlVRequest& request)
|
IPCCommandResult NetWDCommand::IOCtlV(const IOCtlVRequest& request)
|
||||||
{
|
{
|
||||||
s32 return_value = IPC_SUCCESS;
|
|
||||||
|
|
||||||
switch (request.request)
|
switch (request.request)
|
||||||
{
|
{
|
||||||
|
case IOCTLV_WD_INVALID:
|
||||||
|
return GetDefaultReply(u32(ResultCode::UnavailableCommand));
|
||||||
|
case IOCTLV_WD_GET_MODE:
|
||||||
|
return GetDefaultReply(s32(m_mode));
|
||||||
|
case IOCTLV_WD_SET_LINKSTATE:
|
||||||
|
return SetLinkState(request);
|
||||||
|
case IOCTLV_WD_GET_LINKSTATE:
|
||||||
|
return GetLinkState(request);
|
||||||
|
case IOCTLV_WD_DISASSOC:
|
||||||
|
return Disassociate(request);
|
||||||
|
|
||||||
case IOCTLV_WD_SCAN:
|
case IOCTLV_WD_SCAN:
|
||||||
{
|
{
|
||||||
// Gives parameters detailing type of scan and what to match
|
// Gives parameters detailing type of scan and what to match
|
||||||
@ -59,25 +356,19 @@ IPCCommandResult NetWDCommand::IOCtlV(const IOCtlVRequest& request)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case IOCTLV_WD_GET_INFO:
|
case IOCTLV_WD_GET_INFO:
|
||||||
{
|
return GetInfo(request);
|
||||||
Info* info = (Info*)Memory::GetPointer(request.io_vectors.at(0).address);
|
|
||||||
memset(info, 0, sizeof(Info));
|
|
||||||
// Probably used to disallow certain channels?
|
|
||||||
memcpy(info->country, "US", 2);
|
|
||||||
info->ntr_allowed_channels = Common::swap16(0xfffe);
|
|
||||||
|
|
||||||
const Common::MACAddress address = IOS::Net::GetMACAddress();
|
case IOCTLV_WD_RECV_FRAME:
|
||||||
std::copy(address.begin(), address.end(), info->mac);
|
m_recv_frame_requests.emplace_back(request.address);
|
||||||
}
|
return GetNoReply();
|
||||||
break;
|
|
||||||
|
case IOCTLV_WD_RECV_NOTIFICATION:
|
||||||
|
m_recv_notification_requests.emplace_back(request.address);
|
||||||
|
return GetNoReply();
|
||||||
|
|
||||||
case IOCTLV_WD_GET_MODE:
|
|
||||||
case IOCTLV_WD_SET_LINKSTATE:
|
|
||||||
case IOCTLV_WD_GET_LINKSTATE:
|
|
||||||
case IOCTLV_WD_SET_CONFIG:
|
case IOCTLV_WD_SET_CONFIG:
|
||||||
case IOCTLV_WD_GET_CONFIG:
|
case IOCTLV_WD_GET_CONFIG:
|
||||||
case IOCTLV_WD_CHANGE_BEACON:
|
case IOCTLV_WD_CHANGE_BEACON:
|
||||||
case IOCTLV_WD_DISASSOC:
|
|
||||||
case IOCTLV_WD_MP_SEND_FRAME:
|
case IOCTLV_WD_MP_SEND_FRAME:
|
||||||
case IOCTLV_WD_SEND_FRAME:
|
case IOCTLV_WD_SEND_FRAME:
|
||||||
case IOCTLV_WD_CALL_WL:
|
case IOCTLV_WD_CALL_WL:
|
||||||
@ -85,12 +376,11 @@ IPCCommandResult NetWDCommand::IOCtlV(const IOCtlVRequest& request)
|
|||||||
case IOCTLV_WD_GET_LASTERROR:
|
case IOCTLV_WD_GET_LASTERROR:
|
||||||
case IOCTLV_WD_CHANGE_GAMEINFO:
|
case IOCTLV_WD_CHANGE_GAMEINFO:
|
||||||
case IOCTLV_WD_CHANGE_VTSF:
|
case IOCTLV_WD_CHANGE_VTSF:
|
||||||
case IOCTLV_WD_RECV_FRAME:
|
|
||||||
case IOCTLV_WD_RECV_NOTIFICATION:
|
|
||||||
default:
|
default:
|
||||||
request.Dump(GetDeviceName(), Common::Log::IOS_NET, Common::Log::LINFO);
|
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_WD_UNIMPLEMENTED_IOCTL);
|
||||||
|
request.Dump(GetDeviceName(), Common::Log::IOS_NET, Common::Log::LWARNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetDefaultReply(return_value);
|
return GetDefaultReply(IPC_SUCCESS);
|
||||||
}
|
}
|
||||||
} // namespace IOS::HLE::Device
|
} // namespace IOS::HLE::Device
|
||||||
|
@ -4,23 +4,65 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/Flag.h"
|
||||||
|
#include "Common/Network.h"
|
||||||
|
#include "Common/Swap.h"
|
||||||
#include "Core/IOS/Device.h"
|
#include "Core/IOS/Device.h"
|
||||||
|
|
||||||
|
namespace IOS::HLE::WD
|
||||||
|
{
|
||||||
|
// Values 2, 4, 5, 6 exist as well but are not known to be used by games, the Mii Channel
|
||||||
|
// or the system menu.
|
||||||
|
enum class Mode
|
||||||
|
{
|
||||||
|
NotInitialized = 0,
|
||||||
|
// Used by games to broadcast DS programs or to communicate with a DS more generally.
|
||||||
|
DSCommunications = 1,
|
||||||
|
Unknown2 = 2,
|
||||||
|
// AOSS (https://en.wikipedia.org/wiki/AOSS) is a WPS-like feature.
|
||||||
|
// This is only known to be used by the system menu.
|
||||||
|
AOSSAccessPointScan = 3,
|
||||||
|
Unknown4 = 4,
|
||||||
|
Unknown5 = 5,
|
||||||
|
Unknown6 = 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr bool IsValidMode(Mode mode)
|
||||||
|
{
|
||||||
|
return mode >= Mode::DSCommunications && mode <= Mode::Unknown6;
|
||||||
|
}
|
||||||
|
} // namespace IOS::HLE::WD
|
||||||
|
|
||||||
namespace IOS::HLE::Device
|
namespace IOS::HLE::Device
|
||||||
{
|
{
|
||||||
class NetWDCommand : public Device
|
class NetWDCommand : public Device
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
enum class ResultCode : u32
|
||||||
|
{
|
||||||
|
InvalidFd = 0x80008000,
|
||||||
|
IllegalParameter = 0x80008001,
|
||||||
|
UnavailableCommand = 0x80008002,
|
||||||
|
DriverError = 0x80008003,
|
||||||
|
};
|
||||||
|
|
||||||
NetWDCommand(Kernel& ios, const std::string& device_name);
|
NetWDCommand(Kernel& ios, const std::string& device_name);
|
||||||
|
|
||||||
|
IPCCommandResult Open(const OpenRequest& request) override;
|
||||||
|
IPCCommandResult Close(u32 fd) override;
|
||||||
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
|
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
|
||||||
|
void Update() override;
|
||||||
|
bool IsOpened() const override { return true; }
|
||||||
|
void DoState(PointerWrap& p) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
|
IOCTLV_WD_INVALID = 0x1000,
|
||||||
IOCTLV_WD_GET_MODE = 0x1001, // WD_GetMode
|
IOCTLV_WD_GET_MODE = 0x1001, // WD_GetMode
|
||||||
IOCTLV_WD_SET_LINKSTATE = 0x1002, // WD_SetLinkState
|
IOCTLV_WD_SET_LINKSTATE = 0x1002, // WD_SetLinkState
|
||||||
IOCTLV_WD_GET_LINKSTATE = 0x1003, // WD_GetLinkState
|
IOCTLV_WD_GET_LINKSTATE = 0x1003, // WD_GetLinkState
|
||||||
@ -89,14 +131,45 @@ private:
|
|||||||
|
|
||||||
struct Info
|
struct Info
|
||||||
{
|
{
|
||||||
u8 mac[6];
|
Common::MACAddress mac{};
|
||||||
u16 ntr_allowed_channels;
|
Common::BigEndianValue<u16> enabled_channels{};
|
||||||
u16 unk8;
|
Common::BigEndianValue<u16> nitro_allowed_channels{};
|
||||||
char country[2];
|
std::array<char, 4> country_code{};
|
||||||
u32 unkc;
|
u8 channel{};
|
||||||
char wlversion[0x50];
|
bool initialised{};
|
||||||
u8 unk[0x30];
|
std::array<char, 0x80> wl_version{};
|
||||||
};
|
};
|
||||||
|
static_assert(sizeof(Info) == 0x90);
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
enum class Status
|
||||||
|
{
|
||||||
|
Idle,
|
||||||
|
ScanningForAOSSAccessPoint,
|
||||||
|
ScanningForDS,
|
||||||
|
};
|
||||||
|
|
||||||
|
void ProcessRecvRequests();
|
||||||
|
void HandleStateChange();
|
||||||
|
static Status GetTargetStatusForMode(WD::Mode mode);
|
||||||
|
|
||||||
|
IPCCommandResult SetLinkState(const IOCtlVRequest& request);
|
||||||
|
IPCCommandResult GetLinkState(const IOCtlVRequest& request) const;
|
||||||
|
IPCCommandResult Disassociate(const IOCtlVRequest& request);
|
||||||
|
IPCCommandResult GetInfo(const IOCtlVRequest& request) const;
|
||||||
|
|
||||||
|
s32 m_ipc_owner_fd = -1;
|
||||||
|
WD::Mode m_mode = WD::Mode::NotInitialized;
|
||||||
|
u32 m_buffer_flags{};
|
||||||
|
|
||||||
|
Status m_status = Status::Idle;
|
||||||
|
Status m_target_status = Status::Idle;
|
||||||
|
|
||||||
|
u16 m_nitro_enabled_channels{};
|
||||||
|
Info m_info;
|
||||||
|
|
||||||
|
Common::Flag m_clear_all_requests;
|
||||||
|
std::deque<u32> m_recv_frame_requests;
|
||||||
|
std::deque<u32> m_recv_notification_requests;
|
||||||
};
|
};
|
||||||
} // namespace IOS::HLE::Device
|
} // namespace IOS::HLE::Device
|
||||||
|
@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
|
|||||||
static std::thread g_save_thread;
|
static std::thread g_save_thread;
|
||||||
|
|
||||||
// Don't forget to increase this after doing changes on the savestate system
|
// Don't forget to increase this after doing changes on the savestate system
|
||||||
constexpr u32 STATE_VERSION = 126; // Last changed in PR 9348
|
constexpr u32 STATE_VERSION = 127; // Last changed in PR 9300
|
||||||
|
|
||||||
// Maps savestate versions to Dolphin versions.
|
// Maps savestate versions to Dolphin versions.
|
||||||
// Versions after 42 don't need to be added to this list,
|
// Versions after 42 don't need to be added to this list,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user