mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-02-16 10:02:58 -07:00
AMMediaboard: IP override improvements and other cleanups.
Overrides now also apply in reverse for accepted connections. IP Override UI now includes a description. Mario Kart Arcade GP 4x Multicabinet now works on one PC without any tricky IP configurations. Added AMMBCommandBind, NetDIMMBind, and some helper functions. Removed redundant settings.
This commit is contained in:
parent
cf8573421c
commit
d893565b78
@ -645,26 +645,40 @@ const std::array<Info<s16>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUM
|
||||
|
||||
static std::string GetDefaultTriforceIPOverrides()
|
||||
{
|
||||
#if defined(ANDROID)
|
||||
return "0.0.0.0/0=127.0.0.1";
|
||||
#else
|
||||
constexpr std::string_view entries[] = {
|
||||
|
||||
// Mario Kart Arcade GP 1 + 2
|
||||
// This config allows for 4x Multicabinet on the same PC.
|
||||
"192.168.29.150:5000-5008=127.0.0.1:5000-5008 MarioKart Seat #1 Static",
|
||||
"192.168.29.151:5000-5008=127.0.0.1:5010-5018 MarioKart Seat #2 Static",
|
||||
"192.168.29.152:5000-5008=127.0.0.1:5020-5028 MarioKart Seat #3 Static",
|
||||
"192.168.29.153:5000-5008=127.0.0.1:5030-5038 MarioKart Seat #4 Static",
|
||||
// Ephemeral ports are constrained to differentiate incoming connections.
|
||||
"192.168.29.150=127.0.0.1:50000-50999 MarioKart Seat #1 Ephemeral",
|
||||
"192.168.29.151=127.0.0.1:51000-51999 MarioKart Seat #2 Ephemeral",
|
||||
"192.168.29.152=127.0.0.1:52000-52999 MarioKart Seat #3 Ephemeral",
|
||||
"192.168.29.153=127.0.0.1:53000-53999 MarioKart Seat #4 Ephemeral",
|
||||
// The cameras.
|
||||
"192.168.29.104-107=127.0.0.1 MarioKart namcam2",
|
||||
|
||||
// CyCraft Connect IP
|
||||
"192.168.11.111=127.0.0.1",
|
||||
"192.168.11.0/24=127.0.0.1 CyCraft",
|
||||
|
||||
// Key of Avalon
|
||||
// Note: The server and client can't run on the same system,
|
||||
// but it's replaced with 127.0.0.1 here just for reference.
|
||||
"192.168.13.1=127.0.0.1",
|
||||
// The Key of Avalon
|
||||
// This config isn't usable as-is. It's just here for reference.
|
||||
"192.168.13.0/24=127.0.0.1 The Key of Avalon",
|
||||
|
||||
// NAMCO Camera
|
||||
"192.168.29.104-108=127.0.0.1",
|
||||
|
||||
// All.Net Connect IP
|
||||
"192.168.150.16=127.0.0.1",
|
||||
// Sega ALL.Net
|
||||
"192.168.150.0/24=127.0.0.1 ALL.Net",
|
||||
};
|
||||
|
||||
return fmt::format("{}", fmt::join(entries, ","));
|
||||
#endif
|
||||
}
|
||||
|
||||
const Info<std::string> MAIN_TRIFORCE_BIND_IP{{System::Main, "Core", "TriforceBindIP"}, "0.0.0.0"};
|
||||
const Info<std::string> MAIN_TRIFORCE_IP_OVERRIDES{{System::Main, "Core", "TriforceIPOverrides"},
|
||||
GetDefaultTriforceIPOverrides()};
|
||||
|
||||
|
||||
@ -386,7 +386,6 @@ extern const std::array<Info<std::string>, EMULATED_LOGITECH_MIC_COUNT>
|
||||
extern const std::array<Info<bool>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED;
|
||||
extern const std::array<Info<s16>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER;
|
||||
|
||||
extern const Info<std::string> MAIN_TRIFORCE_BIND_IP;
|
||||
extern const Info<std::string> MAIN_TRIFORCE_IP_OVERRIDES;
|
||||
|
||||
// GameCube path utility functions
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <random>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@ -514,95 +515,64 @@ static int PlatformPoll(std::span<WSAPOLLFD> pfds, std::chrono::milliseconds tim
|
||||
#endif
|
||||
}
|
||||
|
||||
static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, socklen_t* len)
|
||||
std::optional<ParsedIPOverride> ParseIPOverride(std::string_view str)
|
||||
{
|
||||
const auto host_socket = GetHostSocket(guest_socket);
|
||||
WSAPOLLFD pfds[1]{{.fd = host_socket, .events = POLLIN}};
|
||||
|
||||
// FYI: Currently using a 0ms timeout to make accept calls always non-blocking.
|
||||
constexpr auto timeout = std::chrono::milliseconds{0};
|
||||
|
||||
const int result = PlatformPoll(pfds, timeout);
|
||||
|
||||
if (result > 0 && (pfds[0].revents & POLLIN) != 0)
|
||||
{
|
||||
const auto client_sock = accept_(host_socket, addr, len);
|
||||
if (client_sock == INVALID_GUEST_SOCKET)
|
||||
{
|
||||
ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: accept() failed in NetDIMMAccept ({})",
|
||||
Common::StrNetworkError());
|
||||
s_last_error = SOCKET_ERROR;
|
||||
return INVALID_GUEST_SOCKET;
|
||||
}
|
||||
s_last_error = SSC_SUCCESS;
|
||||
return client_sock;
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
// Timeout
|
||||
s_last_error = SSC_EWOULDBLOCK;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: poll() failed in NetDIMMAccept ({})",
|
||||
Common::StrNetworkError());
|
||||
s_last_error = SOCKET_ERROR;
|
||||
}
|
||||
return INVALID_GUEST_SOCKET;
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string_view, std::string_view>> ParseIPOverride(std::string_view str)
|
||||
{
|
||||
// Ignore everything after a space. Future proofing to allow for a comment/description string.
|
||||
// Everything after a space is the description.
|
||||
const auto ip_pair_str = std::string_view{str.begin(), std::ranges::find(str, ' ')};
|
||||
|
||||
const auto parts = SplitStringIntoArray<2>(ip_pair_str, '=');
|
||||
if (parts.has_value())
|
||||
return std::make_pair((*parts)[0], (*parts)[1]);
|
||||
if (!parts.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
return std::nullopt;
|
||||
const bool have_description = ip_pair_str.size() != str.size();
|
||||
|
||||
return ParsedIPOverride{
|
||||
.original = (*parts)[0],
|
||||
.replacement = (*parts)[1],
|
||||
.description = have_description ? str.substr(ip_pair_str.size() + 1) : std::string_view{},
|
||||
};
|
||||
}
|
||||
|
||||
struct IPAddressOverride
|
||||
{
|
||||
Common::IPv4PortRange match;
|
||||
Common::IPv4PortRange original;
|
||||
Common::IPv4PortRange replacement;
|
||||
|
||||
// Caller should check if it matches first!
|
||||
Common::IPv4Port ApplyOverride(Common::IPv4Port subject) const
|
||||
{
|
||||
// This logic could probably be better.
|
||||
// Ranges of different sizes will be weird in general.
|
||||
|
||||
const auto replacement_first_ip_u32 = replacement.first.GetIPAddressValue();
|
||||
const auto ip_count = 1u + u64(replacement.last.GetIPAddressValue()) - replacement_first_ip_u32;
|
||||
const auto result_ip =
|
||||
u32(replacement_first_ip_u32 +
|
||||
((subject.GetIPAddressValue() - match.first.GetIPAddressValue()) % ip_count));
|
||||
((subject.GetIPAddressValue() - original.first.GetIPAddressValue()) % ip_count));
|
||||
|
||||
Common::IPv4Port result{
|
||||
.ip_address = std::bit_cast<Common::IPAddress>(Common::BigEndianValue(result_ip)),
|
||||
};
|
||||
subject.ip_address = std::bit_cast<Common::IPAddress>(Common::BigEndianValue{result_ip});
|
||||
|
||||
const auto replacement_first_port_u16 = replacement.first.GetPortValue();
|
||||
const auto port_count = 1u + u32(replacement.last.GetPortValue()) - replacement_first_port_u16;
|
||||
|
||||
if (port_count == 65536)
|
||||
// If the replacement includes all ports then we don't alter the port.
|
||||
// This allows "10.0.0.1:80-88=10.0.0.2" to have the expected behavior.
|
||||
if (port_count != 65536u)
|
||||
{
|
||||
// If the replacement includes all ports then don't alter the port.
|
||||
// This allows "1.1.1.1:80=2.2.2.2" to do the obvious thing.
|
||||
// This logic could probably be better.
|
||||
// Ranges of different sizes will be weird in general.
|
||||
|
||||
result.port = subject.port;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto result_port =
|
||||
const auto result_port_u16 =
|
||||
u16(replacement_first_port_u16 +
|
||||
((subject.GetPortValue() - match.first.GetPortValue()) % port_count));
|
||||
result.port = std::bit_cast<u16>(Common::BigEndianValue(result_port));
|
||||
((subject.GetPortValue() - original.first.GetPortValue()) % port_count));
|
||||
subject.port = std::bit_cast<u16>(Common::BigEndianValue{result_port_u16});
|
||||
}
|
||||
|
||||
return result;
|
||||
return subject;
|
||||
}
|
||||
|
||||
Common::IPv4Port ReverseOverride(Common::IPv4Port subject) const
|
||||
{
|
||||
// Low effort implementation..
|
||||
return IPAddressOverride{.original = replacement, .replacement = original}.ApplyOverride(
|
||||
subject);
|
||||
}
|
||||
};
|
||||
|
||||
@ -618,12 +588,12 @@ static IPOverrides GetIPOverrides()
|
||||
const auto parts = ParseIPOverride(ip_pair_str);
|
||||
if (parts.has_value())
|
||||
{
|
||||
const auto match = Common::StringToIPv4PortRange(parts->first);
|
||||
const auto replacement = Common::StringToIPv4PortRange(parts->second);
|
||||
const auto original = Common::StringToIPv4PortRange(parts->original);
|
||||
const auto replacement = Common::StringToIPv4PortRange(parts->replacement);
|
||||
|
||||
if (match.has_value() && replacement.has_value())
|
||||
if (original.has_value() && replacement.has_value())
|
||||
{
|
||||
result.emplace_back(*match, *replacement);
|
||||
result.emplace_back(*original, *replacement);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -634,18 +604,62 @@ static IPOverrides GetIPOverrides()
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::optional<Common::IPv4Port> GetAdjustedIPv4PortFromConfig(Common::IPv4Port subject)
|
||||
static std::optional<Common::IPv4Port> AdjustIPv4PortFromConfig(Common::IPv4Port subject)
|
||||
{
|
||||
// TODO: We should parse this elsewhere to avoid repeated string manipulations.
|
||||
for (auto&& override : GetIPOverrides())
|
||||
{
|
||||
if (override.match.IsMatch(subject))
|
||||
if (override.original.IsMatch(subject))
|
||||
return override.ApplyOverride(subject);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<Common::IPv4Port> ReverseAdjustIPv4PortFromConfig(Common::IPv4Port subject)
|
||||
{
|
||||
// TODO: We should parse this elsewhere to avoid repeated string manipulations.
|
||||
for (auto&& override : GetIPOverrides())
|
||||
{
|
||||
if (override.replacement.IsMatch(subject))
|
||||
return override.ReverseOverride(subject);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Ports are in host byte order.
|
||||
static bool BindEphemeralPort(SOCKET host_socket, Common::IPAddress ip_address,
|
||||
u16 first_port_value, u16 last_port_value, u32 attempt_count)
|
||||
{
|
||||
std::mt19937 rng(u32(Clock::now().time_since_epoch().count()));
|
||||
std::uniform_int_distribution<u16> port_distribution{first_port_value, last_port_value};
|
||||
|
||||
sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr = std::bit_cast<in_addr>(ip_address),
|
||||
};
|
||||
|
||||
while (attempt_count-- != 0)
|
||||
{
|
||||
const u16 port_value = port_distribution(rng);
|
||||
|
||||
addr.sin_port = htons(port_value);
|
||||
|
||||
const auto bind_result = bind(host_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
|
||||
const int bind_err = WSAGetLastError();
|
||||
|
||||
INFO_LOG_FMT(AMMEDIABOARD, "BindEphemeralPort: bind ({}:{}) = {} ({})",
|
||||
Common::IPAddressToString(ip_address), port_value, bind_result,
|
||||
Common::DecodeNetworkError(bind_err));
|
||||
|
||||
if (bind_result == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& guest_addr)
|
||||
{
|
||||
INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: {}:{}",
|
||||
@ -655,12 +669,13 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu
|
||||
.sin_family = guest_addr.ip_family,
|
||||
};
|
||||
|
||||
const auto adjusted_ipv4port =
|
||||
GetAdjustedIPv4PortFromConfig({guest_addr.ip_address, guest_addr.port});
|
||||
// Adjust destination IP and port.
|
||||
const auto adjusted_ipv4port = AdjustIPv4PortFromConfig({guest_addr.ip_address, guest_addr.port});
|
||||
if (adjusted_ipv4port.has_value())
|
||||
{
|
||||
addr.sin_addr = std::bit_cast<in_addr>(adjusted_ipv4port->ip_address);
|
||||
addr.sin_port = adjusted_ipv4port->port;
|
||||
|
||||
INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: Overriding to: {}:{}",
|
||||
Common::IPAddressToString(adjusted_ipv4port->ip_address),
|
||||
ntohs(adjusted_ipv4port->port));
|
||||
@ -673,6 +688,34 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu
|
||||
|
||||
const auto host_socket = GetHostSocket(guest_socket);
|
||||
|
||||
// See if we have an override for the game modified IP.
|
||||
// If so, adjust the source IP by binding the socket.
|
||||
const auto adjusted_source_ipv4port = AdjustIPv4PortFromConfig({s_game_modified_ip_address, 0});
|
||||
if (adjusted_source_ipv4port.has_value())
|
||||
{
|
||||
// FYI: We don't handle the situation if games bind outgoing TCP themselves.
|
||||
// But I think that's unlikely.
|
||||
|
||||
const u16 first_port_value = adjusted_source_ipv4port->GetPortValue();
|
||||
|
||||
// If port zero is included then we don't care about the port number.
|
||||
const bool use_any_port = first_port_value == 0;
|
||||
const u32 attempt_count = use_any_port ? 1 : 10;
|
||||
|
||||
// TODO: Handle the range properly. AdjustIPv4PortFromConfig should return a port range.
|
||||
// This magic 999 is here just to match our default config..
|
||||
const u16 last_port_value = use_any_port ? 0 : first_port_value + 999;
|
||||
|
||||
const auto bind_result = BindEphemeralPort(host_socket, adjusted_source_ipv4port->ip_address,
|
||||
first_port_value, last_port_value, attempt_count);
|
||||
|
||||
if (!bind_result)
|
||||
{
|
||||
s_last_error = SOCKET_ERROR;
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Set socket to non-blocking
|
||||
{
|
||||
u_long val = 1;
|
||||
@ -752,6 +795,156 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
static GuestSocket NetDIMMAccept(GuestSocket guest_socket, u8* guest_addr_ptr,
|
||||
u8* guest_addrlen_ptr)
|
||||
{
|
||||
// Either both parameters should be provided, or neither.
|
||||
if ((guest_addr_ptr != nullptr) != (guest_addrlen_ptr != nullptr))
|
||||
{
|
||||
ERROR_LOG_FMT(AMMEDIABOARD_NET, "NetDIMMAccept: bad parmeters");
|
||||
|
||||
// TODO: Not hardware tested.
|
||||
s_last_error = SSC_EFAULT;
|
||||
return INVALID_GUEST_SOCKET;
|
||||
}
|
||||
|
||||
const auto host_socket = GetHostSocket(guest_socket);
|
||||
WSAPOLLFD pfds[1]{{.fd = host_socket, .events = POLLIN}};
|
||||
|
||||
// FYI: Currently using a 0ms timeout to make accept calls always non-blocking.
|
||||
constexpr auto timeout = std::chrono::milliseconds{0};
|
||||
|
||||
DEBUG_LOG_FMT(AMMEDIABOARD, "NetDIMMAccept: {}({})", host_socket, int(guest_socket));
|
||||
|
||||
const int poll_result = PlatformPoll(pfds, timeout);
|
||||
|
||||
if (poll_result < 0) [[unlikely]]
|
||||
{
|
||||
// Poll failure.
|
||||
ERROR_LOG_FMT(AMMEDIABOARD, "NetDIMMAccept: PlatformPoll: {}", Common::StrNetworkError());
|
||||
|
||||
s_last_error = SOCKET_ERROR;
|
||||
return INVALID_GUEST_SOCKET;
|
||||
}
|
||||
|
||||
if ((pfds[0].revents & POLLIN) == 0)
|
||||
{
|
||||
// Timeout.
|
||||
DEBUG_LOG_FMT(AMMEDIABOARD, "NetDIMMAccept: Timeout.");
|
||||
|
||||
s_last_error = SSC_EWOULDBLOCK;
|
||||
return INVALID_GUEST_SOCKET;
|
||||
}
|
||||
|
||||
sockaddr_in addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
const auto client_sock = accept_(host_socket, reinterpret_cast<sockaddr*>(&addr), &addrlen);
|
||||
|
||||
if (client_sock == INVALID_GUEST_SOCKET)
|
||||
{
|
||||
ERROR_LOG_FMT(AMMEDIABOARD, "AMMBCommandAccept: accept: ({})", Common::StrNetworkError());
|
||||
s_last_error = SOCKET_ERROR;
|
||||
return INVALID_GUEST_SOCKET;
|
||||
}
|
||||
|
||||
s_last_error = SSC_SUCCESS;
|
||||
|
||||
NOTICE_LOG_FMT(AMMEDIABOARD, "AMMBCommandAccept: {}:{}",
|
||||
Common::IPAddressToString(std::bit_cast<Common::IPAddress>(addr.sin_addr)),
|
||||
ntohs(addr.sin_port));
|
||||
|
||||
if (guest_addr_ptr == nullptr)
|
||||
return client_sock;
|
||||
|
||||
GuestSocketAddress guest_addr{
|
||||
.ip_family = u8(addr.sin_family),
|
||||
.port = addr.sin_port,
|
||||
.ip_address = std::bit_cast<Common::IPAddress>(addr.sin_addr),
|
||||
};
|
||||
|
||||
if (const auto adjusted_ipv4port =
|
||||
ReverseAdjustIPv4PortFromConfig({guest_addr.ip_address, guest_addr.port}))
|
||||
{
|
||||
guest_addr.ip_address = adjusted_ipv4port->ip_address;
|
||||
guest_addr.port = adjusted_ipv4port->port;
|
||||
|
||||
NOTICE_LOG_FMT(AMMEDIABOARD, "AMMBCommandAccept: Translating result to: {}:{}",
|
||||
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));
|
||||
|
||||
// Write out the addr.
|
||||
std::memcpy(guest_addr_ptr, &guest_addr, write_size);
|
||||
|
||||
// Write out the addrlen.
|
||||
*guest_addrlen_ptr = sizeof(guest_addr);
|
||||
|
||||
return client_sock;
|
||||
}
|
||||
|
||||
static Common::IPv4Port GetAdjustedBindIPv4Port(Common::IPv4Port socket_addr)
|
||||
{
|
||||
auto considered_ipv4 = socket_addr;
|
||||
|
||||
if (std::bit_cast<u32>(considered_ipv4.ip_address) == INADDR_ANY)
|
||||
{
|
||||
// Because the game is binding to "0.0.0.0",
|
||||
// use the "game modified" IP for override purposes.
|
||||
// If no override applies, then we still bind "0.0.0.0".
|
||||
considered_ipv4.ip_address = s_game_modified_ip_address;
|
||||
INFO_LOG_FMT(AMMEDIABOARD, "GetAdjustedBindIPv4Port: Considering game modified IP: {}",
|
||||
Common::IPAddressToString(s_game_modified_ip_address));
|
||||
}
|
||||
|
||||
if (const auto adjusted_ipv4 = AdjustIPv4PortFromConfig(considered_ipv4))
|
||||
{
|
||||
socket_addr = *adjusted_ipv4;
|
||||
INFO_LOG_FMT(AMMEDIABOARD, "GetAdjustedBindIPv4Port: Overriding to: {}:{}",
|
||||
Common::IPAddressToString(socket_addr.ip_address), ntohs(socket_addr.port));
|
||||
}
|
||||
|
||||
return socket_addr;
|
||||
}
|
||||
|
||||
static u32 NetDIMMBind(GuestSocket guest_socket, const GuestSocketAddress& guest_addr)
|
||||
{
|
||||
const auto host_socket = GetHostSocket(guest_socket);
|
||||
|
||||
NOTICE_LOG_FMT(AMMEDIABOARD, "NetDIMMBind: {}({}) {}, {}, {}:{}", host_socket, int(guest_socket),
|
||||
guest_addr.unknown_value, guest_addr.ip_family,
|
||||
Common::IPAddressToString(guest_addr.ip_address), ntohs(guest_addr.port));
|
||||
|
||||
const auto adjusted_ipv4port = GetAdjustedBindIPv4Port({guest_addr.ip_address, guest_addr.port});
|
||||
|
||||
sockaddr_in addr{
|
||||
.sin_family = guest_addr.ip_family,
|
||||
.sin_port = adjusted_ipv4port.port,
|
||||
.sin_addr = std::bit_cast<in_addr>(adjusted_ipv4port.ip_address),
|
||||
};
|
||||
|
||||
const int bind_result = bind(host_socket, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr));
|
||||
const int err = WSAGetLastError();
|
||||
|
||||
INFO_LOG_FMT(AMMEDIABOARD_NET, "NetDIMMBind: bind( {}({}), ({},{}:{}) ):{}", host_socket,
|
||||
u32(guest_socket), addr.sin_family,
|
||||
Common::IPAddressToString(adjusted_ipv4port.ip_address),
|
||||
Common::swap16(adjusted_ipv4port.port), bind_result);
|
||||
|
||||
if (bind_result < 0)
|
||||
{
|
||||
const auto* const err_msg = Common::DecodeNetworkError(err);
|
||||
ERROR_LOG_FMT(AMMEDIABOARD, "NetDIMMBind bind() = {} ({})", err, err_msg);
|
||||
|
||||
PanicAlertFmt("Failed to bind socket {}:{}\nError: {} ({})",
|
||||
Common::IPAddressToString(adjusted_ipv4port.ip_address),
|
||||
ntohs(adjusted_ipv4port.port), err, err_msg);
|
||||
}
|
||||
|
||||
return bind_result;
|
||||
}
|
||||
|
||||
static void AMMBCommandRecv(u32 parameter_offset, u32 network_buffer_base)
|
||||
{
|
||||
const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[parameter_offset]));
|
||||
@ -873,43 +1066,44 @@ static void AMMBCommandAccept(u32 parameter_offset, u32 network_buffer_base)
|
||||
const u32 addr_off = s_media_buffer_32[parameter_offset + 1];
|
||||
const u32 addrlen_off = s_media_buffer_32[parameter_offset + 2];
|
||||
|
||||
u32 ret{};
|
||||
auto* const addrlen_ptr =
|
||||
GetSafePtr(s_network_command_buffer, network_buffer_base, addrlen_off, sizeof(u32));
|
||||
|
||||
// Either both parameters should be provided, or neither.
|
||||
if ((addr_off != 0) != (addrlen_off != 0))
|
||||
auto* const addr_ptr = (addrlen_ptr == nullptr) ?
|
||||
nullptr :
|
||||
GetSafePtr(s_network_command_buffer, network_buffer_base, addr_off,
|
||||
Common::BitCastPtr<u32>(addrlen_ptr));
|
||||
|
||||
const auto accept_result = NetDIMMAccept(guest_socket, addr_ptr, addrlen_ptr);
|
||||
|
||||
s_media_buffer_32[1] = u32(accept_result);
|
||||
}
|
||||
|
||||
static void AMMBCommandBind()
|
||||
{
|
||||
const auto guest_socket = GuestSocket(s_media_buffer_32[2]);
|
||||
const u32 addr_offset = s_media_buffer_32[3];
|
||||
const u32 len = s_media_buffer_32[4];
|
||||
|
||||
GuestSocketAddress guest_addr;
|
||||
|
||||
if (len != sizeof(guest_addr))
|
||||
{
|
||||
WARN_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandAccept: Unexpected parameters: {}, {}, {}",
|
||||
u32(guest_socket), addr_off, addrlen_off);
|
||||
|
||||
// TODO: Not hardware tested.
|
||||
s_last_error = SSC_EFAULT;
|
||||
ret = SOCKET_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
sockaddr addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
ret = u32(NetDIMMAccept(guest_socket, &addr, &addrlen));
|
||||
|
||||
INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {} ):{}", u32(guest_socket), int(ret));
|
||||
|
||||
auto* const addrlen_ptr =
|
||||
GetSafePtr(s_network_command_buffer, network_buffer_base, addrlen_off, sizeof(u32));
|
||||
if (addrlen_ptr != nullptr)
|
||||
{
|
||||
// Read the buffer size.
|
||||
addrlen = std::min<socklen_t>(addrlen, Common::BitCastPtr<u32>(addrlen_ptr));
|
||||
// Write out the proper length.
|
||||
Common::BitCastPtr<u32>(addrlen_ptr) = sizeof(addr);
|
||||
|
||||
auto* const addr_ptr =
|
||||
GetSafePtr(s_network_command_buffer, network_buffer_base, addr_off, addrlen);
|
||||
if (addr_ptr != nullptr)
|
||||
memcpy(addr_ptr, &addr, addrlen);
|
||||
}
|
||||
ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandBind: Unexpected length: {}", len);
|
||||
return;
|
||||
}
|
||||
|
||||
s_media_buffer_32[1] = ret;
|
||||
const auto* addr_ptr =
|
||||
GetSafePtr(s_network_command_buffer, NetworkCommandAddress2, addr_offset, sizeof(guest_addr));
|
||||
if (addr_ptr == nullptr)
|
||||
return;
|
||||
|
||||
memcpy(&guest_addr, addr_ptr, sizeof(guest_addr));
|
||||
|
||||
const auto bind_result = NetDIMMBind(guest_socket, guest_addr);
|
||||
|
||||
s_media_buffer_32[1] = bind_result;
|
||||
s_last_error = SSC_SUCCESS;
|
||||
}
|
||||
|
||||
// Expects a pointer to a GuestFdSet or nullptr.
|
||||
@ -1315,80 +1509,8 @@ u32 ExecuteCommand(std::array<u32, 3>& dicmd_buf, u32* diimm_buf, u32 address, u
|
||||
AMMBCommandAccept(2, NetworkCommandAddress2);
|
||||
break;
|
||||
case AMMBCommand::Bind:
|
||||
{
|
||||
const auto guest_socket = GuestSocket(s_media_buffer_32[2]);
|
||||
const auto host_socket = GetHostSocket(guest_socket);
|
||||
const u32 addr_offset = s_media_buffer_32[3];
|
||||
const u32 len = s_media_buffer_32[4];
|
||||
|
||||
GuestSocketAddress guest_addr;
|
||||
|
||||
if (len != sizeof(guest_addr))
|
||||
{
|
||||
ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommand::Bind: Unexpected length: {}", len);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto* addr_ptr = GetSafePtr(s_network_command_buffer, NetworkCommandAddress2,
|
||||
addr_offset, sizeof(guest_addr));
|
||||
if (addr_ptr == nullptr)
|
||||
break;
|
||||
|
||||
memcpy(&guest_addr, addr_ptr, sizeof(guest_addr));
|
||||
|
||||
// Triforce titles typically rely on hardcoded IP addresses.
|
||||
// Our config allows this to be overriden.
|
||||
|
||||
NOTICE_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: {}, {}, {}:{}", guest_addr.struct_size,
|
||||
guest_addr.ip_family, Common::IPAddressToString(guest_addr.ip_address),
|
||||
ntohs(guest_addr.port));
|
||||
|
||||
// Apply "BindIP" if it's valid.
|
||||
const auto override_bind_ip = inet_addr(Config::Get(Config::MAIN_TRIFORCE_BIND_IP).c_str());
|
||||
if (override_bind_ip != INADDR_NONE)
|
||||
{
|
||||
guest_addr.ip_address = std::bit_cast<Common::IPAddress>(override_bind_ip);
|
||||
INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: Overriding IP to: {}",
|
||||
Common::IPAddressToString(guest_addr.ip_address));
|
||||
}
|
||||
|
||||
// Apply "IPOverrides" in case config wants ports adjusted.
|
||||
const auto adjusted_ipv4port =
|
||||
GetAdjustedIPv4PortFromConfig({guest_addr.ip_address, guest_addr.port});
|
||||
if (adjusted_ipv4port.has_value())
|
||||
{
|
||||
guest_addr.ip_address = adjusted_ipv4port->ip_address;
|
||||
guest_addr.port = adjusted_ipv4port->port;
|
||||
INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: Overriding to: {}:{}",
|
||||
Common::IPAddressToString(guest_addr.ip_address), ntohs(guest_addr.port));
|
||||
}
|
||||
|
||||
sockaddr_in addr{
|
||||
.sin_family = guest_addr.ip_family,
|
||||
.sin_port = guest_addr.port,
|
||||
.sin_addr = std::bit_cast<in_addr>(guest_addr.ip_address),
|
||||
};
|
||||
|
||||
const int bind_result =
|
||||
bind(host_socket, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr));
|
||||
const int err = WSAGetLastError();
|
||||
|
||||
INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}({}), ({},{}:{}) ):{} ({})", host_socket,
|
||||
u32(guest_socket), addr.sin_family, inet_ntoa(addr.sin_addr),
|
||||
Common::swap16(addr.sin_port), bind_result, err);
|
||||
|
||||
if (bind_result < 0)
|
||||
{
|
||||
const auto err_msg = Common::DecodeNetworkError(err);
|
||||
PanicAlertFmt("Failed to bind socket (error {}: {})", err, err_msg);
|
||||
ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: AMMBCommand::Bind failed, bind() = {} ({})", err,
|
||||
err_msg);
|
||||
}
|
||||
|
||||
s_media_buffer_32[1] = bind_result;
|
||||
s_last_error = SSC_SUCCESS;
|
||||
AMMBCommandBind();
|
||||
break;
|
||||
}
|
||||
case AMMBCommand::Closesocket:
|
||||
AMMBCommandClosesocket(2);
|
||||
break;
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
@ -243,6 +242,12 @@ bool GetTestMenu();
|
||||
void Shutdown();
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
std::optional<std::pair<std::string_view, std::string_view>> ParseIPOverride(std::string_view str);
|
||||
struct ParsedIPOverride
|
||||
{
|
||||
std::string_view original;
|
||||
std::string_view replacement;
|
||||
std::string_view description;
|
||||
};
|
||||
std::optional<ParsedIPOverride> ParseIPOverride(std::string_view str);
|
||||
|
||||
}; // namespace AMMediaboard
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
#include "DolphinQt/Settings/TriforceBaseboardSettingsDialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLineEdit>
|
||||
@ -22,30 +21,28 @@
|
||||
|
||||
#include "DolphinQt/QtUtils/QtUtils.h"
|
||||
|
||||
static constexpr int COLUMN_EMULATED = 0;
|
||||
static constexpr int COLUMN_REAL = 1;
|
||||
static constexpr int COLUMN_DESCRIPTION = 2;
|
||||
|
||||
TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent) : QDialog{parent}
|
||||
{
|
||||
setWindowTitle(tr("Triforce Baseboard"));
|
||||
|
||||
connect(this, &TriforceBaseboardSettingsDialog::accepted, this,
|
||||
&TriforceBaseboardSettingsDialog::SaveConfig);
|
||||
|
||||
auto* const dialog_layout = new QVBoxLayout{this};
|
||||
|
||||
auto* const ip_override_group = new QGroupBox{tr("IP Address Overrides")};
|
||||
dialog_layout->addWidget(ip_override_group);
|
||||
|
||||
auto* const ip_override_layout = new QFormLayout{ip_override_group};
|
||||
|
||||
m_bind_ip_edit = new QLineEdit;
|
||||
ip_override_layout->addRow(tr("Bind IP: "), m_bind_ip_edit);
|
||||
auto* const ip_override_layout = new QVBoxLayout{ip_override_group};
|
||||
|
||||
m_ip_overrides_table = new QTableWidget;
|
||||
ip_override_layout->addRow(tr("IP Overrides: "), m_ip_overrides_table);
|
||||
ip_override_layout->addWidget(m_ip_overrides_table);
|
||||
|
||||
m_ip_overrides_table->setColumnCount(3);
|
||||
m_ip_overrides_table->setHorizontalHeaderLabels({tr("Emulated"), tr("Real"), tr("Description")});
|
||||
m_ip_overrides_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
|
||||
m_ip_overrides_table->setColumnCount(2);
|
||||
m_ip_overrides_table->setHorizontalHeaderLabels({tr("Match"), tr("Replacement")});
|
||||
m_ip_overrides_table->setSizeAdjustPolicy(QTableWidget::AdjustToContents);
|
||||
|
||||
m_ip_overrides_table->setEditTriggers(QAbstractItemView::DoubleClicked |
|
||||
QAbstractItemView::EditKeyPressed);
|
||||
@ -57,11 +54,18 @@ TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent
|
||||
connect(load_default_button, &QPushButton::clicked, this,
|
||||
&TriforceBaseboardSettingsDialog::LoadDefault);
|
||||
|
||||
auto* const clear_button = new QPushButton(tr("Clear"));
|
||||
connect(clear_button, &QPushButton::clicked, this, &TriforceBaseboardSettingsDialog::OnClear);
|
||||
|
||||
button_box->addButton(load_default_button, QDialogButtonBox::ActionRole);
|
||||
button_box->addButton(clear_button, QDialogButtonBox::ActionRole);
|
||||
|
||||
connect(button_box, &QDialogButtonBox::accepted, this, &TriforceBaseboardSettingsDialog::accept);
|
||||
connect(button_box, &QDialogButtonBox::rejected, this, &TriforceBaseboardSettingsDialog::reject);
|
||||
|
||||
connect(this, &TriforceBaseboardSettingsDialog::accepted, this,
|
||||
&TriforceBaseboardSettingsDialog::SaveConfig);
|
||||
|
||||
connect(m_ip_overrides_table, &QTableWidget::itemChanged, this, [this](QTableWidgetItem* item) {
|
||||
const int row_count = m_ip_overrides_table->rowCount();
|
||||
const bool is_last_row = item->row() == row_count - 1;
|
||||
@ -70,12 +74,11 @@ TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent
|
||||
if (!item->text().isEmpty())
|
||||
m_ip_overrides_table->insertRow(row_count);
|
||||
}
|
||||
else
|
||||
else if (item->column() != COLUMN_DESCRIPTION && item->text().isEmpty())
|
||||
{
|
||||
// Just remove inner rows that have text erased.
|
||||
// This UX could be less weird, but it's good enough for now.
|
||||
if (item->text().isEmpty())
|
||||
m_ip_overrides_table->removeRow(item->row());
|
||||
m_ip_overrides_table->removeRow(item->row());
|
||||
}
|
||||
});
|
||||
|
||||
@ -86,8 +89,6 @@ TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent
|
||||
|
||||
void TriforceBaseboardSettingsDialog::LoadConfig()
|
||||
{
|
||||
m_bind_ip_edit->setText(QString::fromUtf8(Config::Get(Config::MAIN_TRIFORCE_BIND_IP)));
|
||||
|
||||
m_ip_overrides_table->setRowCount(0);
|
||||
|
||||
// Add the final empty row.
|
||||
@ -99,8 +100,8 @@ void TriforceBaseboardSettingsDialog::LoadConfig()
|
||||
for (auto&& ip_pair : ip_overrides_str | std::views::split(','))
|
||||
{
|
||||
const auto ip_pair_str = std::string_view{ip_pair};
|
||||
const auto parts = AMMediaboard::ParseIPOverride(ip_pair_str);
|
||||
if (!parts.has_value())
|
||||
const auto parsed = AMMediaboard::ParseIPOverride(ip_pair_str);
|
||||
if (!parsed.has_value())
|
||||
{
|
||||
ERROR_LOG_FMT(COMMON, "Bad IP pair string: {}", ip_pair_str);
|
||||
continue;
|
||||
@ -108,8 +109,12 @@ void TriforceBaseboardSettingsDialog::LoadConfig()
|
||||
|
||||
m_ip_overrides_table->insertRow(row);
|
||||
|
||||
m_ip_overrides_table->setItem(row, 0, new QTableWidgetItem(QString::fromUtf8(parts->first)));
|
||||
m_ip_overrides_table->setItem(row, 1, new QTableWidgetItem(QString::fromUtf8(parts->second)));
|
||||
m_ip_overrides_table->setItem(row, COLUMN_EMULATED,
|
||||
new QTableWidgetItem(QString::fromUtf8(parsed->original)));
|
||||
m_ip_overrides_table->setItem(row, COLUMN_REAL,
|
||||
new QTableWidgetItem(QString::fromUtf8(parsed->replacement)));
|
||||
m_ip_overrides_table->setItem(row, COLUMN_DESCRIPTION,
|
||||
new QTableWidgetItem(QString::fromUtf8(parsed->description)));
|
||||
|
||||
++row;
|
||||
}
|
||||
@ -117,29 +122,32 @@ void TriforceBaseboardSettingsDialog::LoadConfig()
|
||||
|
||||
void TriforceBaseboardSettingsDialog::SaveConfig()
|
||||
{
|
||||
Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_BIND_IP, m_bind_ip_edit->text().toStdString());
|
||||
|
||||
// Ignore our empty row.
|
||||
const int row_count = m_ip_overrides_table->rowCount() - 1;
|
||||
|
||||
std::vector<std::string> replacements(row_count);
|
||||
for (int row = 0; row < row_count; ++row)
|
||||
{
|
||||
auto* const original_item = m_ip_overrides_table->item(row, COLUMN_EMULATED);
|
||||
auto* const replacement_item = m_ip_overrides_table->item(row, COLUMN_REAL);
|
||||
|
||||
// Skip incomplete rows.
|
||||
if (m_ip_overrides_table->item(row, 0) == nullptr ||
|
||||
m_ip_overrides_table->item(row, 1) == nullptr)
|
||||
{
|
||||
if (original_item == nullptr || replacement_item == nullptr)
|
||||
continue;
|
||||
}
|
||||
|
||||
replacements[row] =
|
||||
std::string(StripWhitespace(m_ip_overrides_table->item(row, 0)->text().toStdString())) +
|
||||
"=" +
|
||||
std::string(StripWhitespace(m_ip_overrides_table->item(row, 1)->text().toStdString()));
|
||||
replacements[row] = fmt::format("{}={}", StripWhitespace(original_item->text().toStdString()),
|
||||
replacement_item->text().toStdString());
|
||||
|
||||
auto* const description_item = m_ip_overrides_table->item(row, COLUMN_DESCRIPTION);
|
||||
if (description_item == nullptr)
|
||||
continue;
|
||||
|
||||
const auto description = description_item->text().toStdString();
|
||||
|
||||
if (!description.empty())
|
||||
(replacements[row] += ' ') += description;
|
||||
}
|
||||
|
||||
// TODO: Ideally we'd remove duplicates based on the first IP.
|
||||
|
||||
Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES,
|
||||
fmt::format("{}", fmt::join(replacements, ",")));
|
||||
}
|
||||
@ -147,8 +155,14 @@ void TriforceBaseboardSettingsDialog::SaveConfig()
|
||||
void TriforceBaseboardSettingsDialog::LoadDefault()
|
||||
{
|
||||
// This alters the config before "OK" is pressed. Bad UX..
|
||||
Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_BIND_IP, Config::DefaultState{});
|
||||
|
||||
Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, Config::DefaultState{});
|
||||
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
void TriforceBaseboardSettingsDialog::OnClear()
|
||||
{
|
||||
Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, "");
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ private:
|
||||
void SaveConfig();
|
||||
|
||||
void LoadDefault();
|
||||
void OnClear();
|
||||
|
||||
QLineEdit* m_bind_ip_edit;
|
||||
QTableWidget* m_ip_overrides_table;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user