diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp index 35c7943b4..575d99340 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp @@ -7,6 +7,47 @@ static constexpr int MAX_DECK_SIZE = 1e5; +namespace +{ + +/** + * @brief Expands card nodes into one lowercase name entry per copy. + * + * @param nodes The card nodes to expand. + * @param prefix Optional prefix prepended to every entry (e.g. "SB:" for the sideboard). + * @return One entry per copy, in node order. + */ +QStringList cardNodesToCopies(const QList &nodes, const QString &prefix = {}) +{ + QStringList result; + for (auto node : nodes) { + for (int i = 0; i < node->getNumber(); ++i) { + result.append(prefix + node->getName().toLower()); + } + } + return result; +} + +/** + * @brief Packs the first five bytes of a SHA-1 digest into a compact base-32 number. + * + * The bytes are placed in most-significant-byte-first order (byte 0 shifted by 32, + * byte 4 unshifted) so decks that only differ in their low-order hash bytes still + * produce distinct identifiers. + * + * @return The 8-character base-32 representation of the packed number. + */ +QString encodeDeckHash(const QByteArray &digest) +{ + quint64 number = 0; + for (int i = 0; i < 5; ++i) { + number |= static_cast(static_cast(digest[i])) << (32 - 8 * i); + } + return QString::number(number, 32).rightJustified(8, '0'); +} + +} // namespace + DecklistNodeTree::DecklistNodeTree() : root(new InnerDecklistNode()) { } @@ -83,25 +124,11 @@ QString DecklistNodeTree::computeDeckHash() const auto mainDeckNodes = getCardNodes({DECK_ZONE_MAIN}); auto sideDeckNodes = getCardNodes({DECK_ZONE_SIDE}); - static auto nodesToCardList = [](const QList &nodes, const QString &prefix = {}) { - QStringList result; - for (auto node : nodes) { - for (int i = 0; i < node->getNumber(); ++i) { - result.append(prefix + node->getName().toLower()); - } - } - return result; - }; - - QStringList cardList = nodesToCardList(mainDeckNodes) + nodesToCardList(sideDeckNodes, "SB:"); + QStringList cardList = cardNodesToCopies(mainDeckNodes) + cardNodesToCopies(sideDeckNodes, "SB:"); cardList.sort(); QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); - quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) + - (((quint64)(unsigned char)deckHashArray[1]) << 24) + - (((quint64)(unsigned char)deckHashArray[2] << 16)) + - (((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4]; - return QString::number(number, 32).rightJustified(8, '0'); + return encodeDeckHash(deckHashArray); } void DecklistNodeTree::write(QXmlStreamWriter *xml) const