AMMediaboard: Allow IP address overrides to be configured.

This commit is contained in:
Jordan Woyak 2026-02-06 20:23:40 -06:00
parent 5545798e00
commit 3e2b7fee46
4 changed files with 169 additions and 32 deletions

View File

@ -6,6 +6,7 @@
#include <sstream>
#include <fmt/format.h>
#include <fmt/ranges.h>
#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<Info<s16>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUM
Info<s16>{{System::Main, "EmulatedUSBDevices", "LogitechMic3VolumeModifier"}, 0},
Info<s16>{{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<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()};
// 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)

View File

@ -386,6 +386,9 @@ 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
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions

View File

@ -4,6 +4,8 @@
#include "Core/HW/DVD/AMMediaboard.h"
#include <algorithm>
#include <bit>
#include <ranges>
#include <string>
#include <unordered_map>
@ -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<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.
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::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 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<u16>(Common::BigEndianValue(result_port));
}
return result;
}
};
using IPOverrides = std::vector<IPAddressOverride>;
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<Common::IPv4Port> GetAdjustedIPv4PortFromConfig(in_addr addr, u16 port)
{
Common::IPv4Port subject{
.ip_address = std::bit_cast<Common::IPAddress>(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<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));
}
addr->sin_family = Common::swap16(addr->sin_family);
@ -1219,11 +1302,30 @@ u32 ExecuteCommand(std::array<u32, 3>& 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<Common::IPAddress>(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<in_addr>(adjusted_ipv4port->ip_address);
addr.sin_port = adjusted_ipv4port->port;
INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: Overriding to: {}:{}",
Common::IPAddressToString(std::bit_cast<Common::IPAddress>(addr.sin_addr)),
ntohs(addr.sin_port));
}
const int ret = bind(fd, reinterpret_cast<const sockaddr*>(&addr), len);
const int err = WSAGetLastError();

View File

@ -4,6 +4,9 @@
#pragma once
#include <array>
#include <optional>
#include <string_view>
#include <utility>
#include "Common/CommonTypes.h"
@ -234,4 +237,6 @@ u32 GetMediaType();
bool GetTestMenu();
void Shutdown();
std::optional<std::pair<std::string_view, std::string_view>> ParseIPOverride(std::string_view str);
}; // namespace AMMediaboard