1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-14 00:14:18 +00:00

UniqueID: Use stable SMBIOS fields to generate ID on Windows

This commit adds fixes for generating unique hardware IDs on Windows. The SMBIOS is parsed to generate a unique ID based on hardware factors of the local machine.
This commit is contained in:
Oliver James 2023-04-21 18:25:31 +01:00 committed by Aymeric
parent 66fa787b01
commit ea917c596d
5 changed files with 441 additions and 30 deletions

View file

@ -1,6 +1,194 @@
JUCE breaking changes
=====================
develop
=======
Change
------
Unique device IDs on Windows have been updated to use a more reliable SMBIOS
parser. The SystemStats::getUniqueDeviceID function now returns new IDs using
this improved parser. Additionally, a new function,
SystemStats::getMachineIdentifiers, has been introduced to aggregate all ID
sources. It is recommended to use this new function to verify any IDs.
Possible Issues
----------------
The SystemStats::getUniqueDeviceID function will return a different ID for the
same machine due to the updated parser.
Workaround
----------
For code that previously relied on SystemStats::getUniqueDeviceID, it is advised
to switch to using SystemStats::getMachineIdentifiers() instead.
Rationale
---------
This update ensures the generation of more stable and reliable unique device IDs,
while also maintaining backward compatibility with the previous ID generation methods.
Change
------
The Grid layout algorithm has been slightly altered to provide more consistent
behaviour. The new approach guarantees that dimensions specified using the
absolute Px quantity will always be correctly rounded when applied to the
integer dimensions of Components.
Possible Issues
---------------
Components laid out using Grid can observe a size or position change of +/- 1px
along each dimension compared with the result of the previous algorithm.
Workaround
----------
If the Grid based graphical layout is sensitive to changes of +/- 1px, then the
UI layout code may have to be adjusted to the new algorithm.
Rationale
---------
The old Grid layout algorithm could exhibit surprising and difficult to control
single pixel artifacts, where an item with a specified absolute size of
e.g. 100px could end up with a layout size of 101px. The new approach
guarantees that such items will have a layout size exactly as specified, and
this new behaviour is also in line with CSS behaviour in browsers. The new
approach makes necessary corrections easier as adding 1px to the size of an
item with absolute dimensions is guaranteed to translate into an observable 1px
increase in the layout size.
Change
------
The k91_4 and k90_4 VST3 layouts are now mapped to the canonical JUCE 9.1.4 and
9.0.4 AudioChannelSets. This has a different ChannelType layout than the
AudioChannelSet previously used with such VST3 SpeakerArrangements.
Possible Issues
---------------
VST3 plugins that were prepared to work with the k91_4 and k90_4
SpeakerArrangements may now have incorrect channel mapping. The channels
previously accessible through ChannelType::left and right are now accessible
through ChannelType::wideLeft and wideRight, and channels previously accessible
through ChannelType::leftCentre and rightCentre are now accessible through
ChannelType::left and right.
Workaround
----------
Code that accesses the channels that correspond to the VST3 Speakers kSpeakerL,
kSpeakerR, kSpeakerLc and kSpeakerRc needs to be updated. These channels are now
accessible respectively through ChannelTypes wideLeft, wideRight, left and
right. Previously they were accessible respectively through left, right,
leftCentre and rightCentre.
Rationale
---------
This change allows developers to handle the 9.1.4 and 9.0.4 surround layouts
with one codepath across all plugin formats. Previously the
AudioChannelSet::create9point1point4() and create9point0point4() layouts would
only be used in CoreAudio and AAX, but a different AudioChannelSet would be used
in VST3 even though they were functionally equivalent.
Change
------
The signatures of the ContentSharer member functions have been updated. The
ContentSharer class itself is no longer a singleton.
Possible Issues
---------------
Projects that use the old signatures will not build until they are updated.
Workaround
----------
Instead of calling content sharer functions through a singleton instance, e.g.
ContentSharer::getInstance()->shareText (...);
call the static member functions directly:
ScopedMessageBox messageBox = ContentSharer::shareTextScoped (...);
The new functions return a ScopedMessageBox instance. On iOS, the content
sharer will only remain open for as long as the ScopedMessageBox remains alive.
On Android, this functionality will be added as/when the native APIs allow.
Rationale
---------
The new signatures are safer and easier to use. The ScopedMessageBox also
allows content sharers to be dismissed programmatically, which wasn't
previously possible.
Change
------
The minimum supported AAX library version has been bumped to 2.4.0 and the
library is now built automatically while building an AAX plugin. The
JucePlugin_AAXLibs_path preprocessor definition is no longer defined in AAX
plugin builds.
Possible Issues
---------------
Projects that use the JucePlugin_AAXLibs_path definition may no longer build
correctly. Projects that reference an AAX library version earlier than 2.4.0
will fail to build.
Workaround
----------
You must download an AAX library distribution with a version of at least 2.4.0.
Use the definition JucePlugin_Build_AAX to check whether the AAX format is
enabled at build time.
Rationale
---------
The JUCE framework now requires features only present in version 2.4.0 of the
AAX library. The build change removes steps from the build process, and ensures
that the same compiler flags are used across the entire project.
Change
------
The implementation of ColourGradient::createLookupTable has been updated to use
non-premultiplied colours.
Possible Issues
---------------
Programs that draw transparent gradients using the OpenGL or software
renderers, or that use lookup tables generated from transparent gradients for
other purposes, may now produce different results.
Workaround
----------
For gradients to render the same as they did previously, transparent colour
stops should be un-premultiplied. For colours with an alpha component of 0, it
may be necessary to specify appropriate RGB components.
Rationale
---------
Previously, transparent gradients rendered using CoreGraphics looked different
to the same gradients drawn using OpenGL or the software renderer. This change
updates the OpenGL and software renderers, so that they produce the same
results as CoreGraphics.
Change
------
Projucer-generated MSVC projects now build VST3s as bundles, rather than as
single DLL files.
Possible Issues
---------------
Build workflows that expect the VST3 to be a single DLL may break.
Workaround
----------
Any post-build scripts that expect to copy or move the built VST3 should be
updated so that the entire bundle directory is copied/moved. The DLL itself
can still be located and extracted from within the generated bundle if
necessary.
Rationale
---------
Distributing VST3s as single files was deprecated in VST3 v3.6.10. JUCE's CMake
scripts already produce VST3s as bundles, so this change increases consistency
between the two build systems.
Version 7.0.3
=============

View file

@ -659,28 +659,180 @@ String SystemStats::getDisplayLanguage()
return languagesBuffer.data();
}
static constexpr DWORD generateProviderID (const char* string)
{
return (DWORD) string[0] << 0x18
| (DWORD) string[1] << 0x10
| (DWORD) string[2] << 0x08
| (DWORD) string[3] << 0x00;
}
static std::optional<std::vector<std::byte>> readSMBIOSData()
{
const auto sig = generateProviderID ("RSMB");
const auto id = generateProviderID ("RSDT");
if (const auto bufLen = GetSystemFirmwareTable (sig, id, nullptr, 0); bufLen > 0)
{
std::vector<std::byte> buffer;
buffer.resize (bufLen);
if (GetSystemFirmwareTable (sig, id, buffer.data(), bufLen) == buffer.size())
return std::make_optional (std::move (buffer));
}
return {};
}
String getLegacyUniqueDeviceID()
{
if (const auto dump = readSMBIOSData())
{
uint64_t hash = 0;
const auto start = dump->data();
const auto end = start + jmin (1024, (int) dump->size());
for (auto dataPtr = start; dataPtr != end; ++dataPtr)
hash = hash * (uint64_t) 101 + (uint8_t) *dataPtr;
return String (hash);
}
return {};
}
String SystemStats::getUniqueDeviceID()
{
#define PROVIDER(string) (DWORD) (string[0] << 24 | string[1] << 16 | string[2] << 8 | string[3])
auto bufLen = GetSystemFirmwareTable (PROVIDER ("RSMB"), PROVIDER ("RSDT"), nullptr, 0);
if (bufLen > 0)
if (const auto smbiosBuffer = readSMBIOSData())
{
HeapBlock<uint8_t> buffer { bufLen };
GetSystemFirmwareTable (PROVIDER ("RSMB"), PROVIDER ("RSDT"), (void*) buffer.getData(), bufLen);
return [&]
#pragma pack (push, 1)
struct RawSMBIOSData
{
uint64_t hash = 0;
const auto start = buffer.getData();
const auto end = start + jmin (1024, (int) bufLen);
uint8_t unused[4];
uint32_t length;
};
for (auto dataPtr = start; dataPtr != end; ++dataPtr)
hash = hash * (uint64_t) 101 + *dataPtr;
struct SMBIOSHeader
{
uint8_t id;
uint8_t length;
uint16_t handle;
};
#pragma pack (pop)
return String (hash);
}();
String uuid;
const auto* asRawSMBIOSData = unalignedPointerCast<const RawSMBIOSData*> (smbiosBuffer->data());
Span<const std::byte> content (smbiosBuffer->data() + sizeof (RawSMBIOSData), asRawSMBIOSData->length);
while (! content.empty())
{
const auto* header = unalignedPointerCast<const SMBIOSHeader*> (content.data());
const auto* stringTable = unalignedPointerCast<const char*> (content.data() + header->length);
std::vector<const char*> strings;
// Each table comprises a struct and a varying number of null terminated
// strings. The string section is delimited by a pair of null terminators.
// Some fields in the header are indices into the string table.
const auto sizeofStringTable = [stringTable, &strings, &content]
{
size_t tableLen = 0;
while (tableLen < content.size())
{
const auto* str = stringTable + tableLen;
const auto n = strlen (str);
if (n == 0)
break;
strings.push_back (str);
tableLen += n + 1;
}
return jmax (tableLen, (size_t) 1) + 1;
}();
const auto stringFromOffset = [&content, &strings = std::as_const (strings)] (size_t byteOffset)
{
if (const auto index = std::to_integer<size_t> (content[byteOffset]); 0 < index && index <= strings.size())
return strings[index - 1];
return "";
};
enum
{
systemManufacturer = 0x04,
systemProductName = 0x05,
systemSerialNumber = 0x07,
systemUUID = 0x08, // 16byte UUID. Can be all 0xFF or all 0x00. Might be user changeable.
systemSKU = 0x19,
systemFamily = 0x1a,
baseboardManufacturer = 0x04,
baseboardProduct = 0x05,
baseboardVersion = 0x06,
baseboardSerialNumber = 0x07,
baseboardAssetTag = 0x08,
processorManufacturer = 0x07,
processorVersion = 0x10,
processorAssetTag = 0x21,
processorPartNumber = 0x22
};
switch (header->id)
{
case 1: // System
{
uuid += stringFromOffset (systemManufacturer);
uuid += "\n";
uuid += stringFromOffset (systemProductName);
uuid += "\n";
char hexBuf[(16 * 2) + 1]{};
const auto* src = content.data() + systemUUID;
for (auto i = 0; i != 16; ++i)
snprintf (hexBuf + 2 * i, 3, "%02hhX", src[i]);
uuid += hexBuf;
uuid += "\n";
break;
}
case 2: // Baseboard
uuid += stringFromOffset (baseboardManufacturer);
uuid += "\n";
uuid += stringFromOffset (baseboardProduct);
uuid += "\n";
uuid += stringFromOffset (baseboardVersion);
uuid += "\n";
uuid += stringFromOffset (baseboardSerialNumber);
uuid += "\n";
uuid += stringFromOffset (baseboardAssetTag);
uuid += "\n";
break;
case 4: // Processor
uuid += stringFromOffset (processorManufacturer);
uuid += "\n";
uuid += stringFromOffset (processorVersion);
uuid += "\n";
uuid += stringFromOffset (processorAssetTag);
uuid += "\n";
uuid += stringFromOffset (processorPartNumber);
uuid += "\n";
break;
}
const auto increment = header->length + sizeofStringTable;
content = Span (content.data() + increment, content.size() - increment);
}
return String (uuid.hashCode64());
}
// Please tell someone at JUCE if this occurs

View file

@ -60,24 +60,64 @@ String SystemStats::getJUCEVersion()
StringArray SystemStats::getDeviceIdentifiers()
{
for (const auto flag : { MachineIdFlags::fileSystemId, MachineIdFlags::macAddresses })
if (auto ids = getMachineIdentifiers (flag); ! ids.isEmpty())
return ids;
jassertfalse; // Failed to create any IDs!
return {};
}
String getLegacyUniqueDeviceID();
StringArray SystemStats::getMachineIdentifiers (MachineIdFlags flags)
{
auto macAddressProvider = [] (StringArray& arr)
{
for (const auto& mac : MACAddress::getAllAddresses())
arr.add (mac.toString());
};
auto fileSystemProvider = [] (StringArray& arr)
{
#if JUCE_WINDOWS
File f (File::getSpecialLocation (File::windowsSystemDirectory));
#else
File f ("~");
#endif
if (auto num = f.getFileIdentifier())
arr.add (String::toHexString ((int64) num));
};
auto legacyIdProvider = [] ([[maybe_unused]] StringArray& arr)
{
#if JUCE_WINDOWS
arr.add (getLegacyUniqueDeviceID());
#endif
};
auto uniqueIdProvider = [] (StringArray& arr)
{
arr.add (getUniqueDeviceID());
};
struct Provider { MachineIdFlags flag; void (*func) (StringArray&); };
static const Provider providers[] =
{
{ MachineIdFlags::macAddresses, macAddressProvider },
{ MachineIdFlags::fileSystemId, fileSystemProvider },
{ MachineIdFlags::legacyUniqueId, legacyIdProvider },
{ MachineIdFlags::uniqueId, uniqueIdProvider }
};
StringArray ids;
#if JUCE_WINDOWS
File f (File::getSpecialLocation (File::windowsSystemDirectory));
#else
File f ("~");
#endif
if (auto num = f.getFileIdentifier())
for (const auto& provider : providers)
{
ids.add (String::toHexString ((int64) num));
}
else
{
for (auto& address : MACAddress::getAllAddresses())
ids.add (address.toString());
if (hasBitValueSet (flags, provider.flag))
provider.func (ids);
}
jassert (! ids.isEmpty()); // Failed to create any IDs!
return ids;
}

View file

@ -157,6 +157,31 @@ public:
*/
static String getUniqueDeviceID();
/** Kinds of identifier that are passed to getMachineIdentifiers(). */
enum class MachineIdFlags
{
macAddresses = 1 << 0, ///< All Mac addresses of the machine.
fileSystemId = 1 << 1, ///< The filesystem id of the user's home directory (or system directory on Windows).
legacyUniqueId = 1 << 2, ///< Only implemented on Windows. A hash of the full smbios table, may be unstable on certain machines.
uniqueId = 1 << 3, ///< The most stable kind of machine identifier. A good default to use.
};
/** Returns a list of strings that can be used to uniquely identify a machine.
To get multiple kinds of identifier at once, you can combine flags using
bitwise-or, e.g. `uniqueId | legacyUniqueId`.
If a particular kind of identifier isn't available, it will be omitted from
the StringArray of results, so passing `uniqueId | legacyUniqueId`
may return 0, 1, or 2 results, depending on the platform and whether any
errors are encountered.
If you've previously generated a machine ID and just want to check it against
all possible identifiers, you can enable all of the flags and check whether
the stored identifier matches any of the results.
*/
static StringArray getMachineIdentifiers (MachineIdFlags flags);
//==============================================================================
// CPU and memory information..
@ -254,4 +279,6 @@ private:
JUCE_DECLARE_NON_COPYABLE (SystemStats)
};
JUCE_DECLARE_SCOPED_ENUM_BITWISE_OPERATORS (SystemStats::MachineIdFlags)
} // namespace juce

View file

@ -324,7 +324,11 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996)
StringArray OnlineUnlockStatus::MachineIDUtilities::getLocalMachineIDs()
{
auto identifiers = SystemStats::getDeviceIdentifiers();
auto flags = SystemStats::MachineIdFlags::macAddresses
| SystemStats::MachineIdFlags::fileSystemId
| SystemStats::MachineIdFlags::legacyUniqueId
| SystemStats::MachineIdFlags::uniqueId;
auto identifiers = SystemStats::getMachineIdentifiers (flags);
for (auto& identifier : identifiers)
identifier = getEncodedIDString (identifier);