mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
61 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2ea11d4599 |
[Installer] Split the NSIS template into includes
Pure refactor, no behavior change. cmake/NSIS.template.in is the CPack template, and CPack substitutes the @...@ placeholders in that file only, so everything without a substitution moves into cmake/nsis/ and is included from it: installer-log.nsh the CockLog macro and the two writers behind it process-guard.nsh running instance handling, installer and uninstaller copy user-data.nsh backup/restore around the previous version's uninstaller runtime-purge.nsh stale runtime purge, diagnostics and the holder probe The page and section order, the Vars the included files share, .onInit (it names the package version in the log), AbortInstall (its Sentry offer is substituted), componentsPagePre and the registry bookkeeping stay in the template. The includes resolve through NSIS_SOURCE_PATH, which the template already uses for the bitmaps, and an include does not change the compile directory, so the holder probe keeps its relative File path. |
||
|
|
bbc7e462ff
|
[Installer] Graceful close and a way out of a stuck update (#7687)
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 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 Desktop / Prune caches (push) Waiting to run
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
* [Installer] Stop calling the payload free while an executable is locked The guard answered "nothing is holding the payload" from two lookups that can both miss a running instance: Restart Manager does not reliably report a program's own image or the modules it mapped, and the image path of a process running under another account cannot be read from the probe at all. A user log showed exactly that: the first guard passed while cockatrice.exe was running from the directory, the old uninstaller silently skipped the file it could not delete, and the update aborted half an uninstall later with "cancelled by user" after a close attempt the running window ignored. Ask the file itself. A running image is opened without write sharing, so the write open replacing it needs is refused with ERROR_SHARING_VIOLATION for as long as the program runs, while a file that is merely being read still accepts it. When neither lookup named anybody and a payload executable refuses that open, the probe answers that the file is in use and that the program holding it could not be identified - the guard's "could not confirm" dialog, whose Retry works once the user has closed it - instead of passing the payload as free. Only the payload executables are tested, not every file the purge removes: a locked executable proves a program is running and a mapped DLL belongs to a running executable, while a locked DLL or qt.conf can be the asking process itself, which is the loop the guard already had. The open is retried once after 300 ms, and only for the files that refused, so a scanner holding a file for a moment cannot stop an install for nothing. The Windows probe check now covers that answer: a payload executable held by the asking process, so that it cannot be named, has to come back as in use rather than as free. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> * [Installer] Close running clients over a local socket The installer could only ask a running client to close with WM_CLOSE, which an instance hidden in the notification area or sitting on its own confirmation cannot answer usefully. Give every client a per-process local socket and have the holder probe ask it there first, falling back to WM_CLOSE for clients from before the socket. * [Installer] Offer to check again or update by hand when files stay locked Replace the two-answer guard prompt with force close, close by hand and check again, or cancel. Retry the automatic close while the set of holders changes, and point a cancelled run at the latest release so a user who does not want anything killed has a way forward that is not cancelling the update again. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
0369aaffa9
|
[Installer] Name only the payload programs installed in the directory (#7684)
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 Desktop / Prune caches (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 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / Ubuntu 26.04 cover (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
The running-instance lookup named every process whose image path began with the install directory, subfolders included. Installing into a folder that holds another application in a subfolder therefore reported that application as holding the previous version, and nothing the guard offers could clear it: the close asks cockatrice.exe only, and the end answer refuses anything that is not a payload program. Both answers were no-ops, so the update could only abort and tell the user to close a program that was never touching the files. Name only the programs the payload installs, and only when the image lies in the install directory itself. A program below the directory that really does hold a payload file is still named by the Restart Manager lookup, which is the lookup for that question. The Windows probe check now covers a payload-named program in a subfolder and a program the payload does not install; both have to come back as nothing. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
ded83aaab5
|
[Installer] Ask a hidden instance to close, then offer to end it once (#7683)
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 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 Desktop / Prune caches (push) Waiting to run
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
* [Installer] Request quit for hidden Cockatrice instances and record holders in abort telemetry * [Installer] Fix compiler warnings in holder probe and installer quit handler * [VisualDeckStorage] Order the includes the way clang-format wants them * [Installer] Ask a hidden instance to close, then offer to end it once The previous attempt had the client answer a registered window message, which cannot work for the update it was reported on: the instance being replaced is a client from before that message exists, so it never answers, and the user is left with "Cockatrice is still running" exactly as reported. Every step that can free the files has to be the installer's, so the client side is reverted. The probe now posts WM_CLOSE to the instance's own window whether that window is on screen or only in the notification area: the request does not need a visible window, an instance in the tray is the one this update has no other way to reach, and the confirmation Cockatrice answers with is a window Qt shows itself, so it is painted - unlike one restored from outside. A visible window is brought forward first, and Qt's bookkeeping windows stay excluded, because a close to the notification-area one terminates the instance without its confirmation. A question of the instance's own no longer suppresses the close. Withholding it protected nobody: the installer asked the user to close a program it had never asked, and a current client refuses the close while its confirmation is pending anyway, leaving the question where it is. The launcher mode keeps the visible-only rule, because a launcher without a window has already answered its own close and the wait ends it after a grace instead of asking a user who is not there. The guard asked the same question until the user gave up: after the automatic close failed it offered to end the programs, then re-ran the probe and showed the same dialog word for word when the answer was No, which is how an abort was reported three times over. It now asks once - Yes ends the Cockatrice, Oracle and Servatrice programs the payload installs and says what that costs, No cancels - and what survives an end is named in the abort instead of being put to the user as the same question again. The uninstaller's guard and the recovery path after a failed purge follow the same shape. Kept: the abort telemetry that records the holders, the close result and the end result as event data. --------- Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com> Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
9e50c5ad6e
|
[Installer] Recover payload files when previous uninstaller is missing or fails to launch (#7682)
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 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 Desktop / Prune caches (push) Waiting to run
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: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com> |
||
|
|
06da7f815a
|
[Installer] End a launcher that has no window to ask instead of prompting (#7681)
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 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 Desktop / Prune caches (push) Waiting to run
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
* [Installer] End a launcher that has no window to ask instead of prompting The in-app updater starts this installer detached and then closes itself. A client that accepts that close hides its window before it exits, and if it then hangs in shutdown there is nothing left to ask: the holder probe deliberately never posts WM_CLOSE to an instance it cannot see a window for, so the "ask it to close again" answer only repeated itself until the user picked Cancel and the installation aborted (Sentry COCKATRICE-DESKTOP-J). The launcher wait now asks the launcher itself before asking the user. A new probe mode, --close-launcher, answers whether that one process has a window a close could reach at all; it is limited to a pid that still runs from the directory being replaced, so a reused process id cannot be hit. When there is no window, the installer gives the shutdown it already started one full grace and then ends it through the handle it waited on, which is what the dialog's Yes answer did. The prompt remains for the case it exists for: a window is up, the instance may be waiting on its own question, and No now means only "keep waiting". The client is left alone: an already-installed version runs the installer during its own update, so a change there cannot reach the run that aborts. * [libcockatrice_rng] Include immintrin.h for AVX2 builds in SFMT --------- Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com> |
||
|
|
a83d82b5e8
|
[Installer] Recover leftover payload files when previous uninstaller leaves them behind (#7676)
* [Installer] Recover leftover payload files when previous uninstaller leaves them behind * [Installer] Fix leftover recovery on the 64-bit auto-uninstall path - Record the 64-bit uninstaller exit code after ExecWait returns. It was captured before the call (always empty), so WaitForOldPayloadToDisappear always aborted before RemoveOldPayloadLeftovers could run. - Restore the pre-uninstall user-data backup before aborting out of the wait, including the guard's cancel and probe-failure prompts, so the only copy cannot be stranded in %TEMP% for the next run to wipe. - Report the leftover removal as done only when every payload executable is gone and RetryDllPurge reports no stale DLLs, instead of checking cockatrice.exe alone. - Return directly after a successful recovery instead of reusing the poll counter, so the log stops claiming the directory "was already clear" and the normal settle delay is kept. * [Installer] Delete the old uninstaller only after recovery succeeds RemoveOldPayloadLeftovers cleared the attribute and deleted $INSTDIR\uninstall.exe on every attempt, before the attempt was known to work. On failure the installer aborts telling the user to run it again, but UninstallString then points at a missing uninstaller: ExecWait leaves the exit code empty, WaitForOldPayloadToDisappear takes the "cancelled or failed" branch, and RemoveOldPayloadLeftovers is gated on exit code 0, so no later run can ever get past it - a half-removed old install the previous revision never produced. Delete it in rop_done instead, after $OldPayloadRemoved is set. WriteUninstaller recreates it on success anyway, and nothing in the success verdict depends on it being gone. --------- Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com> Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
bf3562a85a
|
[Installer] Offer to end running Cockatrice immediately when auto-close fails (#7675)
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 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 Desktop / Prune caches (push) Waiting to run
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
* [Installer] Offer to end running Cockatrice immediately when auto-close fails * [Installer] Keep the way out that costs nothing in the first prompt The prompt now offers to end Cockatrice, so what the deleted close-again text said has to survive in it: an instance the probe reports as waiting for an answer to a question of its own has been brought forward, and answering that window is the way out that ends nothing. Say so, and say where to go when Yes leaves it listed - Task Manager - so an end that cannot take effect does not loop on the same dialog. * [Installer] Bring the comments back in line with the code GuardCloseTried no longer counts closes, and the file header, CloseHolders and GuardAppsNotRunning still described an escalation after a second close, as did the probe's --end-holders note. * [Installer] End Oracle and Servatrice too when the guard's end answer is confirmed The guard's end answer only ever stopped cockatrice.exe, so an Oracle holding the card database or a Servatrice service kept the payload locked and the prompt came back with nothing left that could free it. The probe now ends every program the payload installs, services included: isPayloadProgram() - the list restart_manager_holders already used for its parent walk - decides the set, so a scanner holding a DLL or a program the user keeps in the install directory is still named and left to the user. A service is terminated rather than sent a stop through the SCM, which notices the process exit and marks it stopped afterwards. The guard prompt now says what ending costs: an unsaved deck, an unwritten setting, players on a running Servatrice, and an Oracle mid-way through the card database. It also keeps the notification-area hint, which the probe's "has no window to ask" answer depends on, and drops the claim that Oracle and Servatrice had been brought forward. * [Installer] Give every running-program prompt the same end-or-retry answers Four prompts meet a running program and each offered a different set: the guard ended it, the launcher wait only kept waiting, and the recovery prompt after a failed purge could only ask Cockatrice to close. With Oracle or Servatrice holding the files, that last one had no answer that could free anything - Yes and No both retried the purge and Cancel aborted an install whose old version was already gone. All four now follow the guard's shape: one automatic attempt, then a prompt whose Yes ends the programs it names and whose No retries, with Cancel to give up. - RecoverFromHolders closes once and purges before it asks anything, then offers EndHolders - the same end answer as the guard - instead of a close the user had to trigger himself. - WaitForLauncherToExit can end the launcher by pid, the one process it holds a pid for, rather than looping 60 s waits with Cancel as the only way out. - END_COST and AFTER_CLOSING_GUIDANCE are !defines, so the cost of ending and the guidance for the answers that end nothing read the same in every prompt. * [Installer] End the launcher through the handle it was waited on The "end it" answer used to look the launcher's pid up again after a dialog that can stay open as long as the user takes, so a pid that had been reused in the meantime - or a /PID= that was never the launcher - would be terminated instead. The handle the wait already holds names that process and cannot be handed to another, so the terminate answer now acts on it. PROCESS_TERMINATE is requested along with PROCESS_SYNCHRONIZE; when only the synchronize right is granted the launcher is still waited on and the answer falls back to asking it to close, instead of reporting a live launcher as gone and writing the payload over it. * [Installer] Say what the end answer ends, and report a failed end The prompt offered "end every program listed above", but EndHolders only ends the programs the payload installs: a scanner holding a payload DLL, or a service the Restart Manager names by its long name, was listed and then silently left alone, so Yes looked like it did nothing and the same dialog came back unchanged. The wording now says which programs Yes ends and that anything else has to be closed by hand, and the round after an end that did not free the files says so, so the same dialog no longer reappears word for word. The three prompts are one macro so they cannot drift apart again. The added sentences eat into the 1024 characters one NSIS string holds, so the holder line's budget drops from 400 to 320 characters to keep the longest message (cancel line included) inside it: 977 characters, measured against the worst case the probe can produce. --------- Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com> Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
30d3afc7b4
|
[Updater] Close a running Cockatrice when the installer asks (#7672)
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 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 Desktop / Prune caches (push) Waiting to run
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
* [Updater] Refuse a close that arrives while one is already asking MainWindow::closeEvent() had a re-entrancy guard that returned without touching the event. A QCloseEvent is accepted unless something ignores it, so a second close request - an installer replacing our files posts one every 30 s - accepted the close while the first one was still blocked on its own confirmation. close() then hid this window, hid the confirmation dialog with it, and left the process running inside the nested loop that dialog had opened: no window, no way to answer it, and every later close request accepted into the same dead end. That is what an update looked like when it could not close a running client, and it is why the installer could name the process, bring it forward and ask it to close for minutes without anything happening. Track how far the shutdown has got instead. While a confirmation is up, a close from anywhere else is refused, which leaves the question in front of the user; once the shutdown has been accepted, a duplicate close (Qt delivers the event twice on some platforms) is accepted again so it cannot cancel the quit. * [Installer] Leave an instance that is asking a question alone An instance that answered a close by hiding rather than quitting - the state the client used to get into - has no visible window left, and the confirmation it is waiting on is hidden with the window that owns it. The close probe only ever looked at visible windows, so it restored the main window, posted WM_CLOSE into the same dead end, and reported "still running" without saying why. Show every window the instance left hidden before choosing the one to close, and skip the close when the window it would reach is disabled: a modal dialog of the instance's own is up, and posting the close then only hides the question again. Name what is left of the window in the report as well - gone, still open, hidden again, and whether the thread answers at all - so the log tells a refused close from one that was never delivered. * [Installer] Offer to end a Cockatrice that will not close A close is a request, and an instance that does not answer it - waiting on a question nobody is looking at, or stuck where a close cannot reach - leaves the user with no way forward: the files cannot be replaced while it runs, and clicking Yes again only repeats the request. After a second close that changed nothing, offer the only thing that can free the files: end it. The prompt says what it costs - an unsaved deck, a running game or a setting it has not written yet - the default button is the safe one, and it is a separate answer rather than a relabelled "Yes", so it is not reached by clicking through the close prompt. Only cockatrice.exe is ever ended: Oracle writes the card database in place and Servatrice is a service, so both are named and left to the user, exactly as they are for the close. The probe gains an --end-holders mode for it, and the uninstaller's guard gets the same offer. * [Installer] Never name the process that is doing the asking The old uninstaller is started with _?= so it runs in place, from the very directory it is emptying, and an installer can be downloaded into the folder it installs to. Both are then processes running from the directory, so the probe named them - and the guard asked the user to close the program that was asking. That question cannot be answered: a close and the end offer both skip anything that is not cockatrice.exe, so the guard repeated itself until the update or the uninstall was cancelled. The probe and the processes that started it are now never named, by either lookup. The walk up the parent chain stops at the first program the payload installs, because in an in-app update the installer's own parent is the Cockatrice instance being replaced - the one the guard exists to name. The Windows check held its locked file in the script that starts the probe, so that holder is excluded by design now and the check read it as "nothing found". It holds the file from a process of its own instead, and asserts the exclusion itself: a file that only the asking process holds comes back as nothing. * [Installer] Restore only the windows a user could answer Showing every hidden window an instance owns put Qt's own bookkeeping windows on screen: the tray icon keeps one to receive the TaskbarCreated message, styled WS_OVERLAPPED and titled QTrayIconMessageWindow. Restored, it is an empty white rectangle in the taskbar with no title bar to close it - it has no window procedure that would hide it again, and the process is busy in the very loop the restore is meant to break, so it stayed until the process was force-ended. Only framed, non-empty windows are restored and considered for the close now. That also keeps the close off Qt's tray window, which answers WM_CLOSE by asking the application to terminate - a quit that skips the confirmation the close is supposed to reach. * [Installer] Uninstall the previous version before installing over it A regular install over an existing one asked "a previous version must be uninstalled", then ran the old uninstaller interactively. That uninstaller put its confirmation page behind the installer window, where it could not be answered, and it only removed the installation after the installer had already written the new one over it. Run the previous version's uninstaller silently instead. The installer has already asked the question the user answered, so a second, hidden confirmation is pure risk: with /S there is no window to be behind, and ExecWait is then waiting for something that cannot outlive it. QuietUninstallString is used when the old installation has one, otherwise /S is appended. ExecWait only waits while the uninstaller runs in place: one that did not get a usable _?= copies itself to %TEMP% and returns at once, while the copy keeps deleting - which is how an uninstall can finish after the install it was supposed to precede. Log the exit code and wait for the old cockatrice.exe to disappear before restoring user data or writing anything, and abort with a message instead of installing into a directory that is still being emptied. Cancelling the "must be uninstalled" prompt now quits the installer; it used to skip the page and install over the old version anyway. * [Installer] Do not show windows the application has hidden The guard restored every hidden window an instance owned before asking it to close, so that a confirmation hidden together with its window would come back. Windows does as it is told and shows them; the application does not, because Qt still believes they are hidden and never paints them. What the user gets is a blank white rectangle, on screen and on the taskbar, that answers nothing - and the question it was meant to bring back is not answerable in it either. An instance with no window a user could answer is named and left to the user instead, and the prompt says what to do about it: open it from the notification area. Ending it stays the last resort, offered once a close has been asked for twice. The abort after a failed uninstall now says what it saw - the old version is still there after its uninstaller ran - instead of promising a removal that is not happening, which is the state a cancelled old uninstaller leaves behind. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
d42b9607c5
|
[Installer] Check for running instances with the Restart Manager probe (#7671)
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 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 Desktop / Prune caches (push) Waiting to run
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
* [Installer] Check for running instances with the Restart Manager probe The running-instance guard was built on powershell.exe started from the 32-bit NSIS stub, which is the 32-bit Windows PowerShell: its Get-Process cannot read the path of a 64-bit process, so a running 64-bit Cockatrice was reported as not running, the update went ahead, and the purge then failed on the DLLs that instance still had mapped. Use tools/installer-holder-probe instead - the same Restart Manager lookup the abort path already had - before anything is replaced and in the uninstaller as well. It is bitness-agnostic, so the Sysnative and WMI fallbacks are gone. The guard looks only at the payload, asks Cockatrice to close once by itself (WM_CLOSE, the same request as the window's close button) and then offers close again / retry after closing by hand / cancel for whatever the close cannot reach - Oracle writes the card database in place, Servatrice has no window - without force-stopping anything. A failed purge offers the same close-and-retry, tries one last purge when nobody is named, and every dialog shows in every mode: a silent reinstall stays silent until something goes wrong. * [Installer] Name processes running from the install directory The running-instance guard answers from the Restart Manager, which reports who has a file open. A running instance does not have to have one open: its own image and the modules it loaded are mapped into it rather than opened by it, and a mapping is not reliably reported. That is the state the probe was added for - every DLL still on disk, an instance running from the directory, and "nothing is holding them any more" as the answer. A guard built on file handles alone is blind to exactly that instance, so it passes, the purge fails, the holder lookup after it names nobody either, and the install ends in a warning that no running program was found: no close is attempted and no retry is offered. A manual install with another instance open never asks it to close at all. Name every process whose image lives in the directory being replaced as well, with the same bitness-safe QueryFullProcessImageNameW the probe already uses to label its holders. The image lookup is listed first - it is the instance the user is asked to close - and the Restart Manager entries follow with the pids already named dropped, so the close still reaches everything that holds a file. A Restart Manager call that fails only makes the probe unable to answer when the image lookup named nobody either. Ask instead of dead-ending when the probe cannot answer at all: Retry checks again, Ignore carries on, Abort cancels, in the installer and the uninstaller alike, and the abort dialog says the check failed instead of claiming no running program was found. The Windows CI probe check covers the new lookup with a process started from the directory that holds no file open. * [Installer] Offer to wait again when the launcher has not exited A client that is still closing after 60 s is usually not hung: it is showing its own "quit anyway?" confirmation for an unsaved deck, an open game or a card database update, and the user is the one who has to answer it. The installer answered that with a plain warning and stopped, so the only way forward was to close Cockatrice by hand and run the update again - which on an in-app update means finding the downloaded installer. Ask instead: Retry brings the client to the front, asks it to close again (WM_CLOSE, nothing forced) and keeps waiting on its handle, Cancel stops as before. There is no "install anyway" answer because a live process has its image and Qt DLLs mapped, so writing the payload over them is the broken install this wait exists to prevent - and the guard that runs right after names the same process by its image path and stops there, so ignoring would only move the same question a moment later. * [Installer] Ask about a launcher that has not exited after ten seconds A minute of silence is a long time to sit in front of an installer that has no window yet, and the case it is waiting for is not a slow flush: a client that started an update exits in well under a second, so a minute means it is almost always showing its own "quit anyway?" confirmation with nobody looking at it. Wait ten seconds for the first question instead, and the full grace for the ones after it. The normal path is untouched - WaitForSingleObject returns as soon as the handle signals, so a client that exits in 200 ms still costs 200 ms - and answering the first question once does not turn into a stream of prompts while the user is in Cockatrice's window. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
4793c1fc04
|
[Installer] Offer to close blocking processes and retry purge on upgrade (#7664)
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 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 Desktop / Prune caches (push) Waiting to run
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
* [Installer] Offer to close blocking processes and retry purge on upgrade * [Installer] Retry the purge after closing only our own applications The first version of this fix closed every Restart Manager holder through RmShutdown, forcing what refused to close. RmShutdown also stops services, and the only API that brings them back is RmRestart, which would restart Cockatrice itself into the files that are about to be replaced; the forced pass also contradicts the running-instance guard, which never force-closes Cockatrice because it may be holding an unsaved deck. The guard ran first anyway, so for the case the fix exists for - a Cockatrice restarted during the update - the probe never got to run: the guard waited 60 s and aborted. Keep the prompt and the retry, but ask only Cockatrice, Oracle and Servatrice to close, with WM_CLOSE and nothing else, and leave every other holder named by the probe to the user. Repeat the prompt while the user keeps saying Yes, refresh the names after a failed retry, and keep the /R abort message pointing at the downloaded installer. Also clear the read-only attribute before every purge attempt. Delete and RMDir leave such a file in place, so the retry could never make progress, and the diagnostics branch that named the cause had no action behind it. The holder probe goes back to being diagnostic-only, so its Restart Manager code and the --close argument are dropped. * [Installer] Make an accepted close of the running client actually land WM_CLOSE is a request, and Cockatrice answers it: while a card database update is running, a game is open or a deck is unsaved it puts up its own "are you sure?" confirmation. That dialog opened behind the installer's prompt, so the user saw nothing happen; the next Yes was posted to a main window the app's own modal dialog had disabled, so it was ignored as well, and the identical yes/no prompt came back with no hint of why. - Activate the matching window before posting WM_CLOSE, so the app's own confirmation is in front where it can be answered. - Wait up to 30 s for cockatrice.exe to exit instead of purging after a fixed 2 s, so the purge measures the app gone rather than its dialog. - On the second and later rounds ask with Retry/Cancel and say what is needed, instead of repeating the same offer that just did nothing. - Stop promising Oracle and Servatrice: they are console applications, so CloseMainWindow() has nothing to send WM_CLOSE to. They are not force-stopped either - Oracle writes the card database in place. * [Installer] Close the client from the holder probe, not PowerShell Accepting the offer to close did nothing on a real machine: the instance stayed up, never came forward, and the same prompt came back. The prompt names its holders from the Restart Manager probe, but the close itself went through powershell.exe: Get-Process -Name, a strict "path starts with $INSTDIR" filter, then Process.CloseMainWindow() and a WScript.Shell AppActivate. Every step of that is silent. A process whose path cannot be read is filtered out, a process .NET finds no main window for makes CloseMainWindow() return false without posting anything, and AppActivate is refused for a background caller - so an attempted close, a missed close and a close request the client ignored all looked the same in the log: nothing. Ask the probe instead. installer-holder-probe --close-holders closes exactly the processes Restart Manager just named as holding a file of the version being replaced, so no executable path has to be guessed and a third party application that merely shares the image name is never in the set. Per instance it restores and brings the top-level window forward (AttachThreadInput, so the client's own "really quit?" confirmation is on screen rather than behind the installer), posts WM_CLOSE to the main window, and waits up to 30 s for the process to exit. Its report names every holder with what happened to it, including the ones left to the user, and goes into the installer log as one line. That also removes the separate PowerShell polling loop that used to wait for the close: the probe holds a handle on the process it asked, so the purge that follows measures a settled directory. Oracle and Servatrice are still never closed, and nothing is force-stopped. --------- Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com> Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
858b270fa9
|
[Installer] Retry locked DLL purge before aborting upgrade (#7657)
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 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 Desktop / Prune caches (push) Waiting to run
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
* [Installer] Retry locked DLL purge before aborting upgrade * [Installer] Keep the purge verdict when the portable purge runs $IsUpgrade was read from IsExistingCockatriceInstall after the portable purge, which deletes the very markers that function looks for. A purge whose only survivors were non-Qt DLLs (abseil_dll.dll, libssl-3-x64.dll) or content under Plugins therefore reported "not an install", $IsUpgrade became 0 and the abort was skipped - the mixed DLL set the retry exists to prevent. Measure the directory before either purge block, where it still describes the directory as it arrived, and let RetryDllPurge's own verdict decide the abort. A portable install into a directory without portable.dat is neither purge block, so $StaleRuntimeAfterPurge was never assigned there and the abort could not fire either. Track whether a purge ran and measure that directory with HasStaleRuntimeFiles instead, which is what every upgrade did before the retry. --------- Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com> Co-authored-by: BruebachL <44814898+BruebachL@users.noreply.github.com> Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
bbc6003f0a
|
[CrashReporting] Name the files and processes behind a failed installer purge (#7590)
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 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (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 Desktop / Prune caches (push) Waiting to run
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
* [CrashReporting] Log which files and processes blocked the installer purge A purge that fails aborts the upgrade with a message that can only guess at the cause, and the log only listed which DLLs survived. Record what Windows reports for each survivor and which processes still have the install directory mapped, then repeat the holder list in the abort message. The diagnostics are additive and run only on the abort path: they open each survivor for DELETE access and close it again without deleting, so a failed upgrade can never end up half-removed. The purge itself is unchanged. * [CrashReporting] Name the holders of a failed purge with Restart Manager The process-and-module scan could only name a holder whose image lives in the install directory or that loaded one of its DLLs, so the case this diagnostic exists for - an antivirus scanner holding a plain handle - was reported as "no holder named, only a code". Reaching the Restart Manager from the NSIS script would have meant marshalling RM_PROCESS_INFO through System::Call as a table of hand-computed offsets, which cannot be checked without a Windows toolchain, and that is why it was dropped when this diagnostics was written. Move the call into tools/installer-holder-probe: a small executable the same Windows build compiles, linking nothing from this project so it still starts when the installed runtime is what the purge could not replace. It rides inside the installer and is extracted to $PLUGINSDIR only when the purge has already failed, so the successful path is unchanged and nothing new is installed. .ci/compile.sh runs it against a file held with FileShare.None on the Windows runners, so the lookup is exercised on real Windows instead of only compiled. * [CrashReporting] Fix the holder probe check and cap the holder line The probe block ran after the package steps, where the working directory is already the build dir, so its $BUILD_DIR-prefixed paths named build/build and the PowerShell file could not be written. Move it ahead of install/package, where the build dir is still the cwd, and name both files relative to it. Guard it on MAKE_NO_CLIENT, since the probe is only built with the client. Cap the rendered holder line at HOLDER_LINE_CHAR_MAX: the count limit alone does not keep long service names inside the NSIS string that also carries the abort text and the log path. Whole entries are kept and the rest counted, with the first holder always shown. Also run cmake-format on the probe's CMakeLists so the lint job accepts it. * [CrashReporting] Report a read-only purge survivor and bound the probe check --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
c6f682f9cf
|
[CrashReporting] Offer to send the installer log on abort (#7524)
* [CrashReporting] Installer log send offer on abort (inc 4) DSN parsed to CPACK_SENTRY_* (scheme-aware for http self-hosts) before include(CPack); new SendInstallerLog.ps1.in builds a byte-accurate Sentry envelope (128 KiB tail cap, X-Sentry-Auth, exit 0/1) with the abort reason read from the log file itself, so the NSIS side needs no string functions and quoting stays trivial. AbortInstall asks consent (attended installs only, silent path untouched, abort never swallowed). Empty/malformed DSN disables the offer; no secrets in repo. * [CrashReporting] Embed installer-log sender via a relative NSIS path The AbortInstall hook embedded SendInstallerLog.ps1 through the absolute CPACK_SENTRY_PS1_PATH, which carries CMake's forward slashes (D:/a/...). makensis does not treat a forward-slash drive path as absolute, so it resolved it relative to project.nsi, found nothing and aborted packaging on the Windows jobs of every branch in the stack. Embed it with the same '..\..\..\' relative path that already resolves NSIS.definitions.nsh: both are configure_file'd into the build root, three levels up from where CPack places project.nsi. Drop the now-unused CPACK_SENTRY_PS1_PATH cache variable. * [CrashReporting] Drop the installer /CRASHTEST hook, tidy the log sender Remove the /CRASHTEST switch and the matching COCKATRICE_SENTRY_DSN environment override on the installer abort path. With no deliberate in-installer trigger left, a package built without a baked DSN must never offer to post a user's log to an endpoint chosen by whatever happens to be in the environment, so the prompt is now gated on CPACK_SENTRY_ENABLED alone. This also retires the comment that claimed /S /R /CRASHTEST reaches the prompt: the switch was evaluated before /R set ReinstallMode, so that form never showed it. Alongside: correct the CPACK_SENTRY_RELEASE comment (PROJECT_VERSION keeps its "-<label>" on Development tags where CPACK_PACKAGE_VERSION does not), delete the unused CPACK_SENTRY_HOST/CPACK_SENTRY_PROJECT_ID cache vars, document the DSN regex's path-prefix fallback, log a declined send, keep the uploaded installer log free of helper-injected newlines, and bound the post with -TimeoutSec/-NonInteractive. * [CrashReporting] Derive the installer DSN vars as plain variables Review nit: every CPACK_SENTRY_* value is recomputed from COCKATRICE_SENTRY_DSN on each configure, so CACHE ... FORCE bought nothing. Set empty defaults once, override them in the match branch, and drop the duplicated else() copies. Still CPACK_-prefixed, so include(CPack) still snapshots them into CPackConfig.cmake. * [CrashReporting] Gate the installer log send at build time Review nit: test CPACK_SENTRY_ENABLED with the NSIS preprocessor instead of copying it into $SentryEnabled and branching at runtime, so a package built without a DSN compiles the prompt out rather than carrying dead code. The configure_file that produces the helper is gated the same way, so a build without reporting neither references nor embeds it. * [CrashReporting] Disclose the Windows user name in the send prompt Review nit: say that the log can include the Windows user name (a real part of what gets uploaded) and drop the "No account needed." clause, which answered a question the prompt never raised. * [CrashReporting] Show progress while the installer log is sent Review nit: DetailPrint a line before nsExec runs, so the details pane says something while the send is in flight instead of staying blank for up to the 60 s /TIMEOUT. * [CrashReporting] Rename the Sentry event local in the installer helper Review nit: $Event is a PowerShell automatic variable, so name the local $sentryEvent instead. Also drop $firstLine: Select-String returns the matching line alone, so the reason is already one physical line and the extra split added nothing. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
9f59f5c495
|
[CrashReporting] Breakpad capture, session detection and symbol upload (#7523)
* [CrashReporting] Breakpad everywhere, session detection, symbol CI (inc 3) Breakpad on all platforms (no crashpad packaging); remove the Windows MiniDumpWithFullMemory filter and the installer WER LocalDumps full-dump registration. QLockFile per-process session tracking replaces the Windows-only .dmp scan, placed after parser.process and the instance hand-off so help/version exits and handed-off secondaries never leak a lock. CI: Release-only DSN bake from SENTRY_DSN secret, tag-only sentry-cli symbol upload (org cockatrice, project native). Tests: 533/533 ctest green, format clean, code gate PASS. * [CrashReporting] Enable http_retry, harden symbol upload, fix NSIS context Sets sentry_options_set_http_retry so the cache_keep-held crash envelope in Ask mode is written with a retry filename and actually flushes on giveConsent() instead of sitting in <db>/cache until pruning. Adds continue-on-error to both sentry-cli install steps so a Sentry registry outage or broken Git Bash install cannot block release assets. Cleans the legacy WER CrashDumps folder under SetShellVarContext current (under all it resolves to ProgramData, not the user's AppData) in installer and uninstaller; registry delete works in either context. * [CrashReporting] Extract the startup crash-report flow from main() per review * [CrashReporting] Break up the CrashReporter class doc per review * [CrashReporting] Drop the invalid --release from the symbol upload `debug-files upload` never accepted `--release` (checked against the pinned sentry-cli 3.8.0: the subcommand declares --org/--project and a set of type/id/scan options, and execute() reads no release; the only global flags are --url/--header/--auth-token/--log-level/--quiet/--allow-failure). Clap rejects the unknown flag before any upload happens, so every tag build has been shipping without symbols, with continue-on-error keeping the release pipeline green. Release association is also the wrong mechanism: debug information files are matched to events by debug ID via the event's debug_meta. The Sentry release of a native event comes from the event itself (COCKATRICE_SENTRY_RELEASE), not from the DIF upload. Remove the flag and the now-unused SENTRY_RELEASE env from both invocations (Linux, and Windows/macOS). The symbol upload stays continue-on-error (a Sentry outage must not block the release assets); revisit once it has proven itself on a tag. * [CrashReporting] Split debug information out of packaged release binaries -g is required so the CI symbol upload has DWARF to send, but it was left in the shipped binaries too: the Linux Release client is 563 MiB, 552 MiB of it debug info, and every distribution channel pays for that. Keep the build tree unchanged (CI uploads symbols from build/) and strip only the packaged copy. A CPack pre-build script extracts each ELF's debug info to COCKATRICE_DEBUG_SYMBOL_DIR and then runs `strip --strip-debug --strip-unneeded`. .eh_frame is allocatable and survives, so unwinding is unaffected, and the build ID is identical in the stripped binary and the sidecar. Measured on a local Release build (GCC -O2 -g, WITH_CRASH_REPORTING=ON), bytes: binary build tree packaged before packaged after .debug sidecar cockatrice 590,722,560 590,722,560 28,086,480 579,623,576 oracle 53,432,608 53,432,608 1,649,888 52,075,344 tar.gz of both trees: 289,964,521 -> 19,018,058 (~15x smaller). Only ELF (Linux/BSD) is handled: Windows already links /DEBUG with separate PDBs, and macOS extracts a dSYM from the bundle. Opt-out via COCKATRICE_SPLIT_DEBUG_SYMBOLS; skipped with a warning when objcopy/strip are missing. CPACK_PRE_BUILD_SCRIPTS (not CPACK_INSTALL_SCRIPTS, which runs before staging) invokes it, and the script refuses to run unless CPACK_TEMPORARY_DIRECTORY is a real CPack staging tree so a missing variable can never turn its glob into a filesystem-wide walk. * [CrashReporting] Upload Windows PDBs from where Ninja actually puts them The Windows branch of the symbol upload looked in build/<target>/Release, which is the Visual Studio generator's layout. The Windows leg uses Ninja, which is single-config: linker PDBs land in build/<target>/, next to the binaries — the same paths the "[Windows] Upload PDBs" artifact step below globs with if-no-files-found: error. So paths was always empty on Windows, the step printed "No symbol paths found, skipping symbol upload" and exited 0: a silent no-op behind continue-on-error. Every Windows build would then report "A required debug information file was missing" for cockatrice.exe with a PDB-style debug id (GUID-age), exactly as if no upload step existed at all. Use the artifact step's globs. Also turn the empty-path case into a failure in both symbol steps: a silent skip is indistinguishable from a successful upload (which is how the wrong path went unnoticed), and continue-on-error already keeps a broken upload from failing the release. Verified by extracting both steps' run blocks and executing the Windows branch against a Ninja-style tree: it now passes the three linker PDBs to sentry-cli (and not the CMakeFiles/*.pdb intermediates), and exits non-zero with an ::error:: annotation when there is nothing to upload. * [CrashReporting] Archive PDBs on tag builds so a failed upload is recoverable The "[Windows] Upload PDBs" artifact step skipped tag builds (github.ref_type != 'tag'), so for a release the symbols existed only inside the Sentry upload — which is continue-on-error. A failed upload therefore lost the ability to symbolicate that release permanently: no rebuild produces the same PDB, since the debug id is a per-build GUID+age. That is how every Windows crash reported so far became unreadable. Archive the PDBs for tag builds as well, and order the step after "Upload to release" so a missing PDB can never block a release from being published. Retention stays at the repository default; nothing consumes the artifact automatically, it is the manual recovery path (gh run download <run> -n <name>-PDBs, then sentry-cli debug-files upload). Verified by re-parsing the workflow and asserting the step order (release -> PDBs -> attest) and the broadened condition. * [CrashReporting] Upload the Windows executables with their PDBs Sentry needs two things per image on Windows: symbolication and stack unwinding. The PDB covers symbolication, but for a 64-bit PE the Call Frame Information (.pdata/.xdata) lives in the executable and is not copied into the PDB, so uploading PDBs alone leaves the image at "Stack Unwinding: Missing". That is exactly what the event's Images panel showed for cockatrice.exe, while Sentry's own Microsoft symbol server resolved KERNELBASE.dll and ucrtbase.dll from the same dump. sentry-cli also needs the pair in the same invocation: only then does it associate the PE's code identifier with the PDB's debug identifier, otherwise "the identifiers diverge and Sentry might not be able to resolve the correct file for symbolication" (docs: Debug Identifiers). Do the same for macOS, where the dSYM carries the debug information but the Mach-O carries the unwind information, and include the executables in the symbol artifact so a re-upload still has the pair to offer. Verified by extracting the step and running both branches against stub harnesses: Windows passes the three exe/pdb pairs in a single sentry-cli invocation and still fails loudly on an empty tree; macOS passes the dSYM and the Mach-O together. * [CrashReporting] Upload the shipped Qt runtime DLLs too Qt ships no PDBs with its MSVC SDK, so every Qt frame in a crash report resolved to nothing: an image with neither PDB nor PE has no unwind information (on 64-bit Windows the Call Frame Information lives in the PE and is not copied into the PDB) and no symbol source to fall back on. The list is required-runtime.txt, the manifest .ci/compile.sh already asserts the installer against, so what gets uploaded is exactly what an install carries. Qt frames now get unwinding plus export-table names; line numbers still need Qt's debug information files, which the SDK omits. The guards are not cosmetic: the step runs under `bash -eo pipefail`, where a sed over a missing file or an unmatched ls glob aborts the whole step, and continue-on-error makes that look identical to a successful upload. * [CrashReporting] Upload only the binaries, archive symbols only on tags The Linux leg handed sentry-cli whole build directories: 471 candidate files, of which 7 were object files that can never match a stack frame. Only the three linked binaries carry the build-ids Sentry looks up; the packaged copy is split and stripped but keeps that same build-id. The Windows symbol archive ran on every packaging build even though only tag builds upload to Sentry, so a PR build paid for an artifact nothing could ever consume. Tags keep it as the durable copy for a failed upload. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
6c660d6b21
|
[Installer][Client] Back up user data before old uninstallers can delete it (#7503)
* [Installer][Client] Back up user data before old uninstallers can delete it * [Installer][Client] Fix stale backup resurrection, batch session backups, extend coverage NSIS: clear stale %TEMP% backup and only arm restore when something was copied, directory-existence checks, back up loose .dec decks, drop downloaded pics but keep pics\CUSTOM. Helper: single session dir per run (ms timestamps), same-basename disambiguation, partial copies still report their location. * [Installer][Client] Fix review findings: throttle backups, NSIS old dir, portable messaging * [Installer][Client] Fix review findings: backup markers, NSIS relocate, size cap * [Installer][Client] Fix review findings: sounds, session coverage, NSIS wording Client: back up data/sounds in portable mode and mention it in the warning; reuse a recent session only when complete and covering the current sources (source manifest), estimate size only for fresh copies, normalize dedup via isInsideDir. Helper: source manifest per session with sessionCoversSources(), clear out-params on all failure paths. NSIS: document defensive portable branch, merge backup over surviving data/ to repair partial deletes, copy (not move) wording in relocate, note .txt exclusion in deck notice. * [Installer][Client] Fix review findings: stale backup, empty reuse, relocate, portable warning NSIS: wipe TEMP backup once per run to avoid resurrecting deleted files, check data dir existence in relocate so dir-only trees are moved. Client: never reuse a recent session for an empty source set, only fall back to a covering session on copy failure, warn fresh portable installs instead of staying silent. * [Installer][Client] Fix review findings: usable session reuse, loose decks, NSIS pics, symlinks * [Installer][Client] Fix portable backup exclusions in offender merge * [Client] Split at-risk data warning into focused helpers Ricky's nit on the 200+ line warnAboutDataPathsInProgramDir: extract collection (portable/installed), backup, message building and dialog into named helpers. No behavior change, all tr() strings untouched. * [Client] Read path settings inside isPortableBackupExcluded * [Client] Make stateless at-risk helpers static * [Client] Extract at-risk data warning out of MainWindow * [Installer][Client] Fix review findings: subdir backup, portable warning, backup API NSIS: - Back up unknown top-level install subdirs (e.g. <installdir>\decks), not just loose deck files, so the 3.1.1 -> new upgrade no longer loses data folders the old RMDir /r deletes before the client ever runs. Payload dirs (plugins/translations/sounds/themes) stay in place to avoid restoring stale DLLs; restore + relocate handle the new backup set and the one-time notice covers both decks and folders. - Restart the %TEMP% backup when the 32/64-bit passes point at different dirs instead of merging (which restored one dir's files into the other); document that only the last pass relocates and the %TEMP% copy is not a durable second copy. Client: - Drop the portable at-risk warning: portable installs never shipped an uninstaller and portable updates preserve data/, so every portable user got a spurious dialog plus daily host-machine copies. - AtRiskDataWarning is now a plain class with Q_DECLARE_TR_FUNCTIONS instead of QObject+Q_OBJECT; logging category renamed to at_risk_data_warning; MainWindow wrapper inlined into startupConfigCheck. - Lower the synchronous startup backup cap 500 -> 200 MB. Utility/tests: - Remove test-only backupAtRiskPath()/findRecentBackupSession() and the no-marker/no-manifest legacy paths; isBackupSessionComplete() now requires the complete marker; tests updated to the plural API. * [Client] Restore collectInstalledAtRiskPaths per review |
||
|
|
f83a61979f
|
[Installer] Uninstaller deletes only the installed payload, never the whole tree (#7472)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
1e1f79db0d
|
[Windows] Wait for uninstaller, rework .dll bundling, add logging (#7407)
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
* [Windows] Purge every runtime DLL on upgrade instead of a named few A user updating from 3.0.2 to 3.1 got "abseil_dll.dll is either not designed to run on Windows or it contains an error" and could not start the client. abseil_dll.dll comes from vcpkg, not Qt. It rides into the install directory on the "*.dll" glob in cockatrice/CMakeLists.txt, alongside libprotobuf.dll, liblzma.dll and z.dll, and none of those were named in the upgrade purge. So on an upgrade the old uninstaller's RMDir /r skipped whichever of them was locked, the purge did not look for them, HasStaleRuntimeFiles did not look for them either, and the installer reported success - having written the new executables next to the previous version's protobuf runtime. Windows then refused to load it. The mask list was also already wrong where it existed: it matches zlib*.dll while vcpkg ships z.dll. Delete every top-level DLL instead of naming library families. The payload is written after the purge, so a DLL the new build still needs is put straight back; what the purge has to guarantee is only that nothing from the previous build is left to be loaded next to the new executables. Enumerated families cannot do that - every transitive dependency that arrives between two releases is one more name to forget, and the three previous rounds of this work each reasoned about Qt because that is what the reports named at the time. HasStaleRuntimeFiles collapses to the matching *.dll probe, so the abort fires on any survivor rather than only on the families it was told about. IsExistingCockatriceInstall stays on Qt6*.dll on purpose: it decides whether a directory is ours at all, and a folder that merely holds some DLLs of its own must not be purged. Qt6*.dll is enough even when only the protobuf runtime survived, because the whole Qt set is mapped by the same process that maps abseil_dll.dll and so is locked or free with it. * [CI] Assert the whole Windows runtime, not just the Qt modules required-qt-runtime.txt listed only the Qt modules COCKATRICE_QT_MODULES names, so it could not see any other runtime go missing. The three previous rounds of installer work all reasoned about Qt*.dll because that was all the build knew how to ask about, and the one runtime that actually broke the reported upgrade - abseil_dll.dll - was outside the set entirely. - Rename it to required-runtime.txt and take the third party runtime names from the vcpkg bin directory the app-local copies come from, so a dependency bump cannot leave the list behind and a versioned name such as libcrypto-3-x64.dll becoming libcrypto-4-x64.dll cannot either - Union the three applications' module lists rather than the client's alone: Servatrice contributes Qt6Sql.dll, servatrice.exe is installed next to cockatrice.exe and cannot start without it, and nothing asserted it - Add Core to the client's module list. It is the one module nothing can do without and it only ever arrived transitively, so Qt6Core.dll was shipped and never checked - the new unknown-runtime check flagged it on the first run - Fail the build on a missing runtime, as before, and warn on a runtime nothing requires. Only the test framework is a hard failure there: "no shipped executable links gtest" is a fact, whereas a runtime missing from the manifest may be one the manifest failed to derive, and a check that has never run before should not be able to block a release The warning is meant to be read. Qt6::Quick pulls in Qml, QmlModels, QmlWorkerScript, QuickBasic, QuickTemplates2 and OpenGL transitively and none are named, so the manifest is known to be a partial list; the first Windows run reports the real one, which is evidence rather than a guess about what Qt links. Fold that back in when it lands. * [CI] Stop deriving the required runtime from the vcpkg bin directory The first Windows run of this check failed, and the seven files it named as missing are not runtime at all: gmock.dll, gmock_main.dll, legacy.dll, libprotobuf-lite.dll, libprotoc.dll and pthreadVC3/VCE3/VSE3.dll. None of them is linked by any shipped executable, so a correct package leaves them out. vcpkg_installed/x64-windows/bin is everything vcpkg installed, not everything the applications import. The protoc compiler, the GoogleMock library, a port tool and three CMake FindThreads probes sit beside the genuine runtimes, and a directory listing cannot tell them apart. Asserting it fails the build over a package that is right - which is what happened, and it is the same mistake as the one that produced this bug report: reasoning about a list of names instead of about the build. The real source of truth is the three executables' import tables, which fixup_bundle already walks to decide what to copy. Until CMake can read that result, the Qt half is what can be derived honestly, from the same module lists the targets are built against. So assert that, and report the rest rather than require it: - Drop the vcpkg bin glob, its gtest filter and the vcpkg-runtime-known marker. All three existed only to support the glob. - Report top level DLLs the manifest does not name without failing. These are expected: protobuf, Abseil, OpenSSL, liblzma, ffmpeg and the Qt debug DLLs all arrive on the build output glob because nothing in the build declares them by name. The list is a report of what the package carries, not a verdict, and it is how the missing Qt6Qml/Quick/OpenGL entries above became visible. - Report a leaked gtest or gmock instead of failing on it. "No shipped executable links this" is a fact, but a check that has never passed on a real package has not earned the right to block a release, and the first run of an enforcing version is exactly when its own wrong assumptions show up. - Skip DLLs under a subdirectory, which are platform plugins rather than imported runtimes, and the NSIS plugins, which extract to $PLUGINSDIR and so reach the comparison looking like payload. Verified against the paths 7-Zip actually listed in the failing package: the check passes on it, still fails when Qt6Sql.dll or the qwindows plugin is removed, and still reports gtest.dll and gmock.dll if either is added. The purge in cmake/NSIS.template.in covers the runtimes the manifest does not name. That fix is independent of any list and is what makes the reported update failure go away. * [Installer] Fix the /R upgrade aborting before it does anything WaitForLauncherToExit read the wrong registers. System.dll writes its .rN outputs to $N and its .RN outputs to $RN, but the wait used p.r0/i.r1 while testing $R1. $R1 therefore stayed empty, and the "did not exit" test compared "" with "0" - true every time. Every in-app update passes /PID=, so every upgrade hit AbortInstall before writing anything; because /R also sets silent, the override just vanished with exit code 1 and no message. The bug came in with #7372 and is inherited by this branch, not introduced by it. Use the uppercase .RN forms, and treat a null or plugin-failed OpenProcess as the launcher already being gone - the case the old ${Errors} check never caught, because System.dll does not set the NSIS error flag from the callee's return value. Also restore the abort dialog for the in-app updater: AbortInstall and the two inline warnings gated their message box on ${IfNot} ${Silent}, which is false for a /R upgrade, so any abort on that path was equally silent. The unattended case is a /S install without /R, not /R, so show the box for $ReinstallMode = 1 or an interactive install. The stale-runtime abort now goes through AbortInstall so that decision lives in one place. * [Installer] Log every upgrade step to %TEMP%\Cockatrice-installer.log * [Installer] Actually append to the upgrade log (FileSeek END) * [Installer] Wait for the old uninstaller before replacing files An NSIS uninstaller copies itself to %TEMP% and runs from there so that it can delete its own directory; the process the installer started exits immediately. A plain ExecWait therefore returns before the uninstall has run, and the temp copy's "RMDir /r $INSTDIR" then executes concurrently with the install section, deleting files the payload just wrote. That is why an upgrade could leave Qt6Core/Gui/Multimedia and the other alphabetically-first DLLs missing while the tail of the directory survived: a recursive delete marching from the front while the final listing ran. Pass _?=$INSTDIR to the old uninstaller so it runs in place and ExecWait actually blocks until the uninstall completes. _?= must be the last argument and unquoted, even for paths with spaces. Applied to both the silent /R path (AutoUninstallIfNeeded) and the interactive path (componentsPagePre). * [Installer] Tighten the wording of the upgrade comments Drop the dash-heavy phrasing from the comments this PR adds, and say plainly in the _?= comment that ExecWait returns before the old uninstaller has run and the temp copy then deletes the payload the section is still writing. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
ecd955bc60
|
[Installer] Wait on the launching process so auto-updates cannot write over a mixed Qt runtime (#7372)
* [Installer] Abort instead of installing over leftover runtime files
The silent upgrade could leave a mix of old and new Qt DLLs next to the new
executables, which is what makes the next start fail with "The procedure entry
point X could not be located in the dynamic link library ...Qt6QuickWidgets.dll"
or "The code execution cannot proceed because Qt6Multimedia.dll was not found".
The check that was supposed to catch this probed only Qt6Core.dll and
Qt6Network.dll, so it was blind to the two files the reports name. It also only
warned and then installed anyway, so a caught problem was still compounded.
- Scan every file the purge targets with FindFirst, which takes the same
wildcards the Delete/RMDir calls use, instead of two hardcoded names
- Widen IsExistingCockatriceInstall from Qt6Core.dll to Qt6*.dll so a directory
left holding only some Qt modules is still recognised as an install
- Gate the scan on that, so a first-time install into a directory that happens to
hold unrelated Qt5 DLLs is not turned into an abort
- Abort with a non-zero exit code instead of warning, so the new payload is
never written next to files of the previous version
- Skip the message box on an unattended /S install, which would block on it
forever, matching the two existing abort sites
* [Installer] Cancel a silent upgrade when the running-instance guard fails
The running instance guard is what stands between a locked install directory
and the mixed Qt DLL set that breaks the next start, but it failed open in two
places and let the install carry on anyway:
- powershell.exe could not be started: the check warned and returned
- the process lookup answered "could not determine": WaitForAppToClose returned
on $R0 != 1, so "unknown" was treated exactly like "not running"
On a silent upgrade there is nobody at the keyboard to close the applications in
response to that warning, which is precisely the state in which the launcher was
still holding its Qt DLLs mapped.
- Treat "could not determine" as its own state rather than folding it into "not
running", in all three places WaitForAppToClose inspects the result
- Cancel a silent upgrade in both cases; keep an interactive install on the
existing warn-and-continue path, since refusing there would leave an installer
that can never complete and the stale-runtime check still applies
- Route every abort through one AbortInstall so the dialog-vs-silent decision
lives in a single place, and stop the two pre-existing warning dialogs that a
/S install without /R would have blocked on forever
* [Installer] Wait on the process that launched the update installer
The in-app updater starts the installer and only then quits itself, so the
installer has to decide whether the launching process is still alive while it is
running. It did that by polling powershell.exe for an image name under the
install directory, which is a proxy for the real question - is the process that
started me still holding its Qt DLLs mapped - and a proxy that can answer "not
running" or "could not determine" while the process is very much alive. Either
answer let the install carry on into the mixed Qt DLL set.
Take /PID= from the command line and wait on that process handle directly:
- OpenProcess with PROCESS_SYNCHRONIZE only, which is all that is needed to wait
and the least likely to be refused for an unrelated reason
- WaitForSingleObject for the same 60 s the name based check allows, so a client
slow to flush its card database is not squeezed
- Treat an unopenable handle as "already exited", which is what it means for a
dead process
- Cancel the upgrade if the launcher has not exited, rather than replacing files
it is still using
Run it before EnsureAppsNotRunning, not after: it needs no powershell.exe, so it
is the one check that still works on a machine where the process lookup cannot
run, and it is what lets that case keep upgrading instead of being cancelled by
what was only ever a proxy for it. Keep an older client, which passes no /PID=,
on the existing path.
An older installer ignores /PID=, and /D= stays the last parameter either way.
* [Client] Pass our process id to the update installer
The updater starts the installer and only then quits itself, so the installer
begins replacing files while this process still has every Qt runtime DLL mapped,
and a mapped DLL cannot be deleted. It has to work out that it must wait, and
until now all it had was its own image name to go on.
Pass /PID= so it can wait on this process directly instead of guessing, and
cancel the update rather than replace files this process is still using. /D=
stays the last parameter, which NSIS requires.
* [CI] Fail the build when the installer is missing a Qt runtime DLL
The Windows payload is assembled by globbing every *.dll out of the build output
directories, so nothing but luck decides whether a module the client links
statically - Qt6Multimedia.dll, Qt6QuickWidgets.dll - reaches the installer at
all. A module that is left out is invisible until a user installs an update and
the client then refuses to start, with "The code execution cannot proceed because
Qt6Multimedia.dll was not found" or a missing procedure entry point in a
Qt6QuickWidgets.dll carried over from the previous version.
Derive the required list from SERVATRICE_QT_MODULES, COCKATRICE_QT_MODULES and
ORACLE_QT_MODULES, the same module lists the targets are actually built against,
so it cannot drift from what the binaries need, and write it next to the CMake
cache as required-qt-runtime.txt. Add the platform plugin, without which the
client cannot start at all.
Assert it in the existing 7-Zip pass that already rejects build-tree artifacts,
comparing exact relative paths so a same-named file in the wrong directory does
not satisfy a requirement, and normalizing the archive's own separators so it
does not matter which one NSIS chose. Missing manifest, missing entry or missing
7-Zip-independent failures all stop the build rather than warn, which is the
whole point: the previous check already failed hard for artifacts, and a warning
here is what let a broken package ship.
* [Client] Point the Windows qt.conf translations path at the real one
The Windows qt.conf carried Translations = Resources/translations, copied from
the macOS bundle block, but the Windows install has no Resources directory: the
.qm files are installed to COCKATRICE_WIN32_QM_INSTALL_DIR, which is
translations, and main.cpp looks for them at applicationDirPath() +
"/translations". Leave the macOS value alone, which is correct for it.
* [Installer] Close the /? help string so the installer compiles again
The last line of the continued MessageBox string ended in $\n\" instead of $\n".
NSIS reads \" as an escaped quote, so the string never terminated and makensis
rejected the script, which took the CPack packaging step down with it. The
Windows build failed on "Problem running NSIS command" before ever reaching the
new required-runtime check.
* [Installer] Give FindFirst the two arguments it takes
FindFirst takes the pattern and the output variable and leaves the search
handle in $0. There is no handle argument, and FindNext taking only a variable
is the giveaway: the handle has to live somewhere implicit, and it is $0.
The call passed three arguments, so makensis rejected the script and the CPack
packaging step failed with "Problem running NSIS command" before ever reaching
the required-runtime check.
This was not only a compile error. The handle variable was never populated, so
the stale-runtime check that commit
|
||
|
|
28d684f8aa
|
[Windows] Close running instances and purge stale runtime DLLs during update (#7308)
* [Windows] Close running instances and purge stale runtime DLLs during update
* [Windows] Scope running-instance handling to install dir and fix NSIS build
- Drop !include nsExec.nsh: nsExec is a plugin DLL, not a header, so
makensis aborts before NSIS can build the installer.
- Match processes by image name and executable path under $INSTDIR via
PowerShell, then close (WM_CLOSE) and force-stop only those PIDs, so an
unrelated oracle.exe (Oracle DB) is never killed on a silent /R update.
- Gate the stale-runtime-DLL purge on $INSTDIR\cockatrice.exe existing, so
a first-time install can't recursively delete an unrelated Plugins
directory.
* [Windows] Make process detection rely on an explicit PowerShell exit code
* [Windows] Fix exit-code detection in the uninstaller process check
* [Windows] Pass the install dir to PowerShell without quoting pitfalls
* [Windows] Fail closed when a process path is unreadable
* [Windows] Only pre-check running instances for /R updates
* [Windows] Wait on a wall-clock 60 s deadline instead of a loop counter
* [Windows] Verify the force close and abort if the process survived
* [Windows] Defer silent updates instead of force-killing the client
* [Windows] Purge OpenSSL 3 DLLs on portable upgrades and broaden the stale-DLL guard
* [Windows] Replace the GetTickCount macro with a direct System::Call
* [Windows] Only close running instances whose path is readable
CloseMatchingApps reused the fail-closed detection filter, which counts a
process whose Path cannot be read (Access denied) as a match. The installer
runs elevated, so CloseMainWindow() would then be delivered to an unrelated
oracle.exe / servatrice.exe belonging to some other program, silently closing
a third party application during a Cockatrice update.
The set of processes we act on now has to be the strict one: a readable path
under $INSTDIR. Unreadable paths are simply not ours.
* [Windows] Match running instances by a readable path under the install dir
The Path read in the Where-Object filter raises "Access is denied" for
processes the installer cannot open, and both branches of the try/catch
turned that into a match. Together with mapping every non-zero nsExec result
to "running" this made the guard a hard stop rather than a fail-closed one: a
machine with an Oracle database service (or a servatrice service) running as
SYSTEM always has a process named oracle / servatrice whose path cannot be
read, so every silent update aborted after 60 s and every interactive install
re-asked for Retry indefinitely.
A process whose path cannot be read is not one of ours - the updater is
launched by the application it updates and therefore runs in the same user
session, and an interactive install is elevated and can read the paths of the
user's own processes. Both the detection and the close path now require a
readable path under $INSTDIR, which is what the comment above the helpers
already claimed.
* [Windows] Distinguish "could not determine" from "definitely running"
Mapping every non-zero nsExec result to "running" folds nsExec's "error"
return (powershell.exe could not be started at all - removed, blocked by
policy, refused by antivirus) into "Cockatrice is running". That state can
never clear: closing applications does not help, so silent installs aborted
after the full 60 s wait and interactive installs could not get past the
Retry/Cancel prompt.
IsAppRunning now reports three states (0 not running, 1 running, 2 could not
determine) and a new IsProcessLookupAvailable preflight checks that
powershell.exe can run at all. A broken PowerShell is reported to the user
once - with a message box, since a silent install would otherwise show
nothing - and the install continues instead of being blocked forever. Only a
definite "running" is now waited for.
* [Windows] Force-stop the console applications after the grace period
Servatrice and Oracle are console applications, so CloseMainWindow()
returns false and does nothing for them. Every silent update therefore
waited the full 60 s and then aborted, and every interactive install looped
on the Retry prompt, for any user running Servatrice out of the install
directory.
They now get a second shutdown path: once the grace period is over they are
force-stopped by PID with the same strict install-dir filter, since they hold
no unsaved user data. Cockatrice keeps the graceful-only treatment - a
force-kill there could destroy an unsaved deck - and still aborts instead.
* [Windows] Tell the user when a silent install or uninstall is aborted
On the /R path the installer runs with SetSilent and SetAutoClose, so the
DetailPrint before Abort went nowhere: the user clicks Help -> Check for
Client Updates, the download finishes, Cockatrice closes its window, and
nothing else happens - no update and no error, because the client launches the
installer detached and never looks at its exit code.
Every silent abort path now shows a message box telling the user which
application is still open and that they should close it and run the
installer again. The same applies to the silent uninstall, where the only
output is a log file nobody reads.
* [Windows] Treat leftover runtime files as an install marker for the purge
The guard on the stale-DLL purge only looked for the three executables and
uninstall.exe, so it skipped precisely the case the purge exists for: a
previous failed update whose uninstaller removed what it could and silently
skipped the Qt DLLs that a running instance had locked. None of the four
markers is left in that directory, and the stale DLLs then survive into the
new install.
A leftover Qt6Core.dll (what we ship today) or the qt.conf written by a
portable install now count as an install marker as well.
* [Windows] Run the section instance check before any file is deleted
Call EnsureAppsNotRunning sat after the portable-upgrade block, which had
already deleted the old cockatrice.exe, Qt*.dll and plugins directory. An
interactive install ("Portable mode" pointing at an existing portable folder)
has $ReinstallMode = 0, so the .onInit guard is skipped and locked files were
silently skipped - exactly the mixed-Qt-DLL state this change is about.
The check now runs right after SetOutPath, before the first Delete/RMDir in
the section.
* [Windows] Guard the old install before the old uninstaller deletes files
The interactive upgrade path removes the previous installation long before the
install section: componentsPagePre runs the old UninstallString on the
components page, i.e. before the directory page and before any call of ours
can check anything. Every uninstaller shipped before this change has no
un.EnsureAppsNotRunning, so for exactly the versions affected by #1576 the old
files were deleted while cockatrice.exe was still running and the new
installer had no say in it.
componentsPagePre now runs the same check before both ExecWait calls. At that
point $INSTDIR is still only the default, not the directory the old install
lives in, so the new EnsureOldInstallNotRunning guards the location recorded
in the registry instead. The helpers now work on a $GuardDir the caller
picks; the normal entry points keep seeding it with $INSTDIR.
* [Windows] Do not abort the section check past the point of no return
"nothing has been replaced yet, so aborting just defers the update" only holds
for the guard in .onInit. On the /R path AutoUninstallIfNeeded has already run
the old uninstaller, which RMDir /r's the whole install directory, and the
purge below it deletes the runtime files - so an Abort from the section would
leave the user with the old version uninstalled and the new one not installed
at all, which is strictly worse than the bug this change fixes.
The ordering now guarantees the check already passed: .onInit guards the /R
path before the uninstall, so the section only repeats it where that is not
already true, i.e. for interactive installs, where nothing of this installer
has been deleted yet and an Abort is the correct outcome.
As a safety net for whatever the purge could not remove (a still locked file,
or a check that could not run at all), HasStaleRuntimeFiles reports surviving
runtime files with a message box - also on a silent update, where there is no
other way to let the user know the install came out incomplete.
* [Windows] Only block the in-app updater with a message box
The message box on the silent abort would also fire for a plain /S install,
which is exactly the unattended case: a package manager (or a CI job) running
the installer with a leftover Cockatrice process would now hang forever on a
dialog nobody can see instead of aborting.
The box is therefore limited to /R, the path the in-app updater takes, where
the user just clicked Help -> Check for Client Updates, the client launches
the installer detached and never inspects its exit code, and the window is
about to disappear - without a message, the update would silently not happen.
Every silent abort also sets an error level now, so an unattended caller can
see that the run failed instead of having to read the log.
The silent uninstaller reports through log and exit code for the same reason:
it is run unattended by the /R updater itself (QuietUninstallString) and by
package managers. The installer that invoked it tells the user about the
incomplete result itself.
* [Windows] Treat an unset guard directory as undeterminable
$GuardDir is set by the three entry points into the check, but it is now what
decides whether a process counts as one of ours, and an empty string would
match every process that merely shares an image name - the same false positive
the strict path filter exists to avoid. Report it as "could not determine"
instead of running the check, so a future caller that forgets to set it warns
the user rather than closing or killing a third party process.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
|
||
|
|
329a6ba94c
|
[Installer] Restore cockatrice:// and .cod registration in the Windows NSIS installer (#7355)
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: user.email <Bruebach.Lukas@bdosecurity.de> |
||
|
|
a5e94d8a4f
|
[Build] Keep Windows installs free of build-tree artifacts (#7316)
* [Build] Keep Windows installs free of build-tree artifacts
Several Windows packaging gaps could leak Visual Studio CMake build
output into the installed application or the NSIS installer:
- The per-app DLL sweep used
${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${CMAKE_BUILD_TYPE}, which is
empty on multi-config generators, collapsing the recursive
DIRECTORY install into the whole build tree (containing *.dir,
*_autogen, .qt, .qsb, x64, ...). Point it at the real per-config
output with $<TARGET_FILE_DIR:...> and exclude build artifacts.
- install(FILES ${OPENSSL_INCLUDE_DIRS} ...) tried to install OpenSSL
include directories as files. CMake refuses this
("install FILES given directory"); it only slipped through CI
because the vcpkg OpenSSL config leaves the variable empty. Remove it;
fixup_bundle already ships the OpenSSL runtime DLLs.
- The NSIS uninstaller only deleted *.exe/*.dll and a few known files,
so build-tree leftovers survived an uninstall/reinstall cycle. Wipe
the whole directory tree instead.
- Add a Windows CI gate that lists the packaged installer with 7-Zip
and fails the build if any build-tree artifact path is found.
* Update .ci/compile.sh
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
* [Build] Rework Windows installer artifact exclusions per review
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
|
||
|
|
c4316c40e9
|
[CI] Remove Qt5 (#7071)
* [CI] Remove Qt5 Took 10 minutes Took 9 minutes * Revert CI failure and fix up comments Took 4 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
59d90db3c7
|
Define Cockatrice as an editor/handler for .cod files and cockatrice:// protocol on all platforms (#6775)
* [Application] Add single instance guard and mime types. Took 2 hours 39 minutes Took 18 minutes Took 5 minutes Took 12 seconds Took 11 seconds * Rework Took 30 minutes Took 50 seconds * Only enforce single instance if launched with arguments. Took 5 minutes * Prototype intents Took 53 minutes Took 6 seconds * Connect/disconnect and join game/room intents. Took 3 hours 14 minutes Took 2 seconds Took 15 seconds * Fix include. Took 1 minute Took 23 seconds Took 2 seconds * Mac handling. Took 10 minutes Took 12 seconds Took 3 minutes * Lint. Took 3 minutes * Rebase. Took 3 minutes Took 17 seconds * Implement UrlSchemeEventFilter Took 10 minutes Took 7 seconds * Qt Moc Took 3 minutes * Modern PList. Took 21 minutes Took 1 minute * Debug output. Took 6 minutes Took 19 minutes * Watch file:// prefix. Took 15 minutes Took 7 seconds * Better handler. Took 6 minutes * Don't store reference in member Took 5 minutes * Move impl to cpp, fix lifetime issues. Took 11 minutes Took 2 minutes * Better single-instance handoff, url intent harded copy game link context-menu Polish for installers Took 35 minutes Took 8 seconds --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
dfbe944c31
|
[App][Windows][NSIS] Use QProcess::setNativeArguments on Windows, properly order portable detection (#6989)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
682ac4ed0c
|
Add -R option in Windows NSIS script for silent upgrade (#6818)
* Add -R option in Windows NSIS script Took 23 minutes * Small fix. Took 3 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
14f1925edc
|
Add icon to exe (#6655) | ||
|
|
7f1d891e26
|
[Deprecation] Remove DBConverter from sources. (#6431)
Took 10 minutes Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> |
||
|
|
d3a1538af3
|
Fix Windows Crash Reporter (#5493)
* Fix Windows Crash Reporter * Fix NSIS template |
||
|
|
2ff99f12d8
|
Require Admin Permissions to install Cockatrice Windows (#5383)
Fix #5382 |
||
|
|
2bd06ff0fd
|
Add CrashDump support for Windows OS (#5282) | ||
|
|
872c92a244
|
CI: Use windows-2022 image with Visual Studio 17 2022 (#4999)
Co-authored-by: ZeldaZach <zahalpern+github@gmail.com> |
||
|
|
03e109ef12 | wait > sleep (#3806) | ||
|
|
ada13f6578 |
Card Database converter (#3694)
* Database converter * Fix win compilation and NSIS installer * Maybe fix windows again * Re-fix windows |
||
|
|
638ee1af4a
|
This should fix appveyor compilation (#3434) | ||
|
|
c12c3c071f
|
NSIS: Show hint when user installs 32bit Cockatrice on 64bit Windows (#3331)
* hint for 32bit * final wording |
||
|
|
d37d73017f
|
nsis: add sleep timer to ensure unlocking of vc_redist (#3325)
* add 3s sleep + log hint |
||
|
|
b61c07a205
|
Check for both 32 and 64bit cockatrice when uninstalling (#3323) | ||
|
|
dc727cb620
|
NSIS: misc fixes [wip] (#3300)
NSIS: misc fixes |
||
|
|
42d15c39a0 | change vcredist install option from "/quiet" to "/passive" (#3175) | ||
|
|
3a11aebb21
|
windows/appveyor compilation updates (#3020) | ||
|
|
04d84f9d9a | repo cleanup (#2826) | ||
|
|
1366e5970e |
Portable mode for windows (#2794)
* Portable mode for windows * fix start menu in portable mode * Make gcc an happy puppy * Clean old installation if we are installing over an old portable mode installation * Default to Desktop\CockatricePortable * Settings dialog fixes * wording |
||
|
|
3356b2ede7 |
Change version in about window and installer name (#2570)
* Change version in about window and installer name * Change version in about window and installer name * Cmake get version updates * dev snapshot format changed * alignment changes |
||
|
|
1e3fb6c6e1 |
Rework "paths" settings loading and card database loading
* main.cpp: removed path checking and db loading * card database: merge card loading methods into a single one * settings cache: take care of returning safe paths for decks, replays, etc.. * main window: if db loading fails (eg. first run), propose to run oracle NSIS: propose to run cockatrice instead of oracle Rework card database loading * Move carddatabase-related method out of deckeditor tab * Load cards in another thread and render them progressively * Optimize database reload after enabled sets change Fix deck editor column width * removed the noCard hack. * getCard() no more creates cards instead of just returning existing ones * Fix the “edit tokens” dialog. * PictureLoader: avoid trying to download twice the same card * PictureLoader: correct return of card background * AbstractCardItem: avoid recalculating card color at every paint Use a different file to save custom tokens Misc required improvements * Use nullptr; * Refactor CardInfoWidget to use CardInfoPicture and CardInfoText instead of duplicating code; * Added CardInfo::getColorChar() * Fixed some potential crashes * removed dead code related to CardInfoWidget * Don't require a restart after adding a new custom sets file * Bump CMake requirements to 3.1 |
||
|
|
74fef04a57 | NSIS: Add components sections on uninstall. | ||
|
|
76422854a4 | NSIS: default to INSTDIR\sounds directory | ||
|
|
da5677080b |
NSIS installer improvements; refs #876
* uninstaller: delete zlib1.dll (first issue in #876) * require the user to uninstall the old version before installing the new one * use project-version as branding name (fourth issue in #876) |
||
|
|
1b09b76eee |
Remove --debug-ouput on start menu shortcut
Remove argument that if added by default with out having the proper configuration defined during compiling would cause cockatrice to not open properly. |
||
|
|
40610997c2 |
Corrected NSIS template quoting
There are extra quotes introduced into line #19 that causes the NSIS installer creation to fail. |