Commit graph

6275 commits

Author SHA1 Message Date
BruebachL
e11c915a0c
[Servatrice] Detect MySQL strict mode on startup and exit early (#7251)
* [Servatrice] Detect MySQL strict mode on startup and exit early

* Update servatrice/src/servatrice_database_interface.cpp

Co-authored-by: tooomm <tooomm@users.noreply.github.com>

* [Servatrice] Treat failed strict-mode check as boot error

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
2026-09-18 15:02:45 +02:00
BruebachL
1c93309952
[Client] Show localized card names, texts and pictures (#7294)
* [Client] Show localized card names, texts and pictures

Localization wiring now runs end to end: the oracle importer collects
foreignData for the configured language and the client renders it.

- [Oracle] Import localized names and rules texts for the selected cardLang
  - single-face cards store their foreignData name and full text
  - multi-face (split/adventure/aftermath/prepare) cards collect the joined
    name once and join each face's translated text with the same separator
    as the English merge; an incomplete translation falls back to English;
    the joined text follows the same highest-priority-set policy as the
    single-face path and is only collected when localization is enabled
  - the wizard switching languages re-imports the card database

- [Client] Display localized card info throughout the client
  - card info text/picture widgets and the game board re-render on language
    change
  - pictures resolve cardLang art through Scryfall's named endpoint using the
    localized name, falling back to id-based art when no match exists
  - deck editor keeps canonical English names as card identity (EditRole)
    while showing localized names (DisplayRole), so decks and wire names
    stay stable

- [Card] Add CardLocalization-backed name/text lookup and cards.xml v4
  localization elements with a bounded-size translation cache

- [Tests] Cover oracle foreignData import (incl. multi-face joins, priority
  and fallback paths), XML v4 localization parsing, deck model localized
  display and the language-aware settings default

Existing installations need to re-run Oracle to see translations: localized
data only lands in cards.xml when the Oracle app is started with the
preferred language selected — launch the separate "Oracle" program that
ships with Cockatrice, pick the language in the wizard and let it re-import
the card database.

The client's database cache (cards.xml.cache) is invalidated by the cache
format bump and the source-hash checks, but a cache written before the
re-import can still hold English-only entries (the hash uses file size and
mtime, so a same-size/same-timestamp rewrite may be served as-is); delete
cards.xml.cache and relaunch if no localized names/texts show up after
re-importing.

* [Card] Pass localized card names and texts into CardInfo construction

Address review: instead of constructing the card and then calling
setLocalizedName/setLocalizedText (which emit a cardInfoChanged signal per
language), both constructors, both newInstance overloads and their callers
(cards.xml v4 parser and the binary cache reader) now pass the localized maps
as constructor arguments.

* [Client] Rename LocalizedCard:: helpers namespace to CardLocalization

The namespace now matches its header file name, as the review pointed out;
LocalizedCard reads more like a class or struct. Callers (card info text
widget, board card name rendering) are updated to match.

* [Client] Drop unused info member from the card info text widget

The CardInfoPtr member was only ever initialized to nullptr and never read;
remove it together with its initializer.

* [PictureLoader] Add the localized picture URL explicitly, not implicitly

Address review: silently prepending the Scryfall named-picture URL to the
download list whenever a non-English card language was active was surprising,
consumed quota per card when it failed, and could grab the wrong (canon) art on
name collisions, with no way to turn it off.

The insert is now opt-in and user-controlled: changing the card language adds
the template to the top of the download URLs once (persisted, documented in the
re-import prompt, and editable/removable in the deck editor settings), while the
picture loader no longer injects it at request time.

* [Card] Show card languages in the same native (English) format as the UI

Address review: the card text & images language dropdown listed bare native
names, some in inconsistent lowercase (e.g. "čeština", "español de España"),
which makes the languages easy to mix up for users that do not read the script
(e.g. 日本語 vs 한국어). It now mirrors the UI language dropdown and always pairs
the native name with its English name (e.g. "Deutsch (German)",
"日本語 (Japanese)"), using the same fixed casing.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 12:03:07 +02:00
BruebachL
5ace88c111
[DeckList] Extract deck metadata XML serialization (#7306)
* [DeckList] Extract deck metadata XML serialization

DeckList still serialized its metadata inline: a ten-branch
readElement dispatch and a static writeMetadata that duplicated the
tree plumbing. The metadata arms (name, comments, format, timestamp,
banner card, playmat, tags) move to DeckListMetadataXml free
functions over the Metadata struct, leaving DeckList::readElement a
thin dispatcher between metadata, zones and sideboard plans. The
playmat clamping helper moves along with the element that uses it.

* [DeckList] Make deck metadata XML serialization instance methods

* [DeckList] Inline deck metadata XML serialization

Fold DeckList::Metadata::readElement and write back into deck_list.cpp
alongside isEmpty(), and drop the separate deck_list_metadata_xml
translation unit. The metadata arms are instance methods of the nested
Metadata struct, so keeping them in the same file as its other method
keeps the class from being scattered across two .cpp files; the rest of
the refactor (readElement as a thin dispatcher, element-wise reads,
clamped playmat params) is unchanged.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:31 +02:00
BruebachL
69d32ed853
[DeckList] Extract plain-text deck parser into own file (#7305)
* [DeckList] Extract plain-text deck parser into own file

DeckList::loadFromStream_Plain was a 160-line god-method mixing
deck clearing, name/comment detection, sideboard heuristics, set
and multiplier extraction and normalization. The parsing logic
moves verbatim into DeckListPlainText::parse() so it lives in a
dedicated, testable unit; DeckList keeps a thin delegating wrapper
and still refreshes the deck hash exactly as before (also on the
empty-input path, to match cleanList's original behavior). The
*F* foil suffix handling is relocated unchanged.

* [DeckList] Harden plain-text parser regexes and move metadata clearing up

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:30 +02:00
BruebachL
f4d5fc181d
[DeckList] Drop const from zone lookup that creates nodes (#7304)
DecklistNodeTree::getZoneObjFromName creates a new zone node when
the name is unknown, so declaring it const was a lie that let a
const DecklistNodeTree mutate its tree. It is only called from
mutating paths (addCard, readZoneElement), so the const qualifier
is removed.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:30 +02:00
BruebachL
733deac0bb
[DeckList] Collapse repeated playmat parameter clamping (#7303)
The playmat read path clamped margin, offset and zoom with four
nearly identical qBound + fallback blocks. A single
parseClampedParam helper now owns that logic; behavior is unchanged
(parse whose string is well-formed clamps, unparseable text uses
the documented fallback).

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:30 +02:00
BruebachL
2a3a8982a6
[DeckList] Deduplicate undo/redo state switching (#7302)
undo() and redo() were mirror images that differed only in
which stack was the source. Both now delegate to a single
restoreAndSwap(source, target, deck) helper, so the save-current-
state, apply-memento and signal-emission logic lives in one place.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:29 +02:00
BruebachL
87443d58f7
[DeckList] Remove dead card XML readElement (#7301)
* [DeckList] Remove no-op card XML readElement

AbstractDecklistCardNode::readElement only advanced the XML
reader to </card> and always returned 0; a card's attributes were
already parsed by the parent InnerDecklistNode::readElement. The
containing zone loop skips the card's end tag itself, so the
method was dead weight and is dropped from the node interface
along with the pure virtual it existed to satisfy.

* [DeckList] Document writeElement as the only serialization method

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:29 +02:00
BruebachL
61c1215a30
[DeckList] Remove obsolete Qt5 qHash compatibility shim (#7300)
The codebase is Qt6-only since #7071 dropped Qt5, so the
#if QT_VERSION < 0x050600 branch can never compile. Removing it
deletes a dead qHash overload that only existed to support
QRegularExpression in QSet on old Qt versions.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-18 11:58:28 +02:00
BruebachL
fd82b140a8
[PictureLoader] Serve cached pictures from the disk cache instead of re-fetching them (#7284)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
With picture downloads enabled, requests were issued with AlwaysNetwork
cache control, which per Qt never consults the disk cache. A picture that
had already been downloaded was therefore fetched from the network again
on every session start, with the queue bypass letting those re-fetches
skip the rate limit entirely.

Treat the network cache as the intent of the 'Network Cache' storage
method suggests: if the URL is already cached, serve it with AlwaysCache
(no network, no quota); only a genuine miss goes to the network, and only
when downloads are enabled. Cache hits skip the queue for free since they
never consume the per-second request allowance.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-13 03:36:29 +02:00
tooomm
5d025ca0bd
Use capitalized app names (#7255)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* use capitalized app name

* update urls

* app description

* Update main.cpp
2026-09-12 17:30:46 +02:00
BruebachL
a85203e457
Migrate theme asset loads to scheme-variant resolution (#7209-2) (#7276)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Replace remaining QPixmap("theme:...") call sites with themePixmap() so icons, replay controls, card backs, and other images resolve to -dark/-light variants under theme schemes. Stem-exact 1:1 migration; behavior unchanged for non-variant themes.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-11 17:59:57 +02:00
BruebachL
e9bf1e6e46
[Theme] Add scheme-variant asset resolution (#7275)
* Add scheme-variant theme asset resolution (#7209-1)

ThemeManager::assetPath() and schemeVariantPath() resolve a theme asset to its scheme-variant file (prefix-light/dark.png) with fallback to the plain asset, and themePixmap()/loadBrush()/loadExtraBrush() use them. CSS files load style-dark.css or style-light.css when present. Home widget re-resolves its background on theme change.

Link pixel_map_generator.cpp into the oracle target, which needs Qt6::Xml for QDomDocument.

* Fix clang-format wrap of theme format probe lists

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-11 17:59:57 +02:00
BruebachL
202a5ac958
[Client/Server/Protocol] Surface live metrics in the Developer tab (#7212)
* [Server] Instrument command processing, game starts, and event loops

Add a lock-free MetricsRegistry that accumulates per-command processing
times in preallocated histogram slots (one per protobuf command type,
bucketed at 1/5/10/25/50/100/250/500/1000/2500/5000 ms +Inf). The
hot-path observeCommand() uses only relaxed atomic adds — no locks,
no allocations, no cache-line ping-pong beyond the unavoidable counter
updates.

Wire the registry into AbstractServerSocketInterface::processCommandContainer()
so every processed command is attributed with its container's wall-clock
time. When a container exceeds metrics/slow_command_ms (default 500),
a warning is logged including the connected username.

Add an EventLoopWatchdog heartbeat that runs on every socket pool thread.
If a heartbeat overshoots metrics/stall_warn_ms (default 2000 ms), the
overshoot is recorded in atomic counters and a warning is logged. Both
thresholds are configurable in servatrice.ini; setting stall_warn_ms to 0
disables the watchdogs entirely.

Track game-start durations via a separate histogram in MetricsRegistry.
Server_Game::startGameNow() measures the time from zone creation through
player materialization and reports it via Server::observeGameStartDurationMs().

Add a live card-count gauge: Server_Game exposes getCardsInGame() and
Servatrice::getCardsInGamesTotal() sums across all running games under
the appropriate read locks.

Include a standalone metrics_registry_test (Google Test) that validates
empty registries, single/multi-sample histograms, kind encoding,
overflow-slot collapse, negative-duration clamping, gauge rendering,
and the game-start histogram separation.

Took 10 minutes

* [Client/Server/Protocol] Surface live metrics in the Developer tab

Extend Response_GetServerStats with live counters from the in-process
MetricsRegistry: cards in games, event loop stall totals/worst,
total commands processed, average command time, active command types,
and game-start count/duration. Add a repeated CommandStats message
carrying per-command breakdowns (kind, extension number, resolved
protobuf name, count, total ms) for every type that has seen at
least one sample.

Server-side cmdGetServerStats() populates all new fields after the
existing DB uptime snapshot query, resolving protobuf extension names
via the descriptor pool for human-readable labels like
session/Command_Ping.

Expand TabDeveloper with two tables: an overview section (existing
DB stats plus the new live metrics) and a per-command breakdown table
(Command / Count / Total ms / Avg ms) sorted by total_ms descending
so the hottest commands surface first.

Took 55 minutes

Took 47 seconds

* [Server] Drop dead Prometheus histogram, add developer command metrics, fix watchdog init order

- metrics_registry: remove toPrometheusText/appendCumulativeBuckets and the time-bucket histogram that nothing in production ever emitted (the future /metrics exporter can bring it back); keep counts/totals read by the Developer tab
- Fix +Inf bucket routing that never incremented, and its test that locked the bug in
- Instrument developer_command container (kind 6) in processCommandContainer and stats label resolution
- Read metrics/{slow_command_ms,stall_warn_ms} at the top of initServer() so stall_warn_ms=0 disables the watchdogs before pool threads start
- Shrink KindStride to 1280 (largest extension in use is 1206) with a static_assert; document scrape cost of getCardsInGamesTotal; note slow_command logging has no rate limit in servatrice.ini.example

* [Tests] Give metrics_registry_test an explicit main

* [Server] Record only the dispatched command family; drop unused totals

processCommandContainer recorded every family in a container even though
the base if/else-if dispatch processes at most one. An unauthenticated
client could batch a session command (login) with fabricated developer,
moderator, and admin entries and forge genuine-looking samples that were
never executed or authorized. Mirror the base's selection, skip when the
handler was already deleted, and skip entries whose extension number is
-1 (which would otherwise wrap into the previous kind's id range).

[Server] Drop dead process-lifetime byte/uptime counters

txBytesTotal/rxBytesTotal added an atomic RMW to every socket write and
read for counters nothing consumes (cmdGetServerStats fills tx_bytes,
rx_bytes, and uptime_secs from the DB snapshot). Remove the two atomics
and the getTxBytesTotal/getRxBytesTotal/getUptimeSeconds getters; the
incTxBytes/incRxBytes slots and mutexes remain for the ISL legacy
counters.

[Protocol] Document kind 5 as developer in CommandStats

NumKinds is 6 and the server emits kind_index = 5 for developer
commands; the comment stopped at 4.

* [Client] Togglable auto-refresh for Developer stats tab

* [Oracle] Fix clang-format alignment of card type priority list

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-11 17:18:56 +02:00
BruebachL
d5d99e4dfb
[Server/Client/Protocol] Add developer staff role (#7211)
* [Server/Client/Protocol] Add developer staff role

Introduce a Developer staff level (proto flag 32, DB admin bit 8) that
sits between admin and moderator: no kick/ban/warn/report/admin powers,
but gets server log access via a new developer command container family
(GET_SERVER_STATS, VIEWLOG_HISTORY) and an idle-timeout exemption.

- Protocol: IsDeveloper flag, developer_commands.proto envelope,
  Command_GetServerStats/Command_GetLogHistory, Response_GetServerStats,
  Command_AdjustMod.should_be_developer
- Servatrice: fail-closed developer dispatcher, uptime snapshot handler,
  shared log history handler reuse, bit-8 DB mapping
- Client: burgundy pawn/badge/labels/sort order, prepareDeveloperCommand,
  minimal Developer stats tab, log tab access, promote/demote actions

Took 24 minutes

Took 18 seconds

* [Server/Client/Protocol] Address developer role review feedback

Address ZeizaZach's review of the developer staff role:

- Nudge the developer log query to exclude private chat and sender IPs
  (the ModeratorCommand path still sees everything).
- Deduplicate Command_GetLogHistory into Command_ViewLogHistory, which now
  extends both ModeratorCommand (ext) and DeveloperCommand (dev_ext); the
  client picks the DeveloperCommand-scoped extension by extendee, and the
  server reads it via the extension number.
- Pull the uptime snapshot SQL into Servatrice_DatabaseInterface as
  getLatestUptimeSnapshot() and widen the reported counters to 64-bit.
- Document the admin bitfield (1 admin, 2 moderator, 4 judge, 8 developer)
  and add a server-side test for the developer command path.

* Add missing trailing newline to user_context_menu.cpp

* Remove stale includes of deleted command_get_log_history proto

The Command_GetLogHistory message was folded into Command_ViewLogHistory,
which deleted command_get_log_history.proto, but serversocketinterface
still #included its generated header. Fresh CI builds fail on the missing
file; local builds masked it by reusing a previously generated header.

* [Server] Exclude chat rows when private-chat filter is bypassable

A developer who omits log_location entirely — or sends only "chat" —
leaves chatType, gameType, roomType all false, so getMessageLogHistory
skips the target_type clause and returns every row, private messages
included. When !allowPrivateChat the server now forces game+room when
no surviving location was requested, guaranteeing the query always
carries a target_type restriction.

[Client] Demote mod+dev to moderator path in log-tab dispatch

The developer command family is strictly weaker than the moderator one
(no private chat, no sender_ip, ip filter ignored), so granting the
developer bit to an existing moderator must not silently strip their
capabilities. useDeveloperCommands is now true only when the user holds
the developer bit and not the moderator bit.

[Client] Hide the IP-address filter for developer log tab users

The developer path ignores the ip_address query field server-side.
Showing the field lets a developer type an IP and get results that are
silently unfiltered by it rather than an empty result set — reads as a
broken filter. Hide labelFindIPAddress/findIPAddress alongside the
privateChat checkbox.

* Developer pawn is silver.

* [Client] Fix indentation of merged Card Art Rules / Developer tabs

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-11 17:18:56 +02:00
BruebachL
7d867b9745
[Oracle/Client] Report card database download progress (#7253)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
* [Oracle/Client] Report card database download progress

Card database updates ran invisibly: MTGJSON parsing spun an indeterminate
bar on the UI thread and the set import blocked the window, while the
onboarding wizard spawned `oracle -b` with no progress to show at all.

- Add byte-level scan progress to `RawJson::scanSetRanges` via an optional
  callback, throttled to ~100 reports per scan.
- Emit `OracleImporter::dataReadProgress` during the scan and import sets on a
  worker thread, driving the wizard's progress bar per set.
- With `-b`, write machine-readable `PROGRESS <stage> <done> <total>` lines to
  stdout for the download/scan/import stages; stderr keeps the log output.
- Parse the oracle stdout in `MainWindow` and forward it to the onboarding
  wizard, giving the card database step a determinate bar with stage-specific
  status text.
- Guard the async workers against the wizard being closed mid-run.
- Add Google Test coverage for scan progress reporting.

* [Oracle/Client] Harden oracle progress workers and quit prompt

Address review feedback on the download-progress change: decompress and read
sets files off the UI thread, cancel the load/import workers before the
wizard can tear down the importer, and show an 'Extracting file...' status
plus a clean 100% tail so the poll never looks stuck. Quitting Cockatrice
while a card database update runs now asks for confirmation.

* Show 100% for 500ms on complete.

* Disable buttons on set import until done.

* Clean up progress bar.

* Drop wrapper around lambda

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-09 22:27:16 +02:00
BruebachL
69e8f80fa1
[Oracle] Prefer higher-priority maintype when merging split cards (#7272)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
* [Oracle] Prefer higher-priority maintype when merging split cards

Adventure cards (e.g. Bonecrusher Giant, Virtue of Knowledge) are stored as
split cards in MTGJSON, with a Creature/permanent face and an Instant/Sorcery
adventure face. When the two faces are merged into a single card, the code
previously discarded the second face's maintype entirely, keeping whichever
face was processed first.

If the Instant/Sorcery adventure face appeared first, the merged card got
maintype 'Instant' with tableRow 3. At runtime this made double-clicking the
card on the stack send it to the graveyard instead of the table
(PlayerActions::playCard).

Fix the merge to follow the same priority order used by getMainCardType()
(Planeswalker > Creature > Land > Sorcery > Instant > Artifact > Enchantment),
so the permanent Creature type wins for adventure cards regardless of face
order, matching the physical card and the reported expectations.

Closes #4394

* Extract helper.

* Simplify const

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 21:22:53 +02:00
BruebachL
df3defa890
[PictureLoader] Leave failed pixmap null so solid color is shown instead. (#7274)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 13:37:19 +02:00
BruebachL
030e4f5871
[Game] Stop duplicate attach log entries (#7273)
Fix duplicate attach messages in the game log caused by a redundant
child arrow in CardItem::drawAttachArrow.

Unlike the sibling drawArrow, drawAttachArrow omitted the ``card ==
this`` guard when iterating selectedItems(). Because right-clicking a
card to open the attach menu selects that card, it was always present
in selectedItems(), producing a second arrow for the same source card.

On release both arrows sent an identical Command_AttachCard, so the
server broadcast two Event_AttachCard messages and the log rendered
"attaches to" twice. Mirror the drawArrow skip condition so the active
card is excluded and only one attach command is sent.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 13:37:13 +02:00
RickyRister
6e5c58069b
[Settings] Make chat settings page scrollable (#7271)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-09-07 00:52:06 -07:00
BruebachL
ebeae48652
[Game] Preserve hand card order when restoring connection (#7266)
When a player restores connection to a game, the client rebuilds each
zone from the cards the server sends in the game state. Non-coordinate
zones (hand, piles, stack) report x == 0 on every card, so inserting
each rebuilt card at that coordinate reversed the received order one
card at a time.

Append rebuilt cards in the order they arrive for zones without
coordinates; coordinate-based zones (table) keep using x/y.

Fixes #2759

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 08:21:44 +02:00
BruebachL
36f998e466
[Client] Fix pawn avatar cache key collision for players without a custom avatar (#4086) (#7264)
Player pawns are looked up in QPixmapCache under a key built from the
rendered size, user level, and the avatar pixmap's cacheKey(). A null
pixmap reports cacheKey() 0, so all players without a custom avatar
collided: the first pawn rendered for a given size and user level was
reused for the next one, showing the wrong player's pawn.

Extend the key with the rendered height, the lowercased privlevel
(matching UserLevelPixmapGenerator), and both pawn colors so that every
visually distinct pawn gets its own cache entry.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 08:21:26 +02:00
BruebachL
760fe88fa3
[Client] Add setting to ignore all private messages (#7260)
* [Client] Add setting to ignore all private messages (#1250)

* Early return

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 08:20:46 +02:00
BruebachL
fff506dbbe
[Client] Show confirmation when adding a user to the ignore list (#1875) (#7261)
Nothing in the UI confirmed that a user had been added to the ignore
list, so the action felt ambiguous and could be repeated by accident.

Show an information dialog when the server acknowledges the
add-to-ignore command.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 08:14:39 +02:00
BruebachL
8e0bdafb14
[Client] Prevent contacting users on the ignore list (#7262)
* [Client] Prevent contacting users on the ignore list (#1249)

The ignore list silences incoming messages, but a user on it was still
reachable: the context menu's chat item stayed enabled, private messages
could be sent, and a chat tab could be opened for an ignored user.

Make ignored users uncontactable: disable the chat item for them, refuse
to deliver messages typed in an open PM tab with one, and refuse to open
a new private chat tab with an ignored user (with a hint on how to undo
the ignore).

* Update cockatrice/src/interface/widgets/tabs/tab_message.cpp

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2026-09-07 08:14:15 +02:00
BruebachL
9b0d62c152
Close all zone views of a leaving player to avoid crashing (#4298) (#7270)
When a player left, GameScene::removePlayer iterated zoneViews while close()
synchronously removed the current view from that list. A judge with several open
views of the departing player (e.g. library and hand) only had the first one
closed; the remaining views were skipped and left pointing at a player that was
about to be deleted, crashing the client on the next access.

- GameScene::removePlayer: iterate over a copy of zoneViews
- GameScene::toggleZoneView: same fix for the identical iteration bug

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 02:51:38 +02:00
BruebachL
6cdeb0c428
[Client] Explain the buddy and ignore lists in the user tab (#2072) (#7258)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 02:29:17 +02:00
BruebachL
745e94f332
[Replay] Skip damage animations when skipping backward in replays (#7249)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 02:26:52 +02:00
BruebachL
b01e107908
[Client] Show horizontal art for plane and siege cards in the profile banner (#7118) (#7250)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Use the landscape orientation flag to rotate sideways-layout card art
upright before cropping, so planes/sieges show their horizontal art as
the server profile banner card instead of a rotated full card.

- Curve cropCardArt around the card's landscapeOrientation flag with
  landscape-specific art margins (mirrors CardInfoPictureWidget)
- Add shared CardArtUtils::rotateSidewaysLayoutArt helper and apply it
  to the playmat (game render and settings preview) and card info widget,
  replacing three duplicate 90-degree rotation blocks

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 02:20:54 +02:00
BruebachL
8d30ac54f0
[Game] Prevent spectator duplication when replaying joined events (#7248)
* [Game] Prevent spectator duplication when replaying joined events

The spectator branch of eventJoin emitted spectatorJoined unconditionally
even when the spectator was already present (e.g. replayed during a rewind).
Guard it like the player branch and eventGameStateChanged, and make
PlayerListWidget::addPlayer idempotent as defense in depth.

* In resetChatAndPhase() (the rewound() handler), also clear all spectators from both PlayerManager and PlayerListWidget before the replay rebuilds from event 0. The forward replay then re-adds exactly the spectators whose join events fall within the new time range via eventGameStateChanged/eventJoin.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-07 01:57:35 +02:00
tooomm
fb482f037e
550 MB --> 600 MB (#7257) 2026-09-06 23:13:34 +02:00
tooomm
0f0e46a177
[Oracle] Have space between size value and unit (#7254) 2026-09-06 22:19:13 +02:00
tooomm
2fe59d6326
fix table formatting (#7256) 2026-09-06 22:18:36 +02:00
tooomm
048fe247f4
Add ccache eviction to debug builds as well (#7247)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-09-06 14:05:47 +02:00
RickyRister
0f003eabf9
[Game] Implement total toughness tally (#7252) 2026-09-06 01:46:37 -07:00
BruebachL
ada774f5cc
[Game] Render custom deck zones in the deck view (#7207)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Game] Render custom deck zones in the deck view

The in-game deck view now walks custom zones like the standard
boards, so cards filed under a user-created zone show up in their
zone's card pile instead of disappearing from the view.

* [Game] Collect deck-view cards via DeckList::getCardNodes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:22 +02:00
BruebachL
b0e566ed54
[Client] Show custom zones in the card display widgets (#7206)
* [Client] Show custom zones in the card display widgets

Card group displays and deck zone displays learn to render custom
zones alongside the standard boards.

- Group display widgets treat custom-zone nodes like other group
  headers, keeping counts and layout consistent.
- Zone display widgets resolve their title through visibleNameFromName
  so custom zones show their user-chosen names localized like the
  standard zones.

* [DeckEditor] Apply sort criteria inside custom zones and align display order with the model

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:21 +02:00
BruebachL
0d09e633e3
[Client] Expose custom zone management in the deck editor (#7205)
* [Client] Expose custom zone management in the deck editor

Wires the state layer into every editor surface that shows deck zones.

- Deck dock: context menu on zones gains New/Rename/Delete/Change
  board actions, with per-zone submenus for adding cards.
- Card database dock and visual database display gain an add-to-zone
  submenu listing custom zones per board plus a create-zone entry.
- All prompt call sites pass validateNewZoneName so duplicates and
  reserved names are rejected inline before Ok unlocks.
- Rename reuses the same dialog in name-only mode, keeping one
  validation contract for every zone-name entry point.
- Change board marks the current board instead of offering a no-op,
  and the state layer refuses moves onto boards holding a same-named
  zone from imported decks.

* [DeckEditor] Address custom-zone menu and export review feedback

* [DeckLoader] Keep the sideboard marker and block ordering when exporting nested zones

- saveToStream_DeckZone threads the owning board zone name down to the card
  writer, so cards in a custom zone under the sideboard keep their SB:
  prefix instead of being re-imported into the maindeck
- nested sub-zones are collected during the loop and written after the
  parent zone's own header and cards, so they no longer read as part of the
  zone printed before them

* [DeckEditor] Fix move-to-zone menu use-after-free and per-zone enabled state

- resolve the card name/provider/collector number before createNewCustomZone
  rebuilds the model tree, then re-find the refreshed index via findCard and
  move it (mirrors the decrementCard re-find pattern)
- the enabled test now compares the card's own zone (nearest custom-zone
  ancestor, else its board), matching moveCardToZone's lookup, so moving a
  card out of a custom zone back to the board root is offered and the card's
  own zone is disabled

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:21 +02:00
BruebachL
9677fad342
[Client] Add zone management to the deck state manager (#7204)
* [Client] Add zone management to the deck state manager

State-layer operations for custom deck zones, plus the shared prompt
dialog that later editor menus will call into.

- moveCardToZone relocates every copy of a card row into any zone,
  refusing non-card rows and tokens so miswired selections can never
  shred a group or turn tokens into deck cards. The current zone is
  found by walking ancestors, which also handles legacy top-level
  zones.
- createCustomZone, renameCustomZone, moveCustomZone and
  removeCustomZone wrap the tree API with memento history, model
  rebuilds and deck hash refreshes via modifyTree.
- Same-board zone moves return success without minting a history
  entry, keeping the undo log honest.
- promptForNewZone asks for a name and the parent zone, keeps Ok
  disabled until the trimmed name passes a caller-supplied validator
  (shown inline as an error), and reports its own translation context.

Took 14 minutes

# Commit time for manual adjustment:
# Took 6 minutes

# Commit time for manual adjustment:
# Took 33 seconds

* [DeckEditor] Address zone-management review feedback

- Expose DecklistNodeTree::hasZoneName and use it in validateNewZoneName
  so the uniqueness scan covers custom zones on every board, not just the
  standard ones.
- Hide the board selector in the rename dialog path where it is not used.
- Emit deckHashChanged after refreshDeckHash so the deck hash label stays
  current after zone create/rename/move/remove.

* [DeckEditor] Notify card set changes after zone edits and drop the board scan

- modifyTree emits cardNodesChanged alongside deckHashChanged so the
  banner-card combo and printing in-deck counts refresh after removing a
  zone that still holds cards
- DecklistNodeTree::findCustomZoneByName is public and moveCustomZone uses
  it, locating zones under non-standard boards (e.g. tokens) instead of
  scanning only main/side/maybeboard

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:21 +02:00
BruebachL
e8ec28572f
[Models] Mirror custom deck zones in the deck list model (#7203)
* [Models] Mirror custom deck zones in the deck list model

DeckListModel now surfaces the custom zones from the deck tree so
views can render and edit them alongside criteria groups.

The custom-zone bookkeeping that made the model unwieldy is extracted
into DeckListModelCustomZones (deck_list_model_custom_zones.h/.cpp), a
single self-contained unit owning every "what is / where is a custom
zone" decision for the model's shadow tree:

- rebuildTree mirrors each custom zone as a DecklistModelSubZoneNode
  under its board zone, cards flat inside (no further grouping).
- The freshly built shadow tree is sorted while the model reset is
  still open, so views never observe unsorted intermediate order and
  proxies cannot desync.
- Custom zones always sort after criteria groups within a board,
  regardless of their names. One shared sortWithCustomZonesLast backs
  both the live sortHelper (which remaps persistent indexes from the
  movement mapping) and the silent reset-time sortShadowTree.
- addCard inserts flat into a custom zone by name and keeps grouping
  by active criteria for board zones. findCardNode resolves cards in
  both layouts, legacy top-level zones unchanged.
- New IsCustomZoneRole lets views tell zones apart from groups.
- Empty custom zones survive row removal. Zone rows themselves are
  only mutable through the deck tree API.

A new deck_list_model_custom_zones_test suite locks the extracted
shadow-tree logic (type testing, mirroring, name lookup, and the
sort-with-custom-zones-last mapping).

No behavior change.

* [Models] Route group lookups around mirrored custom zones

Group lookups (createNodeIfNeeded, findCardNode) must not resolve a
mirrored custom zone that shares the group name. Introduce
findGroupChild to search only non-custom children, and make addCard
consult the deck tree before falling back to creating a top-level zone
so cards added to an un-mirrored custom zone land inside it.

mirrorCustomZones now flattens cards nested at any depth into the
mirrored zone so no card is left without a model row.

Add model behaviour tests (addCard routing, same-name group/zone
collision, removeRows guard, empty-zone survival, findCard inside a
custom zone) and fix the missing main() in the unit test binaries.

* [Models] Fix addCard routing for card-named zones and nested custom zones

- hasDeckZone no longer matches board cards that merely share the zone
  name, which previously caused infinite addCard/rebuildTree recursion
- Adding to a custom zone whose deck side holds nested sub-zones appends
  to the deck tree instead of writing past its direct children

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:20 +02:00
Magnus Groß
0c725f9a03
Allow to filter sets by release date (#7239)
This compares the release dates of sets, which enables users to filter
for sets in a certain range, for example to filter for all commanders
with an old card frame, `t:legendary set<8ED` can be used, which will
only include cards appearing before 8th edition.

This acts as a more powerful superset of the "Filter to X most recent
sets" feature.

Fixes #7238
2026-09-05 11:58:37 -07:00
BruebachL
c011ea7ceb
[Oracle] Parse sets lazily to slash importer peak memory (#7217)
* [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

* [Oracle] Fix nesting-depth cap, tolerate unescaped control chars, lazy-parse review fixes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 20:35:28 +02:00
BruebachL
1dc54617ba
[Oracle] Add RAM usage benchmarks for the oracle importer (#7216)
* [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

* [Oracle/Tests] Measure parse against post-fixture baseline; assert release empties sets

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 20:35:27 +02:00
BruebachL
aa96d81e4b
[Build] Enable ccache by default when it is installed (#7236)
* [Build] Enable ccache by default when it is installed

ccache is a near free win for both clean and incremental rebuilds and
has no effect on systems where it is not installed (find_program
guards the whole block). Aligns the CMake default with the documented
behavior; users can still arch with -DUSE_CCACHE=OFF.

* [Build] Disable ccache auto-engage on Windows (MSVC)

* [Build] Report ccache skip on Windows explicitly

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 19:40:37 +02:00
BruebachL
14ecfff700
[Build] Add precompiled headers for Qt-backed executables (#7235)
* [Build] Add precompiled headers for Qt-backed executables

Reparsing QtCore/QtGui/QtWidgets/QtNetwork in ~460 client translation
units is the dominant compilation cost. Precompile the two common layers:
- qtcore_pch.h (Qt Core only; safe even for headless Servatrice)
- qtwidgets_pch.h (adds Gui/Widgets/Network; used by Cockatrice and Oracle)

target_precompile_headers() requires CMake 3.16, now the project minimum.
Estimated 30-50% faster client rebuilds.

* [Build] Format qtwidgets precompiled header

clang-format include regrouping and a missing trailing newline.

* [Build] Add PCH-aware ccache sloppiness config; format cmake/pch headers

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 19:40:36 +02:00
BruebachL
61e6a9913e
[Oracle] Add oracle importer tests and fix set parsing details (#7215)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [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

* [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.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 22:14:16 +02:00
BruebachL
4d4ddd4278
[Oracle] Replace vendored qt-json with native QJson for set import (#7214)
* [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

* [Oracle] Restore property coercion and legality merge in native JSON import

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 22:14:16 +02:00
BruebachL
d6fbfb32a1
[Security] Use a CSPRNG for salts, tokens, and RNG seeding (#7192)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Security] Use a CSPRNG for salts, tokens, and RNG seeding

Password salts and activation tokens were generated with the global SFMT
RNG, which was seeded from a 32-bit timestamp, making registration
salts and activation tokens predictable. The game RNG used the same
timestamp seed across restarts.

Add CryptoUtil backed by OpenSSL RAND_bytes and use it for salt/token
generation and to seed RNG_SFMT with a 64-bit CSPRNG value in both the
client and server. Link libcockatrice_utility against OpenSSL::Crypto.

Took 30 seconds

Took 25 minutes

* Lint.

Took 4 minutes

Took 36 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 13:49:18 +02:00
BruebachL
3ec62df3e7
[Protocol] Remove duplicate event_game_state_changed.proto entry (#7234)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
The .proto file appeared twice in the PROTO_FILES list, causing protoc to
process it twice on every build. Keep a single entry.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 05:20:53 +02:00
BruebachL
fcfb14cf56
[Build] Use pipes for GCC/Clang compilation (#7233)
Pass -pipe so GCC/Clang transfer intermediate representation between
compiler stages over pipes instead of temporary files, reducing build I/O.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 05:20:53 +02:00