WIP: feat(launcher): Send report, to the website's inbox #42
No reviewers
Labels
No labels
area/build
area/install
area/saves
area/ui
area/updates
good first issue
gpu/amd
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
platform/linux
platform/steam-deck
platform/windows
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/LongshotLauncher!42
Loading…
Reference in a new issue
No description provided.
Delete branch "TrashPanda/LongshotLauncher:feat/problem-reports"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
A crash or a stopped build should reach the developers without the player finding a log and filling in an issue. This adds Send report: the report, the player's consent, and sending it to the website's inbox.
Depends on #48 (the problems in plain words and the crash found at the next start), split out of this PR. This branch is #48's commit plus one commit for the reporting; review that commit (
ff7228dc) until #48 merges.What changed
Core/Problems.cs,CrashCheckand the problem page.)Core/ProblemReport.cswrites the report under the issue form's headings, as the game's Bug report does, with the log's end redacted by the game's own rule (home folder and user name out). The crash signature from #48 groups the reports.Core/ReportSender.csPOSTs one https report to the website's inbox,https://longshot.magicbean.dev/report(Longshot #251), which files it as an issue on Longshot, one issue per problem. If a send fails, Send stays available to try again, with Copy report for the issue form.LONGSHOT_REPORT_URLpoints it at another inbox (a website preview answers a dry run). Format and behaviour:docs/launcher-reports.md.tests/launcher_core/ProblemChecks.csadds the redaction, the report, the settings and the inbox rules.Ported from Hookshot's
feat/launcher-problem-reports, where it was written against the launcher in Hookshot's tree. Names, log files (longshot_log.txt) and the issue form are Longshot's.Testing
Tested: on Linux (CachyOS, .NET 10.0.115), at
ff7228dc(rebased on current main):python -I tests/test_launcher_core.py --strict --longshot <Longshot main>: 702 checks pass.dotnet build LongshotLauncher.slnx --configuration Release -p:RestoreLockedMode=true(CI's command): 0 errors, and 1 warning thatmainhas too.Not tested: the report and problem pages in the running launcher, since this port has not been run yet; Windows; a real send (the inbox has no token until #251 is merged and set up; #251 has its own end-to-end run against git.magicbean.dev).
Needs other testers: a Windows run with a crashed build's log in the build folder, expecting the problem page with Send report at the next start, once.
Screenshots
To come: the problem page with the report, and the problem screen, from a run of this branch.
Reviewer notes
The inbox is Longshot #251. Merge and set it up before a release carries this; until then a send fails as "could not be sent" and Copy report still works.
A code-only review: I read the diff against main and the Longshot sources it matches on. I didn't build it or run the tests.
What's good
DefaultInboxis null, the inbox has to be https, and the test proves an http inbox is refused before anything is sent.Redactfollows the game's own rule (vk_bug_report.cpp). Reading it,Z:\home\<name>\…from a Proton log on a native Linux launcher loses the name through the user-name pass, and non-ASCII names count as name characters.Must change
The log goes out with guest memory in it.
report_crash(host/mkw_glue.c) logs the 31 registers and 9 rows of the memory that x0, x1 and x19 point to (CRASH: [x0+0x000] …). That is the game's memory, which counts as made from game files, andMakesends the whole 192 KB tail as it is. With a chosen microphone, theaudio in:lines also carry the device's name. Filter the lines asnx2/crash_report.pyin MagicBeanDev/Longshot#141 does: no register or memory rows, no device names. Add a test with those lines in the fixture."No mesh shaders" is a warning, not a crash.
vk_main.cpplogsvk: this GPU has no mesh shaders: …and the game carries on ("Said once, on its own thread, and the game goes on"). Each run writes a new log, so a player on such a card is shown "Your graphics card can't draw this game" after every session, even though nothing crashed. Report it only together with a real end (CRASH, FATAL or device lost), or leave it out ofFromLog.The plain words hide the real error, and some of them are wrong. For a recognised kind,
BodydropsIt said: {Why}. Two of the matches go wrong on make_game.py's own messages:make_game.py:1182) is shown as "Your antivirus removed some of Longshot's files".make_game.py:576) is shown as "These game files are a different version".Keep make_game.py's message on screen under the plain words. Match whole phrases ("Longshot supports build", "Your antivirus most likely removed them") rather than "antivirus" or "not supported", and add those two messages to the tests as cases that must not match.
This sets up a second report pipeline beside the one in use. MagicBeanDev/Longshot#81 already has a relay and a crash-bot that groups reports by signature. MagicBeanDev/Longshot#141 posts to it with a format that is already settled (version, release flag, OS, CPU and RAM, GPU, renderer, map, the CRASH lines, the 150 lines before them) and keeps the addresses in
nx2/addresses.ini. Its review suggested the crash window move to the launcher. This PR instead says no inbox exists, anddocs/launcher-reports.mddefines a new JSON format (format/kind/text) and asks for a new service. Send the relay's format, or agree the format on #81 first, so there is one inbox and one signature, not two."Always send" with no inbox, and sending from the constructor. With Always chosen,
ReportViewModel's constructor callsSend(). At the next start that runs insideMainViewModel's constructor, beforeHostis set (MainWindow.axaml.cs:42). With no inbox, which is every player today,Copy()copies nothing and opens no form, yet the toast still says "Copied the report". OnceHostis set, the same path overwrites the clipboard and opens a browser without a click. Offer Always only when an inbox is set up, and start an automatic send once the page is on screen.Should change
FATAL:aren't found (hos_fatal,vk: FATAL:fromreplay_fatal). The game's own Bug report countsFATAL:,CRASH:anddevice lostas trouble (vk_bug_report.cpp:177).Service.Gpus.FirstOrDefault()is always empty on Windows (GpuVendorsreturns nothing there), so Windows players never get the driver link. On Linux it is the firstcard*, which on a laptop may not be the card the game ran on. Take the maker from the log'svk: <name>, Vulkan …line.win32_posix.cGetModuleFileNameA), so a space in the path (…/My Games/longshot+0x10) makes(\S+)stop at…/My. The signature becomes0xc0000005 my, which groups wrongly and sends a folder name's fragment unredacted insignature. Take the text after the last separator up to+0x….Tailkeeps only the last 192 KB, so thelongshot vX (source …)line and thevk: …GPU line are gone and the report says "not in the log". Keep the head as well, ascopy_logdoes.report.Valuethrows (the log deleted or unreadable between the check and Send),sendingstays true, the button stays disabled and the status stays "Sending the report..." for good. The task is discarded, so nobody sees the exception. Wrap it in try/finally and turn the failure into a status.Problemsbut no screen shows them; onlyDriverPagehas a button. The problem screen still says "Then build again: what was built so far is kept" under every problem, including a wrong game version. Wire them, or drop them and the doc's "Each has a fix button".launcher/Core/…).ProblemChecksheader promises "an inbox of our own on localhost", which the test doesn't have.Z:\home\<name>line, a non-ASCII user name, a log with only the mesh warning (must be no problem), the register and memory rows left out, and the two make_game.py messages above.SeenCrashLogsonly moves on a crash, and this runs on the UI thread before the window shows. Recording the newest log's time per folder even when it's clean avoids that.@ -530,0 +538,4 @@public string Head => Problem.Kind == ProblemKind.Unknown ? "make_game.py stopped" : Problem.Headline;public string Body => Problem.Kind == ProblemKind.Unknown? $"Building {build.Job.Shown} stopped in {Stage}, {Percent}% of the way through. It said: {Why}": $"{Problem.Detail} (Building {build.Job.Shown} stopped in {Stage}, {Percent}% of the way through.)";For a recognised kind, make_game.py's own message (
Why) disappears from the screen. Keep it under the plain words so a wrong match can't hide the real error.@ -42,0 +48,4 @@var s = Service.Settings;foreach (var b in Service.Registry.Builds){var found = CrashCheck.Find(b.Folder, s.SeenCrashLogs.GetValueOrDefault(b.Folder), Service.Gpus.FirstOrDefault());Gpusis always empty on Windows (SystemInfo.GpuVendorsreturns nothing there), so Windows players never get the driver link. On a Linux laptop the firstcard*may not be the card the game ran on. Take the maker from the log'svk: <name>, Vulkan …line.@ -0,0 +29,4 @@NeverCommand = new Command(() => { Choose(ReportConsent.Never); Close(); });CopyCommand = new Command(() => _ = Copy());CloseCommand = new Command(Close);if (Consent == ReportConsent.Always) _ = Send();At the next start this runs inside MainViewModel's constructor, before
Hostis set (MainWindow.axaml.cs:42). With no inbox,Copy()copies nothing and opens no form, but the toast still says "Copied the report". Once Host is set, it would overwrite the clipboard and open a browser without a click. Offer Always only when an inbox is set up, and send once the page is shown.@ -0,0 +79,4 @@main.Host?.Open(ReportSender.IssueForm);return;}sending = true;If
report.Valuethrows below (the log gone or unreadable),sendingstays true and the page shows "Sending the report..." for good; the discarded task hides the exception. Use try/finally and show the failure as the status.@ -0,0 +15,4 @@<!-- (the first time: the player's choice, which the settings keep) --><Button Content="Never send" Command="{Binding NeverCommand}" IsVisible="{Binding AskConsent}"/><Button Content="Just this once" Command="{Binding OnceCommand}" IsVisible="{Binding AskConsent}"/><Button Classes="primary" Content="Always send" Command="{Binding AlwaysCommand}" IsVisible="{Binding AskConsent}"/>The first choice is made with the preview closed, though the doc and the class comment say the text is shown first. Open "What gets sent" by default while AskConsent is true, and don't offer Always while there is no inbox.
@ -0,0 +60,4 @@}/// The end of a file, at most max bytes, from the start of a linepublic static string Tail(string path, int max = LogTailBytes)Only the end is kept, so in a long run the
longshot vX (source …)line and thevk: …GPU line drop out, and the report says "not in the log". Keep the head too, ascopy_login vk_bug_report.cpp does.@ -0,0 +97,4 @@t.Append("### Longshot version\n\n").Append(version).Append("\n\n");t.Append("### Graphics card and driver version\n\n").Append(GpuLine(lines) ?? "not in the log").Append("\n\n");t.Append("### CPU, RAM and operating system\n\n").Append(system).Append("\n\n");if (logText != null) t.Append("### longshot_log.txt\n\n```\n").Append(R(logText)).Append("\n```\n\n");The whole tail goes out as it is, including
report_crash's register rows and the guest-memory rows (CRASH: [x0+0x000] …, host/mkw_glue.c), which are the game's memory, plusaudio in:lines that name a chosen microphone. Filter these likenx2/crash_report.pyin MagicBeanDev/Longshot#141 does, and add the lines to the test fixture.@ -0,0 +50,4 @@{var w = why ?? "";bool Has(string s) => w.Contains(s, StringComparison.OrdinalIgnoreCase);if (Has("antivirus") || Has("quarantine"))make_game.py:1182 ("Could not clear … A file in it is in use … wait for your antivirus to finish") lands here and is shown as "Your antivirus removed some of Longshot's files" with Repair. Match the removal message itself ("Your antivirus most likely removed them").
@ -0,0 +55,4 @@"This is a false alarm: Longshot's build tools aren't signed, so some antivirus programs flag them. Restore the " +"files from your antivirus's quarantine (Windows Security: Protection history), or allow Longshot's folder, then " +"repair and build again.", [new Fix(FixKind.Repair, "Repair and build again")]);if (Has("Longshot supports build") || Has("isn't supported") || Has("not supported"))"not supported" also matches make_game.py:576 ("symbolic links and reparse points are not supported"), which then shows as "These game files are a different version". "Longshot supports build" alone is enough.
@ -0,0 +75,4 @@"Something went wrong that Longshot doesn't recognise. Sending a report helps us fix it.", [BuildAgain]);}[GeneratedRegex(@"CRASH: exception (0x[0-9a-fA-F]+) \((read|write)[^)]*\) at (\S+)")]On Linux the module is a full realpath (win32_posix.c
GetModuleFileNameA). With a space in the path (…/My Games/longshot+0x10),\S+stops at…/My, so the signature becomes0xc0000005 my: it groups wrongly and sends a folder name's fragment unredacted. Take the name after the last separator up to+0x….@ -0,0 +93,4 @@}if (line.Contains("device lost", StringComparison.OrdinalIgnoreCase) ||line.Contains("VK_ERROR_DEVICE_LOST", StringComparison.Ordinal)) lost = true;if (line.Contains("this GPU has no mesh shaders", StringComparison.Ordinal)) noMesh = true;This line is a warning: vk_main.cpp says "Said once, on its own thread, and the game goes on." Each run writes a new log, so a player on such a card gets "can't draw this game" after every session without a crash. Report it only with a real end (CRASH, FATAL or device lost).
@ -0,0 +103,4 @@return new Problem(ProblemKind.GpuLost, "The graphics driver stopped responding","This is often fixed by the latest graphics driver. If it keeps happening, sending a report helps us find the cause.",[DriverFix(vendor)], Signature(crash, module, "device lost"));if (crash == null) return null;Runs that ended with
FATAL:(hos_fatal,vk: FATAL:fromreplay_fatal) aren't found. The game's Bug report counts FATAL, CRASH and device lost (vk_bug_report.cpp:177).@ -0,0 +17,4 @@public static class ReportSender{/// The inbox; LONGSHOT_REPORT_URL over it (for trying an inbox out). https onlypublic const string? DefaultInbox = null;MagicBeanDev/Longshot#81 already has a relay and crash-bot, and MagicBeanDev/Longshot#141 posts to it in a settled format, with the address in
nx2/addresses.ini. Send that format to that relay, or settle a format on #81 first, rather than defining a second inbox and JSON contract.@ -0,0 +2,4 @@When a build stops or the game closes unexpectedly, the launcher explains what happened in plain words, offers the fix itknows, and can send a report to the developers with one button, so a player needs no forge account and no idea where thelog is. This page is the contract between the launcher (`launcher/Core/Problems.cs`, `ProblemReport.cs`,These are Hookshot's paths (
launcher/Core/…); here they areCore/…. Other places in this doc don't match the code either: "Ask each time" (the window says "Just this once"), "the home screen then says so" (it is a page of its own), "Each has a fix button" (none are wired), and the picture tick (there isn't one).Pushed
d523d468: reports now go to the website's inbox,https://longshot.magicbean.dev/report(ReportSender.DefaultInbox). The inbox is Longshot #251: it files each report as an issue on Longshot, one issue per problem (the same crash from another PC becomes a comment).docs/launcher-reports.mdnow describes the inbox as it is, and a report that fails to send says to try Send again or copy it for the issue form.Order: merge and set up #251 (the bot token as a Worker secret) before a release carries this. Until then a send gets "could not be sent" and the copy path, as before.
Tested:
python -I tests/test_launcher_core.py --strict(663 checks pass, with the new inbox checks), and the app builds (Release). Not tested: a real send. The inbox has no token until #251 is set up; its own end-to-end run against git.magicbean.dev is in #251.A second code-only review, of
d523d468: I read the diff against main, the earlier review, and Longshot MagicBeanDev/Longshot#251 (the inbox). I didn't build it or run the tests.The new commit turns sending on:
DefaultInboxis nowhttps://longshot.magicbean.dev/report, and each report becomes a public issue on Longshot. None of the earlier points were addressed in the code, so the unfiltered log would now leave every player's PC and be posted in public.The earlier points
CRASH: [x0+…]rows, the microphone's name): open, and now it matters for real (see Must change 1).Bodystill dropsWhyfor a known kind, and"antivirus"and"not supported"still match the two make_game.py messages.Hostpath no longer runs. But the send still starts insideMainViewModel's constructor, and after a failed Always send there is no Send button (see Should change).FATAL:isn't detected: open.report.Valueis still outside a try/finally (ReportViewModel.cs:86).launcher/Core/…pathsProblemChecksheader's "inbox of our own on localhost"Z:\home, non-ASCII names, a log with only the mesh warning, filtered register and memory rows, or the two make_game.py messages.Also still open: clean logs are read again at every start, on the UI thread.
The new endpoint
longshot.magicbean.devis the team's website Worker (its custom domain inwebsite/wrangler.jsonc), not a personal domain. Good.Inbox()refuses http, and .NET's HttpClient doesn't follow an https→http redirect. Good.Must change
DefaultInboxset, "Always send" and "Send report" post the whole 192 KB tail, guest-memory rows included, as a public Longshot issue. That breaks the rule that nothing made from game files leaves the PC. KeepDefaultInboxnull until earlier point 1 is fixed and tested. It also needs MagicBeanDev/Longshot#251 merged and its token set first, or a release sends every report into a 404 or 503.--playand--launch. It sends to the #81 relay. This PR offers the same crash at the launcher's next start and files it somewhere else, with a different signature. A player who says yes to both is asked twice, and we get two records that don't link. Agree on one path on MagicBeanDev/Longshot#81 before either merges. For example, the launcher could skip game crashes and keep build problems, or post game crashes to the relay in #141's format.Should change
AskEachis false when the consent is Always, so only Close and Copy report show. Show Send whenever a send failed.docs/launcher-reports.mdpromises the same.kind/bugissues next to real ones. #141 sends a release flag, and the crash bot marks such reports "[not a release]". Send the same, and let the inbox label or drop those reports.```fence. A log line that holds```ends the block, and the rest renders as Markdown on a public issue. Use a fence longer than any run of backticks in the text.413the player can do nothing about./reportand open public issues. 20 a minute in total is up to 28,800 issues a day, and changing the signature opens a new issue every time. A daily cap, or a cap on new issues per hour (with comments still allowed), would bound it.REPORTS_OFFis a good kill switch.urlis never used. A link to the issue after "Sent as report #123" would let the player follow it.Merging origin/main (
0e6e1d7) gives no textual conflicts. The overlap with main is the meaning of the mesh-shader warning (earlier point 2).@ -0,0 +86,4 @@var r = await ReportSender.Send(report.Value, inbox);sending = false;sent = r.Sent;Status = r.Sent ? r.Message : r.Message + " Try Send again later, or Copy report and paste it into the issue form.";With Always chosen,
AskEachis false, so after a failed send there is no Send button, only Close and Copy report, yet the status says "Try Send again later". The doc promises "Send to try again" too. Show Send whenever a send failed.@ -0,0 +42,4 @@<TextBlock Text="{Binding Detail}" Foreground="{StaticResource InkSoft}" TextWrapping="Wrap" MaxWidth="680" HorizontalAlignment="Left"/><Button HorizontalAlignment="Left" Content="{Binding Fix.Label}" Command="{Binding FixCommand}" IsVisible="{Binding Fix, Converter={x:Static ObjectConverters.IsNotNull}}"/><TextBlock Foreground="{StaticResource InkSoft}" TextWrapping="Wrap" MaxWidth="680" HorizontalAlignment="Left" IsVisible="{Binding AskConsent}"Text="Can we send a report about this to the developers? It holds your Longshot version, your graphics card, system and the game's log, with your user name taken out. No saves, no game files. You choose once; the launcher's settings can change it."/>The report becomes a public issue on git.magicbean.dev, with the log, but this says "send a report about this to the developers". Tell the player it is posted publicly before they choose Always or Just this once.
@ -92,0 +93,4 @@<StackPanel VerticalAlignment="Center"><TextBlock Text="Problem reports" FontWeight="SemiBold"/><TextBlock FontSize="12" Foreground="{StaticResource InkSoft}" TextWrapping="Wrap" MaxWidth="460"Text="When a build stops or the game closes unexpectedly: the version, your graphics card and system, and the log, with your user name taken out. No saves or game files."/>Same as on the report page: say that reports are posted publicly as issues on git.magicbean.dev.
@ -0,0 +94,4 @@t.Append("### What happened\n\n").Append(problem.Headline);if (problem.Signature != null) t.Append(" (").Append(problem.Signature).Append(')');t.Append("\n\nSent from the Longshot Launcher").Append(log != null ? " after the game closed unexpectedly" : " after a build stopped").Append(".\n\n");t.Append("### Longshot version\n\n").Append(version).Append("\n\n");There's no release flag. A local or modified build would file a public
kind/bugissue that looks like a release's. MagicBeanDev/Longshot#141 sends one, and the #81 crash bot marks such reports "[not a release]".@ -0,0 +97,4 @@t.Append("### Longshot version\n\n").Append(version).Append("\n\n");t.Append("### Graphics card and driver version\n\n").Append(GpuLine(lines) ?? "not in the log").Append("\n\n");t.Append("### CPU, RAM and operating system\n\n").Append(system).Append("\n\n");if (logText != null) t.Append("### longshot_log.txt\n\n```\n").Append(R(logText)).Append("\n```\n\n");On a public issue a plain
```fence isn't safe: a log line that holds```ends the block, and the rest renders as Markdown. Use a fence longer than any run of backticks in the text. Cap the text at the inbox's 256 KB here too, so the inbox can't answer413to something the player can't shorten.@ -0,0 +96,4 @@if (line.Contains("this GPU has no mesh shaders", StringComparison.Ordinal)) noMesh = true;}if (noMesh)return new Problem(ProblemKind.NoMeshShaders, "Your graphics card can't draw this game",Main now says the same thing: #44 reports a card without mesh shaders as "using the slower compute fallback". After merging, the same player would see the requirements check say the game runs and this page say "can't draw this game".
@ -0,0 +18,4 @@public static class ReportSender{/// The inbox; LONGSHOT_REPORT_URL over it (for trying an inbox out). https onlypublic const string? DefaultInbox = "https://longshot.magicbean.dev/report";This turns sending on for every player while the log still goes out unfiltered: the guest-memory
CRASH: [x0+…]rows and the microphone's name (the comment on ProblemReport.cs). The inbox in MagicBeanDev/Longshot#251 posts the report as a public issue on Longshot. Keep this null until the filter and its test are in, and until #251 is merged and has its token. Without that, a release sends every report into a 404 or 503.d523d46805ff7228dc4bWIP: feat(launcher): problems in plain words, crashes found at the next start, and Send reportto WIP: feat(launcher): Send report, to the website's inboxSplit at HumanBean's request. The problems in plain words, the crash found at the next start and its page are now #48. This branch is #48 plus one commit for the reporting (
ff7228dc), rebased on current main. I force-pushed the branch; the final code is the same as before plus main's new commits. Merge #48 first.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.