From 3e2b7fee46a8fcf623821b47b4fc4d50edeb65a8 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 6 Feb 2026 20:23:40 -0600 Subject: [PATCH] AMMediaboard: Allow IP address overrides to be configured. --- Source/Core/Core/Config/MainSettings.cpp | 27 ++++ Source/Core/Core/Config/MainSettings.h | 3 + Source/Core/Core/HW/DVD/AMMediaboard.cpp | 166 ++++++++++++++++++----- Source/Core/Core/HW/DVD/AMMediaboard.h | 5 + 4 files changed, 169 insertions(+), 32 deletions(-) diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 3d370c8b1e..6edcccf186 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "AudioCommon/AudioCommon.h" #include "Common/Assert.h" @@ -27,6 +28,7 @@ #include "Core/HW/HSP/HSP_Device.h" #include "Core/HW/Memmap.h" #include "Core/HW/SI/SI_Device.h" +#include "Core/IOS/Network/Socket.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" #include "Core/USBUtils.h" @@ -641,6 +643,31 @@ const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUM Info{{System::Main, "EmulatedUSBDevices", "LogitechMic3VolumeModifier"}, 0}, Info{{System::Main, "EmulatedUSBDevices", "LogitechMic4VolumeModifier"}, 0}}; +static std::string GetDefaultTriforceIPOverrides() +{ + constexpr std::string_view entries[] = { + // CyCraft Connect IP + "192.168.11.111=127.0.0.1", + + // 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", + + // NAMCO Camera + "192.168.29.104-108=127.0.0.1", + + // All.Net Connect IP + "192.168.150.16=127.0.0.1", + }; + + return fmt::format("{}", fmt::join(entries, ",")); +} + +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()}; + // The reason we need this function is because some memory card code // expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii. DiscIO::Region ToGameCubeRegion(DiscIO::Region region) diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 00c11ea6dd..ad28ca9119 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -386,6 +386,9 @@ 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 // Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 4225e3a608..0dd60755b7 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -4,6 +4,8 @@ #include "Core/HW/DVD/AMMediaboard.h" #include +#include +#include #include #include @@ -18,6 +20,7 @@ #include "Core/Boot/Boot.h" #include "Core/BootManager.h" +#include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HLE/HLE.h" @@ -521,38 +524,118 @@ static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, sockl 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. + 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]); + + return std::nullopt; +} + +struct IPAddressOverride +{ + Common::IPv4PortRange match; + Common::IPv4PortRange replacement; + + // Caller should check if it matches first! + Common::IPv4Port ApplyOverride(Common::IPv4Port subject) const + { + 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)); + + Common::IPv4Port result{ + .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 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 = + u16(replacement_first_port_u16 + + ((subject.GetPortValue() - match.first.GetPortValue()) % port_count)); + result.port = std::bit_cast(Common::BigEndianValue(result_port)); + } + + return result; + } +}; + +using IPOverrides = std::vector; +static IPOverrides GetIPOverrides() +{ + IPOverrides result; + + const auto ip_overrides_str = Config::Get(Config::MAIN_TRIFORCE_IP_OVERRIDES); + for (auto&& ip_pair : ip_overrides_str | std::views::split(',')) + { + const auto ip_pair_str = std::string_view{ip_pair}; + 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); + + if (match.has_value() && replacement.has_value()) + { + result.emplace_back(*match, *replacement); + continue; + } + + ERROR_LOG_FMT(AMMEDIABOARD, "Bad IP pair string: {}", ip_pair_str); + } + } + + return result; +} + +static std::optional GetAdjustedIPv4PortFromConfig(in_addr addr, u16 port) +{ + Common::IPv4Port subject{ + .ip_address = std::bit_cast(addr), + .port = port, + }; + + // TODO: We should parse this elsewhere to avoid repeated string manipulations. + for (auto&& override : GetIPOverrides()) + { + if (override.match.IsMatch(subject)) + return override.ApplyOverride(subject); + } + + return std::nullopt; +} + static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) { - // All.Net Connect IP - if (addr->sin_addr.s_addr == inet_addr("192.168.150.16")) - { - addr->sin_addr.s_addr = inet_addr("127.0.0.1"); - } + INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: {}:{}", inet_ntoa(addr->sin_addr), + ntohs(addr->sin_port)); - // CyCraft Connect IP - if (addr->sin_addr.s_addr == inet_addr("192.168.11.111")) + const auto adjusted_ipv4port = GetAdjustedIPv4PortFromConfig(addr->sin_addr, addr->sin_port); + if (adjusted_ipv4port.has_value()) { - addr->sin_addr.s_addr = inet_addr("127.0.0.1"); - } - - // NAMCO Camera ( IPs are: 192.168.29.104-108 ) - if ((addr->sin_addr.s_addr & 0xFFFFFF00) == 0xC0A81D00) - { - addr->sin_addr.s_addr = inet_addr("127.0.0.1"); - - // BUG: An invalid family value is being used - addr->sin_family = htons(AF_INET); - } - - // Key of Avalon Client - if (addr->sin_addr.s_addr == inet_addr("192.168.13.1")) - { - // Unlike the other addresses, this one isn't converted to loopback - // because the server and client can't run on the same system. - // - // NOTE: Due to lack of touch-screen support, it's not in a playable state. - // TODO: Make the IP configurable. - addr->sin_addr.s_addr = inet_addr("10.0.0.45"); + 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)); } addr->sin_family = Common::swap16(addr->sin_family); @@ -1219,11 +1302,30 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u addr.sin_family = Common::swap16(addr.sin_family); // Triforce titles typically rely on hardcoded IP addresses. - // This behavior has been modified to bind to the wildcard address instead. - // - // addr.sin_addr.s_addr = htonl(addr.sin_addr.s_addr); + // Our config allows this to be overriden. - addr.sin_addr.s_addr = INADDR_ANY; + INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: {}:{}", inet_ntoa(addr.sin_addr), + ntohs(addr.sin_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) + { + addr.sin_addr.s_addr = override_bind_ip; + INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: Overriding IP to: {}", + Common::IPAddressToString(std::bit_cast(addr.sin_addr))); + } + + // Apply "IPOverrides" in case config wants ports adjusted. + const auto adjusted_ipv4port = GetAdjustedIPv4PortFromConfig(addr.sin_addr, addr.sin_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, "AMMBCommand::Bind: Overriding to: {}:{}", + Common::IPAddressToString(std::bit_cast(addr.sin_addr)), + ntohs(addr.sin_port)); + } const int ret = bind(fd, reinterpret_cast(&addr), len); const int err = WSAGetLastError(); diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index d57a800107..cc0bcf9b89 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -4,6 +4,9 @@ #pragma once #include +#include +#include +#include #include "Common/CommonTypes.h" @@ -234,4 +237,6 @@ u32 GetMediaType(); bool GetTestMenu(); void Shutdown(); +std::optional> ParseIPOverride(std::string_view str); + }; // namespace AMMediaboard