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
1 change: 1 addition & 0 deletions CHANGELOG.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor
## [Unreleased]
### What's new
- Update notice in Portuguese: the new version's highlight shows in the app's language when there is a changelog in it (today, Portuguese); other languages still see it in English. Applies to updates published after this version. (#158)
- Diagnostics in Settings: a new card shows, read-only, the app's version and channel, Windows, the screens (resolution, HDR, which one is the game screen and which ones get hidden), the audio outputs, the connected controllers, whether Steam, Playnite, RTSS and ControlFS were found, and the end of the log. **Copy report** copies it all as text to paste into an issue, without your user name in paths and without keys. (#159)

## [1.6.2]
### What's new
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH
## [Unreleased]
### Novidades
- Aviso de atualização em português: o destaque da nova versão aparece no idioma do app quando há um changelog nele (hoje, português); os outros idiomas continuam vendo o destaque em inglês. Vale a partir das atualizações publicadas depois desta versão. (#158)
- Diagnóstico em Ajustes: um card novo mostra, só para leitura, a versão e o canal do app, o Windows, as telas (resolução, HDR, qual é a de jogo e quais são ocultadas), as saídas de áudio, os controles conectados, se Steam, Playnite, RTSS e ControlFS foram encontrados e o fim do log. **Copiar relatório** copia tudo em texto para colar numa issue, sem o seu nome de usuário nos caminhos e sem chaves. (#159)

## [1.6.2]
### Novidades
Expand Down
13 changes: 13 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,3 +469,16 @@ Pré-requisito: RivaTuner Statistics Server instalado, com "Show On-Screen Displ
- [ ] Interface Console → Sistema: a linha "Minimizar para a bandeja" alterna Ligado/Desligado e o modo PC mostra o mesmo valor; o valor fica salvo ao reabrir o app.
- [ ] Com a sessão ativa e a opção ligada, minimizar e reabrir pela bandeja continua funcionando; o atalho do controle para abrir o app também traz a janela de volta.
- [ ] Fechar no X (sem sessão ativa) continua fechando o app, com a opção ligada ou desligada.

## Diagnóstico (Ajustes → Atalho, ajuda e dados)

- [ ] Abrir o card "Diagnóstico": o anel de carregamento aparece e, sem travar a janela, o relatório mostra versão e canal (instalado/portátil, estável/teste), Windows com build, e a versão do .NET.
- [ ] Telas: aparecem todas as que o app vê, com resolução @ Hz, HDR ligado/desligado/sem suporte; a tela de jogo vem marcada "game screen" e as que serão ocultadas, "hidden in console mode". Trocar a tela de jogo ou as ocultas e clicar em **Atualizar** muda as marcas.
- [ ] Ligar o HDR no Windows e clicar em **Atualizar**: a tela passa a "HDR on".
- [ ] Áudio: lista as saídas, com a padrão do Windows e a escolhida no app marcadas; com "Ao conectar", nenhuma vem como escolhida.
- [ ] Controles: com um Xbox e um DualSense ligados, os dois aparecem (Xbox / PlayStation); sem nenhum, "(none)".
- [ ] Programas: Steam, Playnite, RTSS e ControlFS com caminho e versão quando instalados, e "not found" quando não; "Launches" mostra o iniciador escolhido.
- [ ] Log: as últimas ~50 linhas do `consolemode.log` aparecem no fim.
- [ ] Nenhum caminho mostra o nome do usuário do Windows (`C:\Users\…\`), nem com Playnite instalado em `%LOCALAPPDATA%`.
- [ ] **Copiar relatório** mostra o aviso de copiado e o texto colado no Bloco de Notas é igual ao da tela.
- [ ] Com uma seção falhando (ex.: sem permissão de leitura do log), ela mostra "unavailable" e o resto do relatório continua aparecendo.
4 changes: 4 additions & 0 deletions src/ConsoleMode/Resources/Strings.de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@
"ControllerDevicesHeading": "Geräte",
"ControllerSampleHeading": "Live-Anzeige (Tasten drücken)",
"DiagnosticsCopied": "Diagnosedaten kopiert. Füge sie in den Fehlerbericht oder das Feedbackformular ein.",
"DiagnosticsCard": "Diagnose",
"DiagnosticsDescription": "Versionen, Bildschirme, Audio, Controller, gefundene Programme und das Ende des Protokolls, zum Anhängen an einen Fehlerbericht. Pfade mit deinem Benutzernamen und Schlüssel werden ausgeblendet.",
"RefreshDiagnostics": "Aktualisieren",
"CopyReport": "Bericht kopieren",
"TutorialCard": "Tutorial",
"TutorialDescription": "Die Einführung auf der Startseite erneut anzeigen.",
"SeeAgain": "Erneut anzeigen",
Expand Down
4 changes: 4 additions & 0 deletions src/ConsoleMode/Resources/Strings.en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@
"ControllerDevicesHeading": "Devices",
"ControllerSampleHeading": "Live reading (press buttons)",
"DiagnosticsCopied": "Diagnostics copied. Paste them into the issue or the feedback form.",
"DiagnosticsCard": "Diagnostics",
"DiagnosticsDescription": "Versions, screens, audio, controllers, programs found and the end of the log, to attach to a bug report. Paths with your user name and keys are hidden.",
"RefreshDiagnostics": "Refresh",
"CopyReport": "Copy report",
"TutorialCard": "Tutorial",
"TutorialDescription": "Show the home screen walkthrough again.",
"SeeAgain": "Show again",
Expand Down
4 changes: 4 additions & 0 deletions src/ConsoleMode/Resources/Strings.es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@
"ControllerDevicesHeading": "Dispositivos",
"ControllerSampleHeading": "Lectura en vivo (pulsa botones)",
"DiagnosticsCopied": "Diagnóstico copiado. Pégalo en la issue o en el formulario de comentarios.",
"DiagnosticsCard": "Diagnóstico",
"DiagnosticsDescription": "Versiones, pantallas, audio, mandos, programas encontrados y el final del registro, para adjuntar a un informe de error. Las rutas con tu nombre de usuario y las claves se ocultan.",
"RefreshDiagnostics": "Actualizar",
"CopyReport": "Copiar informe",
"TutorialCard": "Tutorial",
"TutorialDescription": "Vuelve a mostrar la guía de la pantalla de inicio.",
"SeeAgain": "Ver de nuevo",
Expand Down
4 changes: 4 additions & 0 deletions src/ConsoleMode/Resources/Strings.fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@
"ControllerDevicesHeading": "Appareils",
"ControllerSampleHeading": "Lecture en direct (appuyez sur des boutons)",
"DiagnosticsCopied": "Diagnostic copié. Collez-le dans le ticket ou le formulaire de commentaires.",
"DiagnosticsCard": "Diagnostic",
"DiagnosticsDescription": "Versions, écrans, audio, manettes, programmes trouvés et la fin du journal, à joindre à un rapport de bug. Les chemins contenant votre nom d'utilisateur et les clés sont masqués.",
"RefreshDiagnostics": "Actualiser",
"CopyReport": "Copier le rapport",
"TutorialCard": "Tutoriel",
"TutorialDescription": "Revoir la présentation de l'écran d'accueil.",
"SeeAgain": "Revoir",
Expand Down
4 changes: 4 additions & 0 deletions src/ConsoleMode/Resources/Strings.pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@
"ControllerDevicesHeading": "Dispositivos",
"ControllerSampleHeading": "Leitura ao vivo (aperte botões)",
"DiagnosticsCopied": "Diagnóstico copiado. Cole na issue ou no feedback.",
"DiagnosticsCard": "Diagnóstico",
"DiagnosticsDescription": "Versões, telas, áudio, controles, programas encontrados e o fim do log, para anexar a um relato de problema. Caminhos com o seu nome de usuário e chaves ficam ocultos.",
"RefreshDiagnostics": "Atualizar",
"CopyReport": "Copiar relatório",
"TutorialCard": "Tutorial",
"TutorialDescription": "Mostra de novo o passo a passo da tela inicial.",
"SeeAgain": "Ver de novo",
Expand Down
4 changes: 4 additions & 0 deletions src/ConsoleMode/Resources/Strings.ru-RU.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@
"ControllerDevicesHeading": "Устройства",
"ControllerSampleHeading": "Данные в реальном времени (нажимайте кнопки)",
"DiagnosticsCopied": "Диагностика скопирована. Вставьте её в issue или форму отзыва.",
"DiagnosticsCard": "Диагностика",
"DiagnosticsDescription": "Версии, экраны, звук, контроллеры, найденные программы и конец журнала — чтобы приложить к сообщению об ошибке. Пути с вашим именем пользователя и ключи скрываются.",
"RefreshDiagnostics": "Обновить",
"CopyReport": "Копировать отчёт",
"TutorialCard": "Обучение",
"TutorialDescription": "Снова показать обзор главного экрана.",
"SeeAgain": "Показать снова",
Expand Down
126 changes: 126 additions & 0 deletions src/ConsoleMode/Services/DiagnosticsReport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System.Text;
using System.Text.RegularExpressions;

namespace ConsoleMode.Services;

public sealed record DiagnosticsSystem(string AppVersion, string Channel, string Windows, string Runtime);

/// <param name="Mode">"3840 × 2160 @ 120 Hz", or empty when unknown.</param>
/// <param name="Hdr">"on" | "off" | "unsupported" | "" (unknown).</param>
public sealed record DiagnosticsDisplay(string Name, string Mode, string Hdr, bool IsActive, bool IsPrimary, bool IsFocus, bool IsHidden);

public sealed record DiagnosticsAudio(string Name, bool IsDefault, bool IsConfigured);

/// <summary>
/// One connected controller. <paramref name="Detail"/> is free text printed after the type
/// (e.g. a battery level); the collector leaves it empty for now.
/// </summary>
public sealed record DiagnosticsController(string Name, string Type, string? Detail = null);

/// <param name="Path">Null when the program wasn't found.</param>
public sealed record DiagnosticsApp(string Name, string? Path, string? Version = null);

/// <summary>Everything the report shows. A null section is one that failed to load ("unavailable").</summary>
public sealed class DiagnosticsData
{
public DiagnosticsSystem? System { get; init; }
public IReadOnlyList<DiagnosticsDisplay>? Displays { get; init; }
public IReadOnlyList<DiagnosticsAudio>? Audio { get; init; }
public IReadOnlyList<DiagnosticsController>? Controllers { get; init; }
public IReadOnlyList<DiagnosticsApp>? Apps { get; init; }
public string? FullscreenMode { get; init; }
public IReadOnlyList<string>? Log { get; init; }
}

/// <summary>
/// Settings → Diagnostics: the plain-text report users paste into an issue. English on purpose
/// (it's read by whoever answers the issue), and redacted: no user name in paths, no secrets.
/// </summary>
public static class DiagnosticsReport
{
public const string Unavailable = "unavailable";
public const int LogLines = 50;

public static string Format(DiagnosticsData data)
{
var sb = new StringBuilder();

Section(sb, "Console Mode", data.System is null ? null : [
$"Version: {data.System.AppVersion} ({data.System.Channel})",
$"Windows: {data.System.Windows}",
$".NET: {data.System.Runtime}"
]);

Section(sb, "Displays", data.Displays?.Select(d =>
{
var tags = new List<string>();
if (d.IsFocus) tags.Add("game screen");
if (d.IsHidden) tags.Add("hidden in console mode");
if (d.IsPrimary) tags.Add("primary");
if (!d.IsActive) tags.Add("off");
var mode = d.Mode.Length > 0 ? d.Mode : "mode unknown";
var hdr = d.Hdr.Length > 0 ? $", HDR {d.Hdr}" : "";
var tagText = tags.Count > 0 ? $" [{string.Join(", ", tags)}]" : "";
return $"- {d.Name}: {mode}{hdr}{tagText}";
}));

Section(sb, "Audio outputs", data.Audio?.Select(a =>
{
var tags = new List<string>();
if (a.IsDefault) tags.Add("Windows default");
if (a.IsConfigured) tags.Add("chosen in Console Mode");
return $"- {a.Name}{(tags.Count > 0 ? $" [{string.Join(", ", tags)}]" : "")}";
}));

Section(sb, "Controllers", data.Controllers?.Select(c =>
$"- {c.Name}: {c.Type}{(string.IsNullOrWhiteSpace(c.Detail) ? "" : $", {c.Detail}")}"));

IEnumerable<string>? apps = data.Apps?.Select(a =>
a.Path is null ? $"- {a.Name}: not found"
: $"- {a.Name}: {a.Path}{(string.IsNullOrWhiteSpace(a.Version) ? "" : $" ({a.Version})")}");
if (apps is not null && !string.IsNullOrEmpty(data.FullscreenMode))
apps = apps.Prepend($"Launches: {data.FullscreenMode}");
Section(sb, "Programs", apps);

Section(sb, $"Log (last {LogLines} lines)", data.Log?.TakeLast(LogLines));

return Redact(sb.ToString().TrimEnd() + Environment.NewLine);
}

private static void Section(StringBuilder sb, string title, IEnumerable<string>? lines)
{
if (sb.Length > 0) sb.AppendLine();
sb.AppendLine($"== {title} ==");
if (lines is null)
{
sb.AppendLine(Unavailable);
return;
}
var any = false;
foreach (var line in lines)
{
sb.AppendLine(line);
any = true;
}
if (!any) sb.AppendLine("(none)");
}

private static readonly Regex UserPath = new(@"(?i)\b([a-z]:[\\/]+(?:users|documents and settings)[\\/]+)[^\\/\r\n""']+", RegexOptions.Compiled);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only matches drive-letter profile paths. A roaming or network profile can produce a UNC path such as \\server\Users\alice\... in an app path or log line, leaving the account name visible in the copied report despite the privacy guarantee. Please redact UNC Users / Documents and Settings profile prefixes too and cover that form with a test.

Technical details
# UNC profile paths retain account names

## Affected sites
- `src/ConsoleMode/Services/DiagnosticsReport.cs:108` — `UserPath` requires a drive letter, so UNC profile paths are not matched.
- `tests/ConsoleMode.Tests/DiagnosticsReportTests.cs` — add coverage for a UNC path and verify only the profile name is replaced.

## Required outcome
- Redact the account component in both drive-letter and UNC profile paths before the report is displayed or copied.

private static readonly Regex Pem = new(@"-----BEGIN [A-Z ]+-----.*?(-----END [A-Z ]+-----|$)", RegexOptions.Compiled | RegexOptions.Singleline);
private static readonly Regex Dpapi = new(SecretPrefix + @"[A-Za-z0-9+/=]+", RegexOptions.Compiled);
private static readonly Regex NamedSecret = new(
@"(?i)\b(token|password|passwd|secret|api[_-]?key|private[_-]?key|pairing[_-]?key|auth[_-]?key|authorization)(""?\s*[:=]\s*""?)(?:bearer\s+)?[^\s"",;]+",
RegexOptions.Compiled);

// SecretProtector.Prefix, repeated so this file stays free of DPAPI (and testable anywhere).
private const string SecretPrefix = "dpapi:";

/// <summary>Hides the Windows user name in profile paths and anything that looks like a key or token.</summary>
public static string Redact(string text)
{
text = Pem.Replace(text, "[redacted key]");
text = Dpapi.Replace(text, SecretPrefix + "[redacted]");
text = NamedSecret.Replace(text, "$1$2[redacted]");
return UserPath.Replace(text, "$1…");
}
}
4 changes: 4 additions & 0 deletions src/ConsoleMode/Services/LocalizationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ internal void NotifyAll()
public string CopyDiagnostics => LocalizationService.Get(nameof(CopyDiagnostics));
public string ControllerDevicesHeading => LocalizationService.Get(nameof(ControllerDevicesHeading));
public string ControllerSampleHeading => LocalizationService.Get(nameof(ControllerSampleHeading));
public string DiagnosticsCard => LocalizationService.Get(nameof(DiagnosticsCard));
public string DiagnosticsDescription => LocalizationService.Get(nameof(DiagnosticsDescription));
public string RefreshDiagnostics => LocalizationService.Get(nameof(RefreshDiagnostics));
public string CopyReport => LocalizationService.Get(nameof(CopyReport));
public string SessionMenuDescription => LocalizationService.Get(nameof(SessionMenuDescription));
public string ConsoleScreensHeading => LocalizationService.Get(nameof(ConsoleScreensHeading));
public string ConsoleQuickHeading => LocalizationService.Get(nameof(ConsoleQuickHeading));
Expand Down
Loading
Loading