Skip to content

Commit 34a586f

Browse files
taserzclaude
andcommitted
DEV9: expose the MAC in settings, and show the selected adapter
Adds a MAC Address field with a Generate button, so the address added in the previous commit is reachable without hand editing the ini. Generate produces a unicast, locally administered address; entered values are validated and normalised, and clearing the field restores automatic generation. Also adds a read only line showing the selected host adapter's IP, MAC and gateway, read locally through AdapterUtils. That line addresses a real failure mode. DEV9 stores the adapter by GUID, so when a NIC re-enumerates -- a driver update, or a virtual adapter appearing -- the saved GUID silently refers to nothing and bridging fails with no indication. It now says so explicitly and points at the device list. This was found on a real config where the stored GUID no longer matched any adapter on the machine. Finally, a Generate button beside the PS2 IP rerolls the host octet while keeping the subnet, which helps when running more than one instance against a local server. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent d51a413 commit 34a586f

3 files changed

Lines changed: 179 additions & 0 deletions

File tree

pcsx2-qt/Settings/DEV9SettingsWidget.cpp

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
#include <QtWidgets/QMessageBox>
55
#include <QtWidgets/QFileDialog>
66
#include <algorithm>
7+
#include <random>
78

89
#include "common/FileSystem.h"
910
#include "common/Path.h"
1011
#include "common/StringUtil.h"
1112

13+
#include "pcsx2/DEV9/AdapterUtils.h"
1214
#include "pcsx2/Host.h"
1315
#include "pcsx2/INISettingsInterface.h"
1416

@@ -113,6 +115,47 @@ DEV9SettingsWidget::DEV9SettingsWidget(SettingsWindow* settings_dialog, QWidget*
113115
connect(m_ui.ethDNS2Addr, &QLineEdit::editingFinished, this, [&]() { onEthIPChanged(m_ui.ethDNS2Addr, "DEV9/Eth", "DNS2" ); });
114116
// clang-format on
115117

118+
// MAC address. Blank means "generate one on first boot and keep it", which
119+
// is what stops every install sharing the built-in default.
120+
m_ui.ethMacAddr->setText(QString::fromUtf8(dialog()->getStringValue("DEV9/Eth", "Mac", "").value().c_str()));
121+
connect(m_ui.ethMacAddr, &QLineEdit::editingFinished, this, [&]() { onEthMacChanged(); });
122+
connect(m_ui.ethMacGenerate, &QPushButton::clicked, this, [&]() {
123+
u8 mac[6];
124+
std::random_device rd;
125+
for (int i = 0; i < 6; i++)
126+
mac[i] = static_cast<u8>(rd() & 0xFF);
127+
128+
// unicast + locally administered, so a generated address can never be
129+
// mistaken for, or collide with, real vendor-assigned hardware
130+
mac[0] = static_cast<u8>((mac[0] & 0xFC) | 0x02);
131+
132+
m_ui.ethMacAddr->setText(QString::fromUtf8(StringUtil::StdStringFromFormat(
133+
"%02X:%02X:%02X:%02X:%02X:%02X",
134+
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]).c_str()));
135+
onEthMacChanged();
136+
});
137+
138+
// Reroll only the host octet, keeping the subnet. A fully random address
139+
// would land off-subnet or on the router, which is never what you want.
140+
connect(m_ui.ethPS2AddrGenerate, &QPushButton::clicked, this, [&]() {
141+
u8 ip[4] = {192, 168, 1, 2};
142+
const std::string cur = m_ui.ethPS2Addr->text().toStdString();
143+
sscanf(cur.c_str(), "%hhu.%hhu.%hhu.%hhu", &ip[0], &ip[1], &ip[2], &ip[3]);
144+
145+
std::random_device rd;
146+
const u8 was = ip[3];
147+
do
148+
{
149+
ip[3] = static_cast<u8>(2 + (rd() % 253)); // skip .0, .1 and .255
150+
} while (ip[3] == was);
151+
152+
m_ui.ethPS2Addr->setText(QString::fromUtf8(StringUtil::StdStringFromFormat(
153+
"%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]).c_str()));
154+
onEthIPChanged(m_ui.ethPS2Addr, "DEV9/Eth", "PS2IP");
155+
});
156+
157+
refreshAdapterInfo();
158+
116159
//Auto
117160
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.ethNetMaskAuto, "DEV9/Eth", "AutoMask", true);
118161
onEthAutoChanged(m_ui.ethNetMaskAuto, m_ui.ethNetMaskAuto->checkState(), m_ui.ethNetMask, "DEV9/Eth", "AutoMask");
@@ -284,6 +327,57 @@ void DEV9SettingsWidget::onEthDeviceChanged(int index)
284327
dialog()->setStringSettingValue("DEV9/Eth", "EthApi", std::nullopt);
285328
dialog()->setStringSettingValue("DEV9/Eth", "EthDevice", std::nullopt);
286329
}
330+
331+
refreshAdapterInfo();
332+
}
333+
334+
/*
335+
Show what the selected host adapter actually is.
336+
337+
DEV9 stores the adapter by GUID, so when a NIC changes or a VPN/virtual
338+
adapter appears the config can silently point at something that no longer
339+
exists and bridging fails opaquely. This reads the adapter's own details --
340+
address, MAC, gateway -- locally via AdapterUtils. Nothing is sent anywhere;
341+
an emulator settings page has no business making outbound requests.
342+
*/
343+
void DEV9SettingsWidget::refreshAdapterInfo()
344+
{
345+
const std::string guid = dialog()->getStringValue("DEV9/Eth", "EthDevice", "").value();
346+
if (guid.empty())
347+
{
348+
m_ui.ethAdapterInfo->setText(tr("No adapter selected."));
349+
return;
350+
}
351+
352+
AdapterUtils::Adapter adapter;
353+
AdapterUtils::AdapterBuffer buffer;
354+
if (!AdapterUtils::GetAdapter(guid, &adapter, &buffer))
355+
{
356+
m_ui.ethAdapterInfo->setText(
357+
tr("Selected adapter not found on this system - bridging will fail. "
358+
"Pick one from the list above."));
359+
return;
360+
}
361+
362+
QStringList bits;
363+
364+
const std::optional<PacketReader::IP::IP_Address> ip = AdapterUtils::GetAdapterIP(&adapter);
365+
bits << tr("IP: %1").arg(ip.has_value()
366+
? QString::asprintf("%u.%u.%u.%u", ip->bytes[0], ip->bytes[1], ip->bytes[2], ip->bytes[3])
367+
: tr("none"));
368+
369+
const std::optional<PacketReader::MAC_Address> mac = AdapterUtils::GetAdapterMAC(&adapter);
370+
if (mac.has_value())
371+
bits << QString::asprintf("MAC: %02X:%02X:%02X:%02X:%02X:%02X",
372+
mac->bytes[0], mac->bytes[1], mac->bytes[2],
373+
mac->bytes[3], mac->bytes[4], mac->bytes[5]);
374+
375+
const std::vector<PacketReader::IP::IP_Address> gws = AdapterUtils::GetGateways(&adapter);
376+
if (!gws.empty())
377+
bits << tr("Gateway: %1").arg(QString::asprintf("%u.%u.%u.%u",
378+
gws[0].bytes[0], gws[0].bytes[1], gws[0].bytes[2], gws[0].bytes[3]));
379+
380+
m_ui.ethAdapterInfo->setText(bits.join(QStringLiteral(" ")));
287381
}
288382

289383
void DEV9SettingsWidget::onEthDHCPInterceptChanged(Qt::CheckState state)
@@ -317,6 +411,36 @@ void DEV9SettingsWidget::onEthDHCPInterceptChanged(Qt::CheckState state)
317411
onEthDNSModeChanged(m_ui.ethDNS2Mode, m_ui.ethDNS2Mode->currentIndex(), m_ui.ethDNS2Addr, "DEV9/Eth", "ModeDNS2");
318412
}
319413

414+
void DEV9SettingsWidget::onEthMacChanged()
415+
{
416+
const QString text = m_ui.ethMacAddr->text().trimmed();
417+
418+
// empty is meaningful: it clears the setting so DEV9 generates one
419+
if (text.isEmpty())
420+
{
421+
if (dialog()->getStringValue("DEV9/Eth", "Mac", std::nullopt).has_value())
422+
dialog()->setStringSettingValue("DEV9/Eth", "Mac", std::nullopt);
423+
return;
424+
}
425+
426+
u8 mac[6];
427+
if (6 != sscanf(text.toUtf8().constData(), "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
428+
&mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]))
429+
{
430+
QMessageBox::critical(this, tr("Invalid MAC Address"),
431+
tr("MAC addresses look like 00:11:22:33:44:55."));
432+
m_ui.ethMacAddr->setText(QString::fromUtf8(
433+
dialog()->getStringValue("DEV9/Eth", "Mac", "").value().c_str()));
434+
return;
435+
}
436+
437+
const std::string neat = StringUtil::StdStringFromFormat(
438+
"%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
439+
440+
m_ui.ethMacAddr->setText(QString::fromUtf8(neat.c_str()));
441+
dialog()->setStringSettingValue("DEV9/Eth", "Mac", neat.c_str());
442+
}
443+
320444
void DEV9SettingsWidget::onEthIPChanged(QLineEdit* sender, const char* section, const char* key)
321445
{
322446
//Alow clearing a per-game ip setting

pcsx2-qt/Settings/DEV9SettingsWidget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ private Q_SLOTS:
2222
void onEthDeviceTypeChanged(int index);
2323
void onEthDeviceChanged(int index);
2424
void onEthDHCPInterceptChanged(Qt::CheckState state);
25+
void refreshAdapterInfo();
26+
void onEthMacChanged();
2527
void onEthIPChanged(QLineEdit* sender, const char* section, const char* key);
2628
void onEthAutoChanged(QCheckBox* sender, Qt::CheckState state, QLineEdit* input, const char* section, const char* key);
2729
void onEthDNSModeChanged(QComboBox* sender, int index, QLineEdit* input, const char* section, const char* key);

pcsx2-qt/Settings/DEV9SettingsWidget.ui

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,49 @@
5353
</widget>
5454
</item>
5555
<item row="3" column="0" colspan="3">
56+
<widget class="QLabel" name="ethAdapterInfo">
57+
<property name="toolTip">
58+
<string>Live details of the selected host adapter. Everything here is read locally from the adapter itself; nothing is sent anywhere.</string>
59+
</property>
60+
<property name="text">
61+
<string/>
62+
</property>
63+
<property name="textInteractionFlags">
64+
<set>Qt::TextSelectableByMouse</set>
65+
</property>
66+
</widget>
67+
</item>
68+
<item row="4" column="0">
69+
<widget class="QLabel" name="ethMacLabel">
70+
<property name="text">
71+
<string>MAC Address:</string>
72+
</property>
73+
<property name="buddy">
74+
<cstring>ethMacAddr</cstring>
75+
</property>
76+
</widget>
77+
</item>
78+
<item row="4" column="1">
79+
<widget class="QLineEdit" name="ethMacAddr">
80+
<property name="toolTip">
81+
<string>MAC address the emulated PS2 presents on the network. Leave blank to generate a unique one, which is then saved and reused. Ignored if you supply your own eeprom.dat.</string>
82+
</property>
83+
<property name="placeholderText">
84+
<string>Generated automatically</string>
85+
</property>
86+
</widget>
87+
</item>
88+
<item row="4" column="2">
89+
<widget class="QPushButton" name="ethMacGenerate">
90+
<property name="toolTip">
91+
<string>Generate a new random MAC address.</string>
92+
</property>
93+
<property name="text">
94+
<string>Generate</string>
95+
</property>
96+
</widget>
97+
</item>
98+
<item row="5" column="0" colspan="3">
5699
<widget class="QTabWidget" name="ethTabWidget">
57100
<property name="currentIndex">
58101
<number>0</number>
@@ -122,6 +165,16 @@
122165
</property>
123166
</widget>
124167
</item>
168+
<item row="1" column="2">
169+
<widget class="QPushButton" name="ethPS2AddrGenerate">
170+
<property name="toolTip">
171+
<string>Pick a different address on the same subnet. Useful when running more than one instance against a local server.</string>
172+
</property>
173+
<property name="text">
174+
<string>Generate</string>
175+
</property>
176+
</widget>
177+
</item>
125178
<item row="1" column="1">
126179
<widget class="QLineEdit" name="ethPS2Addr">
127180
<property name="inputMask">

0 commit comments

Comments
 (0)