Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Bug report
description: Tell us what went wrong with VX Bridge.
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for helping improve VX Bridge. Check the [FAQ](https://github.com/devigned/vx-bridge/blob/main/docs/FAQ.md) and [existing issues](https://github.com/devigned/vx-bridge/issues) first.

A diagnostic ZIP is helpful, but **optional**. Use **Save diagnostic report...** in the bridge menu, or **VX Bridge Diagnostics** in Start if the bridge will not open. Nothing is sent automatically. Review the ZIP before attaching it below.

Do not attach raw vendor/MSI logs, captures, shot/player/device/license/account details, credentials, or unredacted screenshots. You can submit this form even if export fails. See the [reporting guide](https://github.com/devigned/vx-bridge/blob/main/docs/REPORTING.md).
- type: textarea
id: problem
attributes:
label: What went wrong?
description: Describe the problem, visible error, and how it affects you.
placeholder: "For example: the selected game stays unavailable after..."
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps, expected result, and observed result
description: What did you do? What should have happened? What happened instead? If it happens only sometimes, say so.
validations:
required: true
- type: textarea
id: versions
attributes:
label: Versions and selected game
description: Bridge release, Windows version/build, ProTee Labs version, game/OpenAPI version, and selected game (Infinite Tees, GSPro OpenAPI, or Standby). Write "unknown" for anything you cannot find. Do not include account or device details.
placeholder: |
Bridge:
Windows:
Labs:
Game / OpenAPI:
Selected game:
- type: textarea
id: context
attributes:
label: When and how often?
description: Approximate time and time zone; fresh install, upgrade, startup, normal play, switching, or Quit; frequency; last known working bridge version; and whether it prevents use, loses shots, or is a minor inconvenience.
- type: textarea
id: evidence
attributes:
label: Optional diagnostic ZIP or redacted screenshots
description: Drag a reviewed ZIP or redacted screenshot here. If export failed, describe the save error instead. Do not attach raw vendor logs or captures.
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Reporting and privacy guide
url: https://github.com/devigned/vx-bridge/blob/main/docs/REPORTING.md
about: What to include, how to save a report, and what to keep private.
32 changes: 32 additions & 0 deletions .github/ISSUE_TEMPLATE/feature.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Feature request
description: Describe a task VX Bridge could make easier.
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Check [existing requests](https://github.com/devigned/vx-bridge/issues) first. Tell us the task and desired outcome; you do not need to design the implementation.
- type: textarea
id: task
attributes:
label: What are you trying to do?
description: Describe the user task or problem, and who it affects. Keep private account, device, player, and license details out.
validations:
required: true
- type: textarea
id: outcome
attributes:
label: What outcome would help?
description: Explain what success would look like and why it matters.
validations:
required: true
- type: textarea
id: workaround
attributes:
label: Current workaround
description: How do you handle this today? Write "none" if there is no workaround.
- type: textarea
id: approach
attributes:
label: Optional proposed approach
description: Suggestions, alternatives, or examples are welcome. A particular implementation is not required.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,13 @@ replayed.

Use the [installation guide](docs/INSTALL.md), [everyday guide](docs/USAGE.md) or
[FAQ](docs/FAQ.md) for more detail. The [technical manual](docs/technical/README.md)
covers implementation and testing. Read [Contributing](contributors.md) before
reporting a bug or submitting a change.
covers implementation and testing. Use the [reporting guide](docs/REPORTING.md) to
[report a bug](https://github.com/devigned/vx-bridge/issues/new?template=bug.yml)
or [request a feature](https://github.com/devigned/vx-bridge/issues/new?template=feature.yml).
An optional local diagnostic ZIP can be saved from the bridge menu or
**VX Bridge Diagnostics** in Start, even when the host will not start.
Nothing is uploaded automatically. Read [Contributing](contributors.md)
before submitting a code change.

Original work uses the [MIT license](LICENSE).
[Third-party notices](THIRD_PARTY_NOTICES.txt) cover dependencies and reference
Expand Down
73 changes: 68 additions & 5 deletions cmd/vxbridge/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,43 @@ func buildIdentity() string {
return identity
}

func diagnosticBuild() diagnostics.Build {
return diagnostics.Build{Version: version, Release: releaseVersion, Revision: buildRevision,
Go: runtime.Version(), OS: runtime.GOOS, Architecture: runtime.GOARCH}
}

func run(args []string, out, stderr io.Writer) (exit int) {
flags := flag.NewFlagSet("vxbridge", flag.ContinueOnError)
flags.SetOutput(stderr)
status := flags.Bool("status", false, "same-user local host status (run as vxbridge.exe)")
quit := flags.Bool("quit", false, "request guarded Quit; never forces attached Labs closed")
showVersion := flags.Bool("version", false, "show build identity without starting a host")
reportPath := flags.String("diagnostics", "", "save a local diagnostic ZIP to a new absolute file; no host start or upload")
reportDialog := flags.Bool("diagnostics-dialog", false, "native Save diagnostic report dialog; no host start or upload")
desktopMode := flags.Bool("desktop", defaultDesktop == "true", "native tray and local logs; no console required")
port := flags.Int("target-port", 999, "Infinite Tees IPv4 loopback port (host mode only)")
if err := flags.Parse(args); err != nil || flags.NArg() != 0 {
return 2
}
if *port < 1 || *port > 65535 || *port == 12321 || (*status && *quit) ||
(*showVersion && (*status || *quit)) || ((*status || *quit) && *port != 999) {
modes := 0
for _, selected := range []bool{*status, *quit, *showVersion, *reportPath != "", *reportDialog} {
if selected {
modes++
}
}
explicitHostOption := false
emptyDiagnosticMode := false
flags.Visit(func(f *flag.Flag) {
if f.Name == "desktop" || f.Name == "target-port" {
explicitHostOption = true
}
if (f.Name == "diagnostics" && *reportPath == "") || (f.Name == "diagnostics-dialog" && !*reportDialog) {
emptyDiagnosticMode = true
}
})
if *port < 1 || *port > 65535 || *port == 12321 || modes > 1 || emptyDiagnosticMode ||
(((*reportPath != "" || *reportDialog) && explicitHostOption) ||
((*status || *quit) && *port != 999)) {
fmt.Fprintln(stderr, "vxbridge: invalid or conflicting options; target port must be 1..65535 excluding 12321.")
return 2
}
Expand All @@ -90,6 +114,35 @@ func run(args []string, out, stderr io.Writer) (exit int) {
return 1
}
compatibilityImage := strings.EqualFold(filepath.Base(image), "GSPconnect.exe")
if *reportPath != "" || *reportDialog {
if compatibilityImage {
fmt.Fprintln(stderr, "vxbridge: use distinct vxbridge.exe for diagnostics; no second GSPconnect identity.")
return 2
}
if *reportDialog {
_, err := desktop.ReportDialog(diagnosticBuild())
if err != nil {
fmt.Fprintln(stderr, "vxbridge: diagnostic save failed; nothing was sent. Report the bug without a ZIP if needed.")
return 1
}
return 0
}
ctx, cancel := context.WithTimeout(context.Background(), diagnostics.CollectionTimeout+2*time.Second)
defer cancel()
report, err := diagnostics.NewCollector(diagnosticBuild()).Collect(ctx)
if err == nil {
err = diagnostics.Save(ctx, *reportPath, report)
}
if err != nil {
fmt.Fprintln(stderr, "vxbridge: diagnostic export failed; choose a new absolute local file. Nothing was sent; an incomplete file may remain.")
return 1
}
if _, err := fmt.Fprintf(out, "Diagnostic report saved locally (partial=%t). Nothing was sent; review the ZIP before attaching it.\n", report.Manifest.Partial); err != nil {
fmt.Fprintln(stderr, "vxbridge: report saved, but the completion message could not be printed.")
return 1
}
return 0
}
if *status || *quit {
if compatibilityImage {
fmt.Fprintln(stderr, "vxbridge: use distinct vxbridge.exe for control; no second GSPconnect identity.")
Expand Down Expand Up @@ -258,7 +311,7 @@ func host(listener net.Listener, pipe net.Listener, target string, interrupts <-
} else {
directory = ""
}
logs = diagnostics.New(directory, diagnostics.Build{Version: version, Revision: buildRevision, Go: runtime.Version()})
logs = diagnostics.New(directory, diagnosticBuild())
var manager *startup.Manager
backend, startupError := startup.CurrentUser()
if startupError == nil {
Expand All @@ -268,6 +321,7 @@ func host(listener net.Listener, pipe net.Listener, target string, interrupts <-
logs.WriteEvent(bridge.Event{At: time.Now().UTC(), Reason: "startup_unavailable_use_status"})
}
app = desktop.New(controller, logs, manager, directory, preferences)
app.Reporting(diagnosticBuild())
tray = app.Tray()
defer func() {
flush, stop := context.WithTimeout(context.Background(), 500*time.Millisecond)
Expand Down Expand Up @@ -297,10 +351,19 @@ func host(listener net.Listener, pipe net.Listener, target string, interrupts <-
}
return 1
}
var decorate func(*bridge.Snapshot)
decorate := func(state *bridge.Snapshot) {
state.Release, state.Revision = releaseVersion, buildRevision
state.GoVersion, state.Architecture = runtime.Version(), runtime.GOARCH
state.TargetEndpoint = state.Destination.Endpoint(target)
if state.Destination == destination.Standby {
state.TargetEndpoint = "none"
}
if app != nil {
app.Decorate(state)
}
}
if app != nil {
app.Start()
decorate = app.Decorate
}
controlDone := make(chan error, 1)
go func() {
Expand Down
9 changes: 9 additions & 0 deletions cmd/vxbridge/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ func TestVersionAndInvalidArgumentsDoNotStartHost(t *testing.T) {
for _, args := range [][]string{
{"--target-port=12321"}, {"--target-port=0"}, {"--target-port=65536"},
{"--status", "--quit"}, {"--unknown"}, {"unexpected"}, {"--version", "--quit"},
{"--diagnostics=report.zip", "--status"}, {"--diagnostics=report.zip", "--version"},
{"--diagnostics=report.zip", "--quit"}, {"--diagnostics=report.zip", "--diagnostics-dialog"},
{"--diagnostics-dialog", "--target-port=999"}, {"--diagnostics-dialog", "--desktop"},
{"--diagnostics=report.zip", "--desktop=false"},
{"--diagnostics="}, {"--diagnostics-dialog=false"},
} {
if code := run(args, io.Discard, io.Discard); code != 2 {
t.Fatalf("unsafe arguments accepted: %v", args)
Expand All @@ -127,6 +132,10 @@ func TestReleaseAndDevelopmentVersionIdentity(t *testing.T) {
for _, release := range []string{"development", "v1.0.1"} {
for _, desktop := range []string{"false", "true"} {
releaseVersion, buildRevision, defaultDesktop = release, strings.Repeat("a", 40), desktop
build := diagnosticBuild()
if build.Release != release || build.Revision != buildRevision || build.Version != version {
t.Fatal("structured diagnostics lost release/source identity", build)
}
var out bytes.Buffer
if code := run([]string{"--version"}, &out, io.Discard); code != 0 {
t.Fatal("version query failed")
Expand Down
9 changes: 9 additions & 0 deletions contributors.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ For a confusing setup step, suggest the wording you wish you'd seen. For a bug,
describe what you did, what you expected, what happened, and the bridge/Labs/game
versions involved. Include the visible tray error and selected game when relevant.

Use the [bug form](https://github.com/devigned/vx-bridge/issues/new?template=bug.yml)
and [reporting guide](docs/REPORTING.md). A reviewed diagnostic ZIP is optional;
do not block a report on a working exporter. For a capability request, use the
[feature form](https://github.com/devigned/vx-bridge/issues/new?template=feature.yml)
and describe the task, workaround and desired outcome. Maintainers should follow
the [triage procedure](docs/technical/DIAGNOSTICS.md#maintainer-triage).

Check the [FAQ](docs/FAQ.md) and
[existing issues](https://github.com/devigned/vx-bridge/issues)
before opening a report. Include the release version and the visible error.
Expand Down Expand Up @@ -45,6 +52,8 @@ Start with the runtime, not installer qualification or historical probes:
[`internal/managed`](internal/managed/) checks installed-copy admission,
[`internal/startup`](internal/startup/) owns sign-in registration, and
[`internal/diagnostics`](internal/diagnostics/) owns bounded sanitized logs.
Its shared read-only exporter packages allowlisted diagnostic records for
the tray and host-independent control-client entry points.
These responsibilities do not authorize forced source teardown or automatic repair.

| Area | Where to look |
Expand Down
17 changes: 17 additions & 0 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,23 @@ Logging failure doesn't deliberately close Labs. Use the available menu or local
control status to report the build version and sanitized error. Don't substitute
full vendor logs or a raw capture: those can contain private data.

## How do I report a bug if the bridge will not start?

Open **VX Bridge Diagnostics** from Start to save a local ZIP. It does not
start or repair the host. Missing status or logs are marked unavailable, not
healthy. Review the ZIP, then attach it to the
[bug form](https://github.com/devigned/vx-bridge/issues/new?template=bug.yml)
if you choose. Nothing is sent automatically.

If export fails or an older build lacks that shortcut, submit without an
attachment. Include the visible error, selected game, versions you know,
approximate time/time zone, and what you expected and observed. Do not attach
raw vendor/MSI logs or private data. See [Reporting](REPORTING.md).

For a new capability rather than a failure, use the
[feature form](https://github.com/devigned/vx-bridge/issues/new?template=feature.yml).
Describe your task, workaround and desired outcome; an implementation is optional.

<a id="start-with-windows-is-checked-but-the-bridge-doesnt-start"></a>

## Auto-launch on Windows Startup is checked, but the bridge doesn't start
Expand Down
4 changes: 4 additions & 0 deletions docs/INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ instructions.

## Uninstall

Before removing logs needed for a bug report, save an optional report with
**VX Bridge Diagnostics** in Start. This does not start the host or call MSI.
See [Reporting](REPORTING.md); independently saved reports are not uninstalled.

Disconnect in Labs and Quit normally, then use **Windows Settings > Apps >
Installed apps > VX Bridge > Uninstall** (called **Apps & features** on some
Windows versions). Normal uninstall does not need a working bridge or working
Expand Down
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ The bridge remembers the choice; only one game receives shots.
| Choose a game, check status, find logs or quit safely | [Everyday use](USAGE.md) |
| Fix a connection problem, missed shot or failed installation | [FAQ](FAQ.md) |
| Read initial-release features, files and contributor credits | [v0.1.0 release notes](releases/v0.1.0.md) |
| Report a bug or contribute | [Contributing](../contributors.md) |
| Report a bug, save a local report or request a feature | [Reporting](REPORTING.md) |
| Contribute a code or documentation change | [Contributing](../contributors.md) |

[v0.1.0 is available](https://github.com/devigned/vx-bridge/releases/tag/v0.1.0).
It passed automated build and file checks, not interactive installation,
Expand Down
75 changes: 75 additions & 0 deletions docs/REPORTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Report a bug or request a feature

[User guides](README.md) / Reporting

## Submit a bug

Check the [FAQ](FAQ.md) and
[existing issues](https://github.com/devigned/vx-bridge/issues) first. Then
choose **Report a bug** in the bridge menu, or open the
[bug form](https://github.com/devigned/vx-bridge/issues/new?template=bug.yml).
A GitHub account is needed to submit it.

Describe the problem, steps, expected result and observed result. Include the
visible error and selected game. If you know them, add:

- Approximate problem time and time zone.
- Bridge release, Windows version/build, ProTee Labs version, and game/OpenAPI version.
- Whether this followed a fresh install, upgrade, startup, game change or Quit.
- How often it happens, the last working bridge version, and its effect on play.

Write **unknown** for versions you cannot find. Find Labs and game versions
in their own About screens; the bridge does not search their files.
Do not hit extra shots, force-close an attached receiver or change vendor
settings just to reproduce a fault.

## Save an optional diagnostic report

1. Choose **Save diagnostic report...** from the bridge menu. If the bridge
will not start, its icon is missing or it reports **Repair required**,
open **VX Bridge Diagnostics** from Windows Start instead. No terminal
command is needed.
2. Choose a **new filename in a local folder**. Existing files are not
overwritten. Cancel closes the save dialog without collecting or saving.
3. Wait for the saved, partial, or failure notice. **Nothing was sent.**
4. Open the ZIP and read `summary.txt`. Review its contents before dragging
it into the bug form. You choose whether to attach it.

The report contains bridge versions, numeric Windows build facts, selected
game/local endpoints, bounded status and sanitized events, log-loss counters,
an installer-result summary and installed-file checks. `manifest.json`
identifies sources that were included, unavailable, skipped or truncated.
`report.json` and `events.jsonl` provide structured details.

A partial report is useful. An unreachable host is marked unavailable; export
does not start or repair it, disconnect Labs, change settings, or call Windows
Installer. Missing or corrupt sources do not prevent other sources from being
saved. Reports are best-effort observations, not proof of game readiness,
complete shot history, or installation health.

If saving fails, try a new filename in a writable local folder. An incomplete
output file may remain; do not attach it. Submit the bug **without a ZIP** and
describe the save error. If your build has no reporting menu or shortcut, it
predates this workflow; use the web form without a ZIP. Do not repair, upgrade
or restart solely to make a diagnostic report.

## Keep private information out

Issues and attachments are public. Do not attach raw vendor or verbose MSI
logs, packet captures, crash dumps, shot measurements, player data, device or
hardware identifiers, license/account details, usernames, private paths or
credentials. Crop or redact screenshots before attaching them.

The exporter reads only fixed bridge-owned sources and allowlists exported
fields. It excludes vendor data, raw payloads and private-path/error dumps.
It does not upload, clear, rotate or delete the original logs. Independently
saved reports remain yours; bridge Uninstall does not remove them. See the
[privacy contract](technical/DIAGNOSTICS.md) for precise limits.

## Request a feature

Use the
[feature form](https://github.com/devigned/vx-bridge/issues/new?template=feature.yml).
Describe the task or problem, your current workaround and the outcome that
would help. An implementation suggestion is optional. No diagnostic report
is needed for an ordinary feature request.
Loading