Compare commits

..

8 commits

Author SHA1 Message Date
Lukas Brübach
f7263e1287 [Oracle] Fix nesting-depth cap, tolerate unescaped control chars, lazy-parse review fixes 2026-09-02 15:04:38 +02:00
Lukas Brübach
b6e74928d4 [Oracle] Parse sets lazily to slash importer peak memory
- Add a raw JSON scanner that splits the document into per-set byte ranges
  without materializing the JSON tree
- Keep only the raw document bytes and parse one set at a time in startImport
- Take readSetsFromByteArray by value so the wizard's buffer is moved, not copied
- Clear the retained raw data in releaseSetData()/clear()
- Cover the scanner and lazy parsing with tests

Took 2 minutes
2026-09-02 15:00:36 +02:00
Lukas Brübach
64e5c0de19 [Oracle/Tests] Measure parse against post-fixture baseline; assert release empties sets 2026-09-02 14:55:52 +02:00
Lukas Brübach
81c9edbf89 [Oracle] Add RAM usage benchmarks for the oracle importer
- Measure process peak/current RSS via procfs (Linux) or getrusage (macOS)
- Add a synthetic-scale RAM benchmark and an opt-in real AllPrintings
  run gated by COCKATRICE_ORACLE_RAM_BENCHMARK=1
- Mirror the wizard's magic-byte handling to decompress .xz/.zip payloads
- Wire optional ZLIB/LibLZMA into the benchmark target and raise its timeout

Took 2 minutes
2026-09-02 14:53:16 +02:00
Lukas Brübach
bd1fb69800 [Oracle/Tests] Pin cmc coercion in CI run; scope the reserve pass
The #7214 coercion assertion lived only in oracle_importer_benchmark_test,
which gets no add_test and so never runs under ctest. Add NumericManaValueCoercedToCmc
and LegacyConvertedManaCostCoercedToCmc to oracle_importer_test (a CI-ran
binary): manaValue/convertedManaCost are JSON numbers in AllPrintings, and
QJsonValue::toString() would drop them to an empty cmc without the
#7214 coercion fix.

Wrap the distinct-name reserve pass in a bare block so the ~35k name
QStrings are handed back before the memory-heavy import loop starts.
2026-09-02 14:50:58 +02:00
Lukas Brübach
770282c5b5 [Oracle] Add oracle importer tests and fix set parsing details
- Add oracle_importer_test and oracle_importer_benchmark_test targets
- Preserve the first printing's legalities when an existing card is reused
- Concatenate split-card coloridentity and sort/dedupe card colors
- Use a raw string for the Basic Land format regex
- Pre-allocate the card hash and micro-optimize string handling

Took 2 minutes
2026-08-30 23:06:49 +02:00
Lukas Brübach
b16497327f [Oracle] Restore property coercion and legality merge in native JSON import 2026-08-30 22:54:12 +02:00
Lukas Brübach
23bdc1d986 [Oracle] Replace vendored QtJson with native QJson for set import
- Drop the vendored oracle/src/qt-json/json.{h,cpp} implementation
- Switch SetToDownload and importCardsFromSet from QList<QVariant> to
  native QJsonArray/QJsonObject
- Release set JSON data after import in the save sets page

Took 20 minutes
2026-08-30 13:39:00 +02:00
38 changed files with 216 additions and 359 deletions

View file

@ -8,7 +8,6 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \
gtest \
mariadb-libs \
ninja \
openssl \
protobuf \
qt6-base \
qt6-declarative \

View file

@ -15,7 +15,6 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
libssl-dev \
ninja-build \
protobuf-compiler \
qt6-image-formats-plugins \

View file

@ -16,7 +16,6 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
libssl-dev \
ninja-build \
protobuf-compiler \
qt6-image-formats-plugins \

View file

@ -7,7 +7,6 @@ RUN dnf install -y \
git \
mariadb-devel \
ninja-build \
openssl-devel \
protobuf-devel \
qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
qt6-qtimageformats \

View file

@ -7,7 +7,6 @@ RUN dnf install -y \
git \
mariadb-devel \
ninja-build \
openssl-devel \
protobuf-devel \
qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
qt6-qtimageformats \

View file

@ -12,7 +12,6 @@ RUN apt-get update && \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6sql6-mysql \
libssl-dev \
ninja-build \
protobuf-compiler \
qt6-tools-dev \

View file

@ -15,7 +15,6 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
libssl-dev \
ninja-build \
protobuf-compiler \
qt6-image-formats-plugins \

View file

@ -16,7 +16,6 @@ RUN apt-get update && \
libprotobuf-dev \
libqt6multimedia6 \
libqt6sql6-mysql \
libssl-dev \
ninja-build \
protobuf-compiler \
qt6-image-formats-plugins \

View file

@ -5,8 +5,8 @@
# This file sets all the variables shared between the projects
# like the installation path, compilation flags etc..
# 3.16 required for Qt6 and target_precompile_headers()
cmake_minimum_required(VERSION 3.16)
# cmake 3.16 is required if using qt6
cmake_minimum_required(VERSION 3.10)
# Use compiler cache (ccache)
option(USE_CCACHE "Cache the build results with ccache" OFF)
@ -184,9 +184,6 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${FLAG}")
endif()
endforeach()
# Reduce compiler I/O by using pipes between stages instead of temp files
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
else()
# other: osx/llvm, bsd/llvm
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
@ -195,9 +192,6 @@ else()
else()
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra")
endif()
# Reduce compiler I/O by using pipes between stages instead of temp files
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
endif()
# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning
@ -245,6 +239,11 @@ if(WIN32)
find_package(OpenSSL REQUIRED)
if(OPENSSL_FOUND)
include_directories(${OPENSSL_INCLUDE_DIRS})
else()
message(
WARNING
"Could not find OpenSSL runtime libraries. They are not required for compiling, but needs to be available at runtime."
)
endif()
endif()

View file

@ -14,7 +14,6 @@ RUN apt-get update \
libmariadb-dev-compat \
libprotobuf-dev \
libqt6sql6-mysql \
libssl-dev \
qt6-websockets-dev \
protobuf-compiler \
qt6-tools-dev \
@ -43,7 +42,6 @@ RUN apt-get update \
libprotobuf32t64 \
libqt6sql6-mysql \
libqt6websockets6 \
libssl3 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View file

@ -44,16 +44,11 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
GameScene::~GameScene()
{
// Sever all destroyed->removeAnimatedItem connections before the members below
// are destroyed: the base QGraphicsScene destructor destroys the remaining items,
// and their destroyed() signals must not reach slots that reference members that
// no longer exist. The connection handle overload is used because the string-based
// disconnect(nullptr, nullptr, this, nullptr) is invalid (the sender must never be
// nullptr) and would otherwise fail to sever these pointer-to-member connections.
for (auto it = animationItemConnections.constBegin(); it != animationItemConnections.constEnd(); ++it) {
QObject::disconnect(*it);
}
animationItemConnections.clear();
// Sever all incoming connections (animated item destroy-tracking) before the
// members below are destroyed: the base QGraphicsScene destructor destroys the
// remaining items, and their destroyed() signals must not reach slots that
// reference members that no longer exist.
QObject::disconnect(nullptr, nullptr, this, nullptr);
delete animationTimer;
animationTimer = nullptr;
@ -782,15 +777,8 @@ void GameScene::registerAnimationItem(IAnimatedItem *item)
if (!object) {
return;
}
// Guard against duplicate connections using the connection map, not
// animatedItems: the animation timer removes entries from animatedItems when an
// animation completes, but the destroyed->removeAnimatedItem connection must
// persist until the object is destroyed. Relying on animatedItems here would let
// a re-registered item (e.g. a life counter that flashes repeatedly) accumulate
// duplicate destroyed connections, the older ones of which would survive teardown.
if (!animationItemConnections.contains(object)) {
animationItemConnections.insert(object,
connect(object, &QObject::destroyed, this, &GameScene::removeAnimatedItem));
if (!animatedItems.contains(object)) {
connect(object, &QObject::destroyed, this, &GameScene::removeAnimatedItem);
}
animatedItems.insert(object, item);
if (animationTimer && !animationTimer->isActive()) {
@ -809,7 +797,6 @@ void GameScene::unregisterAnimationItem(IAnimatedItem *item)
void GameScene::removeAnimatedItem(QObject *item)
{
animatedItems.remove(item);
animationItemConnections.remove(item);
if (animationTimer && animatedItems.isEmpty()) {
animationTimer->stop();
}

View file

@ -54,11 +54,9 @@ private:
QPointer<CardItem> hoveredCard; ///< Currently hovered card
QBasicTimer *animationTimer; ///< Timer for scene animations
QHash<QObject *, IAnimatedItem *> animatedItems; ///< Items currently animating
QHash<QObject *, QMetaObject::Connection>
animationItemConnections; ///< destroyed->removeAnimatedItem handles per animated item
int playerRotation; ///< Rotation offset for player layout
bool rearranging = false; ///< Guard against re-entrant rearrange
bool needsReArrange = false; ///< Pending rearrange requested during a pass
int playerRotation; ///< Rotation offset for player layout
bool rearranging = false; ///< Guard against re-entrant rearrange
bool needsReArrange = false; ///< Pending rearrange requested during a pass
/**
* @brief Updates which card is currently hovered based on scene coordinates.

View file

@ -41,8 +41,7 @@ void HandZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
}
}
} else {
bool sameZone = startZone == getLogic();
x = calcDropIndexFromY(dropPoint.y(), !sameZone);
x = calcDropIndexFromY(dropPoint.y());
}
Command_MoveCard cmd;

View file

@ -83,7 +83,7 @@ SelectZone::StackLayoutParams SelectZone::buildStackParams(qreal minOffset) cons
return {cardCount, boundingRect().height(), cardHeight, offset, minOffset};
}
int SelectZone::calcDropIndexFromY(qreal dropY, bool allowCountExpand, qreal minOffset) const
int SelectZone::calcDropIndexFromY(qreal dropY, qreal minOffset) const
{
const auto &cards = getLogic()->getCards();
if (cards.isEmpty()) {
@ -94,8 +94,7 @@ int SelectZone::calcDropIndexFromY(qreal dropY, bool allowCountExpand, qreal min
if (effectiveOffset <= 0.0) {
return 0;
}
int max = allowCountExpand ? params.cardCount : params.cardCount - 1;
return qBound(0, qRound((dropY - start) / effectiveOffset), max);
return qBound(0, qRound((dropY - start) / effectiveOffset), params.cardCount - 1);
}
void SelectZone::restoreStaleEscapedCards()

View file

@ -104,12 +104,8 @@ protected:
/**
* @brief Computes the card index at a given y-coordinate within the zone's vertical layout.
* Returns 0 if the zone has no cards or the offset is zero.
*
* @param dropY The y-coordinate that the card was dropped at
* @param allowCountExpand If false, clamps the index at the number of cards minus 1
* @param minOffset Minimum offset to preserve
*/
int calcDropIndexFromY(qreal dropY, bool allowCountExpand, qreal minOffset = 0.0) const;
int calcDropIndexFromY(qreal dropY, qreal minOffset = 0.0) const;
/**
* @brief Positions cards vertically with alternating left/right x-offsets.

View file

@ -57,14 +57,18 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
return;
}
bool sameZone = startZone == getLogic();
int index = calcDropIndexFromY(dropPoint.y(), !sameZone, MIN_CARD_VISIBLE);
if (sameZone) {
const auto &cards = getLogic()->getCards();
int index;
if (startZone == getLogic()) {
// Reordering within the zone: use drop position
index = calcDropIndexFromY(dropPoint.y(), MIN_CARD_VISIBLE);
// Same-zone no-op: don't move a card onto itself
const auto &cards = getLogic()->getCards();
if (!cards.isEmpty() && cards.at(index)->getId() == dragItems.at(0)->getId()) {
return;
}
} else {
// Coming from another zone: append at end (top of stack, rendered on top)
index = static_cast<int>(cards.size());
}
Command_MoveCard cmd;

View file

@ -154,48 +154,6 @@ AppearanceSettingsPage::AppearanceSettingsPage()
homeTabGroupBox = new QGroupBox;
homeTabGroupBox->setLayout(homeTabGrid);
// Playmat settings
playmatVisibilityCombo.addItem(tr("Show all playmats"), PlaymatVisibilityAll);
playmatVisibilityCombo.addItem(tr("Show own playmat only"), PlaymatVisibilityOwnOnly);
playmatVisibilityCombo.addItem(tr("Don't use playmats"), PlaymatVisibilityNone);
int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility());
if (visIdx >= 0) {
playmatVisibilityCombo.setCurrentIndex(visIdx);
}
connect(&playmatVisibilityCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
SettingsCache::instance().userInterface().setPlaymatVisibility(playmatVisibilityCombo.itemData(index).toInt());
});
playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo);
// Playmat mode: Override / Fallback / Deck-only
playmatModeCombo.addItem(tr("Override deck playmat"), PlaymatModeOverrideDeck);
playmatModeCombo.addItem(tr("Fallback if deck has none"), PlaymatModeFallback);
playmatModeCombo.addItem(tr("Deck only, ignore collection"), PlaymatModeDeckOnly);
int modeIdx = playmatModeCombo.findData(settings.userInterface().getPlaymatMode());
if (modeIdx >= 0) {
playmatModeCombo.setCurrentIndex(modeIdx);
}
connect(&playmatModeCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
SettingsCache::instance().userInterface().setPlaymatMode(playmatModeCombo.itemData(index).toInt());
});
playmatModeLabel.setBuddy(&playmatModeCombo);
// User-level playmat settings: fallback collection.
connect(&playmatDefaultEditButton, &QPushButton::clicked, this,
&AppearanceSettingsPage::openPlaymatCollectionDialog);
auto *playmatGrid = new QGridLayout;
playmatGrid->addWidget(&playmatVisibilityLabel, 0, 0, 1, 1);
playmatGrid->addWidget(&playmatVisibilityCombo, 0, 1, 1, 1);
playmatGrid->addWidget(&playmatModeLabel, 1, 0, 1, 1);
playmatGrid->addWidget(&playmatModeCombo, 1, 1, 1, 1);
playmatGrid->addWidget(&playmatDefaultLabel, 2, 0, 1, 1);
playmatGrid->addWidget(&playmatDefaultEditButton, 2, 1, 1, 1);
playmatGroupBox = new QGroupBox;
playmatGroupBox->setLayout(playmatGrid);
// Styling settings
styleUserListCheckBox.setChecked(settings.appearance().getStyleUserList());
connect(&styleUserListCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.appearance(),
&AppearanceSettings::setStyleUserList);
@ -301,6 +259,7 @@ AppearanceSettingsPage::AppearanceSettingsPage()
cardLayoutGroupBox->setLayout(cardLayoutGrid);
// Card counter colors
auto *cardCounterColorsLayout = new QGridLayout;
cardCounterColorsLayout->setColumnStretch(1, 1);
cardCounterColorsLayout->setColumnStretch(3, 1);
@ -380,6 +339,47 @@ AppearanceSettingsPage::AppearanceSettingsPage()
tableGroupBox = new QGroupBox;
tableGroupBox->setLayout(tableGrid);
// Playmat settings
playmatVisibilityCombo.addItem(tr("Show all playmats"), PlaymatVisibilityAll);
playmatVisibilityCombo.addItem(tr("Show own playmat only"), PlaymatVisibilityOwnOnly);
playmatVisibilityCombo.addItem(tr("Don't use playmats"), PlaymatVisibilityNone);
int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility());
if (visIdx >= 0) {
playmatVisibilityCombo.setCurrentIndex(visIdx);
}
connect(&playmatVisibilityCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
SettingsCache::instance().userInterface().setPlaymatVisibility(playmatVisibilityCombo.itemData(index).toInt());
});
playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo);
// Playmat mode: Override / Fallback / Deck-only
playmatModeCombo.addItem(tr("Override deck playmat"), PlaymatModeOverrideDeck);
playmatModeCombo.addItem(tr("Fallback if deck has none"), PlaymatModeFallback);
playmatModeCombo.addItem(tr("Deck only, ignore collection"), PlaymatModeDeckOnly);
int modeIdx = playmatModeCombo.findData(settings.userInterface().getPlaymatMode());
if (modeIdx >= 0) {
playmatModeCombo.setCurrentIndex(modeIdx);
}
connect(&playmatModeCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
SettingsCache::instance().userInterface().setPlaymatMode(playmatModeCombo.itemData(index).toInt());
});
playmatModeLabel.setBuddy(&playmatModeCombo);
// User-level playmat settings: fallback collection.
connect(&playmatDefaultEditButton, &QPushButton::clicked, this,
&AppearanceSettingsPage::openPlaymatCollectionDialog);
auto *playmatGrid = new QGridLayout;
playmatGrid->addWidget(&playmatVisibilityLabel, 0, 0, 1, 1);
playmatGrid->addWidget(&playmatVisibilityCombo, 0, 1, 1, 1);
playmatGrid->addWidget(&playmatModeLabel, 1, 0, 1, 1);
playmatGrid->addWidget(&playmatModeCombo, 1, 1, 1, 1);
playmatGrid->addWidget(&playmatDefaultLabel, 2, 0, 1, 1);
playmatGrid->addWidget(&playmatDefaultEditButton, 2, 1, 1, 1);
playmatGroupBox = new QGroupBox;
playmatGroupBox->setLayout(playmatGrid);
// putting it all together
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(themeGroupBox);
@ -512,12 +512,6 @@ void AppearanceSettingsPage::retranslateUi()
homeTabButtonColorSourceBox.setToolTip(
tr("Automatic: extract from background if present, otherwise use theme default"));
playmatGroupBox->setTitle(tr("Playmat settings"));
playmatVisibilityLabel.setText(tr("Playmat visibility:"));
playmatModeLabel.setText(tr("Default collection behavior:"));
playmatDefaultLabel.setText(tr("Default playmat collection:"));
playmatDefaultEditButton.setText(tr("Edit..."));
stylingGroupBox->setTitle(tr("Styling settings"));
styleUserListCheckBox.setText(tr("Style user list"));
@ -560,4 +554,9 @@ void AppearanceSettingsPage::retranslateUi()
tableGroupBox->setTitle(tr("Table grid layout"));
invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate"));
minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:"));
playmatGroupBox->setTitle(tr("Playmat settings"));
playmatVisibilityLabel.setText(tr("Playmat visibility:"));
playmatModeLabel.setText(tr("Default collection behavior:"));
playmatDefaultLabel.setText(tr("Default playmat collection:"));
playmatDefaultEditButton.setText(tr("Edit..."));
}

View file

@ -44,55 +44,46 @@ private:
QLabel homeTabButtonColorSourceLabel;
QComboBox homeTabButtonColorSourceBox;
QLabel playmatVisibilityLabel;
QComboBox playmatVisibilityCombo;
QLabel playmatModeLabel;
QComboBox playmatModeCombo;
QLabel playmatDefaultLabel;
QPushButton playmatDefaultEditButton;
QCheckBox styleUserListCheckBox;
QCheckBox showShortcutsCheckBox;
QCheckBox showGameSelectorFilterToolbarCheckBox;
QLabel minPlayersForMultiColumnLayoutLabel;
QLabel maxFontSizeForCardsLabel;
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;
QCheckBox bumpSetsWithCardsInDeckToTopCheckBox;
QCheckBox displayCardNamesCheckBox;
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
QCheckBox cardScalingCheckBox;
QCheckBox roundCardCornersCheckBox;
QLabel maxFontSizeForCardsLabel;
QSpinBox maxFontSizeForCardsEdit;
QLabel verticalCardOverlapPercentLabel;
QSpinBox verticalCardOverlapPercentBox;
QLabel cardViewInitialRowsMaxLabel;
QSpinBox cardViewInitialRowsMaxBox;
QLabel cardViewExpandedRowsMaxLabel;
QSpinBox cardViewExpandedRowsMaxBox;
QList<QLabel *> cardCounterNames;
QCheckBox horizontalHandCheckBox;
QCheckBox leftJustifiedHandCheckBox;
QCheckBox invertVerticalCoordinateCheckBox;
QLabel minPlayersForMultiColumnLayoutLabel;
QSpinBox minPlayersForMultiColumnLayoutEdit;
QLabel playmatVisibilityLabel;
QComboBox playmatVisibilityCombo;
QLabel playmatModeLabel;
QComboBox playmatModeCombo;
QLabel playmatDefaultLabel;
QPushButton playmatDefaultEditButton;
QGroupBox *themeGroupBox;
QGroupBox *homeTabGroupBox;
QGroupBox *playmatGroupBox;
QGroupBox *stylingGroupBox;
QGroupBox *menuGroupBox;
QGroupBox *printingsGroupBox;
QGroupBox *cardsGroupBox;
QGroupBox *cardLayoutGroupBox;
QGroupBox *cardCountersGroupBox;
QGroupBox *handGroupBox;
QGroupBox *playmatGroupBox;
QGroupBox *tableGroupBox;
QGroupBox *cardCountersGroupBox;
QList<QLabel *> cardCounterNames;
QSpinBox minPlayersForMultiColumnLayoutEdit;
QSpinBox maxFontSizeForCardsEdit;
public:
AppearanceSettingsPage();

View file

@ -425,28 +425,29 @@ void GeneralSettingsPage::updateStartupServerControlsVisibility()
void GeneralSettingsPage::retranslateUi()
{
const auto &settings = SettingsCache::instance();
languageGroupBox->setTitle(tr("Language settings"));
languageLabel.setText(tr("Language:"));
advertiseTranslationPageLabel.setText(
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
versionGroupBox->setTitle(tr("Version settings"));
cardDatabaseGroupBox->setTitle(tr("Card database"));
startupGroupBox->setTitle(tr("Startup settings"));
if (SettingsCache::instance().getIsPortableBuild()) {
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
} else {
pathsGroupBox->setTitle(tr("Paths"));
}
advertiseTranslationPageLabel.setText(
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
deckPathLabel.setText(tr("Decks directory:"));
filtersPathLabel.setText(tr("Filters directory:"));
replaysPathLabel.setText(tr("Replays directory:"));
picsPathLabel.setText(tr("Pictures directory:"));
cardDatabasePathLabel.setText(tr("Card database:"));
customCardDatabasePathLabel.setText(tr("Custom database directory:"));
tokenDatabasePathLabel.setText(tr("Token database:"));
updateReleaseChannelLabel.setText(tr("Update channel"));
startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup"));
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
// We can't change the strings after they're put into the QComboBox, so this is our workaround
int oldIndex = updateReleaseChannelBox.currentIndex();
updateReleaseChannelBox.clear();
for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) {
updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8()));
}
updateReleaseChannelBox.setCurrentIndex(oldIndex);
cardDatabaseGroupBox->setTitle(tr("Card database"));
startupCardUpdateCheckBehaviorLabel.setText(tr("Check for card database updates on startup"));
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexNone, tr("Don't check"));
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexPrompt,
@ -455,13 +456,8 @@ void GeneralSettingsPage::retranslateUi()
tr("Always update in the background"));
cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every"));
cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days"));
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
lastCardUpdateCheckDateLabel.setText(
tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo));
startupGroupBox->setTitle(tr("Startup settings"));
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
showTipsOnStartup.setText(tr("Show tips on startup"));
startupTabLabel.setText(tr("Startup tab:"));
startupTabSelector.setItemText(StartupTab::StartupTabHome, tr("Home"));
@ -477,18 +473,21 @@ void GeneralSettingsPage::retranslateUi()
startupServerLabel.setText(tr("Server:"));
startupRoomLabel.setText(tr("Room:"));
startupRoomNameEdit->setPlaceholderText(tr("Room name"));
if (settings.getIsPortableBuild()) {
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
} else {
pathsGroupBox->setTitle(tr("Paths"));
}
deckPathLabel.setText(tr("Decks directory:"));
filtersPathLabel.setText(tr("Filters directory:"));
replaysPathLabel.setText(tr("Replays directory:"));
picsPathLabel.setText(tr("Pictures directory:"));
cardDatabasePathLabel.setText(tr("Card database:"));
customCardDatabasePathLabel.setText(tr("Custom database directory:"));
tokenDatabasePathLabel.setText(tr("Token database:"));
resetAllPathsButton->setText(tr("Reset all paths"));
}
const auto &settings = SettingsCache::instance();
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
lastCardUpdateCheckDateLabel.setText(
tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo));
// We can't change the strings after they're put into the QComboBox, so this is our workaround
int oldIndex = updateReleaseChannelBox.currentIndex();
updateReleaseChannelBox.clear();
for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) {
updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8()));
}
updateReleaseChannelBox.setCurrentIndex(oldIndex);
}

View file

@ -42,37 +42,6 @@ private:
QGroupBox *startupGroupBox;
QGroupBox *pathsGroupBox;
QLabel languageLabel;
QComboBox languageBox;
QLabel advertiseTranslationPageLabel;
QLabel updateReleaseChannelLabel;
QComboBox updateReleaseChannelBox;
QCheckBox startupUpdateCheckCheckBox;
QCheckBox updateNotificationCheckBox;
QCheckBox newVersionOracleCheckBox;
QLabel startupCardUpdateCheckBehaviorLabel;
QComboBox startupCardUpdateCheckBehaviorSelector;
QLabel cardUpdateCheckIntervalLabel;
QSpinBox cardUpdateCheckIntervalSpinBox;
QLabel lastCardUpdateCheckDateLabel;
QCheckBox showTipsOnStartup;
QLabel startupTabLabel;
QComboBox startupTabSelector;
QLabel startupServerLabel;
QComboBox startupServerSelector;
QLabel startupRoomLabel;
QLineEdit *startupRoomNameEdit;
QLabel deckPathLabel;
QLabel filtersPathLabel;
QLabel replaysPathLabel;
QLabel picsPathLabel;
QLabel cardDatabasePathLabel;
QLabel customCardDatabasePathLabel;
QLabel tokenDatabasePathLabel;
QLineEdit *deckPathEdit;
QLineEdit *filtersPathEdit;
QLineEdit *replaysPathEdit;
@ -82,6 +51,33 @@ private:
QLineEdit *tokenDatabasePathEdit;
QPushButton *resetAllPathsButton;
QLabel *allPathsResetLabel;
QComboBox languageBox;
QCheckBox startupUpdateCheckCheckBox;
QLabel startupCardUpdateCheckBehaviorLabel;
QComboBox startupCardUpdateCheckBehaviorSelector;
QLabel cardUpdateCheckIntervalLabel;
QSpinBox cardUpdateCheckIntervalSpinBox;
QLabel lastCardUpdateCheckDateLabel;
QCheckBox updateNotificationCheckBox;
QCheckBox newVersionOracleCheckBox;
QComboBox updateReleaseChannelBox;
QLabel languageLabel;
QLabel deckPathLabel;
QLabel filtersPathLabel;
QLabel replaysPathLabel;
QLabel picsPathLabel;
QLabel cardDatabasePathLabel;
QLabel customCardDatabasePathLabel;
QLabel tokenDatabasePathLabel;
QLabel updateReleaseChannelLabel;
QLabel advertiseTranslationPageLabel;
QCheckBox showTipsOnStartup;
QLabel startupTabLabel;
QComboBox startupTabSelector;
QLabel startupServerLabel;
QComboBox startupServerSelector;
QLabel startupRoomLabel;
QLineEdit *startupRoomNameEdit;
};
#endif // COCKATRICE_GENERAL_SETTINGS_PAGE_H

View file

@ -20,7 +20,26 @@ enum visualDeckStoragePromptForConversionIndex
UserInterfaceSettingsPage::UserInterfaceSettingsPage()
{
// general settings
// general settings and notification settings
notificationsEnabledCheckBox.setChecked(SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&notificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
&InterfaceSettings::setNotificationsEnabled);
connect(&notificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
&UserInterfaceSettingsPage::setNotificationEnabled);
specNotificationsEnabledCheckBox.setChecked(
SettingsCache::instance().userInterface().getSpectatorNotificationsEnabled());
specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
&InterfaceSettings::setSpectatorNotificationsEnabled);
buddyConnectNotificationsEnabledCheckBox.setChecked(
SettingsCache::instance().userInterface().getBuddyConnectNotificationsEnabled());
buddyConnectNotificationsEnabledCheckBox.setEnabled(
SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED,
&SettingsCache::instance().userInterface(), &InterfaceSettings::setBuddyConnectNotificationsEnabled);
doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().userInterface().getDoubleClickToPlay());
connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
&InterfaceSettings::setDoubleClickToPlay);
@ -84,26 +103,6 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
generalGroupBox = new QGroupBox;
generalGroupBox->setLayout(generalGrid);
// notification settings
notificationsEnabledCheckBox.setChecked(SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&notificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
&InterfaceSettings::setNotificationsEnabled);
connect(&notificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
&UserInterfaceSettingsPage::setNotificationEnabled);
specNotificationsEnabledCheckBox.setChecked(
SettingsCache::instance().userInterface().getSpectatorNotificationsEnabled());
specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
&InterfaceSettings::setSpectatorNotificationsEnabled);
buddyConnectNotificationsEnabledCheckBox.setChecked(
SettingsCache::instance().userInterface().getBuddyConnectNotificationsEnabled());
buddyConnectNotificationsEnabledCheckBox.setEnabled(
SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED,
&SettingsCache::instance().userInterface(), &InterfaceSettings::setBuddyConnectNotificationsEnabled);
auto *notificationsGrid = new QGridLayout;
notificationsGrid->addWidget(&notificationsEnabledCheckBox, 0, 0);
notificationsGrid->addWidget(&specNotificationsEnabledCheckBox, 1, 0);
@ -356,7 +355,6 @@ void UserInterfaceSettingsPage::retranslateUi()
notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar"));
specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating"));
buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect"));
animationGroupBox->setTitle(tr("Animation settings"));
enableAllAnimationsButton.setText(tr("&Enable all animations"));
disableAllAnimationsButton.setText(tr("&Disable all animations"));
@ -364,7 +362,6 @@ void UserInterfaceSettingsPage::retranslateUi()
arrowDrawAnimationCheckBox.setText(tr("&Arrow draw animation"));
lifeCounterAnimationsCheckBox.setText(tr("Life counter flash"));
battlefieldFlashCheckBox.setText(tr("Battlefield flash on damage"));
deckEditorGroupBox->setTitle(tr("Deck editor/storage settings"));
openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default"));
visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby"));
@ -400,8 +397,8 @@ void UserInterfaceSettingsPage::retranslateUi()
0, CommanderBracketNames::CommanderSpellbookBracketNames);
commanderSpellbookIntegrationBracketNamingSelector.setItemText(
1, CommanderBracketNames::OfficialCommanderBracketNames);
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setToolTip(CommanderBracketNames::Explainer);
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setToolTip(CommanderBracketNames::Explainer);
replayGroupBox->setTitle(tr("Replay settings"));
rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:"));
rewindBufferingMsBox.setSuffix(" ms");

View file

@ -23,6 +23,9 @@ private slots:
void updateCommanderSpellbookUiState();
private:
QCheckBox notificationsEnabledCheckBox;
QCheckBox specNotificationsEnabledCheckBox;
QCheckBox buddyConnectNotificationsEnabledCheckBox;
QCheckBox doubleClickToPlayCheckBox;
QCheckBox clickPlaysAllSelectedCheckBox;
QCheckBox playToStackCheckBox;
@ -34,18 +37,12 @@ private:
QCheckBox showTotalSelectionCountCheckBox;
QCheckBox useTearOffMenusCheckBox;
QCheckBox keepGameChatFocusCheckBox;
QCheckBox notificationsEnabledCheckBox;
QCheckBox specNotificationsEnabledCheckBox;
QCheckBox buddyConnectNotificationsEnabledCheckBox;
QPushButton enableAllAnimationsButton;
QPushButton disableAllAnimationsButton;
QCheckBox tapAnimationCheckBox;
QCheckBox arrowDrawAnimationCheckBox;
QCheckBox lifeCounterAnimationsCheckBox;
QCheckBox battlefieldFlashCheckBox;
QCheckBox openDeckInNewTabCheckBox;
QLabel visualDeckStoragePromptForConversionLabel;
QComboBox visualDeckStoragePromptForConversionSelector;
@ -60,10 +57,8 @@ private:
QLabel commanderSpellbookIntegrationUseOfficialBracketNamesLabel;
QToolButton commanderSpellbookIntegrationUseOfficialBracketNamesExplainer;
QComboBox commanderSpellbookIntegrationBracketNamingSelector;
QLabel rewindBufferingMsLabel;
QSpinBox rewindBufferingMsBox;
QGroupBox *generalGroupBox;
QGroupBox *notificationsGroupBox;
QGroupBox *animationGroupBox;

View file

@ -116,10 +116,9 @@ void CloseButton::paintEvent(QPaintEvent * /*event*/)
}
TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *parent)
: QTabWidget(parent), userInfo(nullptr), client(_client), tabsMenu(tabsMenu), tabHome(nullptr),
tabVisualDeckStorage(nullptr), tabServer(nullptr), tabAccount(nullptr), tabDeckStorage(nullptr),
tabReplays(nullptr), tabAdmin(nullptr), tabCardArtRules(nullptr), tabLog(nullptr), tabReport(nullptr),
tabModeration(nullptr), isLocalGame(false)
: QTabWidget(parent), userInfo(nullptr), client(_client), tabsMenu(tabsMenu), tabVisualDeckStorage(nullptr),
tabServer(nullptr), tabAccount(nullptr), tabDeckStorage(nullptr), tabReplays(nullptr), tabAdmin(nullptr),
tabLog(nullptr), tabReport(nullptr), tabModeration(nullptr), isLocalGame(false)
{
setElideMode(Qt::ElideRight);
setMovable(true);
@ -246,7 +245,6 @@ void TabSupervisor::retranslateUi()
aTabLog->setText(tr("Logs"));
aTabReport->setText(tr("Report Queue"));
aTabModeration->setText(tr("Moderation"));
aTabCardArtRules->setText(tr("Card Art Rules"));
// tabs
QList<Tab *> tabs;
@ -258,7 +256,6 @@ void TabSupervisor::retranslateUi()
tabs.append(tabLog);
tabs.append(tabReport);
tabs.append(tabModeration);
tabs.append(tabCardArtRules);
QMapIterator<int, TabRoom *> roomIterator(roomTabs);
while (roomIterator.hasNext()) {
tabs.append(roomIterator.next().value());
@ -523,9 +520,7 @@ void TabSupervisor::start(const ServerInfo_User &_userInfo)
if (SettingsCache::instance().tabs().getTabModerationOpen()) {
openTabModeration();
}
if (SettingsCache::instance().tabs().getTabCardArtRulesOpen()) {
openTabCardArtRules();
}
openTabCardArtRules();
}
retranslateUi();
@ -587,9 +582,6 @@ void TabSupervisor::stop()
if (tabModeration) {
tabModeration->close();
}
if (tabCardArtRules) {
tabCardArtRules->close();
}
}
QList<Tab *> tabsToDelete;
@ -783,7 +775,6 @@ void TabSupervisor::openTabAdmin()
void TabSupervisor::actTabCardArtRules(bool checked)
{
SettingsCache::instance().tabs().setTabCardArtRulesOpen(checked);
if (checked && !tabCardArtRules) {
openTabCardArtRules();
setCurrentWidget(tabCardArtRules);

View file

@ -125,7 +125,9 @@ void VisualDeckStorageFolderDisplayWidget::continueDeckPass()
}
const bool matches = index.data(VisualDeckStorageRoles::FilterMatchRole).toBool();
deckPreviewWidget->setVisible(matches);
if (matches == deckPreviewWidget->isHidden()) {
deckPreviewWidget->setVisible(matches);
}
if (matches) {
++visibleDeckCount;
}

View file

@ -53,7 +53,6 @@
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/settings/network_settings.h>
#include <libcockatrice/settings/personal_settings.h>
#include <libcockatrice/utility/cryptoutil.h>
QTranslator *translator, *qtTranslator;
RNG_Abstract *rng;
@ -293,7 +292,7 @@ int main(int argc, char *argv[])
}
}
rng = new RNG_SFMT(CryptoUtil::randomUInt64());
rng = new RNG_SFMT;
themeManager = new ThemeManager;
soundEngine = new SoundEngine;

View file

@ -21,7 +21,6 @@ public:
[[nodiscard]] virtual bool getTabLogOpen() const = 0;
[[nodiscard]] virtual bool getTabReportOpen() const = 0;
[[nodiscard]] virtual bool getTabModerationOpen() const = 0;
[[nodiscard]] virtual bool getTabCardArtRulesOpen() const = 0;
};
#endif // COCKATRICE_INTERFACE_TABS_SETTINGS_PROVIDER_H

View file

@ -90,6 +90,7 @@ set(PROTO_FILES
event_game_log_notice.proto
event_game_say.proto
event_game_state_changed.proto
event_game_state_changed.proto
event_join.proto
event_join_room.proto
event_kicked.proto

View file

@ -1,5 +1,6 @@
#include "rng_sfmt.h"
#include <QDateTime>
#include <algorithm>
#include <climits>
#include <stdexcept>
@ -10,11 +11,10 @@
#define UINT64_MAX (~(uint64_t)0)
#endif
RNG_SFMT::RNG_SFMT(uint64_t seed, QObject *parent) : RNG_Abstract(parent)
RNG_SFMT::RNG_SFMT(QObject *parent) : RNG_Abstract(parent)
{
// initialize the random number generator with a 64bit seed, e.g. from a CSPRNG
uint32_t seedArray[2] = {static_cast<uint32_t>(seed), static_cast<uint32_t>(seed >> 32)};
sfmt_init_by_array(&sfmt, seedArray, 2);
// initialize the random number generator with a 32bit integer seed (timestamp)
sfmt_init_gen_rand(&sfmt, QDateTime::currentDateTime().toSecsSinceEpoch());
}
/**

View file

@ -36,7 +36,7 @@ private:
unsigned int cdf(unsigned int min, unsigned int max);
public:
explicit RNG_SFMT(uint64_t seed, QObject *parent = nullptr);
explicit RNG_SFMT(QObject *parent = nullptr);
unsigned int rand(int min, int max) override;
};

View file

@ -106,11 +106,6 @@ bool TabsSettings::getTabModerationOpen() const
return getValue("moderation", QString(), QString(), false).toBool();
}
bool TabsSettings::getTabCardArtRulesOpen() const
{
return getValue("cardArtRules", QString(), QString(), false).toBool();
}
void TabsSettings::setTabVisualDeckStorageOpen(bool value)
{
setValue(value, "visualDeckStorage");
@ -155,8 +150,3 @@ void TabsSettings::setTabModerationOpen(bool value)
{
setValue(value, "moderation");
}
void TabsSettings::setTabCardArtRulesOpen(bool value)
{
setValue(value, "cardArtRules");
}

View file

@ -43,7 +43,6 @@ public:
[[nodiscard]] bool getTabLogOpen() const override;
[[nodiscard]] bool getTabReportOpen() const override;
[[nodiscard]] bool getTabModerationOpen() const override;
[[nodiscard]] bool getTabCardArtRulesOpen() const override;
void setStartupTabIndex(int value);
void setStartupServerHost(const QString &host);
@ -58,7 +57,6 @@ public:
void setTabLogOpen(bool value);
void setTabReportOpen(bool value);
void setTabModerationOpen(bool value);
void setTabCardArtRulesOpen(bool value);
signals:
void startupTabIndexChanged(int index);

View file

@ -6,15 +6,13 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(UTILITY_SOURCES
libcockatrice/utility/cryptoutil.cpp libcockatrice/utility/expression.cpp libcockatrice/utility/levenshtein.cpp
libcockatrice/utility/passwordhasher.cpp libcockatrice/utility/server_rate_limiter.cpp
libcockatrice/utility/warning_categories.cpp
libcockatrice/utility/expression.cpp libcockatrice/utility/levenshtein.cpp libcockatrice/utility/passwordhasher.cpp
libcockatrice/utility/server_rate_limiter.cpp libcockatrice/utility/warning_categories.cpp
)
set(UTILITY_HEADERS
libcockatrice/utility/card_ref.h
libcockatrice/utility/color.h
libcockatrice/utility/cryptoutil.h
libcockatrice/utility/expression.h
libcockatrice/utility/levenshtein.h
libcockatrice/utility/macros.h
@ -34,9 +32,7 @@ add_library(libcockatrice_utility STATIC ${UTILITY_SOURCES} ${UTILITY_HEADERS})
target_include_directories(libcockatrice_utility PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
find_package(OpenSSL REQUIRED)
target_link_libraries(libcockatrice_utility PUBLIC libcockatrice_rng OpenSSL::Crypto ${QT_CORE_MODULE})
target_link_libraries(libcockatrice_utility PUBLIC libcockatrice_rng ${QT_CORE_MODULE})
set(ORACLE_LIBS)

View file

@ -1,25 +0,0 @@
#include "cryptoutil.h"
#include <openssl/rand.h>
namespace CryptoUtil
{
QByteArray randomBytes(int count)
{
QByteArray bytes(count, '\0');
if (RAND_bytes(reinterpret_cast<unsigned char *>(bytes.data()), count) != 1) {
// Randomness failure is fatal: never fall back to a predictable source.
qFatal("CryptoUtil::randomBytes: RAND_bytes failed");
}
return bytes;
}
quint64 randomUInt64()
{
quint64 value;
if (RAND_bytes(reinterpret_cast<unsigned char *>(&value), sizeof(value)) != 1) {
qFatal("CryptoUtil::randomUInt64: RAND_bytes failed");
}
return value;
}
} // namespace CryptoUtil

View file

@ -1,13 +0,0 @@
#ifndef CRYPTOUTIL_H
#define CRYPTOUTIL_H
#include <QByteArray>
#include <QtGlobal>
namespace CryptoUtil
{
QByteArray randomBytes(int count);
quint64 randomUInt64();
} // namespace CryptoUtil
#endif

View file

@ -1,7 +1,7 @@
#include "passwordhasher.h"
#include <QCryptographicHash>
#include <libcockatrice/utility/cryptoutil.h>
#include <libcockatrice/rng/rng_sfmt.h>
QString PasswordHasher::computeHash(const QString &password, const QString &salt)
{
@ -21,28 +21,12 @@ QString PasswordHasher::generateRandomSalt(const int len)
static const char alphanum[] = "0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const int size = sizeof(alphanum) - 1;
// Two bytes per character, corrected for modulo bias via rejection sampling.
const int bucketSize = 65536 / size;
const int limit = bucketSize * size;
QString ret;
ret.reserve(len);
QByteArray random = CryptoUtil::randomBytes(len * 2);
int bytesUsed = 0;
int size = sizeof(alphanum) - 1;
for (int i = 0; i < len; ++i) {
unsigned int value;
do {
if (bytesUsed >= random.size()) {
random = CryptoUtil::randomBytes(len * 2);
bytesUsed = 0;
}
value = static_cast<unsigned int>(static_cast<unsigned char>(random.at(bytesUsed))) << 8 |
static_cast<unsigned int>(static_cast<unsigned char>(random.at(bytesUsed + 1)));
bytesUsed += 2;
} while (value >= limit);
ret.append(alphanum[value / bucketSize]);
ret.append(alphanum[rng->rand(0, size)]);
}
return ret;
@ -50,5 +34,5 @@ QString PasswordHasher::generateRandomSalt(const int len)
QString PasswordHasher::generateActivationToken()
{
return QString(CryptoUtil::randomBytes(16).toBase64().left(16));
return QCryptographicHash::hash(generateRandomSalt().toUtf8(), QCryptographicHash::Md5).toBase64().left(16);
}

View file

@ -33,7 +33,6 @@
#include <QtGlobal>
#include <iostream>
#include <libcockatrice/rng/rng_sfmt.h>
#include <libcockatrice/utility/cryptoutil.h>
#include <libcockatrice/utility/passwordhasher.h>
RNG_Abstract *rng;
@ -170,7 +169,7 @@ int main(int argc, char *argv[])
signalhandler = new SignalHandler();
rng = new RNG_SFMT(CryptoUtil::randomUInt64());
rng = new RNG_SFMT;
std::cerr << "Servatrice " << VERSION_STRING << " starting." << std::endl;
std::cerr << "-------------------------" << std::endl;

View file

@ -1,9 +1,25 @@
#include "gtest/gtest.h"
#include <cstring>
#include <libcockatrice/rng/rng_abstract.h>
#include <libcockatrice/rng/rng_sfmt.h>
#include <libcockatrice/utility/passwordhasher.h>
RNG_Abstract *rng;
namespace
{
class PasswordHashTest : public ::testing::Test
{
protected:
void SetUp() override
{
rng = new RNG_SFMT;
}
void TearDown() override
{
delete rng;
}
};
TEST(PasswordHashTest, RegressionTest)
{
@ -13,29 +29,6 @@ TEST(PasswordHashTest, RegressionTest)
QString hash = PasswordHasher::computeHash(password, salt);
ASSERT_EQ(hash, salt + expected) << "The computed hash value remains the same";
}
TEST(PasswordHashTest, SaltUsesAlphanumericCharset)
{
static const char alphanum[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const QString salt = PasswordHasher::generateRandomSalt();
ASSERT_EQ(salt.size(), 16);
for (const QChar &c : salt) {
ASSERT_NE(strchr(alphanum, c.toLatin1()), nullptr);
}
}
TEST(PasswordHashTest, SaltsAreUnique)
{
const QString salt1 = PasswordHasher::generateRandomSalt();
const QString salt2 = PasswordHasher::generateRandomSalt();
ASSERT_NE(salt1, salt2);
}
TEST(PasswordHashTest, TokenHasExpectedLength)
{
const QString token = PasswordHasher::generateActivationToken();
ASSERT_EQ(token.size(), 16);
}
} // namespace
int main(int argc, char **argv)

View file

@ -238,12 +238,6 @@ TEST_F(SettingsDefaultsTest, Tabs_ModerationOpen_Default)
ASSERT_EQ(s.getTabModerationOpen(), false);
}
TEST_F(SettingsDefaultsTest, Tabs_CardArtRulesOpen_Default)
{
TabsSettings s(settingsPath, nullptr);
ASSERT_EQ(s.getTabCardArtRulesOpen(), false);
}
// --- ChatSettings ---
TEST_F(SettingsDefaultsTest, Chat_Mention_Default)