Skip to content

Commit d51a413

Browse files
taserzclaude
andcommitted
DEV9: allow a configurable / generated MAC address
The PS2 SMAP MAC lives in the first four words of the DEV9 EEPROM. The built-in default image hardcodes 76:6D:61:63:30:31 ("vmac01"), and it is only replaced when the user happens to have an eeprom.dat next to the binary. Every install without one therefore presents an identical MAC. Two instances bridged onto the same LAN are an address collision: switch MAC tables flap and DHCP hands out a single lease. Nothing on the network can tell two emulated consoles apart. Add DEV9Options::Mac and DEV9Options::AutoMac. On init, and only when the built-in image is in use, write the configured MAC into the EEPROM and fix the checksum. A user supplied eeprom.dat is never touched. When no MAC is configured and AutoMac is set, generate one, persist it via the host settings and reuse it from then on. Generated once rather than per boot: re-rolling on every launch would break DHCP leases and switch MAC tables, which are the problems this is meant to fix. Generated addresses clear the multicast bit and set the locally administered bit so they cannot be mistaken for a vendor assigned address. EEPROM words 0-2 hold the MAC and word 3 holds their 16-bit sum: 0x6D76 + 0x6361 + 0x3130 = 0x10207, truncated to 0x0207, which is exactly the checksum stored in the existing default. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent e1dd0a0 commit d51a413

3 files changed

Lines changed: 107 additions & 0 deletions

File tree

pcsx2/Config.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,14 @@ struct Pcsx2Config
10661066

10671067
std::vector<HostEntry> EthHosts;
10681068

1069+
// PS2 SMAP MAC. All-zero means "not set yet"; with AutoMac a unique
1070+
// address is generated once and persisted, so each install is
1071+
// distinguishable on the network instead of sharing the built-in
1072+
// default. Generated once rather than per boot: a MAC that changes
1073+
// every launch breaks DHCP leases and switch MAC tables.
1074+
u8 Mac[6]{};
1075+
bool AutoMac{true};
1076+
10691077
bool HddEnable{false};
10701078
std::string HddFile;
10711079

@@ -1079,6 +1087,8 @@ struct Pcsx2Config
10791087
protected:
10801088
static void LoadIPHelper(u8* field, const std::string& setting);
10811089
static std::string SaveIPHelper(u8* field);
1090+
static void LoadMacHelper(u8* field, const std::string& setting);
1091+
static std::string SaveMacHelper(u8* field);
10821092
};
10831093

10841094
// ------------------------------------------------------------------------

pcsx2/DEV9/DEV9.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#include "common/Path.h"
66
#include "common/StringUtil.h"
77

8+
#include <cstring>
9+
#include <random>
10+
11+
#include "Host.h"
812
#include "IopDma.h"
913

1014
#ifdef _WIN32
@@ -70,6 +74,36 @@ u8 eeprom[] = {
7074
};
7175
// clang-format on
7276

77+
// The MAC occupies EEPROM words 0-2, with word 3 holding their 16-bit sum.
78+
// Verified against the default above: 0x6D76 + 0x6361 + 0x3130 == 0x10207,
79+
// truncated to 0x0207, which is exactly the 0x07,0x02 stored there.
80+
static u16 DEV9MacChecksum(const u8* mac)
81+
{
82+
const u16 w0 = static_cast<u16>(mac[0] | (mac[1] << 8));
83+
const u16 w1 = static_cast<u16>(mac[2] | (mac[3] << 8));
84+
const u16 w2 = static_cast<u16>(mac[4] | (mac[5] << 8));
85+
return static_cast<u16>(w0 + w1 + w2);
86+
}
87+
88+
static void DEV9SetEepromMac(u8* ee, const u8* mac)
89+
{
90+
std::memcpy(ee, mac, 6);
91+
const u16 sum = DEV9MacChecksum(ee);
92+
ee[6] = static_cast<u8>(sum & 0xFF);
93+
ee[7] = static_cast<u8>(sum >> 8);
94+
}
95+
96+
// A generated address must never be mistakable for a vendor-assigned one:
97+
// clear the multicast bit and set the locally-administered bit.
98+
static void DEV9GenerateMac(u8* mac)
99+
{
100+
std::random_device rd;
101+
for (int i = 0; i < 6; i++)
102+
mac[i] = static_cast<u8>(rd() & 0xFF);
103+
104+
mac[0] = static_cast<u8>((mac[0] & 0xFC) | 0x02);
105+
}
106+
73107
#ifdef _WIN32
74108
HANDLE hEeprom;
75109
HANDLE mapping;
@@ -159,6 +193,36 @@ s32 DEV9init()
159193
}
160194
#endif
161195

196+
// Only ever touch the built-in image. If the user supplied an eeprom.dat
197+
// it is theirs, MAC included, and must be left exactly as found.
198+
if (dev9.eeprom == reinterpret_cast<u16*>(eeprom))
199+
{
200+
u8 mac[6];
201+
std::memcpy(mac, EmuConfig.DEV9.Mac, 6);
202+
203+
const bool unset = (mac[0] | mac[1] | mac[2] | mac[3] | mac[4] | mac[5]) == 0;
204+
if (unset && EmuConfig.DEV9.AutoMac)
205+
{
206+
DEV9GenerateMac(mac);
207+
std::memcpy(EmuConfig.DEV9.Mac, mac, 6);
208+
209+
// EmuConfig is a runtime copy populated FROM the settings store, so
210+
// writing it alone is discarded on exit and a fresh MAC would be
211+
// generated every boot. Persist through the host settings instead,
212+
// which is what makes this stable across restarts.
213+
const std::string macStr = StringUtil::StdStringFromFormat(
214+
"%02X:%02X:%02X:%02X:%02X:%02X",
215+
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
216+
Host::SetBaseStringSettingValue("DEV9/Eth", "Mac", macStr.c_str());
217+
Host::CommitBaseSettingChanges();
218+
219+
Console.WriteLn("DEV9: generated MAC %s", macStr.c_str());
220+
}
221+
222+
if (!unset || EmuConfig.DEV9.AutoMac)
223+
DEV9SetEepromMac(eeprom, mac);
224+
}
225+
162226
for (int rxbi = 0; rxbi < (SMAP_BD_SIZE / 8); rxbi++)
163227
{
164228
smap_bd_t* pbd = (smap_bd_t*)&dev9.dev9R[SMAP_BD_RX_BASE & 0xffff];

pcsx2/Pcsx2Config.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,14 @@ void Pcsx2Config::DEV9Options::LoadSave(SettingsWrapper& wrap)
13231323
SettingsWrapEntry(EthLogDHCP);
13241324
SettingsWrapEntry(EthLogDNS);
13251325

1326+
// Persisted so a generated MAC is stable across restarts. Rotating it
1327+
// every boot would break DHCP leases and switch MAC tables.
1328+
SettingsWrapEntry(AutoMac);
1329+
std::string macStr = SaveMacHelper(Mac);
1330+
SettingsWrapEntryEx(macStr, "Mac");
1331+
if (wrap.IsLoading())
1332+
LoadMacHelper(Mac, macStr);
1333+
13261334
SettingsWrapEntry(InterceptDHCP);
13271335

13281336
std::string ps2IPStr = "0.0.0.0";
@@ -1446,6 +1454,31 @@ std::string Pcsx2Config::DEV9Options::SaveIPHelper(u8* field)
14461454
return StringUtil::StdStringFromFormat("%u.%u.%u.%u", field[0], field[1], field[2], field[3]);
14471455
}
14481456

1457+
void Pcsx2Config::DEV9Options::LoadMacHelper(u8* field, const std::string& setting)
1458+
{
1459+
if (setting.empty())
1460+
{
1461+
std::fill(field, field + 6, 0); // unset; DEV9 will generate one
1462+
return;
1463+
}
1464+
1465+
if (6 == sscanf(setting.c_str(), "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
1466+
&field[0], &field[1], &field[2], &field[3], &field[4], &field[5]))
1467+
return;
1468+
1469+
Console.Error("Invalid MAC address in settings file");
1470+
std::fill(field, field + 6, 0);
1471+
}
1472+
1473+
std::string Pcsx2Config::DEV9Options::SaveMacHelper(u8* field)
1474+
{
1475+
if ((field[0] | field[1] | field[2] | field[3] | field[4] | field[5]) == 0)
1476+
return std::string(); // keep "unset" out of the ini
1477+
1478+
return StringUtil::StdStringFromFormat("%02X:%02X:%02X:%02X:%02X:%02X",
1479+
field[0], field[1], field[2], field[3], field[4], field[5]);
1480+
}
1481+
14491482
bool Pcsx2Config::DEV9Options::HostEntry::operator==(const HostEntry& right) const
14501483
{
14511484
return OpEqu(Url) &&

0 commit comments

Comments
 (0)