diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 6edcccf186..bc749b4b37 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -645,26 +645,40 @@ const std::array, 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 MAIN_TRIFORCE_BIND_IP{{System::Main, "Core", "TriforceBindIP"}, "0.0.0.0"}; const Info MAIN_TRIFORCE_IP_OVERRIDES{{System::Main, "Core", "TriforceIPOverrides"}, GetDefaultTriforceIPOverrides()}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index ad28ca9119..dfbf166758 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -386,7 +386,6 @@ extern const std::array, EMULATED_LOGITECH_MIC_COUNT> extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED; extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER; -extern const Info MAIN_TRIFORCE_BIND_IP; extern const Info MAIN_TRIFORCE_IP_OVERRIDES; // GameCube path utility functions diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 2fd1897783..4a79a01252 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -514,95 +515,64 @@ static int PlatformPoll(std::span pfds, std::chrono::milliseconds tim #endif } -static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, socklen_t* len) +std::optional 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> 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::BigEndianValue(result_ip)), - }; + subject.ip_address = std::bit_cast(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(Common::BigEndianValue(result_port)); + ((subject.GetPortValue() - original.first.GetPortValue()) % port_count)); + subject.port = std::bit_cast(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 GetAdjustedIPv4PortFromConfig(Common::IPv4Port subject) +static std::optional 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 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 port_distribution{first_port_value, last_port_value}; + + sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr = std::bit_cast(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(&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(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(&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(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(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(Common::BitCastPtr(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(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(adjusted_ipv4port.ip_address), + }; + + const int bind_result = bind(host_socket, reinterpret_cast(&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(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(addrlen, Common::BitCastPtr(addrlen_ptr)); - // Write out the proper length. - Common::BitCastPtr(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& 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(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(guest_addr.ip_address), - }; - - const int bind_result = - bind(host_socket, reinterpret_cast(&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; diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index 5079a7fdd3..937d2b9228 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -7,7 +7,6 @@ #include #include #include -#include #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" @@ -243,6 +242,12 @@ bool GetTestMenu(); void Shutdown(); void DoState(PointerWrap& p); -std::optional> ParseIPOverride(std::string_view str); +struct ParsedIPOverride +{ + std::string_view original; + std::string_view replacement; + std::string_view description; +}; +std::optional ParseIPOverride(std::string_view str); }; // namespace AMMediaboard diff --git a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp index 23d300dc43..5ae697c148 100644 --- a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp +++ b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp @@ -4,7 +4,6 @@ #include "DolphinQt/Settings/TriforceBaseboardSettingsDialog.h" #include -#include #include #include #include @@ -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 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(); +} diff --git a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h index e472739f71..f57d562da1 100644 --- a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h +++ b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h @@ -20,7 +20,7 @@ private: void SaveConfig(); void LoadDefault(); + void OnClear(); - QLineEdit* m_bind_ip_edit; QTableWidget* m_ip_overrides_table; };