From ea917c596d094f2e531abaec3ac35b43804e7ac2 Mon Sep 17 00:00:00 2001 From: Oliver James Date: Fri, 21 Apr 2023 18:25:31 +0100 Subject: [PATCH] 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. --- BREAKING-CHANGES.txt | 188 ++++++++++++++++++ .../native/juce_win32_SystemStats.cpp | 184 +++++++++++++++-- modules/juce_core/system/juce_SystemStats.cpp | 66 ++++-- modules/juce_core/system/juce_SystemStats.h | 27 +++ .../marketplace/juce_OnlineUnlockStatus.cpp | 6 +- 5 files changed, 441 insertions(+), 30 deletions(-) diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 973f3e0746..2c54649b35 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -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 ============= diff --git a/modules/juce_core/native/juce_win32_SystemStats.cpp b/modules/juce_core/native/juce_win32_SystemStats.cpp index 63163d148b..bb4713e970 100644 --- a/modules/juce_core/native/juce_win32_SystemStats.cpp +++ b/modules/juce_core/native/juce_win32_SystemStats.cpp @@ -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> readSMBIOSData() +{ + const auto sig = generateProviderID ("RSMB"); + const auto id = generateProviderID ("RSDT"); + + if (const auto bufLen = GetSystemFirmwareTable (sig, id, nullptr, 0); bufLen > 0) + { + std::vector 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 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 (smbiosBuffer->data()); + Span content (smbiosBuffer->data() + sizeof (RawSMBIOSData), asRawSMBIOSData->length); + + while (! content.empty()) + { + const auto* header = unalignedPointerCast (content.data()); + const auto* stringTable = unalignedPointerCast (content.data() + header->length); + std::vector 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 (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 diff --git a/modules/juce_core/system/juce_SystemStats.cpp b/modules/juce_core/system/juce_SystemStats.cpp index 8601bc0849..7e9faf5bb8 100644 --- a/modules/juce_core/system/juce_SystemStats.cpp +++ b/modules/juce_core/system/juce_SystemStats.cpp @@ -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; } diff --git a/modules/juce_core/system/juce_SystemStats.h b/modules/juce_core/system/juce_SystemStats.h index a80cc541de..5d415db15b 100644 --- a/modules/juce_core/system/juce_SystemStats.h +++ b/modules/juce_core/system/juce_SystemStats.h @@ -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 diff --git a/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp b/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp index 0114483b9b..f11a55c356 100644 --- a/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp +++ b/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockStatus.cpp @@ -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);