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:
parent
66fa787b01
commit
ea917c596d
5 changed files with 441 additions and 30 deletions
|
|
@ -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
|
||||
=============
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue