summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorPierre Bourdon <delroth@gmail.com>2021-01-06 00:51:33 +0100
committerGitHub <noreply@github.com>2021-01-06 00:51:33 +0100
commit27013e8d184a956090ec3299e2136cc6665df89f (patch)
tree49712ee54e968531d675fa9d521a8c5ed0b7b65e /Source/Core
parenteafb9de0479f6b507063fe78d6ceeeb8343ead66 (diff)
parent9b3cdd06451cf961f84adc9075b776264ba8a5ab (diff)
Merge pull request #9300 from leoetlino/ncd-wd-fixes
IOS: WD and NCD fixes
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/Core/Analytics.cpp6
-rw-r--r--Source/Core/Core/Analytics.h7
-rw-r--r--Source/Core/Core/IOS/Device.cpp3
-rw-r--r--Source/Core/Core/IOS/Device.h3
-rw-r--r--Source/Core/Core/IOS/Network/NCD/Manage.cpp48
-rw-r--r--Source/Core/Core/IOS/Network/NCD/Manage.h3
-rw-r--r--Source/Core/Core/IOS/Network/WD/Command.cpp342
-rw-r--r--Source/Core/Core/IOS/Network/WD/Command.h87
-rw-r--r--Source/Core/Core/State.cpp2
9 files changed, 463 insertions, 38 deletions
diff --git a/Source/Core/Core/Analytics.cpp b/Source/Core/Core/Analytics.cpp
index e46dcd2832..a0b4fc2175 100644
--- a/Source/Core/Core/Analytics.cpp
+++ b/Source/Core/Core/Analytics.cpp
@@ -133,7 +133,7 @@ void DolphinAnalytics::ReportGameStart()
}
// 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",
"uses-DVDLowStopLaser",
"uses-DVDLowOffset",
@@ -144,7 +144,9 @@ constexpr std::array<const char*, 12> GAME_QUIRKS_NAMES{"icache-matters",
"uses-different-partition-command",
"uses-di-interrupt-command",
"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),
"Game quirks names and enum definition are out of sync.");
diff --git a/Source/Core/Core/Analytics.h b/Source/Core/Core/Analytics.h
index 94f7eb5a78..6416257b93 100644
--- a/Source/Core/Core/Analytics.h
+++ b/Source/Core/Core/Analytics.h
@@ -54,6 +54,13 @@ enum class GameQuirk
MISMATCHED_GPU_TEXGENS_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,
};
diff --git a/Source/Core/Core/IOS/Device.cpp b/Source/Core/Core/IOS/Device.cpp
index b3324503f0..9d04dca6ba 100644
--- a/Source/Core/Core/IOS/Device.cpp
+++ b/Source/Core/Core/IOS/Device.cpp
@@ -78,7 +78,8 @@ IOCtlVRequest::IOCtlVRequest(const u32 address_) : Request(address_)
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())
return &in_vectors[index];
return &io_vectors[index - in_vectors.size()];
diff --git a/Source/Core/Core/IOS/Device.h b/Source/Core/Core/IOS/Device.h
index bffe1cdc1c..b3944eb16e 100644
--- a/Source/Core/Core/IOS/Device.h
+++ b/Source/Core/Core/IOS/Device.h
@@ -154,7 +154,10 @@ struct IOCtlVRequest final : Request
// merging them into a single std::vector would make using the first out vector more complicated.
std::vector<IOVector> in_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;
+
explicit IOCtlVRequest(u32 address);
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,
diff --git a/Source/Core/Core/IOS/Network/NCD/Manage.cpp b/Source/Core/Core/IOS/Network/NCD/Manage.cpp
index 2fcfb91beb..602a721374 100644
--- a/Source/Core/Core/IOS/Network/NCD/Manage.cpp
+++ b/Source/Core/Core/IOS/Network/NCD/Manage.cpp
@@ -20,6 +20,12 @@ NetNCDManage::NetNCDManage(Kernel& ios, const std::string& device_name) : Device
config.ReadConfig(ios.GetFS().get());
}
+void NetNCDManage::DoState(PointerWrap& p)
+{
+ Device::DoState(p);
+ p.Do(m_ipc_fd);
+}
+
IPCCommandResult NetNCDManage::IOCtlV(const IOCtlVRequest& request)
{
s32 return_value = IPC_SUCCESS;
@@ -29,11 +35,51 @@ IPCCommandResult NetNCDManage::IOCtlV(const IOCtlVRequest& request)
switch (request.request)
{
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;
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;
+ }
case IOCTLV_NCD_GETCONFIG:
INFO_LOG_FMT(IOS_NET, "NET_NCD_MANAGE: IOCTLV_NCD_GETCONFIG");
diff --git a/Source/Core/Core/IOS/Network/NCD/Manage.h b/Source/Core/Core/IOS/Network/NCD/Manage.h
index 4105bf7408..8da300e719 100644
--- a/Source/Core/Core/IOS/Network/NCD/Manage.h
+++ b/Source/Core/Core/IOS/Network/NCD/Manage.h
@@ -20,6 +20,8 @@ public:
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
+ void DoState(PointerWrap& p) override;
+
private:
enum
{
@@ -34,5 +36,6 @@ private:
};
Net::WiiNetConfig config;
+ u32 m_ipc_fd = 0;
};
} // namespace IOS::HLE::Device
diff --git a/Source/Core/Core/IOS/Network/WD/Command.cpp b/Source/Core/Core/IOS/Network/WD/Command.cpp
index 363e49b091..0f6eb0b49d 100644
--- a/Source/Core/Core/IOS/Network/WD/Command.cpp
+++ b/Source/Core/Core/IOS/Network/WD/Command.cpp
@@ -4,33 +4,330 @@
#include "Core/IOS/Network/WD/Command.h"
-#include <algorithm>
#include <cstring>
#include <string>
+#include "Common/BitSet.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/Network.h"
#include "Common/Swap.h"
-
+#include "Core/Analytics.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/Network/MACUtils.h"
namespace IOS::HLE::Device
{
+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;
}
-// 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)
+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
{
- s32 return_value = IPC_SUCCESS;
+ 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);
+}
+IPCCommandResult NetWDCommand::IOCtlV(const IOCtlVRequest& 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:
{
// Gives parameters detailing type of scan and what to match
@@ -59,25 +356,19 @@ IPCCommandResult NetWDCommand::IOCtlV(const IOCtlVRequest& request)
break;
case IOCTLV_WD_GET_INFO:
- {
- 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();
- std::copy(address.begin(), address.end(), info->mac);
- }
- break;
+ return GetInfo(request);
+
+ case IOCTLV_WD_RECV_FRAME:
+ m_recv_frame_requests.emplace_back(request.address);
+ return GetNoReply();
+
+ 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_GET_CONFIG:
case IOCTLV_WD_CHANGE_BEACON:
- case IOCTLV_WD_DISASSOC:
case IOCTLV_WD_MP_SEND_FRAME:
case IOCTLV_WD_SEND_FRAME:
case IOCTLV_WD_CALL_WL:
@@ -85,12 +376,11 @@ IPCCommandResult NetWDCommand::IOCtlV(const IOCtlVRequest& request)
case IOCTLV_WD_GET_LASTERROR:
case IOCTLV_WD_CHANGE_GAMEINFO:
case IOCTLV_WD_CHANGE_VTSF:
- case IOCTLV_WD_RECV_FRAME:
- case IOCTLV_WD_RECV_NOTIFICATION:
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
diff --git a/Source/Core/Core/IOS/Network/WD/Command.h b/Source/Core/Core/IOS/Network/WD/Command.h
index c23c42f21a..78d294036e 100644
--- a/Source/Core/Core/IOS/Network/WD/Command.h
+++ b/Source/Core/Core/IOS/Network/WD/Command.h
@@ -4,23 +4,65 @@
#pragma once
+#include <deque>
#include <string>
#include "Common/CommonTypes.h"
+#include "Common/Flag.h"
+#include "Common/Network.h"
+#include "Common/Swap.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
{
class NetWDCommand : public Device
{
public:
+ enum class ResultCode : u32
+ {
+ InvalidFd = 0x80008000,
+ IllegalParameter = 0x80008001,
+ UnavailableCommand = 0x80008002,
+ DriverError = 0x80008003,
+ };
+
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;
+ void Update() override;
+ bool IsOpened() const override { return true; }
+ void DoState(PointerWrap& p) override;
private:
enum
{
+ IOCTLV_WD_INVALID = 0x1000,
IOCTLV_WD_GET_MODE = 0x1001, // WD_GetMode
IOCTLV_WD_SET_LINKSTATE = 0x1002, // WD_SetLinkState
IOCTLV_WD_GET_LINKSTATE = 0x1003, // WD_GetLinkState
@@ -89,14 +131,45 @@ private:
struct Info
{
- u8 mac[6];
- u16 ntr_allowed_channels;
- u16 unk8;
- char country[2];
- u32 unkc;
- char wlversion[0x50];
- u8 unk[0x30];
+ Common::MACAddress mac{};
+ Common::BigEndianValue<u16> enabled_channels{};
+ Common::BigEndianValue<u16> nitro_allowed_channels{};
+ std::array<char, 4> country_code{};
+ u8 channel{};
+ bool initialised{};
+ std::array<char, 0x80> wl_version{};
};
+ static_assert(sizeof(Info) == 0x90);
#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
diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp
index 8326ae8815..4f15aea52b 100644
--- a/Source/Core/Core/State.cpp
+++ b/Source/Core/Core/State.cpp
@@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// 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.
// Versions after 42 don't need to be added to this list,