Triforce, MagCard, Bluetooth: bytes lent as pointer and size, no spans or maps

Serial devices lend their Rx bytes in place and take Tx as pointer plus
size; JVS frames unescape onto the stack and are consumed without a
scope guard. IO port inputs and outputs are plain pointers with fixed
counts. Mediaboard addresses resolve to a lent pointer and size; the
game table is a flat array. Bluetooth passthrough keeps its link keys
in a fixed sorted array, links live transfers through the header that
already fronts each buffer, and lends ACL packets as pointer and size.
AsU8Span/AsWritableU8Span are gone from BitUtils.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-07 14:55:48 +01:00
1 parent c878c0d818
commit aa9a6e9140
28 files changed
+843 -715

No files matched your search

-33
View File
@@ -9,7 +9,6 @@
#include <cstddef>
#include <cstring>
#include <initializer_list>
#include <span>
#include <type_traits>
#include <utility>
@@ -255,36 +254,4 @@ T ExpandValue(T value, size_t left_shift_amount)
(T(-ExtractBit<0>(value)) >> (BitSize<T>() - left_shift_amount));
}
// Convert a contiguous range of a trivially-copyable type to a `span<const u8>`
template <std::ranges::contiguous_range T>
requires(std::is_trivially_copyable_v<std::ranges::range_value_t<T>>)
constexpr auto AsU8Span(const T& range)
{
return std::span{reinterpret_cast<const u8*>(range.data()), range.size() * sizeof(*range.data())};
}
// Convert a contiguous range of a non-const trivially-copyable type to a `span<u8>`
template <std::ranges::contiguous_range T>
requires(std::is_trivially_copyable_v<std::ranges::range_value_t<T>>)
constexpr auto AsWritableU8Span(T& range)
{
return std::span{reinterpret_cast<u8*>(range.data()), range.size() * sizeof(*range.data())};
}
// Convert a trivially-copyable object to a `span<const u8>`
template <typename T>
requires(!std::ranges::contiguous_range<T> && std::is_trivially_copyable_v<T>)
constexpr auto AsU8Span(const T& obj)
{
return std::span{reinterpret_cast<const u8*>(std::addressof(obj)), sizeof(obj)};
}
// Convert a non-const trivially-copyable object to a `span<u8>`
template <typename T>
requires(!std::ranges::contiguous_range<T> && std::is_trivially_copyable_v<T>)
constexpr auto AsWritableU8Span(T& obj)
{
return std::span{reinterpret_cast<u8*>(std::addressof(obj)), sizeof(obj)};
}
} // namespace Common
+135 -102
View File
@@ -5,11 +5,9 @@
#include <cassert>
#include <algorithm>
#include <bit>
#include <random>
#include <string>
#include <unordered_map>
#include <fmt/format.h>
@@ -81,15 +79,15 @@ namespace AMMediaboard
static constexpr u32 TEST_OK_WORD0 = 0x54455354; // "TEST"
static constexpr u32 TEST_OK_WORD1 = 0x204F4B00; // " OK\0"
MediaBoardRange::MediaBoardRange(u32 start_, u32 size_, std::span<u8> buffer_)
: start{start_}, end{start_ + std::min(size_, u32(buffer_.size()))}, buffer{buffer_.data()},
buffer_size{buffer_.size()}
MediaBoardRange::MediaBoardRange(u32 start_, u32 size_, u8* buffer_, std::size_t buffer_size_)
: start{start_}, end{start_ + (size_ < buffer_size_ ? size_ : u32(buffer_size_))},
buffer{buffer_}, buffer_size{buffer_size_}
{
if (size_ <= buffer_.size())
if (size_ <= buffer_size_)
return;
WARN_LOG_FMT(AMMEDIABOARD,
"Invalid MediaBoardRange: start=0x{:08x}, size=0x{:06x}, buffer_size=0x{:06x}",
start, size_, buffer_.size());
start, size_, buffer_size_);
}
using Common::SEND_FLAGS;
@@ -189,32 +187,43 @@ constexpr char s_allnet_reply[] = {
"name2=asia&region_name3=export&end"};
static const MediaBoardRange s_mediaboard_ranges[] = {
{DIMMCommandVersion1, 0x40, Common::AsWritableU8Span(s_media_buffer_32)},
{DIMMCommandVersion2, 0x60, Common::AsWritableU8Span(s_media_buffer_32)},
{DIMMCommandVersion2_2, 0x220, Common::AsWritableU8Span(s_media_buffer_32)},
{NetworkCommandAddress1, 0x1040, s_network_command_buffer},
{NetworkCommandAddress2, 0x40000, s_network_command_buffer}, // TODO: Guesswork, verify this.
{NetworkBufferAddress1, 0x10000, s_network_buffer},
{NetworkBufferAddress2, 0x10000, s_network_buffer},
{NetworkBufferAddress3, 0x50000, s_network_buffer},
{NetworkBufferAddress4, 0xc0000, s_network_buffer}, // TODO: size bigger than buffer's?
{NetworkBufferAddress5, 0x10000, s_network_buffer},
{AllNetSettings, 0x8000, s_allnet_settings},
{AllNetBuffer, 0x1000, s_allnet_buffer},
{DIMMCommandVersion1, 0x40, s_media_buffer, sizeof(s_media_buffer_32)},
{DIMMCommandVersion2, 0x60, s_media_buffer, sizeof(s_media_buffer_32)},
{DIMMCommandVersion2_2, 0x220, s_media_buffer, sizeof(s_media_buffer_32)},
{NetworkCommandAddress1, 0x1040, s_network_command_buffer.data(), s_network_command_buffer.size()},
{NetworkCommandAddress2, 0x40000, s_network_command_buffer.data(), s_network_command_buffer.size()}, // TODO: Guesswork, verify this.
{NetworkBufferAddress1, 0x10000, s_network_buffer.data(), s_network_buffer.size()},
{NetworkBufferAddress2, 0x10000, s_network_buffer.data(), s_network_buffer.size()},
{NetworkBufferAddress3, 0x50000, s_network_buffer.data(), s_network_buffer.size()},
{NetworkBufferAddress4, 0xc0000, s_network_buffer.data(), s_network_buffer.size()}, // TODO: size bigger than buffer's?
{NetworkBufferAddress5, 0x10000, s_network_buffer.data(), s_network_buffer.size()},
{AllNetSettings, 0x8000, s_allnet_settings.data(), s_allnet_settings.size()},
{AllNetBuffer, 0x1000, s_allnet_buffer.data(), s_allnet_buffer.size()},
};
static std::span<u8> GetSpanForMediaboardAddress(u32 address)
// The host bytes behind a mediaboard address, lent; nullptr when unmapped.
static u8* GetMediaboardPointer(u32 address, std::size_t* size)
{
for (const auto& range : s_mediaboard_ranges)
{
if (address >= range.start && address < range.end)
return std::span{range.buffer, range.buffer_size}.subspan(address - range.start);
{
*size = range.buffer_size - (address - range.start);
return range.buffer + (address - range.start);
}
}
return {};
*size = 0;
return nullptr;
}
static const std::unordered_map<u16, GameType> s_game_map = {
struct GameMapEntry
{
u16 triforce_id;
GameType game_type;
};
static const GameMapEntry s_game_map[] = {
{0x4747, FZeroAX},
{0x4841, FZeroAXMonster},
{0x4B50, MarioKartGP},
@@ -287,29 +296,29 @@ static SOCKET GetHostSocket(GuestSocket x)
static GuestSocket GetGuestSocket(SOCKET x)
{
const auto it = std::find(std::begin(s_sockets) + FIRST_VALID_FD, std::end(s_sockets), x);
if (it == std::end(s_sockets))
for (std::size_t i = FIRST_VALID_FD; i < std::size(s_sockets); i++)
{
ERROR_LOG_FMT(AMMEDIABOARD, "GuestSocket not found. This should not happen.");
return INVALID_GUEST_SOCKET;
if (s_sockets[i] == x)
return GuestSocket(i);
}
return GuestSocket(it - std::begin(s_sockets));
ERROR_LOG_FMT(AMMEDIABOARD, "GuestSocket not found. This should not happen.");
return INVALID_GUEST_SOCKET;
}
static std::string_view GetSafeString(u32 offset, u32 max_length)
{
const auto str_span = GetSpanForMediaboardAddress(offset);
std::size_t str_size;
u8* const str_ptr = GetMediaboardPointer(offset, &str_size);
if (str_span.empty())
if (!str_size)
return {};
// Don't exceed max_length or end of buffer.
const auto adjusted_length = std::min<std::size_t>(max_length, str_span.size());
const auto length = strnlen(reinterpret_cast<char*>(str_span.data()), adjusted_length);
const std::size_t adjusted_length = max_length < str_size ? max_length : str_size;
const auto length = strnlen(reinterpret_cast<char*>(str_ptr), adjusted_length);
return {reinterpret_cast<char*>(str_span.data()), length};
return {reinterpret_cast<char*>(str_ptr), length};
}
static bool SafeCopyToEmu(Memory::MemoryManager& memory, u32 address, const u8* source,
@@ -511,7 +520,8 @@ void Init()
return;
}
const u64 length = std::min<u64>(sega_boot.GetSize(), sizeof(s_firmware));
const u64 boot_size = sega_boot.GetSize();
const u64 length = boot_size < sizeof(s_firmware) ? boot_size : u64(sizeof(s_firmware));
sega_boot.ReadBytes(s_firmware.data(), length);
s_test_menu = true;
@@ -523,19 +533,19 @@ void InitDIMM(const DiscIO::Volume& volume)
s_dimm_disc = DiscIO::CreateCachedBlobReader(volume.GetBlobReader().CopyReader());
}
static int PlatformPoll(std::span<WSAPOLLFD> pfds, std::chrono::milliseconds timeout)
static int PlatformPoll(WSAPOLLFD* pfds, std::size_t count, std::chrono::milliseconds timeout)
{
#if defined(_WIN32)
return WSAPoll(pfds.data(), ULONG(pfds.size()), INT(timeout.count()));
return WSAPoll(pfds, ULONG(count), INT(timeout.count()));
#else
return UnixUtil::RetryOnEINTR(poll, pfds.data(), pfds.size(), timeout.count());
return UnixUtil::RetryOnEINTR(poll, pfds, count, timeout.count());
#endif
}
std::optional<ParsedIPRedirection> ParseIPRedirection(std::string_view str)
{
// Everything after a space is the description.
const auto ip_pair_str = std::string_view{str.begin(), std::ranges::find(str, ' ')};
const auto ip_pair_str = str.substr(0, str.find(' '));
const auto parts = SplitStringIntoArray<2>(ip_pair_str, '=');
if (!parts.has_value())
@@ -779,7 +789,7 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu
duration_cast<std::chrono::milliseconds>(std::chrono::microseconds{s_timeouts[0]});
// TODO: Might block if timeout is too big
const int poll_result = PlatformPoll(pfds, timeout);
const int poll_result = PlatformPoll(pfds, 1, timeout);
if (poll_result < 0) [[unlikely]]
{
@@ -842,7 +852,7 @@ static GuestSocket NetDIMMAccept(GuestSocket guest_socket, u8* guest_addr_ptr,
DEBUG_LOG_FMT(AMMEDIABOARD, "NetDIMMAccept: {}({})", host_socket, int(guest_socket));
const int poll_result = PlatformPoll(pfds, timeout);
const int poll_result = PlatformPoll(pfds, 1, timeout);
if (poll_result < 0) [[unlikely]]
{
@@ -899,8 +909,9 @@ static GuestSocket NetDIMMAccept(GuestSocket guest_socket, u8* guest_addr_ptr,
Common::IPAddressToString(guest_addr.ip_address), ntohs(guest_addr.port));
}
const auto write_size =
std::min<u32>(Common::BitCastPtr<u32>(guest_addrlen_ptr), sizeof(guest_addr));
const u32 guest_addrlen = Common::BitCastPtr<u32>(guest_addrlen_ptr);
const u32 write_size =
guest_addrlen < sizeof(guest_addr) ? guest_addrlen : u32(sizeof(guest_addr));
// Write out the addr.
std::memcpy(guest_addr_ptr, &guest_addr, write_size);
@@ -978,8 +989,9 @@ static void AMMBCommandRecv(u32 parameter_offset)
const u32 off = s_media_buffer_32[parameter_offset + 1];
u32 len = s_media_buffer_32[parameter_offset + 2];
const auto data_span = GetSpanForMediaboardAddress(off);
if (data_span.size() < len)
std::size_t data_size;
u8* const data_ptr = GetMediaboardPointer(off, &data_size);
if (data_size < len)
{
ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandRecv: Bad data offset or length: off={:08x} len={}",
off, len);
@@ -987,7 +999,7 @@ static void AMMBCommandRecv(u32 parameter_offset)
}
// TODO: Might be blocking depending on the timeout (see SetTimeouts command).
const int ret = recv(fd, reinterpret_cast<char*>(data_span.data()), len, 0);
const int ret = recv(fd, reinterpret_cast<char*>(data_ptr), len, 0);
const int err = WSAGetLastError();
if (ret < 0 && err != WSAEWOULDBLOCK)
@@ -1017,8 +1029,9 @@ static void AMMBCommandSend(u32 parameter_offset)
const u32 off = s_media_buffer_32[parameter_offset + 1];
u32 len = s_media_buffer_32[parameter_offset + 2];
const auto data_span = GetSpanForMediaboardAddress(off);
if (data_span.size() < len)
std::size_t data_size;
u8* const data_ptr = GetMediaboardPointer(off, &data_size);
if (data_size < len)
{
ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandSend: Bad data offset or length: off={:08x} len={}",
off, len);
@@ -1026,7 +1039,7 @@ static void AMMBCommandSend(u32 parameter_offset)
}
// TODO: Might be blocking depending on the timeout (see SetTimeouts command).
const int ret = send(fd, reinterpret_cast<char*>(data_span.data()), len, SEND_FLAGS);
const int ret = send(fd, reinterpret_cast<char*>(data_ptr), len, SEND_FLAGS);
const int err = WSAGetLastError();
if (ret < 0 && err != WSAEWOULDBLOCK)
@@ -1088,14 +1101,15 @@ static void AMMBCommandConnect(u32 parameter_offset)
return;
}
const auto addr_span = GetSpanForMediaboardAddress(addr_offset);
if (addr_span.size() < sizeof(addr))
std::size_t addr_size;
u8* const addr_ptr = GetMediaboardPointer(addr_offset, &addr_size);
if (addr_size < sizeof(addr))
{
ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandConnect: Bad address offset: {:08x}", addr_offset);
return;
}
memcpy(&addr, addr_span.data(), sizeof(addr));
memcpy(&addr, addr_ptr, sizeof(addr));
const int ret = NetDIMMConnect(guest_socket, addr);
@@ -1109,20 +1123,22 @@ static void AMMBCommandAccept(u32 parameter_offset)
const u32 addr_off = s_media_buffer_32[parameter_offset + 1];
const u32 addrlen_off = s_media_buffer_32[parameter_offset + 2];
const auto addrlen_span = GetSpanForMediaboardAddress(addrlen_off);
std::size_t addrlen_size;
u8* const addrlen_view = GetMediaboardPointer(addrlen_off, &addrlen_size);
u8* addr_ptr{};
u8* addrlen_ptr{};
if (addrlen_span.size() >= sizeof(u32))
if (addrlen_size >= sizeof(u32))
{
addrlen_ptr = addrlen_span.data();
addrlen_ptr = addrlen_view;
const u32 addrlen = Common::BitCastPtr<u32>(addrlen_ptr);
const auto addr_span = GetSpanForMediaboardAddress(addr_off);
if (addr_span.size() >= addrlen)
std::size_t addr_size;
u8* const addr_view = GetMediaboardPointer(addr_off, &addr_size);
if (addr_size >= addrlen)
{
addr_ptr = addr_span.data();
addr_ptr = addr_view;
}
else
{
@@ -1151,14 +1167,15 @@ static void AMMBCommandBind()
return;
}
const auto addr_span = GetSpanForMediaboardAddress(addr_offset);
if (addr_span.size() < sizeof(guest_addr))
std::size_t addr_size;
u8* const addr_ptr = GetMediaboardPointer(addr_offset, &addr_size);
if (addr_size < sizeof(guest_addr))
{
ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandBind: Bad address offset: {:08x}", addr_offset);
return;
}
memcpy(&guest_addr, addr_span.data(), sizeof(guest_addr));
memcpy(&guest_addr, addr_ptr, sizeof(guest_addr));
const auto bind_result = NetDIMMBind(guest_socket, guest_addr);
@@ -1166,54 +1183,55 @@ static void AMMBCommandBind()
s_last_error = SSC_SUCCESS;
}
static void FillPollFdsFromGuestFdSet(std::span<WSAPOLLFD> pfds, u32 guest_fds_offset,
short requested_events)
static void FillPollFdsFromGuestFdSet(WSAPOLLFD* pfds, std::size_t pfd_count,
u32 guest_fds_offset, short requested_events)
{
if (guest_fds_offset == 0)
return;
GuestFdSet guest_fds;
const auto guest_fds_span = GetSpanForMediaboardAddress(guest_fds_offset);
if (guest_fds_span.size() < sizeof(guest_fds))
std::size_t guest_fds_size;
u8* const guest_fds_ptr = GetMediaboardPointer(guest_fds_offset, &guest_fds_size);
if (guest_fds_size < sizeof(guest_fds))
{
ERROR_LOG_FMT(AMMEDIABOARD_NET, "Bad FDSET offset: {:08x}", guest_fds_offset);
return;
}
std::memcpy(&guest_fds, guest_fds_span.data(), sizeof(guest_fds));
std::memcpy(&guest_fds, guest_fds_ptr, sizeof(guest_fds));
u32 index = 0;
for (auto& pfd : pfds)
for (u32 index = 0; index < pfd_count; ++index)
{
WSAPOLLFD& pfd = pfds[index];
const auto guest_socket = GuestSocket(index);
if (guest_fds.IsFdSet(guest_socket))
{
pfd.fd = GetHostSocket(guest_socket);
pfd.events |= requested_events;
}
++index;
}
}
static void WriteGuestFdSetFromPollFds(u32 guest_fds_offset, std::span<const WSAPOLLFD> fds,
short returned_events)
static void WriteGuestFdSetFromPollFds(u32 guest_fds_offset, const WSAPOLLFD* fds,
std::size_t fd_count, short returned_events)
{
if (guest_fds_offset == 0)
return;
GuestFdSet guest_fds;
const auto guest_fds_span = GetSpanForMediaboardAddress(guest_fds_offset);
if (guest_fds_span.size() < sizeof(guest_fds))
std::size_t guest_fds_size;
u8* const guest_fds_ptr = GetMediaboardPointer(guest_fds_offset, &guest_fds_size);
if (guest_fds_size < sizeof(guest_fds))
{
ERROR_LOG_FMT(AMMEDIABOARD_NET, "Bad FDSET offset: {:08x}", guest_fds_offset);
return;
}
for (const auto& fd : fds)
for (std::size_t i = 0; i < fd_count; i++)
{
const WSAPOLLFD& fd = fds[i];
if ((fd.revents & returned_events) == 0)
continue;
@@ -1224,7 +1242,7 @@ static void WriteGuestFdSetFromPollFds(u32 guest_fds_offset, std::span<const WSA
guest_fds.SetFd(guest_socket);
}
std::ranges::copy(Common::AsU8Span(guest_fds), guest_fds_span.data());
std::memcpy(guest_fds_ptr, &guest_fds, sizeof(guest_fds));
}
static void AMMBCommandSelect(u32 parameter_offset)
@@ -1236,23 +1254,25 @@ static void AMMBCommandSelect(u32 parameter_offset)
const u32 timeout_offset = s_media_buffer_32[parameter_offset + 4];
// Games sometimes send 256 (the bit size of GuestFdSet).
nfds = std::min<u32>(nfds, std::size(s_sockets));
if (nfds > std::size(s_sockets))
nfds = u32(std::size(s_sockets));
std::chrono::milliseconds timeout{-1};
if (timeout_offset != 0)
{
const auto guest_timeout_span = GetSpanForMediaboardAddress(timeout_offset);
std::size_t guest_timeout_size;
u8* const guest_timeout_ptr = GetMediaboardPointer(timeout_offset, &guest_timeout_size);
TimeVal guest_timeout;
if (guest_timeout_span.size() < sizeof(guest_timeout))
if (guest_timeout_size < sizeof(guest_timeout))
{
ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandSelect: Bad timeout offset: {:08x}",
timeout_offset);
}
else
{
std::memcpy(&guest_timeout, guest_timeout_span.data(), sizeof(guest_timeout));
std::memcpy(&guest_timeout, guest_timeout_ptr, sizeof(guest_timeout));
timeout = duration_cast<std::chrono::milliseconds>(
std::chrono::seconds(guest_timeout.seconds) +
@@ -1274,25 +1294,33 @@ static void AMMBCommandSelect(u32 parameter_offset)
// Fill with the host sockets for each guest socket less-than `nfds` in each GuestFdSet.
std::vector<WSAPOLLFD> pollfds(nfds, WSAPOLLFD{.fd = INVALID_SOCKET});
FillPollFdsFromGuestFdSet(pollfds, readfds_offset, POLLIN);
FillPollFdsFromGuestFdSet(pollfds, writefds_offset, POLLOUT);
FillPollFdsFromGuestFdSet(pollfds, exceptfds_offset, POLLPRI);
FillPollFdsFromGuestFdSet(pollfds.data(), pollfds.size(), readfds_offset, POLLIN);
FillPollFdsFromGuestFdSet(pollfds.data(), pollfds.size(), writefds_offset, POLLOUT);
FillPollFdsFromGuestFdSet(pollfds.data(), pollfds.size(), exceptfds_offset, POLLPRI);
// Erase "INVALID" entries and also entries that weren't in any GuestFdSet.
std::erase_if(pollfds, [](const WSAPOLLFD& fd) { return fd.fd == INVALID_SOCKET; });
{
std::size_t kept = 0;
for (std::size_t i = 0; i < pollfds.size(); i++)
{
if (pollfds[i].fd != INVALID_SOCKET)
pollfds[kept++] = pollfds[i];
}
pollfds.resize(kept);
}
// TODO: There may be some edge cases where
// poll's (POLLIN,POLLOUT,POLLPRI) don't map 1:1 with select's (readfds,writefds,exceptfds).
DEBUG_LOG_FMT(AMMEDIABOARD, "AMMBCommandSelect: Polling with socket count: {}", pollfds.size());
const int ret = PlatformPoll(pollfds, timeout);
const int ret = PlatformPoll(pollfds.data(), pollfds.size(), timeout);
if (ret >= 0)
{
WriteGuestFdSetFromPollFds(readfds_offset, pollfds, POLLIN);
WriteGuestFdSetFromPollFds(writefds_offset, pollfds, POLLOUT);
WriteGuestFdSetFromPollFds(exceptfds_offset, pollfds, POLLPRI);
WriteGuestFdSetFromPollFds(readfds_offset, pollfds.data(), pollfds.size(), POLLIN);
WriteGuestFdSetFromPollFds(writefds_offset, pollfds.data(), pollfds.size(), POLLOUT);
WriteGuestFdSetFromPollFds(exceptfds_offset, pollfds.data(), pollfds.size(), POLLPRI);
DEBUG_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: select result: {}", ret);
}
else
@@ -1313,8 +1341,9 @@ static void AMMBCommandSetSockOpt(u32 parameter_offset)
const u32 optval_offset = s_media_buffer_32[parameter_offset + 3];
const u32 optlen = s_media_buffer_32[parameter_offset + 4];
const auto optval_span = GetSpanForMediaboardAddress(optval_offset);
if (optval_span.size() < optlen)
std::size_t optval_size;
u8* const optval_ptr = GetMediaboardPointer(optval_offset, &optval_size);
if (optval_size < optlen)
{
ERROR_LOG_FMT(AMMEDIABOARD_NET,
"AMMBCommandSetSockOpt: Bad optval offset or length: off={:08x} len={}",
@@ -1322,7 +1351,7 @@ static void AMMBCommandSetSockOpt(u32 parameter_offset)
return;
}
const char* optval = reinterpret_cast<char*>(optval_span.data());
const char* optval = reinterpret_cast<char*>(optval_ptr);
// TODO: Ensure parameters are compatible with host's setsockopt
const int ret = setsockopt(fd, level, optname, optval, optlen);
@@ -1545,7 +1574,8 @@ u32 ExecuteCommand(std::array<u32, 3>& dicmd_buf, u32* diimm_buf, u32 address, u
// Intercept 128-byte read after GetNetworkConfig: serve network config from trinetcfg.bin
if (s_netconfig_read_pending && length == 0x80)
{
if (!GetSpanForMediaboardAddress(offset).empty())
std::size_t mediaboard_size;
if (GetMediaboardPointer(offset, &mediaboard_size))
{
s_netconfig_read_pending = false;
@@ -1585,10 +1615,11 @@ u32 ExecuteCommand(std::array<u32, 3>& dicmd_buf, u32* diimm_buf, u32 address, u
return 0;
}
if (const auto mediaboard_span = GetSpanForMediaboardAddress(offset); !mediaboard_span.empty())
std::size_t mediaboard_size;
if (u8* const mediaboard_ptr = GetMediaboardPointer(offset, &mediaboard_size))
{
DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MediaBoard ({:08x},{:08x})", offset, length);
SafeCopyToEmu(memory, address, mediaboard_span.data(), mediaboard_span.size(), 0, length);
SafeCopyToEmu(memory, address, mediaboard_ptr, mediaboard_size, 0, length);
PrintMBBuffer(address, length);
return 0;
}
@@ -2049,7 +2080,8 @@ u32 ExecuteCommand(std::array<u32, 3>& dicmd_buf, u32* diimm_buf, u32 address, u
}
}
if (const auto mediaboard_span = GetSpanForMediaboardAddress(offset); !mediaboard_span.empty())
std::size_t write_size;
if (u8* const write_ptr = GetMediaboardPointer(offset, &write_size))
{
// Persist network config to trinetcfg.bin for SET IP ADDRESS.
// The DMA Write (0x80 bytes) arrives before the corresponding command (0x0204),
@@ -2062,7 +2094,7 @@ u32 ExecuteCommand(std::array<u32, 3>& dicmd_buf, u32* diimm_buf, u32 address, u
}
DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MediaBoard ({:08x},{:08x})", offset, length);
SafeCopyFromEmu(memory, mediaboard_span.data(), address, mediaboard_span.size(), 0, length);
SafeCopyFromEmu(memory, write_ptr, address, write_size, 0, length);
PrintMBBuffer(address, length);
return 0;
}
@@ -2222,14 +2254,15 @@ u32 ExecuteCommand(std::array<u32, 3>& dicmd_buf, u32* diimm_buf, u32 address, u
NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Offset: ({:04x})", off);
NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:08x})", addr);
const auto data_span = GetSpanForMediaboardAddress(off + addr);
if (data_span.size() < 0x20)
std::size_t data_size;
u8* const data_ptr = GetMediaboardPointer(off + addr, &data_size);
if (data_size < 0x20)
{
ERROR_LOG_FMT(AMMEDIABOARD_NET, "SearchDevices: Bad data offset: {:08x}", off + addr);
break;
}
const u8* const data = data_span.data();
const u8* const data = data_ptr;
for (u32 i = 0; i < 0x20; i += 0x10)
{
@@ -2315,10 +2348,10 @@ u32 GetGameType()
triforce_id = 0x454A; // Fallback (VirtuaStriker3)
}
auto it = s_game_map.find(triforce_id);
if (it != s_game_map.end())
for (const GameMapEntry& entry : s_game_map)
{
return it->second;
if (entry.triforce_id == triforce_id)
return entry.game_type;
}
PanicAlertFmtT("Unknown game ID:{0:08x}, using default controls.", triforce_id);
+1 -2
View File
@@ -5,7 +5,6 @@
#include <array>
#include <optional>
#include <span>
#include <string_view>
#include "Common/ChunkFile.h"
@@ -75,7 +74,7 @@ struct MediaBoardRange
u8* buffer;
std::size_t buffer_size;
MediaBoardRange(u32 start_, u32 size_, std::span<u8> buffer_);
MediaBoardRange(u32 start_, u32 size_, u8* buffer_, std::size_t buffer_size_);
};
enum class AMMBDICommand : u16
@@ -7,8 +7,7 @@
#include "Core/HW/MagCard/MagneticCardReader.h"
#include <algorithm>
#include <numeric>
#include <cstring>
#include "Common/DirectIOFile.h"
#include "Common/FileUtil.h"
@@ -21,11 +20,10 @@
namespace
{
void AppendRange(auto* container, const auto& range)
void AppendBytes(std::vector<u8>* container, const void* data, std::size_t size)
{
const auto prev_size = container->size();
container->resize(prev_size + range.size());
std::ranges::copy(range, container->data() + prev_size);
const u8* const bytes = static_cast<const u8*>(data);
container->insert(container->end(), bytes, bytes + size);
}
} // namespace
@@ -191,7 +189,7 @@ void MagneticCardReader::Command_33_ReadData()
return;
}
AppendRange(&m_command_payload, *track_data);
AppendBytes(&m_command_payload, track_data->data(), track_data->size());
}
break;
}
@@ -224,7 +222,7 @@ void MagneticCardReader::Command_35_GetData()
return;
}
AppendRange(&m_command_payload, *track_data);
AppendBytes(&m_command_payload, track_data->data(), track_data->size());
}
break;
default:
@@ -263,10 +261,11 @@ void MagneticCardReader::Command_53_WriteData()
const auto track_indices = GetTrackIndicesForTrackType(track);
const auto proper_payload_size = track_indices.size() * TRACK_SIZE;
const auto payload = std::span{m_current_packet}.subspan(3);
const u8* const payload = m_current_packet.data() + 3;
const std::size_t payload_size = m_current_packet.size() - 3;
if (mode != Mode::Standard || !IsCardLoaded() || track_indices.size() == 0 ||
payload.size() != proper_payload_size)
payload_size != proper_payload_size)
{
WARN_LOG_FMT(SERIALINTERFACE_CARD, "Command_53_WriteData: Failed.");
SetPError(P::ILLEGAL_ERR);
@@ -280,7 +279,7 @@ void MagneticCardReader::Command_53_WriteData()
{
auto& track_data = m_card_data[track_index];
track_data.emplace();
std::ranges::copy(payload.subspan(track_index * TRACK_SIZE, TRACK_SIZE), track_data->data());
std::memcpy(track_data->data(), payload + track_index * TRACK_SIZE, TRACK_SIZE);
}
Core::DisplayMessage("Writing Magnetic Card", 4000);
@@ -501,7 +500,7 @@ void MagneticCardReader::Command_D0_ControlShutter()
void MagneticCardReader::Command_F0_GetVersion()
{
AppendRange(&m_command_payload, VERSION_STRING);
AppendBytes(&m_command_payload, VERSION_STRING.data(), VERSION_STRING.size());
m_status.SoftReset();
FinishCommand();
@@ -531,7 +530,8 @@ void MagneticCardReader::ReadCardFile()
ERROR_LOG_FMT(SERIALINTERFACE_CARD, "ReadCardFile: Incorrect card file size: {}", file_size);
}
const auto tracks_to_read = std::min<u32>(complete_track_count, NUM_TRACKS);
const u32 tracks_to_read =
complete_track_count < NUM_TRACKS ? u32(complete_track_count) : u32(NUM_TRACKS);
for (u32 track_num = 0; track_num != tracks_to_read; ++track_num)
{
auto& track_data = m_card_data[track_num];
@@ -602,11 +602,12 @@ void MagneticCardReader::Update()
{
while (true)
{
const auto read = GetRxByteSpan();
if (read.empty())
const u8* const read = GetRxBytes();
const std::size_t read_size = GetRxByteCount();
if (!read_size)
break;
const u8 first_byte = read.front();
const u8 first_byte = read[0];
if (first_byte == ENQUIRY)
{
// ENQUIRY
@@ -639,14 +640,14 @@ void MagneticCardReader::Update()
// This is a command packet. The next byte provides the size.
// Upon read they ACK or NACK and start processing of a command.
if (read.size() < 2)
if (read_size < 2)
break; // Wait for more data.
const std::size_t packet_size = read[1];
if (packet_size > read.size() - 2)
if (packet_size > read_size - 2)
break; // Wait for more data.
if (ReceivePacket(read.subspan(2, packet_size)))
if (ReceivePacket(read + 2, packet_size))
{
DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Writing ACK");
WriteTxByte(ACK);
@@ -661,17 +662,17 @@ void MagneticCardReader::Update()
}
}
bool MagneticCardReader::ReceivePacket(std::span<const u8> packet)
bool MagneticCardReader::ReceivePacket(const u8* packet, std::size_t size)
{
DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "ReceivePacket: {}", HexDump(packet.data(), packet.size()));
DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "ReceivePacket: {}", HexDump(packet, size));
if (packet.size() < 6)
if (size < 6)
{
WARN_LOG_FMT(SERIALINTERFACE_CARD, "ReceivePacket: Bad packet size!");
return false;
}
if (packet[packet.size() - 2] != END_OF_TEXT)
if (packet[size - 2] != END_OF_TEXT)
{
WARN_LOG_FMT(SERIALINTERFACE_CARD, "ReceivePacket: Missing END_OF_TEXT!");
return false;
@@ -679,7 +680,10 @@ bool MagneticCardReader::ReceivePacket(std::span<const u8> packet)
// The final byte is an XOR checksum of all previous bytes,
// including the count byte that we already chopped off.
if (std::accumulate(packet.begin(), packet.end(), u8(packet.size()), std::bit_xor{}) != 0)
u8 checksum = u8(size);
for (std::size_t i = 0; i < size; i++)
checksum ^= packet[i];
if (checksum != 0)
{
WARN_LOG_FMT(SERIALINTERFACE_CARD, "ReceivePacket: Bad checksum!");
return false;
@@ -708,8 +712,7 @@ bool MagneticCardReader::ReceivePacket(std::span<const u8> packet)
// Copy the payload.
// We're skipping the command number and the host's 3 status bytes,
// and dropping the END_OF_TEXT and checksum.
m_current_packet.resize(packet.size() - 6);
std::ranges::copy(packet.subspan(4, m_current_packet.size()), m_current_packet.data());
m_current_packet.assign(packet + 4, packet + size - 2);
// TODO: Is this sensible for every command ?
m_status.SoftReset();
@@ -861,23 +864,21 @@ void MagneticCardReader::BuildPacket()
WriteTxByte(START_OF_TEXT); // Not included in the checksum.
const auto lead_in = std::to_array<u8>({
payload_size,
GetCurrentCommand(),
GetPositionValue(),
u8(m_status.p),
u8(m_status.s),
});
WriteTxBytes(lead_in);
const u8 lead_in[5] = {
payload_size, GetCurrentCommand(), GetPositionValue(), u8(m_status.p), u8(m_status.s),
};
WriteTxBytes(lead_in, sizeof(lead_in));
WriteTxBytes(m_command_payload);
WriteTxBytes(m_command_payload.data(), m_command_payload.size());
WriteTxByte(END_OF_TEXT);
// Checksum is XOR of bytes after START_OF_TEXT.
const u8 packet_checksum = std::accumulate(lead_in.begin(), lead_in.end(), u8{}, std::bit_xor{}) ^
std::accumulate(m_command_payload.begin(), m_command_payload.end(),
END_OF_TEXT, std::bit_xor{});
u8 packet_checksum = END_OF_TEXT;
for (const u8 b : lead_in)
packet_checksum ^= b;
for (const u8 b : m_command_payload)
packet_checksum ^= b;
WriteTxByte(packet_checksum);
}
@@ -9,7 +9,6 @@
#include <array>
#include <optional>
#include <span>
#include <string>
#include <vector>
@@ -111,7 +110,7 @@ protected:
ResetSpecifications = 0x32,
};
bool ReceivePacket(std::span<const u8> packet);
bool ReceivePacket(const u8* packet, std::size_t size);
void BuildPacket();
void StepStateMachine(DT elapsed_time);
+18 -19
View File
@@ -3,8 +3,7 @@
#include "Core/HW/SI/SI_DeviceAMBaseboard.h"
#include <algorithm>
#include <numeric>
#include <cstring>
#include <string>
#include <fmt/format.h>
@@ -244,8 +243,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
{
case GCAMCommand::StatusSwitches:
{
const auto status_switches = m_io_ports.GetStatusSwitches();
const auto out_length = u8(status_switches.size());
const u8* const status_switches = m_io_ports.GetStatusSwitches();
const auto out_length = u8(Triforce::IOPorts::STATUS_SWITCH_BYTE_COUNT);
if (!validate_data_in_out(1, 2 + out_length, "StatusSwitches"))
break;
@@ -257,7 +256,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
data_out[data_offset++] = gcam_command;
data_out[data_offset++] = out_length;
std::ranges::copy(status_switches, data_out.data() + data_offset);
std::memcpy(data_out.data() + data_offset, status_switches, out_length);
data_offset += out_length;
break;
@@ -415,7 +414,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
if (m_serial_device_a != nullptr)
{
m_serial_device_a->WriteRxBytes({data_in, in_length});
m_serial_device_a->WriteRxBytes(data_in, in_length);
}
data_in += in_length;
@@ -432,7 +431,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
if (m_serial_device_b != nullptr)
{
m_serial_device_b->WriteRxBytes({data_in, in_length});
m_serial_device_b->WriteRxBytes(data_in, in_length);
}
data_in += in_length;
@@ -448,7 +447,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
if (!validate_data_in_out(in_length, 0, "JVSIO"))
break;
m_jvs_io_board->WriteRxBytes({data_in, in_length});
m_jvs_io_board->WriteRxBytes(data_in, in_length);
data_in += in_length;
m_jvs_io_board->Update();
@@ -466,9 +465,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
data_out[data_offset++] = gcam_command;
data_out[data_offset++] = u8(out_length);
const auto out_span = std::span{data_out}.subspan(data_offset, out_length);
m_jvs_io_board->TakeTxBytes(out_span);
m_jvs_io_board->TakeTxBytes(data_out.data() + data_offset, out_length);
data_offset += out_length;
break;
@@ -506,8 +503,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
serial_device->Update();
const auto out_length =
std::min(u32(serial_device->GetTxByteCount()), SERIAL_PORT_MAX_READ_SIZE);
const u32 tx_count = u32(serial_device->GetTxByteCount());
const u32 out_length =
tx_count < SERIAL_PORT_MAX_READ_SIZE ? tx_count : SERIAL_PORT_MAX_READ_SIZE;
if (out_length == 0)
return;
@@ -520,9 +518,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
data_out[data_offset++] = u8(cmd);
data_out[data_offset++] = u8(out_length);
const auto out_span = std::span{data_out}.subspan(data_offset, out_length);
serial_device->TakeTxBytes(out_span);
serial_device->TakeTxBytes(data_out.data() + data_offset, out_length);
data_offset += out_length;
};
@@ -534,10 +530,13 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
data_out[1] = data_offset - response_header_size; // Length
// Zero-fill the rest of the buffer.
std::fill(data_out.data() + data_offset, data_out.data() + usable_out_size, 0x00);
std::memset(data_out.data() + data_offset, 0x00, usable_out_size - data_offset);
// Compute the checksum (bitwise-xor of additive sum).
const u8 checksum = ~std::accumulate(data_out.data(), data_out.data() + data_offset, u8{});
u8 sum = 0;
for (u32 i = 0; i < data_offset; i++)
sum += data_out[i];
const u8 checksum = u8(~sum);
data_out.back() = checksum;
DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "Command response: {}",
@@ -553,7 +552,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
if (!buffer_has_data)
return 0;
std::ranges::copy(response_buffer, buffer);
std::memcpy(buffer, response_buffer.data(), response_buffer.size());
return int(response_buffer.size());
}
default:
+51 -39
View File
@@ -14,7 +14,6 @@
#include "Common/FileUtil.h"
#include "Common/JsonUtil.h"
#include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
#include "Common/Swap.h"
#include "Core/ConfigManager.h"
@@ -156,12 +155,21 @@ namespace Triforce
void DeckReader::Update()
{
Common::ScopeGuard update_ic_card_reader{[this] {
// Update and take any response data from the IC card reader.
m_ic_card_reader.Update();
PassThroughTxBytes(m_ic_card_reader);
}};
HandleCommand();
// Update and take any response data from the IC card reader.
m_ic_card_reader.Update();
PassThroughTxBytes(m_ic_card_reader);
}
void DeckReader::WriteTxU32BE(u32 value)
{
const u8 bytes[4] = {u8(value >> 24), u8(value >> 16), u8(value >> 8), u8(value)};
WriteTxBytes(bytes, sizeof(bytes));
}
void DeckReader::HandleCommand()
{
// All Deck Reader commands are exactly one byte (except FirmwareUpdate).
// Responses must be the correct length, which varies by command.
// Only the ReadCard response actually includes some kind of length field.
@@ -177,12 +185,13 @@ void DeckReader::Update()
return;
}
const auto input_span = GetRxByteSpan();
const u8* const input = GetRxBytes();
const std::size_t input_size = GetRxByteCount();
if (input_span.empty())
if (!input_size)
return; // Wait for more data.
const u8 cd_reader_command = input_span.front();
const u8 cd_reader_command = input[0];
if (cd_reader_command == 0x00)
{
@@ -191,16 +200,16 @@ void DeckReader::Update()
// How does the actual hardware deal with this ?
// Does the Deck Reader really parse the IC Card Reader commands ?
if (input_span.size() < 4)
if (input_size < 4)
return; // Wait for more data.
const u16 input_payload_size = Common::swap16(input_span.data() + 2);
const u16 input_payload_size = Common::swap16(input + 2);
const u32 total_request_size = input_payload_size + 5u;
if (input_span.size() >= total_request_size)
if (input_size >= total_request_size)
{
// Forward the data to the attached IC Card Reader.
m_ic_card_reader.WriteRxBytes(input_span.first(total_request_size));
m_ic_card_reader.WriteRxBytes(input, total_request_size);
ConsumeRxBytes(total_request_size);
}
@@ -208,7 +217,8 @@ void DeckReader::Update()
}
// Write header.
WriteTxBytes(std::array<u8, 2>{0xaa, cd_reader_command});
WriteTxByte(0xaa);
WriteTxByte(cd_reader_command);
switch (CDReaderCommand(cd_reader_command))
{
@@ -236,10 +246,12 @@ void DeckReader::Update()
{
INFO_LOG_FMT(SERIALINTERFACE_CARD, "ProgramVersion");
WriteTxBytes(Common::AsU8Span((CDR_PROGRAM_VERSION)));
WriteTxBytes(reinterpret_cast<const u8*>(CDR_PROGRAM_VERSION.data()),
CDR_PROGRAM_VERSION.size());
// Pad with zeros.
WriteTxBytes(std::array<u8, EXPECTED_VERSION_STR_LENGTH - CDR_PROGRAM_VERSION.size()>{});
for (std::size_t i = CDR_PROGRAM_VERSION.size(); i < EXPECTED_VERSION_STR_LENGTH; i++)
WriteTxByte(0x00);
break;
}
@@ -247,10 +259,11 @@ void DeckReader::Update()
{
INFO_LOG_FMT(SERIALINTERFACE_CARD, "BootVersion");
WriteTxBytes(Common::AsU8Span(CDR_BOOT_VERSION));
WriteTxBytes(reinterpret_cast<const u8*>(CDR_BOOT_VERSION.data()), CDR_BOOT_VERSION.size());
// Pad with zeros.
WriteTxBytes(std::array<u8, EXPECTED_VERSION_STR_LENGTH - CDR_BOOT_VERSION.size()>{});
for (std::size_t i = CDR_BOOT_VERSION.size(); i < EXPECTED_VERSION_STR_LENGTH; i++)
WriteTxByte(0x00);
break;
}
@@ -259,8 +272,7 @@ void DeckReader::Update()
INFO_LOG_FMT(SERIALINTERFACE_CARD, "ProgramChecksum");
// Avalon seems to not care about the actual value.
Common::BigEndianValue<u32> fake_checksum{0x89abcdef};
WriteTxBytes(Common::AsU8Span(fake_checksum));
WriteTxU32BE(0x89abcdef);
break;
}
@@ -269,8 +281,7 @@ void DeckReader::Update()
INFO_LOG_FMT(SERIALINTERFACE_CARD, "BootChecksum");
// Avalon seems to not care about the actual value.
Common::BigEndianValue<u32> fake_checksum{0x01234567};
WriteTxBytes(Common::AsU8Span(fake_checksum));
WriteTxU32BE(0x01234567);
break;
}
@@ -280,8 +291,8 @@ void DeckReader::Update()
// FYI: If `header[1] != 0x68` the game inspects some bits and 9 bytes. Some error state ?
std::array<u8, 10> response{};
WriteTxBytes(response);
for (int i = 0; i < 10; i++)
WriteTxByte(0x00);
break;
}
@@ -289,8 +300,7 @@ void DeckReader::Update()
{
INFO_LOG_FMT(SERIALINTERFACE_CARD, "ShutterGet");
Common::BigEndianValue<u32> shutter_time{SHUTTER_TIME};
WriteTxBytes(Common::AsU8Span(shutter_time));
WriteTxU32BE(SHUTTER_TIME);
break;
}
@@ -298,8 +308,7 @@ void DeckReader::Update()
{
INFO_LOG_FMT(SERIALINTERFACE_CARD, "ShutterAuto");
Common::BigEndianValue<u32> shutter_time{SHUTTER_TIME};
WriteTxBytes(Common::AsU8Span(shutter_time));
WriteTxU32BE(SHUTTER_TIME);
break;
}
@@ -338,19 +347,22 @@ void DeckReader::Update()
if (is_deck_empty)
{
// I think this means that there are no cards or maybe "not ready".
WriteTxBytes(std::array<u8, 2>{0xaa, 0x52});
WriteTxByte(0xaa);
WriteTxByte(0x52);
}
else
{
// We've had one, yes, but what about second header ?
WriteTxBytes(std::array<u8, 2>{0xaa, u8(CDReaderCommand::ReadCard)});
WriteTxByte(0xaa);
WriteTxByte(u8(CDReaderCommand::ReadCard));
if (const auto deck = LoadCardDeckFromFile())
{
// What happens with more than 30 cards ?
WriteTxByte(u8(deck->size() * sizeof(CardIdentifier)));
WriteTxBytes(Common::AsU8Span(*deck));
WriteTxBytes(reinterpret_cast<const u8*>(deck->data()),
deck->size() * sizeof(CardIdentifier));
}
else
{
@@ -402,16 +414,17 @@ void DeckReader::Update()
void DeckReader::HandleFirmwareUpdate()
{
const auto input_span = GetRxByteSpan();
const u8* const input = GetRxBytes();
const std::size_t input_size = GetRxByteCount();
// No idea how the real hardware works.
// There doesn't seem to be any sort of size field so.. here's a timeout.
if (input_span.empty())
if (!input_size)
{
if (--m_firmware_update_timeout == 0)
{
constexpr std::array<u8, 3> fw_update_done{0xaa, u8(CDReaderCommand::FirmwareUpdate), 0x00};
WriteTxBytes(fw_update_done);
static const u8 fw_update_done[3] = {0xaa, u8(CDReaderCommand::FirmwareUpdate), 0x00};
WriteTxBytes(fw_update_done, sizeof(fw_update_done));
INFO_LOG_FMT(SERIALINTERFACE_CARD, "Fake FirmwareUpdate done.");
@@ -422,14 +435,13 @@ void DeckReader::HandleFirmwareUpdate()
{
if (m_firmware_dump_file.IsOpen())
{
if (!m_firmware_dump_file.Write(input_span.data(), input_span.size()))
if (!m_firmware_dump_file.Write(input, input_size))
{
ERROR_LOG_FMT(SERIALINTERFACE_CARD, "Failed to write {} bytes to firmware.",
input_span.size());
ERROR_LOG_FMT(SERIALINTERFACE_CARD, "Failed to write {} bytes to firmware.", input_size);
}
}
ConsumeRxBytes(input_span.size());
ConsumeRxBytes(input_size);
m_firmware_update_timeout = FIRMWARE_UPDATE_TIMEOUT;
}
}
@@ -31,7 +31,9 @@ private:
// Unless FirmwareUpdate temporarily cuts off the IC Card Reader ?
ICCardReader m_ic_card_reader{0};
void HandleCommand();
void HandleFirmwareUpdate();
void WriteTxU32BE(u32 value);
u8 m_firmware_update_timeout = 0;
+14 -14
View File
@@ -3,13 +3,13 @@
#include "Core/HW/Triforce/FZeroAX.h"
#include <numeric>
#include <fmt/ranges.h>
#include "Common/BitUtils.h"
#include "Common/ChunkFile.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/Swap.h"
#include "Core/HW/GCPad.h"
@@ -81,8 +81,7 @@ void FZeroAXCommon_IOAdapter::Update()
analog_inputs[6] = IOPorts::NEUTRAL_ANALOG_VALUE;
}
void FZeroAXCommon_IOAdapter::HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared)
void FZeroAXCommon_IOAdapter::HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared)
{
const u8 bits_changed_0 = bits_set[0] | bits_cleared[0];
@@ -111,7 +110,7 @@ void FZeroAXDeluxe_IOAdapter::Update()
const auto generic_outputs = io_ports->GetGenericOutputs();
// Not fully understood trickery to satisfy the game's initialization sequence.
const u16 seat_state = Common::swap16(generic_outputs.data() + 1) >> 2;
const u16 seat_state = Common::swap16(generic_outputs + 1) >> 2;
switch (seat_state)
{
case 0x70:
@@ -186,21 +185,20 @@ void FZeroAXDeluxe_IOAdapter::DoState(PointerWrap& p)
void FZeroAXSteeringWheel::Update()
{
constexpr std::size_t REQUEST_SIZE = 4;
std::size_t rx_position = 0;
while (true)
{
const auto rx_span = GetRxByteSpan().subspan(rx_position);
if (rx_span.size() < REQUEST_SIZE)
if (GetRxByteCount() - rx_position < REQUEST_SIZE)
break; // Wait for more data.
const auto request = rx_span.first<REQUEST_SIZE>();
const u8* const request = GetRxBytes() + rx_position;
// The first byte is XOR'd with 0x80.
// The last byte is an XOR of the previous bytes.
if (std::accumulate(request.begin(), request.end(), u8{0x80}, std::bit_xor{}) != 0)
u8 checksum = 0x80;
for (std::size_t i = 0; i < REQUEST_SIZE; i++)
checksum ^= request[i];
if (checksum != 0)
{
WARN_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: Bad checksum!");
++rx_position;
@@ -214,9 +212,10 @@ void FZeroAXSteeringWheel::Update()
ConsumeRxBytes(rx_position);
}
void FZeroAXSteeringWheel::ProcessRequest(std::span<const u8> request)
void FZeroAXSteeringWheel::ProcessRequest(const u8* request)
{
DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: Request: {:02x}", fmt::join(request, " "));
DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: Request: {}",
Common::BytesToHexString(request, REQUEST_SIZE));
// The first byte is XOR'd with 0x80.
const u8 cmd = request[0] ^ 0x80u;
@@ -258,7 +257,8 @@ void FZeroAXSteeringWheel::ProcessRequest(std::span<const u8> request)
}
// Simple 4 byte response.
WriteTxBytes(std::array<u8, 4>{});
static const u8 response[4] = {};
WriteTxBytes(response, sizeof(response));
}
bool FZeroAXSteeringWheel::IsInitializing() const
+4 -3
View File
@@ -22,7 +22,9 @@ public:
void DoState(PointerWrap&) override;
private:
void ProcessRequest(std::span<const u8>);
static constexpr std::size_t REQUEST_SIZE = 4;
void ProcessRequest(const u8* request);
u8 m_init_state = 0;
@@ -41,8 +43,7 @@ public:
void Update() override;
protected:
void HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared) override;
void HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared) override;
private:
FZeroAXSteeringWheel* const m_steering_wheel;
+110 -98
View File
@@ -3,7 +3,7 @@
#include "Core/HW/Triforce/ICCardReader.h"
#include <numeric>
#include <cstring>
#include <fmt/ranges.h>
@@ -12,7 +12,7 @@
#include "Common/DirectIOFile.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
#include "Common/StringUtil.h"
#include "Common/Swap.h"
#include "Core/ConfigManager.h"
@@ -27,12 +27,15 @@ constexpr u16 CARD_SESSION_BASE = 0x2300;
// The serial number is located here. Games seem to expect it to be read-only.
constexpr u32 READ_ONLY_PAGE_INDEX = 4;
constexpr u8 CheckSumXOR(std::span<const u8> data)
u8 CheckSumXOR(const u8* data, std::size_t size)
{
return std::accumulate(data.data(), data.data() + data.size(), u8{}, std::bit_xor{});
u8 checksum = 0;
for (std::size_t i = 0; i < size; i++)
checksum ^= data[i];
return checksum;
}
bool LoadCardData(const std::string& filename, std::span<u8> data)
bool LoadCardData(const std::string& filename, u8* data, std::size_t size)
{
File::DirectIOFile file{filename, File::AccessMode::Read};
@@ -40,14 +43,14 @@ bool LoadCardData(const std::string& filename, std::span<u8> data)
return false;
const auto file_size = file.GetSize();
if (file_size > data.size())
if (file_size > size)
{
WARN_LOG_FMT(SERIALINTERFACE_CARD, "Unexpected size of {} for file: {}", file_size, filename);
}
const u64 read_size = file_size < data.size() ? file_size : u64(data.size());
const u64 read_size = file_size < size ? file_size : u64(size);
if (!file.Read(data.data(), read_size))
if (!file.Read(data, read_size))
{
ERROR_LOG_FMT(SERIALINTERFACE_CARD, "Failed to read from file: {}", filename);
return false;
@@ -57,10 +60,9 @@ bool LoadCardData(const std::string& filename, std::span<u8> data)
return true;
}
void SanitizeSerialNumber(std::span<u8> data)
// data: the 8-byte serial number
void SanitizeSerialNumber(u8* data)
{
DEBUG_ASSERT(data.size() == 8);
// This seems to be treated as a 16-digit BCD value.
// Avalon: 9571 264_ XXXX XXXX
@@ -85,8 +87,8 @@ void SanitizeSerialNumber(std::span<u8> data)
data[3] = 0x40;
u32 checksum = 0;
for (auto bcd_pair : data.subspan(4))
checksum += (bcd_pair & 0x0f) + (bcd_pair >> 4);
for (int i = 4; i < 8; i++)
checksum += (data[i] & 0x0f) + (data[i] >> 4);
data[3] |= (checksum % 10);
break;
@@ -109,16 +111,16 @@ void SanitizeSerialNumber(std::span<u8> data)
}
}
void InitializeCardData(std::span<u8> data)
void InitializeCardData(u8* data)
{
SanitizeSerialNumber(data.subspan(READ_ONLY_PAGE_INDEX * 8, 8));
SanitizeSerialNumber(data + READ_ONLY_PAGE_INDEX * 8);
constexpr u32 use_count_offset = 0x28;
constexpr u16 use_count = 0;
// The use count seems to be a big endian count down from 0xffff.
Common::WriteSwap16(data.data() + use_count_offset, 0xffff - use_count);
Common::WriteSwap16(data + use_count_offset, 0xffff - use_count);
}
// TODO: I'm not so sure that this is really a device status.
@@ -191,8 +193,9 @@ void ICCardReader::Update()
if (m_eject_timer != 0)
--m_eject_timer;
const auto rx_byte_span = GetRxByteSpan();
if (rx_byte_span.size() < 4)
const u8* const rx = GetRxBytes();
const std::size_t rx_size = GetRxByteCount();
if (rx_size < 4)
return; // Wait for more data.
// For reference:
@@ -205,24 +208,23 @@ void ICCardReader::Update()
// u8 checksum; // XOR of all previous bytes.
// };
const u16 input_payload_size = Common::swap16(rx_byte_span.data() + 2);
const u16 input_payload_size = Common::swap16(rx + 2);
// 4 header bytes + 1 checksum byte
const u32 entire_request_size = input_payload_size + 5u;
if (rx_byte_span.size() < entire_request_size)
if (rx_size < entire_request_size)
return; // Wait for more data.
if (CheckSumXOR(rx_byte_span.first(entire_request_size)) != 0)
if (CheckSumXOR(rx, entire_request_size) != 0)
{
ERROR_LOG_FMT(SERIALINTERFACE_CARD, "Bad checksum!");
ConsumeRxBytes(1);
return;
}
Common::ScopeGuard consume_request{[&] { ConsumeRxBytes(entire_request_size); }};
const u8 card_command = rx_byte_span[1];
const auto input_payload = rx_byte_span.subspan(4, input_payload_size);
// Lent from the Rx buffer; the request is consumed after the reply is sent.
const u8 card_command = rx[1];
const u8* const input_payload = rx + 4;
u16 status_code = 0x00;
@@ -265,7 +267,8 @@ void ICCardReader::Update()
std::array<u8, 8> small_reply_payload{};
// Will be later assigned to `small_reply_payload` or some region of the card data itself.
std::span<const u8> reply_payload_span;
const u8* reply_payload = nullptr;
std::size_t reply_payload_size = 0;
// FYI: Many of the big-endian u16 parameters may just be u8 values.
// Avalon writes single bytes, but at odd addresses, so u16 seems like the intention.
@@ -297,7 +300,8 @@ void ICCardReader::Update()
if (!validate_input_payload_size(8))
break;
INFO_LOG_FMT(SERIALINTERFACE_CARD, "SetBaudrate: {:02x}", fmt::join(input_payload, " "));
INFO_LOG_FMT(SERIALINTERFACE_CARD, "SetBaudrate: {}",
Common::BytesToHexString(input_payload, input_payload_size));
break;
}
case ICCardCommand::FieldOn:
@@ -342,7 +346,7 @@ void ICCardReader::Update()
// Avalon sends 0 or 1 here, not sure what the meaning is.
// This is maybe the "Time Slot Number" (TSN)? 0=1-slot, 1=2-slots, etc.
const u16 unknown_parameter = Common::swap16(input_payload.data() + 0);
const u16 unknown_parameter = Common::swap16(input_payload + 0);
INFO_LOG_FMT(SERIALINTERFACE_CARD, "InsertCheck: {}", unknown_parameter);
@@ -367,17 +371,25 @@ void ICCardReader::Update()
break;
// Avalon's logic optionally sets param0=0x20 and 8 memcpy'd bytes but never seems to do it.
const u16 unknown_param0 = Common::swap16(input_payload.data() + 0);
const auto unknown_param1 = input_payload.subspan(2, 8);
const u16 unknown_param0 = Common::swap16(input_payload + 0);
INFO_LOG_FMT(SERIALINTERFACE_CARD, "AntiCollision: unk0:{:04x} unk1:{:02x}", unknown_param0,
fmt::join(unknown_param1, " "));
INFO_LOG_FMT(SERIALINTERFACE_CARD, "AntiCollision: unk0:{:04x} unk1:{}", unknown_param0,
Common::BytesToHexString(input_payload + 2, 8));
const auto first_non_halted_card = std::ranges::find_if_not(m_ic_cards, &ICCard::IsHalted);
if (first_non_halted_card != m_ic_cards.end())
const ICCard* first_non_halted_card = nullptr;
for (const auto& card : m_ic_cards)
{
reply_payload_span = (*first_non_halted_card)->GetUID();
if (!card->IsHalted())
{
first_non_halted_card = card.get();
break;
}
}
if (first_non_halted_card)
{
reply_payload = first_non_halted_card->GetUID().data();
reply_payload_size = first_non_halted_card->GetUID().size();
size_t active_cards = 0;
for (const auto& card : m_ic_cards)
@@ -400,19 +412,24 @@ void ICCardReader::Update()
break;
// FYI: The request includes the UID that we produced in `AntiCollision`.
const auto card_uid = input_payload.first(8);
const u8* const card_uid = input_payload;
INFO_LOG_FMT(SERIALINTERFACE_CARD, "SelectCard: {:02x}", fmt::join(card_uid, " "));
INFO_LOG_FMT(SERIALINTERFACE_CARD, "SelectCard: {}", Common::BytesToHexString(card_uid, 8));
const auto found_card = std::ranges::find_if(
m_ic_cards, std::bind_front(std::ranges::equal, card_uid), &ICCard::GetUID);
if (found_card != m_ic_cards.end())
std::size_t found_card = 0;
while (found_card < m_ic_cards.size() &&
std::memcmp(m_ic_cards[found_card]->GetUID().data(), card_uid, 8))
{
const u16 card_session = CARD_SESSION_BASE | u16(found_card - m_ic_cards.begin());
found_card++;
}
if (found_card < m_ic_cards.size())
{
const u16 card_session = CARD_SESSION_BASE | u16(found_card);
Common::WriteSwap16(small_reply_payload.data(), card_session);
reply_payload_span = small_reply_payload;
reply_payload = small_reply_payload.data();
reply_payload_size = small_reply_payload.size();
// Avalon's tests specifically want 0x00.
status_code = 0x00;
@@ -435,8 +452,8 @@ void ICCardReader::Update()
if (!validate_input_payload_size(8))
break;
const u16 card_session = Common::swap16(input_payload.data() + 0);
const u16 page = Common::swap16(input_payload.data() + 2) & IC_PAGE_INDEX_MASK;
const u16 card_session = Common::swap16(input_payload + 0);
const u16 page = Common::swap16(input_payload + 2) & IC_PAGE_INDEX_MASK;
INFO_LOG_FMT(SERIALINTERFACE_CARD, "ReadPage: session:{:04x} page:{}", card_session, page);
@@ -444,15 +461,9 @@ void ICCardReader::Update()
if (!ic_card)
break;
const auto read_span = ic_card->ReadData(page, 1);
if (read_span.empty())
{
reply_payload = ic_card->ReadData(page, 1, &reply_payload_size);
if (!reply_payload)
status_code = HardwareUntestedErrorCode;
}
else
{
reply_payload_span = read_span;
}
break;
}
@@ -461,10 +472,10 @@ void ICCardReader::Update()
if (!validate_input_payload_size(16))
break;
const u16 card_session = Common::swap16(input_payload.data() + 0);
const u16 card_session = Common::swap16(input_payload + 0);
// Avalon specifically puts a zero here.
const u16 unknown = Common::swap16(input_payload.data() + 2);
const u16 page = Common::swap16(input_payload.data() + 4) & IC_PAGE_INDEX_MASK;
const u16 unknown = Common::swap16(input_payload + 2);
const u16 page = Common::swap16(input_payload + 4) & IC_PAGE_INDEX_MASK;
INFO_LOG_FMT(SERIALINTERFACE_CARD, "WritePage: session:{:04x} unk:{} page:{}", card_session,
unknown, page);
@@ -473,7 +484,7 @@ void ICCardReader::Update()
if (!ic_card)
break;
if (!ic_card->WriteData(page, input_payload.subspan(6, IC_PAGE_SIZE)))
if (!ic_card->WriteData(page, input_payload + 6, IC_PAGE_SIZE))
{
status_code = HardwareUntestedErrorCode;
}
@@ -485,10 +496,10 @@ void ICCardReader::Update()
if (!validate_input_payload_size(8))
break;
const u16 card_session = Common::swap16(input_payload.data() + 0);
const u16 card_session = Common::swap16(input_payload + 0);
// Avalon seems to always sends 5 and 1. Guessing on the meaning and behavior.
const u16 page = Common::swap16(input_payload.data() + 2) & IC_PAGE_INDEX_MASK;
const u16 amount = Common::swap16(input_payload.data() + 4);
const u16 page = Common::swap16(input_payload + 2) & IC_PAGE_INDEX_MASK;
const u16 amount = Common::swap16(input_payload + 4);
INFO_LOG_FMT(SERIALINTERFACE_CARD, "DecreaseUseCount: session:{:04x}, page:{} amount:{}",
card_session, page, amount);
@@ -500,7 +511,8 @@ void ICCardReader::Update()
const u16 new_count = ic_card->DecreaseUseCount(page, amount);
Common::WriteSwap16(small_reply_payload.data(), new_count);
reply_payload_span = small_reply_payload;
reply_payload = small_reply_payload.data();
reply_payload_size = small_reply_payload.size();
break;
}
@@ -509,7 +521,7 @@ void ICCardReader::Update()
if (!validate_input_payload_size(8))
break;
const u16 card_session = Common::swap16(input_payload.data() + 0);
const u16 card_session = Common::swap16(input_payload + 0);
INFO_LOG_FMT(SERIALINTERFACE_CARD, "HaltCard: session:{:04x}", card_session);
@@ -526,9 +538,9 @@ void ICCardReader::Update()
if (!validate_input_payload_size(8))
break;
const u16 card_session = Common::swap16(input_payload.data() + 0);
const u16 page = Common::swap16(input_payload.data() + 2) & IC_PAGE_INDEX_MASK;
const u16 page_count = Common::swap16(input_payload.data() + 4);
const u16 card_session = Common::swap16(input_payload + 0);
const u16 page = Common::swap16(input_payload + 2) & IC_PAGE_INDEX_MASK;
const u16 page_count = Common::swap16(input_payload + 4);
INFO_LOG_FMT(SERIALINTERFACE_CARD, "ReadPages: session:{:04x} page:{} page_count:{}",
card_session, page, page_count);
@@ -537,15 +549,9 @@ void ICCardReader::Update()
if (!ic_card)
break;
const auto read_span = ic_card->ReadData(page, page_count);
if (read_span.empty())
{
reply_payload = ic_card->ReadData(page, page_count, &reply_payload_size);
if (!reply_payload)
status_code = HardwareUntestedErrorCode;
}
else
{
reply_payload_span = read_span;
}
break;
}
@@ -558,9 +564,9 @@ void ICCardReader::Update()
break;
}
const u16 card_session = Common::swap16(input_payload.data() + 0);
const u32 page = Common::swap16(input_payload.data() + 2) & IC_PAGE_INDEX_MASK;
const u32 page_count = Common::swap16(input_payload.data() + 4);
const u16 card_session = Common::swap16(input_payload + 0);
const u32 page = Common::swap16(input_payload + 2) & IC_PAGE_INDEX_MASK;
const u32 page_count = Common::swap16(input_payload + 4);
INFO_LOG_FMT(SERIALINTERFACE_CARD, "WritePages: session:{:04x} page:{} page_count:{}",
card_session, page, page_count);
@@ -574,7 +580,7 @@ void ICCardReader::Update()
if (!validate_input_payload_size(8 + byte_count))
break;
if (!ic_card->WriteData(page, input_payload.subspan(6, byte_count)))
if (!ic_card->WriteData(page, input_payload + 6, byte_count))
{
status_code = HardwareUntestedErrorCode;
}
@@ -588,10 +594,12 @@ void ICCardReader::Update()
}
}
SendReply(card_command, status_code, reply_payload_span);
SendReply(card_command, status_code, reply_payload, reply_payload_size);
ConsumeRxBytes(entire_request_size);
}
void ICCardReader::SendReply(u8 command, u16 status_code, std::span<const u8> payload)
void ICCardReader::SendReply(u8 command, u16 status_code, const u8* payload,
std::size_t payload_size)
{
struct ICCardReplyHeader
{
@@ -602,37 +610,41 @@ void ICCardReader::SendReply(u8 command, u16 status_code, std::span<const u8> pa
};
ICCardReplyHeader header{.command = command};
header.length = u16(sizeof(header.status) + payload.size());
header.length = u16(sizeof(header.status) + payload_size);
header.status = status_code;
const auto header_span = Common::AsU8Span(header);
const u8* const header_bytes = reinterpret_cast<const u8*>(&header);
const u8 checksum = CheckSumXOR(header_span) ^ CheckSumXOR(payload);
const u8 checksum =
CheckSumXOR(header_bytes, sizeof(header)) ^ CheckSumXOR(payload, payload_size);
WriteTxBytes(header_span);
WriteTxBytes(payload);
WriteTxBytes(header_bytes, sizeof(header));
if (payload_size)
WriteTxBytes(payload, payload_size);
WriteTxByte(checksum);
}
std::span<const u8> ICCardReader::ICCard::ReadData(u16 page, u16 page_count)
const u8* ICCardReader::ICCard::ReadData(u16 page, u16 page_count, std::size_t* size)
{
const u32 byte_offset = page * IC_PAGE_SIZE;
const u32 byte_count = page_count * IC_PAGE_SIZE;
*size = 0;
if (byte_count + byte_offset > m_data.size())
{
WARN_LOG_FMT(SERIALINTERFACE_CARD, "ReadData: Attempt to read beyond end of card.");
return {};
return nullptr;
}
const auto read_span = std::span{m_data}.subspan(byte_offset, byte_count);
const u8* const data = m_data.data() + byte_offset;
DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "\n{}", HexDump(read_span.data(), read_span.size()));
DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "\n{}", HexDump(data, byte_count));
return read_span;
*size = byte_count;
return data;
}
bool ICCardReader::ICCard::WriteData(u16 page, std::span<const u8> write_span)
bool ICCardReader::ICCard::WriteData(u16 page, const u8* data, std::size_t size)
{
constexpr u32 read_only_area_begin = READ_ONLY_PAGE_INDEX * IC_PAGE_SIZE;
constexpr u32 read_only_area_end = read_only_area_begin + IC_PAGE_SIZE;
@@ -640,24 +652,24 @@ bool ICCardReader::ICCard::WriteData(u16 page, std::span<const u8> write_span)
const u32 byte_offset = page * IC_PAGE_SIZE;
if ((byte_offset < read_only_area_end) &&
(byte_offset + write_span.size() > read_only_area_begin))
(byte_offset + size > read_only_area_begin))
{
WARN_LOG_FMT(SERIALINTERFACE_CARD, "WriteData: Read-only page.");
// Read-only page, must return error.
return false;
}
if (write_span.size() + byte_offset > m_data.size())
if (size + byte_offset > m_data.size())
{
WARN_LOG_FMT(SERIALINTERFACE_CARD, "WriteData: Attempt to write beyond end of card.");
return false;
}
std::memcpy(m_data.data() + byte_offset, write_span.data(), write_span.size());
std::memcpy(m_data.data() + byte_offset, data, size);
DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "\n{}", HexDump(write_span.data(), write_span.size()));
DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "\n{}", HexDump(data, size));
FlushData(byte_offset, u32(write_span.size()));
FlushData(byte_offset, u32(size));
return true;
}
@@ -729,10 +741,10 @@ ICCardReader::ICCard::ICCard(std::string filename, const UID& uid)
void ICCardReader::ICCard::Initialize()
{
if (!LoadCardData(m_filename, m_data))
if (!LoadCardData(m_filename, m_data.data(), m_data.size()))
{
NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "Creating new IC Card data.");
InitializeCardData(m_data);
InitializeCardData(m_data.data());
FlushData(READ_ONLY_PAGE_INDEX * IC_PAGE_SIZE, IC_PAGE_SIZE * 2);
}
}
+4 -5
View File
@@ -5,7 +5,6 @@
#include <array>
#include <memory>
#include <span>
#include <string>
#include <vector>
@@ -39,7 +38,7 @@ public:
private:
void CreateCards(u8 card_count);
void SendReply(u8 command, u16 status_code, std::span<const u8> payload);
void SendReply(u8 command, u16 status_code, const u8* payload, std::size_t payload_size);
static constexpr std::size_t IC_PAGE_SIZE = 8;
static constexpr u32 IC_PAGE_COUNT = 256;
@@ -60,10 +59,10 @@ private:
const UID& GetUID() const { return m_uid; }
bool IsHalted() const { return m_current_state == State::Halted; }
// Returns an empty span on error.
std::span<const u8> ReadData(u16 page, u16 page_count);
// The card's own bytes, lent; nullptr on error.
const u8* ReadData(u16 page, u16 page_count, std::size_t* size);
bool WriteData(u16 page, std::span<const u8> write_span);
bool WriteData(u16 page, const u8* data, std::size_t size);
// Returns the new value.
u16 DecreaseUseCount(u16 page, u16 amount);
+21 -26
View File
@@ -3,7 +3,8 @@
#include "Core/HW/Triforce/IOPorts.h"
#include <algorithm>
#include <cstring>
#include <fmt/ranges.h>
@@ -31,23 +32,22 @@ void IOPorts::Update()
m_analog_inputs.fill(NEUTRAL_ANALOG_VALUE);
m_coin_inputs.fill(false);
std::ranges::for_each(m_io_adapters, &IOAdapter::Update);
for (auto& adapter : m_io_adapters)
adapter->Update();
}
std::span<u8> IOPorts::GetSwitchInputs(u32 player_index)
u8* IOPorts::GetSwitchInputs(u32 player_index)
{
ASSERT(player_index < PLAYER_COUNT);
return std::span{m_switch_inputs}.subspan(SWITCH_INPUT_BYTES_PER_PLAYER * player_index,
SWITCH_INPUT_BYTES_PER_PLAYER);
return m_switch_inputs.data() + SWITCH_INPUT_BYTES_PER_PLAYER * player_index;
}
std::span<const u8> IOPorts::GetSwitchInputs(u32 player_index) const
const u8* IOPorts::GetSwitchInputs(u32 player_index) const
{
ASSERT(player_index < PLAYER_COUNT);
return std::span{m_switch_inputs}.subspan(SWITCH_INPUT_BYTES_PER_PLAYER * player_index,
SWITCH_INPUT_BYTES_PER_PLAYER);
return m_switch_inputs.data() + SWITCH_INPUT_BYTES_PER_PLAYER * player_index;
}
void IOPorts::DoState(PointerWrap& p)
@@ -71,19 +71,17 @@ void IOPorts::AddIOAdapter(std::unique_ptr<IOAdapter> adapter)
IOAdapter::IOAdapter() = default;
IOAdapter::~IOAdapter() = default;
void IOPorts::SetGenericOutputs(std::span<const u8> bytes)
void IOPorts::SetGenericOutputs(const u8* bytes, std::size_t size)
{
if (bytes.size() > m_generic_outputs.size())
if (size > m_generic_outputs.size())
{
WARN_LOG_FMT(SERIALINTERFACE_JVSIO, "SetGenericOutputs: Unexpected byte count: {}",
bytes.size());
WARN_LOG_FMT(SERIALINTERFACE_JVSIO, "SetGenericOutputs: Unexpected byte count: {}", size);
}
const auto bytes_to_copy = std::min(bytes.size(), GENERIC_OUTPUT_BYTE_COUNT);
const std::size_t bytes_to_copy =
size < GENERIC_OUTPUT_BYTE_COUNT ? size : GENERIC_OUTPUT_BYTE_COUNT;
const bool no_change = std::ranges::equal(bytes.first(bytes_to_copy),
std::span{m_generic_outputs}.first(bytes_to_copy));
if (no_change)
if (!std::memcmp(bytes, m_generic_outputs.data(), bytes_to_copy))
return;
decltype(m_generic_outputs) bits_set{};
@@ -103,13 +101,14 @@ void IOPorts::SetGenericOutputs(std::span<const u8> bytes)
for (auto& adapter : m_io_adapters)
{
adapter->HandleGenericOutputsChanged(bits_set, bits_cleared);
adapter->HandleGenericOutputsChanged(bits_set.data(), bits_cleared.data());
}
}
void IOPorts::ResetGenericOutputs()
{
SetGenericOutputs(std::array<u8, GENERIC_OUTPUT_BYTE_COUNT>{});
static const u8 cleared[GENERIC_OUTPUT_BYTE_COUNT] = {};
SetGenericOutputs(cleared, sizeof(cleared));
}
void IOAdapter::Update()
@@ -120,8 +119,7 @@ void IOAdapter::DoState(PointerWrap& p)
{
}
void IOAdapter::HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared)
void IOAdapter::HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared)
{
}
@@ -279,8 +277,7 @@ void VirtuaStriker4Common_IOAdapter::Update()
}
}
void VirtuaStriker4Common_IOAdapter::HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared)
void VirtuaStriker4Common_IOAdapter::HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared)
{
const u8 bits_changed_0 = bits_set[0] | bits_cleared[0];
@@ -297,8 +294,7 @@ void VirtuaStriker4Common_IOAdapter::HandleGenericOutputsChanged(std::span<const
}
}
void VirtuaStriker4_2006_IOAdapter::HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared)
void VirtuaStriker4_2006_IOAdapter::HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared)
{
(void)bits_cleared;
@@ -357,8 +353,7 @@ void GekitouProYakyuu_IOAdapter::Update()
}
}
void GekitouProYakyuu_IOAdapter::HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared)
void GekitouProYakyuu_IOAdapter::HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared)
{
(void)bits_set;
+25 -26
View File
@@ -5,7 +5,6 @@
#include <array>
#include <memory>
#include <span>
#include <vector>
#include "Common/CommonTypes.h"
@@ -30,28 +29,38 @@ class IOPorts
public:
void Update();
std::span<u8> GetStatusSwitches() { return m_status_switches; }
std::span<const u8> GetStatusSwitches() const { return m_status_switches; }
// STATUS_SWITCH_BYTE_COUNT bytes
u8* GetStatusSwitches() { return m_status_switches.data(); }
const u8* GetStatusSwitches() const { return m_status_switches.data(); }
u8* GetSystemInputs() { return &m_system_inputs; }
const u8* GetSystemInputs() const { return &m_system_inputs; }
std::span<u8> GetSwitchInputs(u32 player_index);
std::span<const u8> GetSwitchInputs(u32 player_index) const;
// SWITCH_INPUT_BYTES_PER_PLAYER bytes
u8* GetSwitchInputs(u32 player_index);
const u8* GetSwitchInputs(u32 player_index) const;
std::span<u16> GetAnalogInputs() { return m_analog_inputs; }
std::span<const u16> GetAnalogInputs() const { return m_analog_inputs; }
// ANALOG_INPUT_COUNT values
u16* GetAnalogInputs() { return m_analog_inputs.data(); }
const u16* GetAnalogInputs() const { return m_analog_inputs.data(); }
std::span<const u8> GetGenericOutputs() const { return m_generic_outputs; }
// GENERIC_OUTPUT_BYTE_COUNT bytes
const u8* GetGenericOutputs() const { return m_generic_outputs.data(); }
void SetGenericOutputs(std::span<const u8> bytes);
void SetGenericOutputs(const u8* bytes, std::size_t size);
void ResetGenericOutputs();
static constexpr std::size_t PLAYER_COUNT = 2;
static constexpr std::size_t COIN_SLOT_COUNT = 2;
std::span<bool, COIN_SLOT_COUNT> GetCoinInputs() { return m_coin_inputs; }
std::span<const bool, COIN_SLOT_COUNT> GetCoinInputs() const { return m_coin_inputs; }
// COIN_SLOT_COUNT flags
bool* GetCoinInputs() { return m_coin_inputs.data(); }
const bool* GetCoinInputs() const { return m_coin_inputs.data(); }
static constexpr std::size_t STATUS_SWITCH_BYTE_COUNT = 2;
static constexpr std::size_t SWITCH_INPUT_BYTES_PER_PLAYER = 2;
static constexpr std::size_t ANALOG_INPUT_COUNT = 8;
static constexpr std::size_t GENERIC_OUTPUT_BYTE_COUNT = 4;
void AddIOAdapter(std::unique_ptr<IOAdapter> adapter);
@@ -62,21 +71,15 @@ public:
private:
std::vector<std::unique_ptr<IOAdapter>> m_io_adapters;
std::array<u8, 2> m_status_switches{0xff, 0xff};
std::array<u8, STATUS_SWITCH_BYTE_COUNT> m_status_switches{0xff, 0xff};
// The first byte in JVSIO switch input data.
u8 m_system_inputs = 0x00;
static constexpr std::size_t SWITCH_INPUT_BYTES_PER_PLAYER = 2;
std::array<u8, PLAYER_COUNT * SWITCH_INPUT_BYTES_PER_PLAYER> m_switch_inputs{};
static constexpr std::size_t ANALOG_INPUT_COUNT = 8;
std::array<u16, ANALOG_INPUT_COUNT> m_analog_inputs{};
static constexpr std::size_t GENERIC_OUTPUT_BYTE_COUNT = 4;
std::array<u8, GENERIC_OUTPUT_BYTE_COUNT> m_generic_outputs{};
std::array<bool, COIN_SLOT_COUNT> m_coin_inputs{};
@@ -103,8 +106,7 @@ protected:
IOPorts* GetIOPorts() { return m_io_ports; }
// Invoked with newly set and cleared bits.
virtual void HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared);
virtual void HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared);
private:
void SetIOPorts(IOPorts* io_ports) { m_io_ports = io_ports; }
@@ -144,8 +146,7 @@ protected:
static constexpr u8 SLOT_A_LOCK_BIT = 0x40;
static constexpr u8 SLOT_B_LOCK_BIT = 0x10;
void HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared) override;
void HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared) override;
private:
std::array<ICCardReader*, IOPorts::PLAYER_COUNT> const m_card_readers;
@@ -160,8 +161,7 @@ public:
}
protected:
void HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared) override;
void HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared) override;
private:
static constexpr u8 SLOT_A_EJECT_BIT = 0x80;
@@ -181,8 +181,7 @@ public:
void Update() override;
protected:
void HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared) override;
void HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared) override;
private:
static constexpr u8 SLOT_A_LOCK_BIT = 0x40;
+98 -86
View File
@@ -3,8 +3,7 @@
#include "Core/HW/Triforce/JVSIO.h"
#include <numeric>
#include <optional>
#include <cstring>
#include <vector>
#include <fmt/ranges.h>
@@ -12,7 +11,7 @@
#include "Common/BitUtils.h"
#include "Common/ChunkFile.h"
#include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
#include "Common/StringUtil.h"
#include "Common/Swap.h"
namespace
@@ -117,13 +116,10 @@ enum class JVSIOCommand : u8
class JVSIORequestReader
{
public:
explicit JVSIORequestReader(std::span<const u8> data)
: m_data{data.data()}, m_data_end{data.data() + data.size()}
{
}
JVSIORequestReader(const u8* data, std::size_t size) : m_data{data}, m_data_end{data + size} {}
// Returns a span of all remaining bytes.
constexpr std::span<const u8> PeekBytes() const { return {m_data, m_data_end}; }
// The remaining bytes, lent; RemainingByteCount() of them.
constexpr const u8* PeekBytes() const { return m_data; }
// Returns the remaining readable byte count.
constexpr std::size_t RemainingByteCount() const { return std::size_t(m_data_end - m_data); }
@@ -152,13 +148,10 @@ public:
void AddData(u8 value) { m_buffer.emplace_back(value); }
void AddData(std::span<const u8> data)
void AddData(const void* data, std::size_t size)
{
#if defined(__cpp_lib_containers_ranges)
m_buffer.append_range(data);
#else
m_buffer.insert(m_buffer.end(), data.begin(), data.end());
#endif
const u8* const bytes = static_cast<const u8*>(data);
m_buffer.insert(m_buffer.end(), bytes, bytes + size);
}
void StartFrame(u8 destination_node)
@@ -189,7 +182,9 @@ public:
m_buffer[2] = u8(status_code);
// Write checksum.
const u8 checksum = std::accumulate(m_buffer.begin(), m_buffer.end(), u8{});
u8 checksum = 0;
for (const u8 byte_value : m_buffer)
checksum += byte_value;
AddData(checksum);
}
@@ -208,29 +203,33 @@ private:
std::size_t m_last_report_code_index{};
};
// Attempts to decode exactly output.size() bytes.
// On success, returns the number of escaped bytes read.
static std::optional<std::size_t> UnescapeData(std::span<const u8> input, std::span<u8> output)
// Attempts to decode exactly output_size bytes.
// On success, stores the number of escaped bytes read.
static bool UnescapeData(const u8* input, std::size_t input_size, u8* output,
std::size_t output_size, std::size_t* escaped_size)
{
auto in = input.begin();
const auto in_end = input.end();
const u8* in = input;
const u8* const in_end = input + input_size;
auto out = output.begin();
const auto out_end = output.end();
u8* out = output;
u8* const out_end = output + output_size;
while (true)
{
if (out == out_end)
return in - input.begin(); // Success.
{
*escaped_size = std::size_t(in - input); // Success.
return true;
}
if (in == in_end)
return std::nullopt;
return false;
u8 byte_value = *(in++);
if (byte_value == JVSIO_MARK)
{
if (in == in_end)
return std::nullopt;
return false;
byte_value = *(in++) + 0x01;
}
@@ -246,8 +245,8 @@ JVSIOBoard::JVSIOBoard(IOPorts* io_ports) : m_io_ports{io_ports}
void JVSIOBoard::Update()
{
// Update coin counters.
const auto coin_inputs = m_io_ports->GetCoinInputs();
for (std::size_t i = 0; i != coin_inputs.size(); ++i)
const bool* const coin_inputs = m_io_ports->GetCoinInputs();
for (std::size_t i = 0; i != IOPorts::COIN_SLOT_COUNT; ++i)
{
if (!std::exchange(m_coin_prev_states[i], coin_inputs[i]) && coin_inputs[i])
++m_coin_counts[i];
@@ -255,12 +254,13 @@ void JVSIOBoard::Update()
while (true)
{
const auto rx_span = GetRxByteSpan();
const u8* const rx = GetRxBytes();
const std::size_t rx_size = GetRxByteCount();
if (rx_span.empty())
if (!rx_size)
break; // Wait for more data.
if (rx_span[0] != JVSIO_SYNC)
if (rx[0] != JVSIO_SYNC)
{
ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "Expected JVSIO_SYNC");
ConsumeRxBytes(1);
@@ -270,25 +270,23 @@ void JVSIOBoard::Update()
std::array<u8, 2 + 255> unescaped_data;
// Read 2 header bytes.
const auto header_escaped_size =
UnescapeData(rx_span.subspan(1), std::span{unescaped_data}.first(2));
if (!header_escaped_size.has_value())
std::size_t header_escaped_size;
if (!UnescapeData(rx + 1, rx_size - 1, unescaped_data.data(), 2, &header_escaped_size))
break; // Wait for more data.
const u8 destination_node = unescaped_data[0];
const u8 payload_size = unescaped_data[1];
// Read remaining frame bytes.
const auto payload_escaped_size =
UnescapeData(rx_span.subspan(1 + *header_escaped_size),
std::span{unescaped_data}.subspan(2, payload_size));
if (!payload_escaped_size.has_value())
std::size_t payload_escaped_size;
if (!UnescapeData(rx + 1 + header_escaped_size, rx_size - 1 - header_escaped_size,
unescaped_data.data() + 2, payload_size, &payload_escaped_size))
{
break; // Wait for more data.
}
Common::ScopeGuard consume_frame{
[&] { ConsumeRxBytes(1 + *header_escaped_size + *payload_escaped_size); }};
// The frame is consumed whatever happens below; the bytes stay unescaped on the stack.
ConsumeRxBytes(1 + header_escaped_size + payload_escaped_size);
if (payload_size < 1)
{
@@ -297,9 +295,9 @@ void JVSIOBoard::Update()
}
// Verify checksum.
const auto range_to_checksum = std::span{unescaped_data}.first(payload_size + 1);
const u8 expected_checksum =
std::accumulate(range_to_checksum.begin(), range_to_checksum.end(), u8());
u8 expected_checksum = 0;
for (std::size_t i = 0; i < std::size_t(payload_size) + 1; i++)
expected_checksum += unescaped_data[i];
if (expected_checksum != unescaped_data[payload_size + 1])
{
ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "Bad checksum");
@@ -307,11 +305,11 @@ void JVSIOBoard::Update()
JVSIOResponseWriter writer{&m_last_response};
writer.StartFrame(JVSIO_HOST_ADDRESS);
writer.EndFrame(JVSIOStatusCode::ChecksumError);
WriteResponse(m_last_response);
WriteResponse(m_last_response.data(), m_last_response.size());
continue;
}
JVSIORequestReader request{range_to_checksum.subspan(2)};
JVSIORequestReader request{unescaped_data.data() + 2, std::size_t(payload_size) - 1};
if (destination_node == JVSIO_BROADCAST_ADDRESS)
{
@@ -382,7 +380,7 @@ void JVSIOBoard::ProcessBroadcastRequest(JVSIORequestReader* request)
writer.StartFrame(JVSIO_HOST_ADDRESS);
writer.AddData(u8(JVSIOReportCode::Okay));
writer.EndFrame(JVSIOStatusCode::Okay);
WriteResponse(m_last_response);
WriteResponse(m_last_response.data(), m_last_response.size());
break;
}
default:
@@ -394,11 +392,11 @@ void JVSIOBoard::ProcessBroadcastRequest(JVSIORequestReader* request)
void JVSIOBoard::ProcessUnicastRequest(JVSIORequestReader* request)
{
if (request->RemainingByteCount() > 0 &&
JVSIOCommand(request->PeekBytes().front()) == JVSIOCommand::DataRetransmit)
JVSIOCommand(request->PeekBytes()[0]) == JVSIOCommand::DataRetransmit)
{
request->SkipBytes(1);
INFO_LOG_FMT(SERIALINTERFACE_JVSIO, "DataRetransmit");
WriteResponse(m_last_response);
WriteResponse(m_last_response.data(), m_last_response.size());
return;
}
@@ -441,15 +439,16 @@ void JVSIOBoard::ProcessUnicastRequest(JVSIORequestReader* request)
}
}
WriteResponse(m_last_response);
WriteResponse(m_last_response.data(), m_last_response.size());
}
void JVSIOBoard::WriteResponse(std::span<const u8> response)
void JVSIOBoard::WriteResponse(const u8* response, std::size_t size)
{
WriteTxByte(JVSIO_SYNC);
for (const u8 byte_value : response)
for (std::size_t i = 0; i < size; i++)
{
const u8 byte_value = response[i];
const bool needs_escaping = byte_value == JVSIO_SYNC || byte_value == JVSIO_MARK;
if (needs_escaping)
{
@@ -487,14 +486,15 @@ auto JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx) -> HandlerRes
}
case JVSIOCommand::MainID:
{
const auto remaining_bytes = ctx.request.PeekBytes();
const auto null_iter = std::ranges::find(remaining_bytes, u8{});
const u8* const remaining_bytes = ctx.request.PeekBytes();
const std::size_t remaining_count = ctx.request.RemainingByteCount();
const void* const null_byte = std::memchr(remaining_bytes, 0, remaining_count);
if (null_iter == remaining_bytes.end())
if (!null_byte)
return JVSIOReportCode::ParameterDataError;
std::string_view main_id_str{reinterpret_cast<const char*>(remaining_bytes.data()),
reinterpret_cast<const char*>(std::to_address(null_iter))};
std::string_view main_id_str{reinterpret_cast<const char*>(remaining_bytes),
std::size_t(static_cast<const u8*>(null_byte) - remaining_bytes)};
ctx.request.SkipBytes(main_id_str.size() + 1);
INFO_LOG_FMT(SERIALINTERFACE_JVSIO, "MainID: {}", main_id_str);
@@ -515,10 +515,13 @@ auto JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx) -> HandlerRes
for (u8 player_index = 0; player_index != player_count; ++player_index)
{
const auto switch_inputs = m_io_ports->GetSwitchInputs(player_index);
const auto bytes_to_use = std::min<std::size_t>(bytes_per_player, switch_inputs.size());
const u8* const switch_inputs = m_io_ports->GetSwitchInputs(player_index);
const std::size_t bytes_to_use =
bytes_per_player < IOPorts::SWITCH_INPUT_BYTES_PER_PLAYER ?
bytes_per_player :
IOPorts::SWITCH_INPUT_BYTES_PER_PLAYER;
ctx.response.AddData(switch_inputs.first(bytes_to_use));
ctx.response.AddData(switch_inputs, bytes_to_use);
// Pad data if the game requests more inputs than we have (unlikely).
for (std::size_t i = bytes_to_use; i != bytes_per_player; ++i)
@@ -536,15 +539,23 @@ auto JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx) -> HandlerRes
DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "AnalogInput: channel_count:{}", channel_count);
const auto analog_inputs = m_io_ports->GetAnalogInputs();
const auto inputs_to_use = std::min<std::size_t>(channel_count, analog_inputs.size());
const u16* const analog_inputs = m_io_ports->GetAnalogInputs();
const std::size_t inputs_to_use = channel_count < IOPorts::ANALOG_INPUT_COUNT ?
channel_count :
IOPorts::ANALOG_INPUT_COUNT;
for (auto value : analog_inputs.first(inputs_to_use))
ctx.response.AddData(Common::AsU8Span(Common::swap16(value)));
for (std::size_t i = 0; i != inputs_to_use; ++i)
{
ctx.response.AddData(u8(analog_inputs[i] >> 8));
ctx.response.AddData(u8(analog_inputs[i]));
}
// Pad data if the game requests more inputs than we have (unlikely).
for (std::size_t i = inputs_to_use; i != channel_count; ++i)
ctx.response.AddData(Common::AsU8Span(Common::swap16(IOPorts::NEUTRAL_ANALOG_VALUE)));
{
ctx.response.AddData(u8(IOPorts::NEUTRAL_ANALOG_VALUE >> 8));
ctx.response.AddData(u8(IOPorts::NEUTRAL_ANALOG_VALUE));
}
return JVSIOReportCode::Okay;
}
@@ -558,11 +569,11 @@ auto JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx) -> HandlerRes
if (ctx.request.RemainingByteCount() < byte_count)
return JVSIOReportCode::ParameterSizeError;
const auto output_span = ctx.request.PeekBytes().first(byte_count);
const u8* const outputs = ctx.request.PeekBytes();
DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "GenericOutput: {}", fmt::join(output_span, " "));
DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "GenericOutput: {}", Common::BytesToHexString(outputs, byte_count));
m_io_ports->SetGenericOutputs(output_span);
m_io_ports->SetGenericOutputs(outputs, byte_count);
ctx.request.SkipBytes(byte_count);
@@ -593,10 +604,10 @@ auto JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx) -> HandlerRes
// Top 2 bits contain the condition code.
// Bottom 14 bits contain the coin count.
const Common::BigEndianValue<u16> data{
u16((u32(condition_code) << 14) | (coin_count & 0x3fffu))};
const u16 data = u16((u32(condition_code) << 14) | (coin_count & 0x3fffu));
ctx.response.AddData(Common::AsU8Span(data));
ctx.response.AddData(u8(data >> 8));
ctx.response.AddData(u8(data));
}
return JVSIOReportCode::Okay;
@@ -617,7 +628,10 @@ auto JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx) -> HandlerRes
NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "CoinCounter: slot:{} adjustment:{}", slot, adjustment);
if (slot < m_coin_counts.size())
m_coin_counts[slot] = std::clamp(m_coin_counts[slot] + adjustment, 0, 0x3fff);
{
const s32 count = m_coin_counts[slot] + adjustment;
m_coin_counts[slot] = u16(count < 0 ? 0 : count > 0x3fff ? 0x3fff : count);
}
return JVSIOReportCode::Okay;
}
@@ -636,8 +650,8 @@ auto Namco_FCA_JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx) ->
case JVSIOCommand::IOIdentify:
{
INFO_LOG_FMT(SERIALINTERFACE_JVSIO, "IOIdentify");
ctx.response.AddData(
Common::AsU8Span(std::span{"namco ltd.;FCA-1;Ver1.01;JPN,Multipurpose + Rotary Encoder"}));
static const char id[] = "namco ltd.;FCA-1;Ver1.01;JPN,Multipurpose + Rotary Encoder";
ctx.response.AddData(id, sizeof(id));
return JVSIOReportCode::Okay;
}
case JVSIOCommand::FeatureCheck:
@@ -652,7 +666,7 @@ auto Namco_FCA_JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx) ->
{JVSIOFeature::GeneralPurposeOutput, 6},
{JVSIOFeature::AnalogOutput, 4},
});
ctx.response.AddData(Common::AsU8Span(features));
ctx.response.AddData(features.data(), sizeof(features));
ctx.response.AddData(u8{}); // End code
return JVSIOReportCode::Okay;
@@ -671,10 +685,8 @@ auto Namco_FCA_JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx) ->
if (ctx.request.RemainingByteCount() < param_size)
return JVSIOReportCode::ParameterSizeError;
const auto params = ctx.request.PeekBytes().first(param_size);
INFO_LOG_FMT(SERIALINTERFACE_JVSIO, "NAMCO_COMMAND({:02x}): {:02x}", namco_command,
fmt::join(params, " "));
INFO_LOG_FMT(SERIALINTERFACE_JVSIO, "NAMCO_COMMAND({:02x}): {}", namco_command,
Common::BytesToHexString(ctx.request.PeekBytes(), param_size));
ctx.request.SkipBytes(param_size);
ctx.response.AddData(0xff);
@@ -699,8 +711,8 @@ auto Sega_837_13551_JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx
case JVSIOCommand::IOIdentify:
{
INFO_LOG_FMT(SERIALINTERFACE_JVSIO, "IOIdentify");
ctx.response.AddData(
Common::AsU8Span(std::span{"SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551 ;Ver1.00;98/10"}));
static const char id[] = "SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551 ;Ver1.00;98/10";
ctx.response.AddData(id, sizeof(id));
return JVSIOReportCode::Okay;
}
case JVSIOCommand::FeatureCheck:
@@ -712,7 +724,7 @@ auto Sega_837_13551_JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx
{JVSIOFeature::AnalogInput, 8},
{JVSIOFeature::GeneralPurposeOutput, 6},
});
ctx.response.AddData(Common::AsU8Span(features));
ctx.response.AddData(features.data(), sizeof(features));
ctx.response.AddData(u8{}); // End code
return JVSIOReportCode::Okay;
@@ -729,8 +741,8 @@ auto Sega_837_13844_JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx
case JVSIOCommand::IOIdentify:
{
INFO_LOG_FMT(SERIALINTERFACE_JVSIO, "IOIdentify");
ctx.response.AddData(Common::AsU8Span(
std::span{"SEGA ENTERPRISES,LTD.;837-13844-01 I/O CNTL BD2 ;Ver1.00;99/07"}));
static const char id[] = "SEGA ENTERPRISES,LTD.;837-13844-01 I/O CNTL BD2 ;Ver1.00;99/07";
ctx.response.AddData(id, sizeof(id));
return JVSIOReportCode::Okay;
}
case JVSIOCommand::FeatureCheck:
@@ -742,7 +754,7 @@ auto Sega_837_13844_JVSIOBoard::HandleCommand(JVSIOCommand cmd, FrameContext ctx
{JVSIOFeature::AnalogInput, 8},
{JVSIOFeature::GeneralPurposeOutput, 22},
});
ctx.response.AddData(Common::AsU8Span(features));
ctx.response.AddData(features.data(), sizeof(features));
ctx.response.AddData(u8{}); // End code
return JVSIOReportCode::Okay;
+1 -2
View File
@@ -5,7 +5,6 @@
#include <array>
#include <expected>
#include <span>
#include <vector>
#include "Common/CommonTypes.h"
@@ -49,7 +48,7 @@ private:
void ProcessBroadcastRequest(JVSIORequestReader* request);
void ProcessUnicastRequest(JVSIORequestReader* request);
void WriteResponse(std::span<const u8> response);
void WriteResponse(const u8* response, std::size_t size);
// 0 == address not yet assigned.
u8 m_client_address = 0;
+14 -17
View File
@@ -8,6 +8,7 @@
#include "Common/BitUtils.h"
#include "Common/ChunkFile.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/Swap.h"
#include "Core/HW/GCPad.h"
@@ -55,8 +56,7 @@ void MarioKartGPCommon_IOAdapter::Update()
analog_inputs[2] = Common::ExpandValue<u16>(pad_status.triggerLeft, 8);
}
void MarioKartGPCommon_IOAdapter::HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared)
void MarioKartGPCommon_IOAdapter::HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared)
{
if (bits_set[0] & 0x80u)
m_steering_wheel->Reset();
@@ -80,37 +80,34 @@ void MarioKartGPCommon_IOAdapter::HandleGenericOutputsChanged(std::span<const u8
void MarioKartGPSteeringWheel::Update()
{
constexpr std::size_t REQUEST_SIZE = 10;
std::size_t rx_position = 0;
while (true)
{
const auto rx_span = GetRxByteSpan().subspan(rx_position);
if (rx_span.size() < REQUEST_SIZE)
if (GetRxByteCount() - rx_position < REQUEST_SIZE)
break; // Wait for more data.
ProcessRequest(rx_span.first<REQUEST_SIZE>());
ProcessRequest(GetRxBytes() + rx_position);
rx_position += REQUEST_SIZE;
}
ConsumeRxBytes(rx_position);
}
void MarioKartGPSteeringWheel::ProcessRequest(std::span<const u8> request)
void MarioKartGPSteeringWheel::ProcessRequest(const u8* request)
{
DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: Request: {:02x}", fmt::join(request, " "));
DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: Request: {}",
Common::BytesToHexString(request, REQUEST_SIZE));
const u32 cmd = Common::swap32(request.data());
const u32 cmd = Common::swap32(request);
if (cmd != 0xffffff01)
{
WARN_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: Unknown command: {:08x}", cmd);
return;
}
const u16 centering_force = Common::swap16(request.data() + 4);
const u16 friction_force = Common::swap16(request.data() + 6);
const u16 roll = Common::swap16(request.data() + 8);
const u16 centering_force = Common::swap16(request + 4);
const u16 friction_force = Common::swap16(request + 6);
const u16 roll = Common::swap16(request + 8);
DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: FFB: {:04x} {:04x} {:04x}", centering_force,
friction_force, roll);
@@ -119,7 +116,7 @@ void MarioKartGPSteeringWheel::ProcessRequest(std::span<const u8> request)
{
case 0:
// The game seems to expect this on power up.
WriteTxBytes(ACK_NO_ERROR);
WriteTxBytes(ACK_NO_ERROR.data(), ACK_NO_ERROR.size());
++m_init_state;
break;
@@ -131,12 +128,12 @@ void MarioKartGPSteeringWheel::ProcessRequest(std::span<const u8> request)
if (m_init_state == 1)
{
WriteTxBytes(ACK_POWER_OFF);
WriteTxBytes(ACK_POWER_OFF.data(), ACK_POWER_OFF.size());
++m_init_state;
}
else
{
WriteTxBytes(ACK_POWER_ON);
WriteTxBytes(ACK_POWER_ON.data(), ACK_POWER_ON.size());
m_init_state = 1;
}
break;
+4 -3
View File
@@ -20,7 +20,9 @@ public:
void DoState(PointerWrap&) override;
private:
void ProcessRequest(std::span<const u8>);
static constexpr std::size_t REQUEST_SIZE = 10;
void ProcessRequest(const u8* request);
u8 m_init_state = 0;
};
@@ -37,8 +39,7 @@ public:
void Update() override;
protected:
void HandleGenericOutputsChanged(std::span<const u8> bits_set,
std::span<const u8> bits_cleared) override;
void HandleGenericOutputsChanged(const u8* bits_set, const u8* bits_cleared) override;
private:
MarioKartGPSteeringWheel* const m_steering_wheel;
+9 -25
View File
@@ -3,7 +3,6 @@
#include "Core/HW/Triforce/SerialDevice.h"
#include <algorithm>
#include "Common/Assert.h"
#include "Common/ChunkFile.h"
@@ -11,21 +10,18 @@
namespace Triforce
{
void SerialDevice::WriteRxBytes(std::span<const u8> bytes)
void SerialDevice::WriteRxBytes(const u8* bytes, std::size_t size)
{
#if defined(__cpp_lib_containers_ranges)
m_rx_buffer.append_range(bytes);
#else
m_rx_buffer.insert(m_rx_buffer.end(), bytes.begin(), bytes.end());
#endif
m_rx_buffer.insert(m_rx_buffer.end(), bytes, bytes + size);
}
void SerialDevice::TakeTxBytes(std::span<u8> bytes)
void SerialDevice::TakeTxBytes(u8* bytes, std::size_t size)
{
DEBUG_ASSERT(m_tx_buffer.size() >= bytes.size());
DEBUG_ASSERT(m_tx_buffer.size() >= size);
std::copy_n(m_tx_buffer.begin(), bytes.size(), bytes.data());
m_tx_buffer.erase(m_tx_buffer.begin(), m_tx_buffer.begin() + bytes.size());
for (std::size_t i = 0; i < size; i++)
bytes[i] = m_tx_buffer[i];
m_tx_buffer.erase(m_tx_buffer.begin(), m_tx_buffer.begin() + size);
}
void SerialDevice::ConsumeRxBytes(std::size_t count)
@@ -35,13 +31,9 @@ void SerialDevice::ConsumeRxBytes(std::size_t count)
m_rx_buffer.erase(m_rx_buffer.begin(), m_rx_buffer.begin() + count);
}
void SerialDevice::WriteTxBytes(std::span<const u8> bytes)
void SerialDevice::WriteTxBytes(const u8* bytes, std::size_t size)
{
#if defined(__cpp_lib_containers_ranges)
m_tx_buffer.append_range(bytes);
#else
m_tx_buffer.insert(m_tx_buffer.end(), bytes.begin(), bytes.end());
#endif
m_tx_buffer.insert(m_tx_buffer.end(), bytes, bytes + size);
}
void SerialDevice::PassThroughTxBytes(SerialDevice& other)
@@ -49,20 +41,12 @@ void SerialDevice::PassThroughTxBytes(SerialDevice& other)
// The destination buffer will often be empty or near-empty.
if (m_tx_buffer.size() < other.m_tx_buffer.size()) [[likely]]
{
#if defined(__cpp_lib_containers_ranges)
other.m_tx_buffer.prepend_range(m_tx_buffer);
#else
other.m_tx_buffer.insert(other.m_tx_buffer.begin(), m_tx_buffer.begin(), m_tx_buffer.end());
#endif
m_tx_buffer.swap(other.m_tx_buffer);
}
else
{
#if defined(__cpp_lib_containers_ranges)
m_tx_buffer.append_range(other.m_tx_buffer);
#else
m_tx_buffer.insert(m_tx_buffer.end(), other.m_tx_buffer.begin(), other.m_tx_buffer.end());
#endif
}
other.m_tx_buffer.clear();
+7 -6
View File
@@ -4,7 +4,6 @@
#pragma once
#include <deque>
#include <span>
#include <vector>
#include "Common/CommonTypes.h"
@@ -26,24 +25,26 @@ public:
SerialDevice(SerialDevice&&) = delete;
SerialDevice& operator=(SerialDevice&&) = delete;
void WriteRxBytes(std::span<const u8> bytes);
void WriteRxBytes(const u8* bytes, std::size_t size);
std::size_t GetTxByteCount() const { return m_tx_buffer.size(); }
// Caller should ensure GetTxByteCount() >= byte.size().
void TakeTxBytes(std::span<u8> bytes);
// Caller should ensure GetTxByteCount() >= size.
void TakeTxBytes(u8* bytes, std::size_t size);
virtual void Update() = 0;
virtual void DoState(PointerWrap& p);
protected:
std::span<const u8> GetRxByteSpan() const { return m_rx_buffer; }
// Pending received bytes, lent until the next Rx write or consume.
const u8* GetRxBytes() const { return m_rx_buffer.data(); }
std::size_t GetRxByteCount() const { return m_rx_buffer.size(); }
void ConsumeRxBytes(std::size_t count);
void WriteTxByte(u8 byte) { m_tx_buffer.emplace_back(byte); }
void WriteTxBytes(std::span<const u8> bytes);
void WriteTxBytes(const u8* bytes, std::size_t size);
// Transfer the entirety of other's tx buffer to this tx buffer.
void PassThroughTxBytes(SerialDevice& other);
+11 -6
View File
@@ -3,7 +3,8 @@
#include "Core/HW/Triforce/Touchscreen.h"
#include <numeric>
#include <cstddef>
#include "Common/BitUtils.h"
#include "Common/Logging/Log.h"
@@ -34,12 +35,12 @@ namespace Triforce
void Touchscreen::Update()
{
if (const auto input = GetRxByteSpan(); !input.empty())
if (const std::size_t input_size = GetRxByteCount())
{
// The Key of Avalon doesn't write to the device, it only reads.
WARN_LOG_FMT(SERIALINTERFACE_AMBB, "Unexpected write of {} bytes to touchscreen.",
input.size());
ConsumeRxBytes(input.size());
input_size);
ConsumeRxBytes(input_size);
}
// Our touchscreen conveniently produces exactly one packet every Update cycle.
@@ -64,9 +65,13 @@ void Touchscreen::Update()
.pressure = pad_status.triggerRight,
};
packet.checksum = std::accumulate(&packet.lead_in, &packet.checksum, u8{0xaa});
const u8* const packet_bytes = reinterpret_cast<const u8*>(&packet);
u8 checksum = 0xaa;
for (std::size_t i = 0; i < offsetof(SmartSetDataPacket, checksum); i++)
checksum += packet_bytes[i];
packet.checksum = checksum;
WriteTxBytes(Common::AsU8Span(packet));
WriteTxBytes(packet_bytes, sizeof(packet));
}
} // namespace Triforce
+22 -12
View File
@@ -3,7 +3,7 @@
#include "Core/IOS/USB/Bluetooth/BTEmu.h"
#include <algorithm>
#include <cstring>
#include <cstring>
#include <memory>
#include <string>
@@ -48,8 +48,12 @@ BluetoothEmuDevice::BluetoothEmuDevice(EmulationKernel& ios, const std::string&
const bdaddr_t tmp_bd = {0x11, 0x02, 0x19, 0x79, 0, i};
// Previous records can be safely overwritten, since they are backed up
std::ranges::copy(tmp_bd, std::rbegin(bt_dinf.active[i].bdaddr));
std::ranges::copy(tmp_bd, std::rbegin(bt_dinf.registered[i].bdaddr));
// stored reversed
for (std::size_t b = 0; b < tmp_bd.size(); b++)
{
bt_dinf.active[i].bdaddr[tmp_bd.size() - 1 - b] = tmp_bd[b];
bt_dinf.registered[i].bdaddr[tmp_bd.size() - 1 - b] = tmp_bd[b];
}
const auto& wm_name =
(i == WIIMOTE_BALANCE_BOARD) ? "Nintendo RVL-WBC-01" : "Nintendo RVL-CNT-01";
@@ -378,7 +382,7 @@ void BluetoothEmuDevice::ACLPool::Store(const u8* data, const u16 size, const u1
m_queue.push_back(Packet());
auto& packet = m_queue.back();
std::copy_n(data, size, packet.data);
std::memcpy(packet.data, data, size);
packet.size = size;
packet.conn_handle = conn_handle;
}
@@ -403,7 +407,7 @@ void BluetoothEmuDevice::ACLPool::WriteToEndpoint(const USB::V0BulkMessage& endp
header->length = size;
// Write the packet to the buffer
std::copy_n(data, size, (u8*)header + sizeof(hci_acldata_hdr_t));
std::memcpy((u8*)header + sizeof(hci_acldata_hdr_t), data, size);
m_queue.pop_front();
@@ -438,16 +442,22 @@ bool BluetoothEmuDevice::SendEventInquiryResponse()
static_assert(
sizeof(SHCIEventInquiryResult) - 2 + (num_responses * sizeof(hci_inquiry_response)) < 256);
const auto iter = std::ranges::find_if(m_wiimotes, &WiimoteDevice::IsInquiryScanEnabled);
if (iter == m_wiimotes.end())
const WiimoteDevice* wiimote = nullptr;
for (const auto& candidate : m_wiimotes)
{
if (candidate->IsInquiryScanEnabled())
{
wiimote = candidate.get();
break;
}
}
if (!wiimote)
{
// No remotes are discoverable.
SendEventInquiryComplete(0);
return false;
}
const auto& wiimote = *iter;
SQueuedEvent event(
u32(sizeof(SHCIEventInquiryResult) + num_responses * sizeof(hci_inquiry_response)), 0);
@@ -462,7 +472,7 @@ bool BluetoothEmuDevice::SendEventInquiryResponse()
response->page_scan_rep_mode = 1;
response->page_scan_period_mode = 0;
response->page_scan_mode = 0;
std::copy_n(wiimote->GetClass().begin(), HCI_CLASS_SIZE, response->uclass);
std::memcpy(response->uclass, wiimote->GetClass().data(), HCI_CLASS_SIZE);
response->clock_offset = 0x3818;
DEBUG_LOG_FMT(IOS_WIIMOTE, "Event: Send Fake Inquiry of one controller");
@@ -639,7 +649,7 @@ bool BluetoothEmuDevice::SendEventReadRemoteFeatures(u16 connection_handle)
read_remote_features->PayloadLength = sizeof(SHCIEventReadRemoteFeatures) - 2;
read_remote_features->EventStatus = 0x00;
read_remote_features->ConnectionHandle = connection_handle;
std::copy_n(wiimote->GetFeatures().begin(), HCI_FEATURES_SIZE, read_remote_features->features);
std::memcpy(read_remote_features->features, wiimote->GetFeatures().data(), HCI_FEATURES_SIZE);
DEBUG_LOG_FMT(IOS_WIIMOTE, "Event: SendEventReadRemoteFeatures");
DEBUG_LOG_FMT(IOS_WIIMOTE, " Connection_Handle: {:#06x}",
@@ -844,7 +854,7 @@ bool BluetoothEmuDevice::SendEventLinkKeyNotification(const u8 num_to_send)
hci_link_key_rep_cp* link_key_info =
(hci_link_key_rep_cp*)((u8*)&event_link_key->bdaddr + sizeof(hci_link_key_rep_cp) * i);
link_key_info->bdaddr = m_wiimotes[i]->GetBD();
std::copy_n(m_wiimotes[i]->GetLinkKey().begin(), HCI_KEY_SIZE, link_key_info->key);
std::memcpy(link_key_info->key, m_wiimotes[i]->GetLinkKey().data(), HCI_KEY_SIZE);
DEBUG_LOG_FMT(IOS_WIIMOTE, " bd: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
link_key_info->bdaddr[0], link_key_info->bdaddr[1], link_key_info->bdaddr[2],
+82 -36
View File
@@ -5,11 +5,9 @@
#include <cassert>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <ranges>
#include <string>
#include <utility>
@@ -92,9 +90,9 @@ std::optional<IPCReply> BluetoothRealDevice::IOCtlV(const IOCtlVRequest& request
memory.CopyFromEmu(&delete_cmd, cmd->data_address, sizeof(delete_cmd));
if (delete_cmd.delete_all != 0)
m_link_keys.clear();
m_link_key_count = 0;
else
m_link_keys.erase(delete_cmd.bdaddr);
EraseLinkKey(delete_cmd.bdaddr);
}
else if (opcode == HCI_CMD_SNIFF_MODE)
{
@@ -102,9 +100,13 @@ std::optional<IPCReply> BluetoothRealDevice::IOCtlV(const IOCtlVRequest& request
DEBUG_LOG_FMT(IOS_WIIMOTE, "HCI_CMD_SNIFF_MODE");
}
const auto payload = memory.GetSpanForAddress(cmd->data_address).first(cmd->length);
m_lib_usb_bt_adapter->SendControlTransfer(cmd->request_type, cmd->request, cmd->value,
cmd->index, payload);
// Lent from guest memory; the adapter copies it into the transfer.
const u8* const payload = memory.GetPointerForRange(cmd->data_address, cmd->length);
if (payload)
{
m_lib_usb_bt_adapter->SendControlTransfer(cmd->request_type, cmd->request, cmd->value,
cmd->index, payload, cmd->length);
}
if (opcode == HCI_CMD_RESET)
{
@@ -131,8 +133,9 @@ std::optional<IPCReply> BluetoothRealDevice::IOCtlV(const IOCtlVRequest& request
}
auto& memory = GetSystem().GetMemory();
const auto payload = memory.GetSpanForAddress(cmd->data_address).first(cmd->length);
m_lib_usb_bt_adapter->SendBulkTransfer(cmd->endpoint, payload);
const u8* const payload = memory.GetPointerForRange(cmd->data_address, cmd->length);
if (payload)
m_lib_usb_bt_adapter->SendBulkTransfer(cmd->endpoint, payload, cmd->length);
break;
}
// Incoming HCI events
@@ -210,7 +213,9 @@ auto BluetoothRealDevice::ProcessHCIEvent(BufferType buffer) -> BufferType
hci_link_key_notification_ep notification;
std::memcpy(&notification, buffer.data() + sizeof(hci_event_hdr_t), sizeof(notification));
std::ranges::copy(notification.key, std::begin(m_link_keys[notification.bdaddr]));
linkkey_t key;
std::memcpy(key.data(), notification.key, key.size());
SetLinkKey(notification.bdaddr, key);
}
if (m_lib_usb_bt_adapter->IsWiiBTModule())
@@ -280,7 +285,7 @@ auto BluetoothRealDevice::ProcessHCIEvent(BufferType buffer) -> BufferType
payload.command.latency = 10000;
payload.command.delay_variation = 0xffffffff;
m_lib_usb_bt_adapter->SendControlTransfer(Common::AsU8Span(payload));
m_lib_usb_bt_adapter->SendControlTransfer(&payload, sizeof(payload));
}
else if (event == HCI_EVENT_QOS_SETUP_COMPL)
{
@@ -299,14 +304,15 @@ void BluetoothRealDevice::TryToFillACLEndpoint()
if (!m_acl_endpoint)
return;
const auto buffer = m_lib_usb_bt_adapter->PeekACLData();
if (buffer.empty())
std::size_t size;
const u8* const buffer = m_lib_usb_bt_adapter->PeekACLData(&size);
if (!buffer)
return;
assert(buffer.size() <= m_acl_endpoint->length);
assert(size <= m_acl_endpoint->length);
m_acl_endpoint->FillBuffer(buffer.data(), buffer.size());
GetEmulationKernel().EnqueueIPCReply(m_acl_endpoint->ios_request, s32(buffer.size()));
m_acl_endpoint->FillBuffer(buffer, size);
GetEmulationKernel().EnqueueIPCReply(m_acl_endpoint->ios_request, s32(size));
m_lib_usb_bt_adapter->PopACLData();
m_acl_endpoint.reset();
@@ -376,8 +382,8 @@ void BluetoothRealDevice::TriggerSyncButtonHeldEvent()
void BluetoothRealDevice::SendHCIResetCommand()
{
INFO_LOG_FMT(IOS_WIIMOTE, "SendHCIResetCommand");
m_lib_usb_bt_adapter->SendControlTransfer(
Common::AsU8Span(hci_cmd_hdr_t{.opcode = HCI_CMD_RESET, .length = 0}));
const hci_cmd_hdr_t reset{.opcode = HCI_CMD_RESET, .length = 0};
m_lib_usb_bt_adapter->SendControlTransfer(&reset, sizeof(reset));
}
void BluetoothRealDevice::SendHCIDeleteLinkKeyCommand()
@@ -388,17 +394,18 @@ void BluetoothRealDevice::SendHCIDeleteLinkKeyCommand()
payload.command.bdaddr = {};
payload.command.delete_all = 0x01;
m_lib_usb_bt_adapter->SendControlTransfer(Common::AsU8Span(payload));
m_lib_usb_bt_adapter->SendControlTransfer(&payload, sizeof(payload));
}
bool BluetoothRealDevice::SendHCIStoreLinkKeyCommand()
{
if (m_link_keys.empty())
if (!m_link_key_count)
return false;
// Range: 0x01 to 0x0B per Bluetooth spec.
static constexpr std::size_t MAX_LINK_KEYS = 0x0B;
const auto num_link_keys = u8(std::min(m_link_keys.size(), MAX_LINK_KEYS));
const auto num_link_keys =
u8(m_link_key_count < MAX_LINK_KEYS ? m_link_key_count : MAX_LINK_KEYS);
INFO_LOG_FMT(IOS_WIIMOTE, "SendHCIStoreLinkKeyCommand num_link_keys: {}", num_link_keys);
@@ -421,11 +428,10 @@ bool BluetoothRealDevice::SendHCIStoreLinkKeyCommand()
payload.header.length = payload_size - sizeof(payload.header);
payload.command.num_keys_write = num_link_keys;
int index = 0;
for (auto& [bdaddr, linkkey] : m_link_keys | std::views::take(num_link_keys))
payload.link_keys[index++] = {bdaddr, linkkey};
for (u8 index = 0; index < num_link_keys; index++)
payload.link_keys[index] = {m_link_keys[index].bdaddr, m_link_keys[index].linkkey};
m_lib_usb_bt_adapter->SendControlTransfer(Common::AsU8Span(payload).first(payload_size));
m_lib_usb_bt_adapter->SendControlTransfer(&payload, payload_size);
return true;
}
@@ -450,15 +456,51 @@ void BluetoothRealDevice::FakeVendorCommandReply(u16 opcode, USB::V0IntrMessage&
GetEmulationKernel().EnqueueIPCReply(ctrl.ios_request, s32(sizeof(payload)));
}
void BluetoothRealDevice::FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, std::span<const u8> payload)
void BluetoothRealDevice::FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, const u8* payload,
std::size_t size)
{
const hci_event_hdr_t hci_event{.event = HCI_EVENT_VENDOR, .length = u8(payload.size())};
const hci_event_hdr_t hci_event{.event = HCI_EVENT_VENDOR, .length = u8(size)};
auto& memory = GetSystem().GetMemory();
memory.CopyToEmu(ctrl.data_address, &hci_event, sizeof(hci_event));
memory.CopyToEmu(ctrl.data_address + sizeof(hci_event), payload.data(), payload.size());
GetEmulationKernel().EnqueueIPCReply(ctrl.ios_request, s32(sizeof(hci_event) + payload.size()));
memory.CopyToEmu(ctrl.data_address + sizeof(hci_event), payload, size);
GetEmulationKernel().EnqueueIPCReply(ctrl.ios_request, s32(sizeof(hci_event) + size));
}
void BluetoothRealDevice::SetLinkKey(const bdaddr_t& bdaddr, const linkkey_t& linkkey)
{
// sorted by address: find the slot, replace or insert
std::size_t index = 0;
while (index < m_link_key_count && m_link_keys[index].bdaddr < bdaddr)
index++;
if (index < m_link_key_count && m_link_keys[index].bdaddr == bdaddr)
{
m_link_keys[index].linkkey = linkkey;
return;
}
if (m_link_key_count == MAX_STORED_LINK_KEYS)
{
WARN_LOG_FMT(IOS_WIIMOTE, "Too many link keys; dropping one.");
return;
}
for (std::size_t i = m_link_key_count; i > index; i--)
m_link_keys[i] = m_link_keys[i - 1];
m_link_keys[index] = {bdaddr, linkkey};
m_link_key_count++;
}
void BluetoothRealDevice::EraseLinkKey(const bdaddr_t& bdaddr)
{
for (std::size_t index = 0; index < m_link_key_count; index++)
{
if (m_link_keys[index].bdaddr != bdaddr)
continue;
for (std::size_t i = index + 1; i < m_link_key_count; i++)
m_link_keys[i - 1] = m_link_keys[i];
m_link_key_count--;
return;
}
}
// When the red sync button is pressed, a HCI event is generated.
@@ -467,7 +509,7 @@ void BluetoothRealDevice::FakeSyncButtonPressedEvent(USB::V0IntrMessage& ctrl)
{
NOTICE_LOG_FMT(IOS_WIIMOTE, "Faking 'sync button pressed' (0x08) event packet");
constexpr u8 payload[1] = {0x08};
FakeSyncButtonEvent(ctrl, payload);
FakeSyncButtonEvent(ctrl, payload, sizeof(payload));
SetSyncButtonState(SyncButtonState::Ignored);
}
@@ -476,7 +518,7 @@ void BluetoothRealDevice::FakeSyncButtonHeldEvent(USB::V0IntrMessage& ctrl)
{
NOTICE_LOG_FMT(IOS_WIIMOTE, "Faking 'sync button held' (0x09) event packet");
constexpr u8 payload[1] = {0x09};
FakeSyncButtonEvent(ctrl, payload);
FakeSyncButtonEvent(ctrl, payload, sizeof(payload));
SetSyncButtonState(SyncButtonState::Ignored);
}
@@ -501,8 +543,9 @@ void BluetoothRealDevice::LoadLinkKeys()
continue;
}
auto& mac = *address;
std::ranges::reverse(mac);
bdaddr_t mac;
for (std::size_t i = 0; i < mac.size(); i++)
mac[i] = (*address)[mac.size() - 1 - i];
const auto key_string = pair.substr(index + 1);
linkkey_t key{};
@@ -510,18 +553,21 @@ void BluetoothRealDevice::LoadLinkKeys()
for (size_t i = 0; i < key_string.length() && pos != key.size(); i += 2)
TryParse(std::string(key_string.substr(i, 2)), &key[pos++], 16);
m_link_keys[mac] = key;
SetLinkKey(mac, key);
}
}
void BluetoothRealDevice::SaveLinkKeys()
{
std::string config_string;
for (const auto& [bdaddr, linkkey] : m_link_keys)
for (std::size_t index = 0; index < m_link_key_count; index++)
{
const bdaddr_t& bdaddr = m_link_keys[index].bdaddr;
const linkkey_t& linkkey = m_link_keys[index].linkkey;
bdaddr_t address;
// Reverse the address so that it is stored in the correct order in the config file
std::ranges::reverse_copy(bdaddr, address.begin());
for (std::size_t i = 0; i < address.size(); i++)
address[i] = bdaddr[address.size() - 1 - i];
config_string += Common::MacAddressToString(address);
config_string += '=';
+13 -4
View File
@@ -6,7 +6,6 @@
#if defined(__LIBUSB__)
#include <array>
#include <functional>
#include <map>
#include <memory>
#include <queue>
#include <string>
@@ -75,10 +74,20 @@ private:
// Enqueued when we observe a command to which we need to fake a reply.
std::queue<std::function<void(USB::V0IntrMessage&)>> m_fake_replies;
// This stores the address of paired devices and associated link keys.
// This stores the address of paired devices and associated link keys, sorted by address.
// It is needed because some adapters forget all stored link keys when they are reset,
// which breaks pairings because the Wii relies on the Bluetooth module to remember them.
std::map<bdaddr_t, linkkey_t> m_link_keys;
struct LinkKeyEntry
{
bdaddr_t bdaddr;
linkkey_t linkkey;
};
static constexpr std::size_t MAX_STORED_LINK_KEYS = 64;
LinkKeyEntry m_link_keys[MAX_STORED_LINK_KEYS];
std::size_t m_link_key_count = 0;
void SetLinkKey(const bdaddr_t& bdaddr, const linkkey_t& linkkey);
void EraseLinkKey(const bdaddr_t& bdaddr);
// Endpoints provided by the emulated software.
std::unique_ptr<USB::V0IntrMessage> m_hci_endpoint;
@@ -97,7 +106,7 @@ private:
void FakeVendorCommandReply(u16 opcode, USB::V0IntrMessage& ctrl);
void FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, std::span<const u8> payload);
void FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, const u8* payload, std::size_t size);
void FakeSyncButtonPressedEvent(USB::V0IntrMessage& ctrl);
void FakeSyncButtonHeldEvent(USB::V0IntrMessage& ctrl);
@@ -5,10 +5,8 @@
#include <cassert>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <ranges>
#include <utility>
#include <libusb.h>
@@ -208,9 +206,10 @@ void LibUSBBluetoothAdapter::Close()
// Cancel all LibUSB transfers, wait for their callbacks and clean up all the buffers. A
// callback that read the run flag before it was cleared may resubmit its transfer after the
// cancel, so whatever is still in flight is cancelled again on every pass.
while (!m_transfer_buffers.empty())
while (m_live_transfers)
{
std::ranges::for_each(m_transfer_buffers | std::ranges::views::keys, libusb_cancel_transfer);
for (FreeNode* node = m_live_transfers; node; node = node->next)
libusb_cancel_transfer(node->transfer);
const int key = retro_eventcount_prepare_wait(&m_adapter_event.ec);
if (!mpsc_stack_empty(&m_transfers_to_free))
retro_eventcount_cancel_wait(&m_adapter_event.ec);
@@ -287,32 +286,33 @@ auto LibUSBBluetoothAdapter::ReceiveHCIEvent() -> BufferType
const auto event = buffer[0];
if (event == HCI_EVENT_COMMAND_COMPL)
{
AcknowledgeCommand<hci_command_compl_ep>(buffer);
AcknowledgeCommand<hci_command_compl_ep>(buffer.data(), buffer.size());
}
else if (event == HCI_EVENT_COMMAND_STATUS)
{
AcknowledgeCommand<hci_command_status_ep>(buffer);
AcknowledgeCommand<hci_command_status_ep>(buffer.data(), buffer.size());
}
Update();
return buffer;
}
std::span<const u8> LibUSBBluetoothAdapter::PeekACLData()
const u8* LibUSBBluetoothAdapter::PeekACLData(std::size_t* size)
{
const u16 length = PeekPacketLength(&m_acl_data_queue);
*size = length;
if (length == 0)
return {};
return nullptr;
m_acl_peeked = sizeof(length) + length;
const void* span;
if (retro_spsc_read_begin(&m_acl_data_queue, &span) >= m_acl_peeked)
return {static_cast<const u8*>(span) + sizeof(length), length};
const void* ring;
if (retro_spsc_read_begin(&m_acl_data_queue, &ring) >= m_acl_peeked)
return static_cast<const u8*>(ring) + sizeof(length);
// Only a ring without a mirror splits a packet at its end.
m_acl_bounce.resize(m_acl_peeked);
retro_spsc_peek(&m_acl_data_queue, m_acl_bounce.data(), m_acl_peeked);
return {m_acl_bounce.data() + sizeof(length), length};
return m_acl_bounce.data() + sizeof(length);
}
void LibUSBBluetoothAdapter::PopACLData()
@@ -326,23 +326,27 @@ bool LibUSBBluetoothAdapter::IsControllerReadyForCommand() const
}
template <typename EventType>
void LibUSBBluetoothAdapter::AcknowledgeCommand(std::span<const u8> buffer)
void LibUSBBluetoothAdapter::AcknowledgeCommand(const u8* buffer, std::size_t size)
{
if (buffer.size() < sizeof(hci_event_hdr_t) + sizeof(EventType)) [[unlikely]]
if (size < sizeof(hci_event_hdr_t) + sizeof(EventType)) [[unlikely]]
{
WARN_LOG_FMT(IOS_WIIMOTE, "Undersized HCI event");
return;
}
EventType ev;
std::memcpy(&ev, buffer.data() + sizeof(hci_event_hdr_t), sizeof(ev));
std::memcpy(&ev, buffer + sizeof(hci_event_hdr_t), sizeof(ev));
const auto it =
std::ranges::find(m_unacknowledged_commands, ev.opcode, &OutstandingCommand::opcode);
if (it != m_unacknowledged_commands.end())
std::size_t index = 0;
while (index < m_unacknowledged_commands.size() &&
m_unacknowledged_commands[index].opcode != ev.opcode)
{
index++;
}
if (index < m_unacknowledged_commands.size())
{
DEBUG_LOG_FMT(IOS_WIIMOTE, "HCI command acknowledged: 0x{:04x}", ev.opcode);
m_unacknowledged_commands.erase(it);
m_unacknowledged_commands.erase(m_unacknowledged_commands.begin() + index);
}
else if (ev.opcode != 0x0000)
{
@@ -354,28 +358,45 @@ void LibUSBBluetoothAdapter::AcknowledgeCommand(std::span<const u8> buffer)
libusb_transfer* LibUSBBluetoothAdapter::AllocateTransfer(std::size_t buffer_size)
{
auto& item = *m_transfer_buffers
.emplace(libusb_alloc_transfer(0),
Common::UniqueBuffer<u8>{BUFFER_HEADER_SIZE + buffer_size})
.first;
// One block: the free-list node and live-list links, then the transfer's bytes.
u8* const block = static_cast<u8*>(std::malloc(BUFFER_HEADER_SIZE + buffer_size));
libusb_transfer* const transfer = block ? libusb_alloc_transfer(0) : nullptr;
if (!transfer)
{
std::free(block);
return nullptr;
}
auto* const transfer = item.first;
transfer->buffer = item.second.get() + BUFFER_HEADER_SIZE;
FreeNode* const node = reinterpret_cast<FreeNode*>(block);
node->transfer = transfer;
node->prev = nullptr;
node->next = m_live_transfers;
if (m_live_transfers)
m_live_transfers->prev = node;
m_live_transfers = node;
transfer->buffer = block + BUFFER_HEADER_SIZE;
return transfer;
}
void LibUSBBluetoothAdapter::FreeTransfer(libusb_transfer* transfer)
{
FreeNode* const node = reinterpret_cast<FreeNode*>(transfer->buffer - BUFFER_HEADER_SIZE);
if (node->prev)
node->prev->next = node->next;
else
m_live_transfers = node->next;
if (node->next)
node->next->prev = node->prev;
libusb_free_transfer(transfer);
m_transfer_buffers.erase(transfer);
std::free(node);
}
// Any thread.
void LibUSBBluetoothAdapter::QueueFree(libusb_transfer* transfer)
{
// node->transfer was set at allocation; only the free-list link is written here.
FreeNode* const node = reinterpret_cast<FreeNode*>(transfer->buffer - BUFFER_HEADER_SIZE);
node->transfer = transfer;
mpsc_stack_push(&m_transfers_to_free, &node->link);
retro_eventcount_notify(&m_adapter_event.ec);
}
@@ -420,30 +441,34 @@ void LibUSBBluetoothAdapter::SubmitTransfer(libusb_transfer* transfer)
QueueFree(transfer);
}
void LibUSBBluetoothAdapter::SendBulkTransfer(u8 endpoint, std::span<const u8> data)
void LibUSBBluetoothAdapter::SendBulkTransfer(u8 endpoint, const u8* data, std::size_t size)
{
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleOutputTransfer>();
auto* const transfer = AllocateTransfer(data.size());
libusb_fill_bulk_transfer(transfer, m_handle, endpoint, transfer->buffer, int(data.size()),
callback, this, 0);
auto* const transfer = AllocateTransfer(size);
if (!transfer)
return;
libusb_fill_bulk_transfer(transfer, m_handle, endpoint, transfer->buffer, int(size), callback,
this, 0);
std::ranges::copy(data, transfer->buffer);
std::memcpy(transfer->buffer, data, size);
SubmitTransfer(transfer);
}
void LibUSBBluetoothAdapter::SendControlTransfer(u8 type, u8 request, u8 value, u8 index,
std::span<const u8> data)
const u8* data, std::size_t size)
{
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleOutputTransfer>();
auto* const transfer = AllocateTransfer(data.size() + LIBUSB_CONTROL_SETUP_SIZE);
auto* const transfer = AllocateTransfer(size + LIBUSB_CONTROL_SETUP_SIZE);
if (!transfer)
return;
libusb_fill_control_setup(transfer->buffer, type, request, value, index, u16(data.size()));
libusb_fill_control_setup(transfer->buffer, type, request, value, index, u16(size));
libusb_fill_control_transfer(transfer, m_handle, transfer->buffer, callback, this, 0);
std::ranges::copy(data, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE);
std::memcpy(transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, data, size);
if (IsControllerReadyForCommand())
{
@@ -456,16 +481,18 @@ void LibUSBBluetoothAdapter::SendControlTransfer(u8 type, u8 request, u8 value,
}
}
void LibUSBBluetoothAdapter::SendControlTransfer(std::span<const u8> data)
void LibUSBBluetoothAdapter::SendControlTransfer(const void* data, std::size_t size)
{
SendControlTransfer(REQUEST_TYPE, 0, 0, 0, data);
SendControlTransfer(REQUEST_TYPE, 0, 0, 0, static_cast<const u8*>(data), size);
}
bool LibUSBBluetoothAdapter::SendBlockingCommand(std::span<const u8> data, std::span<u8> response)
bool LibUSBBluetoothAdapter::SendBlockingCommand(const void* data, std::size_t size,
void* response, std::size_t response_size)
{
SendControlTransfer(data);
SendControlTransfer(data, size);
const hci_cmd_hdr_t cmd = Common::BitCastPtr<hci_cmd_hdr_t>(data.data());
hci_cmd_hdr_t cmd;
std::memcpy(&cmd, data, sizeof(cmd));
while (AreCommandsPendingResponse())
{
@@ -505,14 +532,14 @@ bool LibUSBBluetoothAdapter::SendBlockingCommand(std::span<const u8> data, std::
if (compl_event.opcode != cmd.opcode)
continue;
if (event.size() < sizeof(hci_event_hdr_t) + sizeof(hci_command_compl_ep) + response.size())
if (event.size() < sizeof(hci_event_hdr_t) + sizeof(hci_command_compl_ep) + response_size)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Undersized command result");
break;
}
std::copy_n(event.data() + sizeof(hci_event_hdr_t) + sizeof(hci_command_compl_ep),
response.size(), response.data());
std::memcpy(response, event.data() + sizeof(hci_event_hdr_t) + sizeof(hci_command_compl_ep),
response_size);
return true;
}
@@ -3,9 +3,7 @@
#pragma once
#include <map>
#include <queue>
#include <span>
#include <string>
#include <vector>
@@ -48,20 +46,22 @@ public:
// ReceiveHCIEvent is expected to be called regularly to do some internal processing.
[[nodiscard]] BufferType ReceiveHCIEvent();
// The ACL packet at the front of the queue, read in place; empty when nothing is queued.
// The ACL packet at the front of the queue, read in place; nullptr when nothing is queued.
// PopACLData releases it.
[[nodiscard]] std::span<const u8> PeekACLData();
[[nodiscard]] const u8* PeekACLData(std::size_t* size);
void PopACLData();
// Submit a transfer now; libusb completes it asynchronously.
void SendBulkTransfer(u8 endpoint, std::span<const u8> data);
void SendControlTransfer(u8 type, u8 request, u8 value, u8 index, std::span<const u8> data);
void SendControlTransfer(std::span<const u8> data);
void SendBulkTransfer(u8 endpoint, const u8* data, std::size_t size);
void SendControlTransfer(u8 type, u8 request, u8 value, u8 index, const u8* data,
std::size_t size);
void SendControlTransfer(const void* data, std::size_t size);
// Blocks and eats events until a command complete event of the correct opcode is received.
// Returns true on success or false on error or when the device stops responding.
// The written response does not include the event header or command complete data.
bool SendBlockingCommand(std::span<const u8> data, std::span<u8> response);
bool SendBlockingCommand(const void* data, std::size_t size, void* response,
std::size_t response_size);
bool IsWiiBTModule() const;
@@ -91,11 +91,14 @@ private:
retro_atomic_int_t m_input_failed;
// Callback and worker threads push transfers here, lock-free, when they are ready for clean up.
// The node lives in the front of the transfer's own buffer, so this allocates nothing.
// The node lives in the front of the transfer's own buffer, so this allocates nothing; the
// same header links every live transfer for Close to cancel.
struct FreeNode
{
mpsc_stack_node_t link;
libusb_transfer* transfer;
FreeNode* prev;
FreeNode* next;
};
static constexpr std::size_t BUFFER_HEADER_SIZE = (sizeof(FreeNode) + 15) & ~std::size_t{15};
void QueueFree(libusb_transfer* transfer);
@@ -107,8 +110,8 @@ private:
std::size_t m_acl_peeked = 0;
std::vector<u8> m_acl_bounce;
// All transfers are alloc'd and kept here until complete.
std::map<libusb_transfer*, Common::UniqueBuffer<u8>> m_transfer_buffers;
// All transfers are alloc'd and linked here until complete.
FreeNode* m_live_transfers = nullptr;
// Set to reduce OSD and log spam.
bool m_showed_failed_transfer = false;
@@ -144,7 +147,7 @@ private:
void PushHCICommand(libusb_transfer*);
template <typename EventType>
void AcknowledgeCommand(std::span<const u8> buffer);
void AcknowledgeCommand(const u8* buffer, std::size_t size);
libusb_transfer* AllocateTransfer(std::size_t buffer_size);
void Close();
@@ -8,7 +8,6 @@
#include "Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.h"
#include <algorithm>
#include "Common/BitUtils.h"
#include "Common/FileUtil.h"
@@ -203,22 +202,27 @@ bool IsKnownRealtekBluetoothDevice(u16 vid, u16 pid)
{0x0cb5, 0xc547},
};
return std::ranges::find(realtek_ids, std::pair(vid, pid)) != std::end(realtek_ids);
for (const auto& id : realtek_ids)
{
if (id.first == vid && id.second == pid)
return true;
}
return false;
}
// Returns the data to be loaded, or an empty buffer on error.
static Common::UniqueBuffer<char> ParseFirmware(std::span<const char> fw_data,
std::span<const char> cfg_data,
static Common::UniqueBuffer<char> ParseFirmware(const char* fw_data, std::size_t fw_size,
const char* cfg_data, std::size_t cfg_size,
const hci_read_local_ver_rp& local_ver,
u8 rom_version)
{
if (fw_data.size() < sizeof(rtl_epatch_header))
if (fw_size < sizeof(rtl_epatch_header))
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Undersized firmware file");
return {};
}
const rtl_epatch_header epatch_info = Common::BitCastPtr<rtl_epatch_header>(fw_data.data());
const rtl_epatch_header epatch_info = Common::BitCastPtr<rtl_epatch_header>(fw_data);
if (std::memcmp(epatch_info.signature.data(), RTL_EPATCH_SIGNATURE.data(),
epatch_info.signature.size()) != 0)
{
@@ -240,8 +244,9 @@ static Common::UniqueBuffer<char> ParseFirmware(std::span<const char> fw_data,
INFO_LOG_FMT(IOS_WIIMOTE, "fw_version={} num_patches={}", epatch_info.fw_version, num_patches);
static constexpr u8 EXTENSION_SIGNATURE[] = {0x51, 0x04, 0xfd, 0x77};
const auto extension = fw_data.last(std::size(EXTENSION_SIGNATURE));
if (std::memcmp(extension.data(), EXTENSION_SIGNATURE, extension.size()) != 0)
const char* const extension = fw_data + fw_size - sizeof(EXTENSION_SIGNATURE);
if (fw_size < sizeof(rtl_epatch_header) + sizeof(EXTENSION_SIGNATURE) ||
std::memcmp(extension, EXTENSION_SIGNATURE, sizeof(EXTENSION_SIGNATURE)) != 0)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Bad extension section signature");
return {};
@@ -249,7 +254,7 @@ static Common::UniqueBuffer<char> ParseFirmware(std::span<const char> fw_data,
// Look for the project ID starting from the end of the file.
int project_id = -1;
for (auto iter = extension.begin(); iter >= fw_data.begin() + (sizeof(rtl_epatch_header) + 3);)
for (const char* iter = extension; iter >= fw_data + (sizeof(rtl_epatch_header) + 3);)
{
const u8 opcode = *--iter;
const u8 length = *--iter;
@@ -289,9 +294,16 @@ static Common::UniqueBuffer<char> ParseFirmware(std::span<const char> fw_data,
};
// Verify that the project ID is what we expect for our LMP subversion.
const auto* const it =
std::ranges::find(subver_project_ids, project_id, &LMPSubverProjectID::project_id);
if (it == std::end(subver_project_ids) || it->lmp_subver != local_ver.lmp_subversion)
const LMPSubverProjectID* it = nullptr;
for (const LMPSubverProjectID& entry : subver_project_ids)
{
if (entry.project_id == project_id)
{
it = &entry;
break;
}
}
if (!it || it->lmp_subver != local_ver.lmp_subversion)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Unexpected project_id: {}", project_id);
return {};
@@ -299,13 +311,13 @@ static Common::UniqueBuffer<char> ParseFirmware(std::span<const char> fw_data,
// Structure is:
// u16 chip_ids[num_patches]; u16 patch_lengths[num_patches]; u32 patch_offsets[num_patches];
if (fw_data.size() <
if (fw_size <
sizeof(rtl_epatch_header) + num_patches * (sizeof(u16) + sizeof(u16) + sizeof(u32)))
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Undersized firmware file");
return {};
}
const auto* chip_id_base = fw_data.data() + sizeof(rtl_epatch_header);
const char* chip_id_base = fw_data + sizeof(rtl_epatch_header);
const auto* const patch_length_base = chip_id_base + (sizeof(u16) * num_patches);
const auto* const patch_offset_base = patch_length_base + (sizeof(u16) * num_patches);
for (int i = 0; i != num_patches; ++i)
@@ -318,21 +330,22 @@ static Common::UniqueBuffer<char> ParseFirmware(std::span<const char> fw_data,
INFO_LOG_FMT(IOS_WIIMOTE, "Patch length:{} offset:{}", patch_length, patch_offset);
if (patch_offset + patch_length > fw_data.size())
if (patch_offset + patch_length > fw_size)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Undersized firmware file");
return {};
}
auto result = Common::UniqueBuffer<char>{patch_length + cfg_data.size()};
std::copy_n(fw_data.data() + patch_offset, patch_length - sizeof(fw_version), result.data());
auto result = Common::UniqueBuffer<char>{patch_length + cfg_size};
std::memcpy(result.data(), fw_data + patch_offset, patch_length - sizeof(fw_version));
// The last 4 bytes are replaced with fw_version.
std::memcpy(result.data() + patch_length - sizeof(fw_version), &fw_version,
sizeof(fw_version));
// Append the config binary.
std::ranges::copy(cfg_data, result.data() + patch_length);
if (cfg_size)
std::memcpy(result.data() + patch_length, cfg_data, cfg_size);
return result;
}
@@ -343,58 +356,59 @@ static Common::UniqueBuffer<char> ParseFirmware(std::span<const char> fw_data,
}
// Loads the provided data on the adapter.
static bool LoadFirmware(LibUSBBluetoothAdapter& adapter, std::span<const char> data)
static bool WriteFirmwareBlock(LibUSBBluetoothAdapter& adapter, const void* block,
std::size_t size)
{
if (data.empty())
rtl_download_response result{};
if (!adapter.SendBlockingCommand(block, size, &result, sizeof(result)))
return false;
if (result.status != 0x00)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Bad load firmware status: 0x{:02x}", result.status);
return false;
}
return true;
}
static bool LoadFirmware(LibUSBBluetoothAdapter& adapter, const char* data, std::size_t size)
{
if (!size)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Firmware data is empty");
return false;
}
INFO_LOG_FMT(IOS_WIIMOTE, "Loading firmware, total size: {}", data.size());
INFO_LOG_FMT(IOS_WIIMOTE, "Loading firmware, total size: {}", size);
HCICommandPayload<0xfc20, rtl_download_cmd> payload{};
const auto write_block = [&](std::span<const u8> block) {
rtl_download_response result{};
if (!adapter.SendBlockingCommand(block, Common::AsWritableU8Span(result)))
return false;
if (result.status != 0x00)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Bad load firmware status: 0x{:02x}", result.status);
return false;
}
return true;
};
// Write all blocks except the last one.
auto block_iter = data.begin();
while (data.end() - block_iter > rtl_download_cmd::DATA_LEN)
std::size_t offset = 0;
while (size - offset > rtl_download_cmd::DATA_LEN)
{
const auto block_end = block_iter + rtl_download_cmd::DATA_LEN;
std::copy(block_iter, block_end, payload.command.data.data());
std::memcpy(payload.command.data.data(), data + offset, rtl_download_cmd::DATA_LEN);
if (!write_block(Common::AsU8Span(payload)))
if (!WriteFirmwareBlock(adapter, &payload, sizeof(payload)))
return false;
if (++payload.command.index == 0x80)
payload.command.index = 1;
block_iter = block_end;
offset += rtl_download_cmd::DATA_LEN;
}
// Write the last block.
const auto last_block_size = data.end() - block_iter;
const auto bytes_short_of_full_block = rtl_download_cmd::DATA_LEN - last_block_size;
const std::size_t last_block_size = size - offset;
const std::size_t bytes_short_of_full_block = rtl_download_cmd::DATA_LEN - last_block_size;
// Mark data end.
payload.command.index |= 0x80u;
payload.header.length -= bytes_short_of_full_block;
std::copy(block_iter, data.end(), payload.command.data.data());
payload.header.length -= u8(bytes_short_of_full_block);
std::memcpy(payload.command.data.data(), data + offset, last_block_size);
if (!write_block(Common::AsU8Span(payload).first(sizeof(payload) - bytes_short_of_full_block)))
if (!WriteFirmwareBlock(adapter, &payload, sizeof(payload) - bytes_short_of_full_block))
return false;
NOTICE_LOG_FMT(IOS_WIIMOTE, "Loading firmware complete");
@@ -521,9 +535,9 @@ bool InitializeRealtekBluetoothDevice(LibUSBBluetoothAdapter& adapter)
{
// Read local version.
hci_read_local_ver_rp local_ver{};
if (!adapter.SendBlockingCommand(
Common::AsU8Span(hci_cmd_hdr_t{.opcode = HCI_CMD_READ_LOCAL_VER, .length = 0}),
Common::AsWritableU8Span(local_ver)))
const hci_cmd_hdr_t read_local_ver{.opcode = HCI_CMD_READ_LOCAL_VER, .length = 0};
if (!adapter.SendBlockingCommand(&read_local_ver, sizeof(read_local_ver), &local_ver,
sizeof(local_ver)))
{
return false;
}
@@ -581,13 +595,14 @@ bool InitializeRealtekBluetoothDevice(LibUSBBluetoothAdapter& adapter)
return false;
}
return LoadFirmware(adapter, fw_data);
return LoadFirmware(adapter, fw_data.data(), fw_data.size());
}
// Read ROM version.
rtl_rom_version_evt read_rom_ver{};
if (!adapter.SendBlockingCommand(Common::AsU8Span(hci_cmd_hdr_t{.opcode = 0xfc6d, .length = 0}),
Common::AsWritableU8Span(read_rom_ver)))
const hci_cmd_hdr_t read_rom{.opcode = 0xfc6d, .length = 0};
if (!adapter.SendBlockingCommand(&read_rom, sizeof(read_rom), &read_rom_ver,
sizeof(read_rom_ver)))
{
return false;
}
@@ -604,6 +619,7 @@ bool InitializeRealtekBluetoothDevice(LibUSBBluetoothAdapter& adapter)
std::string config_data;
File::ReadFileToString(config_path, config_data);
const auto firmware = ParseFirmware(fw_data, config_data, local_ver, rom_version);
return LoadFirmware(adapter, firmware);
const auto firmware = ParseFirmware(fw_data.data(), fw_data.size(), config_data.data(),
config_data.size(), local_ver, rom_version);
return LoadFirmware(adapter, firmware.data(), firmware.size());
}
@@ -997,7 +997,7 @@ void WiimoteDevice::InterruptDataInputCallback(u8 hid_type, const u8* data, u32
data_frame.header.dcid = channel->remote_cid;
data_frame.header.length = u16(sizeof(hid_type) + size);
data_frame.hid_type = hid_type;
std::copy_n(data, size, data_frame.data.data());
std::memcpy(data_frame.data.data(), data, size);
const u32 data_frame_size = data_frame.header.length + sizeof(l2cap_hdr_t);