|
4 | 4 | #include <QtWidgets/QMessageBox> |
5 | 5 | #include <QtWidgets/QFileDialog> |
6 | 6 | #include <algorithm> |
| 7 | +#include <random> |
7 | 8 |
|
8 | 9 | #include "common/FileSystem.h" |
9 | 10 | #include "common/Path.h" |
10 | 11 | #include "common/StringUtil.h" |
11 | 12 |
|
| 13 | +#include "pcsx2/DEV9/AdapterUtils.h" |
12 | 14 | #include "pcsx2/Host.h" |
13 | 15 | #include "pcsx2/INISettingsInterface.h" |
14 | 16 |
|
@@ -113,6 +115,47 @@ DEV9SettingsWidget::DEV9SettingsWidget(SettingsWindow* settings_dialog, QWidget* |
113 | 115 | connect(m_ui.ethDNS2Addr, &QLineEdit::editingFinished, this, [&]() { onEthIPChanged(m_ui.ethDNS2Addr, "DEV9/Eth", "DNS2" ); }); |
114 | 116 | // clang-format on |
115 | 117 |
|
| 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 | + |
116 | 159 | //Auto |
117 | 160 | SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.ethNetMaskAuto, "DEV9/Eth", "AutoMask", true); |
118 | 161 | onEthAutoChanged(m_ui.ethNetMaskAuto, m_ui.ethNetMaskAuto->checkState(), m_ui.ethNetMask, "DEV9/Eth", "AutoMask"); |
@@ -284,6 +327,57 @@ void DEV9SettingsWidget::onEthDeviceChanged(int index) |
284 | 327 | dialog()->setStringSettingValue("DEV9/Eth", "EthApi", std::nullopt); |
285 | 328 | dialog()->setStringSettingValue("DEV9/Eth", "EthDevice", std::nullopt); |
286 | 329 | } |
| 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(" "))); |
287 | 381 | } |
288 | 382 |
|
289 | 383 | void DEV9SettingsWidget::onEthDHCPInterceptChanged(Qt::CheckState state) |
@@ -317,6 +411,36 @@ void DEV9SettingsWidget::onEthDHCPInterceptChanged(Qt::CheckState state) |
317 | 411 | onEthDNSModeChanged(m_ui.ethDNS2Mode, m_ui.ethDNS2Mode->currentIndex(), m_ui.ethDNS2Addr, "DEV9/Eth", "ModeDNS2"); |
318 | 412 | } |
319 | 413 |
|
| 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 | + |
320 | 444 | void DEV9SettingsWidget::onEthIPChanged(QLineEdit* sender, const char* section, const char* key) |
321 | 445 | { |
322 | 446 | //Alow clearing a per-game ip setting |
|
0 commit comments