diff --git a/.github/workflows/gameboost-build.yml b/.github/workflows/gameboost-build.yml new file mode 100644 index 0000000..771ee3f --- /dev/null +++ b/.github/workflows/gameboost-build.yml @@ -0,0 +1,102 @@ +name: build + +on: + push: + branches: [main] + paths: ['GameBoost/**', '.github/workflows/build.yml'] + pull_request: + paths: ['GameBoost/**'] + workflow_dispatch: + +defaults: + run: + working-directory: GameBoost + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Restaurar + run: dotnet restore GameBoost.sln + + - name: Compilar + run: dotnet build GameBoost.sln -c Release --no-restore -warnaserror + + - name: Testar + run: dotnet test GameBoost.sln -c Release --no-build --logger "trx;LogFileName=testes.trx" + + - name: Publicar testes + if: always() + uses: actions/upload-artifact@v4 + with: + name: resultado-testes + path: GameBoost/**/TestResults/*.trx + + - name: Publicar executavel + run: > + dotnet publish src/GameBoost.App/GameBoost.App.csproj + -c Release -r win-x64 --self-contained + -p:PublishSingleFile=true + -p:IncludeNativeLibrariesForSelfExtract=true + -p:PublishReadyToRun=true + -o publish + + # Trimming quebra WPF e reflexao (secao 10 do spec). Falha a build se alguem ligar. + - name: Conferir que o trimming segue desligado + shell: pwsh + run: | + $props = Get-Content Directory.Build.props -Raw + if ($props -notmatch 'false') { + throw 'PublishTrimmed precisa continuar false: trimming quebra WPF.' + } + + - name: Montar o instalador + shell: pwsh + run: | + choco install innosetup --no-progress -y --no-color + + # $env:ProgramFiles(x86) nao e sintaxe valida: o PowerShell le + # "$env:ProgramFiles" e deixa "(x86)" solto, virando + # "C:\Program Files(x86)". A forma correta e ${env:...}. + # O ISCC tambem muda de lugar conforme a instalacao, entao vale + # procurar em vez de fixar um caminho. + $candidatos = @( + "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe", + "$env:ProgramFiles\Inno Setup 6\ISCC.exe", + "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe" + ) + + $iscc = $candidatos | Where-Object { Test-Path $_ } | Select-Object -First 1 + if (-not $iscc) { + throw "ISCC.exe nao encontrado. Procurado em: $($candidatos -join '; ')" + } + + Write-Host "Usando $iscc" + & $iscc installer\GameBoost.iss + if ($LASTEXITCODE -ne 0) { throw "ISCC falhou com codigo $LASTEXITCODE" } + + - name: Gerar SHA256SUMS + shell: pwsh + run: | + $arquivos = @(Get-ChildItem publish\GameBoost.exe, installer\Output\*.exe -ErrorAction SilentlyContinue) + $arquivos | ForEach-Object { + $h = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower() + "$h $($_.Name)" + } | Set-Content SHA256SUMS.txt -Encoding utf8 + Get-Content SHA256SUMS.txt + + - name: Publicar artefatos + uses: actions/upload-artifact@v4 + with: + name: gameboost + path: | + GameBoost/publish/GameBoost.exe + GameBoost/installer/Output/*.exe + GameBoost/SHA256SUMS.txt diff --git a/.github/workflows/gameboost-release.yml b/.github/workflows/gameboost-release.yml new file mode 100644 index 0000000..9e7d4dd --- /dev/null +++ b/.github/workflows/gameboost-release.yml @@ -0,0 +1,220 @@ +name: release + +# Dispara só em tag `gameboost-vX.Y.Z`. O repositório guarda mais de um +# projeto, então a tag carrega o nome do produto: uma tag `v2.0.0` solta +# construiria o GameBoost por engano quando outro projeto for versionado. +on: + push: + tags: ['gameboost-v*'] + workflow_dispatch: + inputs: + versao: + description: 'Versão a empacotar (ex.: 2.0.0). Só para teste manual.' + required: true + +defaults: + run: + working-directory: GameBoost + +permissions: + contents: write + +jobs: + release: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Descobrir a versao + id: versao + shell: pwsh + run: | + if ($env:GITHUB_REF -like 'refs/tags/gameboost-v*') { + $v = $env:GITHUB_REF -replace '.*/gameboost-v', '' + } else { + $v = '${{ github.event.inputs.versao }}' + } + + if ($v -notmatch '^\d+\.\d+\.\d+$') { + throw "Versao invalida: '$v'. Esperado X.Y.Z." + } + + # A tag e o Directory.Build.props precisam concordar. Publicar uma + # tag 2.1.0 com o assembly dizendo 2.0.0 deixaria o auto-update + # comparando numeros que nao batem com o que o usuario tem. + $props = Get-Content Directory.Build.props -Raw + if ($props -notmatch "$([regex]::Escape($v))") { + throw "A tag diz $v mas o Directory.Build.props diz outra coisa. Alinhe os dois." + } + + "versao=$v" >> $env:GITHUB_OUTPUT + Write-Host "Versao: $v" + + - name: Restaurar + run: dotnet restore GameBoost.sln + + - name: Testar + run: dotnet test GameBoost.sln -c Release --logger "trx;LogFileName=testes.trx" + + - name: Publicar executavel + run: > + dotnet publish src/GameBoost.App/GameBoost.App.csproj + -c Release -r win-x64 --self-contained + -p:PublishSingleFile=true + -p:IncludeNativeLibrariesForSelfExtract=true + -p:PublishReadyToRun=true + -p:Version=${{ steps.versao.outputs.versao }} + -o publish + + # ------------------------------------------------------------------ + # Assinatura de codigo + # ------------------------------------------------------------------ + # + # Condicional de proposito. O certificado OV vive num token USB ou HSM + # (exigencia das CAs desde jun/2023), entao nem sempre esta disponivel + # para o CI. Sem o segredo, a release sai **sem assinatura** e o passo + # seguinte avisa em alto e bom som, em vez de falhar a build inteira. + # + # Ver docs/ASSINATURA.md para o que foi apurado sobre as opcoes. + - name: Assinar o executavel + if: ${{ secrets.GAMEBOOST_CERT_PFX != '' }} + env: + CERT_PFX: ${{ secrets.GAMEBOOST_CERT_PFX }} + CERT_SENHA: ${{ secrets.GAMEBOOST_CERT_SENHA }} + shell: pwsh + run: | + $pfx = "$env:RUNNER_TEMP\cert.pfx" + [IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:CERT_PFX)) + + $signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Recurse -Filter signtool.exe | + Where-Object { $_.FullName -like '*\x64\*' } | + Select-Object -First 1 -ExpandProperty FullName + + if (-not $signtool) { throw 'signtool.exe nao encontrado no runner.' } + + & $signtool sign /f $pfx /p $env:CERT_SENHA ` + /fd SHA256 /tr http://timestamp.sectigo.com /td SHA256 ` + publish\GameBoost.exe + + if ($LASTEXITCODE -ne 0) { throw "signtool falhou com codigo $LASTEXITCODE" } + Remove-Item $pfx -Force + + - name: Avisar que a release sai sem assinatura + if: ${{ !secrets.GAMEBOOST_CERT_PFX }} + shell: pwsh + run: | + Write-Host '::warning::Sem certificado configurado: esta release sai SEM assinatura de codigo. O SmartScreen vai avisar no primeiro download. Ver docs/ASSINATURA.md.' + + # ------------------------------------------------------------------ + # Pacote de atualizacao (Velopack) + # ------------------------------------------------------------------ + - name: Empacotar com o Velopack + shell: pwsh + run: | + dotnet tool install -g vpk + $env:PATH = "$env:USERPROFILE\.dotnet\tools;$env:PATH" + + vpk pack ` + --packId GameBoost ` + --packVersion ${{ steps.versao.outputs.versao }} ` + --packDir publish ` + --mainExe GameBoost.exe ` + --packTitle GameBoost ` + --packAuthors "Jonathan Vaz" ` + --outputDir velopack + + if ($LASTEXITCODE -ne 0) { throw "vpk pack falhou com codigo $LASTEXITCODE" } + + - name: Montar o instalador + shell: pwsh + run: | + choco install innosetup --no-progress -y --no-color + + $candidatos = @( + "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe", + "$env:ProgramFiles\Inno Setup 6\ISCC.exe", + "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe" + ) + + $iscc = $candidatos | Where-Object { Test-Path $_ } | Select-Object -First 1 + if (-not $iscc) { throw "ISCC.exe nao encontrado. Procurado em: $($candidatos -join '; ')" } + + & $iscc /DAppVersion=${{ steps.versao.outputs.versao }} installer\GameBoost.iss + if ($LASTEXITCODE -ne 0) { throw "ISCC falhou com codigo $LASTEXITCODE" } + + # ------------------------------------------------------------------ + # Portatil + # ------------------------------------------------------------------ + # + # O mesmo exe, mais um portable.txt vazio ao lado. O AppPaths procura + # esse arquivo e, achando, guarda tudo em .\data\ em vez de + # %LOCALAPPDATA%. Nada muda no codigo. + - name: Montar o portatil + shell: pwsh + run: | + $pasta = "portatil\GameBoost-${{ steps.versao.outputs.versao }}-portatil" + New-Item -ItemType Directory -Force $pasta | Out-Null + + Copy-Item publish\GameBoost.exe $pasta + Copy-Item README.md "$pasta\LEIA-ME.txt" + + @( + 'Este arquivo faz o GameBoost guardar tudo em .\data\, ao lado do executavel,' + 'em vez de %LOCALAPPDATA%\GameBoost.' + '' + 'Apague este arquivo para voltar ao comportamento normal.' + '' + 'O modo portatil nao tem atualizacao automatica: baixe a versao nova pela' + 'pagina de releases quando quiser trocar.' + ) | Set-Content "$pasta\portable.txt" -Encoding utf8 + + Compress-Archive -Path "$pasta\*" -DestinationPath "portatil\GameBoost-${{ steps.versao.outputs.versao }}-portatil.zip" -Force + + - name: Gerar SHA256SUMS + shell: pwsh + run: | + $arquivos = @( + Get-ChildItem publish\GameBoost.exe -ErrorAction SilentlyContinue + Get-ChildItem installer\Output\*.exe -ErrorAction SilentlyContinue + Get-ChildItem portatil\*.zip -ErrorAction SilentlyContinue + Get-ChildItem velopack\*.nupkg, velopack\*.exe -ErrorAction SilentlyContinue + ) + + if ($arquivos.Count -eq 0) { throw 'Nenhum artefato para somar.' } + + $arquivos | ForEach-Object { + $h = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower() + "$h $($_.Name)" + } | Set-Content SHA256SUMS.txt -Encoding utf8 + + Get-Content SHA256SUMS.txt + + - name: Anexar tudo ao Release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + name: GameBoost ${{ steps.versao.outputs.versao }} + body_path: GameBoost/installer/NOTAS-DA-RELEASE.md + files: | + GameBoost/publish/GameBoost.exe + GameBoost/installer/Output/*.exe + GameBoost/portatil/*.zip + GameBoost/velopack/* + GameBoost/SHA256SUMS.txt + + - name: Guardar artefatos da execucao manual + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: actions/upload-artifact@v4 + with: + name: gameboost-${{ steps.versao.outputs.versao }} + path: | + GameBoost/publish/GameBoost.exe + GameBoost/installer/Output/*.exe + GameBoost/portatil/*.zip + GameBoost/velopack/* + GameBoost/SHA256SUMS.txt diff --git a/GameBoost/.gitignore b/GameBoost/.gitignore new file mode 100644 index 0000000..ac42ad8 --- /dev/null +++ b/GameBoost/.gitignore @@ -0,0 +1,16 @@ +# Build +bin/ +obj/ +publish/ +*.user +*.suo +.vs/ + +# Publish e instalador +installer/Output/ +*.exe +*.msi + +# Dados locais do app em modo portatil +data/ +*.log diff --git a/GameBoost/CHANGELOG.md b/GameBoost/CHANGELOG.md new file mode 100644 index 0000000..ac7d3ab --- /dev/null +++ b/GameBoost/CHANGELOG.md @@ -0,0 +1,510 @@ +# Changelog + +Formato baseado em Keep a Changelog. Versionamento semantico. + +## [2.0.0] - 2026-09-14 + +### QA manual, quinta rodada - a deteccao de jogo funcionava, o resto nao (2026-09-14) + +Seis correcoes no bloco de perfis. A deteccao em si sempre funcionou — o log registrava +os tres criterios do spec, com confianca e tela cheia. Tudo o que vinha depois dela +estava quebrado. + +#### Corrigido +- **O convite era disparado e ninguem escutava.** O shell dispara `AoPedirAviso` e nenhum + assinante existia: nem balao, nem faixa. O jogo era detectado, o log registrava, e o + usuario nao via nada em lugar nenhum. Agora vai para a bandeja E para uma faixa na + janela — com o jogo em tela cheia o Windows engole o balao, e a faixa espera. +- **Aceitar o convite nao aplicava o perfil.** So navegava para a tela Inicio. Prioridade, + afinidade e tweaks ficavam parados no arquivo, e o ProfileRunner nao era chamado por + ninguem. +- **Fechar o jogo nao desfazia nada**, mas o aviso dizia "tudo o que o GameBoost alterou + foi desfeito". Afirmar reversao sem reverter e o oposto da regra 1. +- **O perfil nao casava com o jogo.** O perfil e chaveado pelo executavel + ("warframe.x64") e o casamento usava o nome da pasta ("Warframe"). A linha aparecia + duas vezes: uma dizendo que tem perfil e outra que nao tem. Agora casa pela pasta de + instalacao, que os dois tem em comum. +- **Falso positivo na deteccao: o Agent do Battle.net.** Ele mora em + `ProgramData\Battle.net\Agent`, que bate com a lista de pastas de launcher, e engorda + enquanto baixa atualizacao — passou do limite de memoria e foi anunciado como jogo numa + sessao real. Nome que descreve funcao ("agent", "launcher", "updater", "helper") dentro + de pasta de launcher e infraestrutura, nunca jogo. +- **"Nao havia perfil aplicado" quando havia.** Quando o jogo fecha antes do Desfazer, nao + sobra prioridade para restaurar — ela morre com o processo. O texto negava que houvesse + perfil em vez de explicar isso. +- O contador do cabecalho da pagina Jogos nao acompanhava criar e remover perfil. + +#### Adicionado +- Teste que prova que maquinaria de launcher nunca e jogo, com 2 GB de RAM para garantir + que o filtro de nome vale mesmo com o processo gordo — e que um jogo de verdade na mesma + pasta continua sendo detectado. + +### QA manual, quarta rodada - o Reverter da inicializacao nao revertia (2026-09-14) + +#### Corrigido +- **Reverter da Inicializacao devolvia sucesso sem fazer nada.** A tela passa GUIDs de + ChangeRecord, mas o modulo esperava NOMES de programa e tentava casar com + `id.Contains(e.Nome)`. Um GUID nunca contem "WallpaperEngine": a lista de alvos saia + vazia, nada era desfeito e o metodo retornava sucesso. A tela dizia "Pronto" e o item + continuava desativado. + + Alem de errado, aquilo dependia de alguem ter varrido na mesma sessao: reabrir o app e + clicar em Reverter nao funcionaria nem com o casamento certo. Agora usa o + RollbackEngine, que le o proprio ChangeRecord — mesmo caminho dos Tweaks. + +#### Adicionado +- Teste do ciclo completo da inicializacao, com uma entrada descartavel criada so para + ele: desativa, confere os 12 bytes com o primeiro em 0x03, reativa e confere que voltou. + +#### Notas +- Reativar pode apagar o valor em vez de gravar 0x02, e as duas formas valem: no + StartupApproved a ausencia significa habilitado. Apagar e mais limpo, porque nao deixa + residuo de uma chave que nao existia antes. + +### QA manual, terceira rodada - a Lixeira derrubava o aplicativo (2026-09-14) + +#### Corrigido +- **`Pack = 1` na struct do SHFileOperation derrubava o processo.** Em x64 o `Pack = 1` + tira o preenchimento entre os campos, e o ponteiro `pFrom` sai no deslocamento 12 em vez + de 16. O shell32 continua lendo do 16, pega metade de um ponteiro colada na metade de + outro e usa aquilo como endereco: violacao de acesso (0xc0000005), processo morto sem + excecao gerenciada, sem log e sem caixa de erro. + + **A Lixeira nunca funcionou em lugar nenhum.** Limpeza, Espaco e Restos usam a mesma + funcao. Era o item que estava marcado como nao testado no docs/QA.md desde a Fase 2, e o + que ele escondia era um crash garantido em toda tentativa. + +#### Adicionado +- Teste que manda um arquivo de verdade para a Lixeira e confere que ele saiu da origem. + Marshalling errado nao quebra a compilacao: so uma chamada real pega. + +### QA manual, segunda rodada - medicao de velocidade e quatro correcoes (2026-09-14) + +Achados nos blocos de tweaks e de rede do docs/QA.md. + +#### Corrigido +- **O teste de velocidade media a rajada, nao a linha.** Ele transferia 25 MB e + cronometrava. Numa fibra domestica a operadora deixa passar bem acima do contratado por + um ou dois segundos, e uma transferencia que termina em 0,6 s mede so isso. Com 8, 25 e + 50 MB o upload saiu 131, 354 e 275 Mbps — tres numeros que nao se parecem, na mesma + linha e no mesmo minuto. Agora mede por **tempo** (8 s por amostra), descartando os 2 + primeiros segundos, onde moram a rajada e o slow start do TCP. O erro caiu de +44% para + -15% contra um teste dedicado, e 15% abaixo e o resultado certo para um teste de um + servidor so e uma conexao so. +- **A lista nao atualizava depois de agir.** O badge de um servico continuava dizendo + "automatico" depois de ele ja ter virado "manual": a tela parecia nao ter feito nada. + Onde a varredura e barata (Tweaks), ela refaz sozinha; onde e cara (Apps), a tela avisa + que a lista mostra o estado de antes. +- **Markdown cru aparecia na tela.** Textos escritos com `**negrito**` mostravam os + asteriscos, porque o WPF nao renderiza markdown. A enfase passou a vir da estrutura da + frase. +- **Glifos de icone se perdiam.** Os icones do menu sao caracteres da area de uso privado + do Unicode, e todo reescrita do arquivo por outra ferramenta os apagava. Agora estao em + escape `\uXXXX`, que e ASCII puro. + +#### Notas +- O teste de velocidade ganhou um teto de 120 MB por amostra, para nao comer a franquia de + quem tem limite. +- Um teste de velocidade dedicado tambem erra quando o servidor escolhido esta + congestionado: na mesma maquina e no mesmo minuto, um servidor deu 73 Mbps de upload e + outro deu 264. A tela agora diz que o numero sai 10% a 15% abaixo e por que. + +### QA manual, primeira rodada - seis correcoes (2026-09-14) + +Achados rodando o app elevado na maquina real, no bloco de reversibilidade do +docs/QA.md. Dois deles apareciam em toda sessao. + +#### Corrigido +- **Sessao nunca era encerrada.** O botao "Restaurar agora" revertia tudo e limpava a tela, + mas nao marcava o session.json como encerrado. O aviso "foi fechado com o Modo Game ainda + ativo" voltava em TODA abertura, mesmo com zero pendencias. Um aviso que aparece sempre + deixa de ser aviso. +- **Excecao em toda saida.** `GameWatcher.Dispose()` era chamado pelo App e de novo pelo + container de DI, que descarta todo singleton IDisposable. Na segunda chamada o `Cancel()` + caia num CancellationTokenSource ja descartado, e a excecao escapava para a caixa de erro + porque o `ServiceProvider.Dispose()` estava fora do try. Dispose agora e idempotente. +- **Lista de restauracao nao existia.** O rodape anunciava "N aguardando na lista de + restauracao" e nao havia lista nenhuma. Agora ha o bloco "Aguardando para reabrir", com + botao por app e a explicacao de por que ele nao voltou sozinho. +- **O contador contradizia a acao.** O resumo dizia "38 apps marcados" com um unico item + marcado, porque era o texto da varredura e nao acompanhava a selecao. Agora ha contagem + viva ao lado, e o resumo da varredura fica em segundo plano. + +#### Adicionado +- **Busca em toda pagina de modulo.** Entrou na classe base, entao vale para Inicio, Apps, + Limpeza, Tweaks e todas as outras. Ignora acento e caixa: quem digita "gravacao" acha + "Gravação". Filtrar **nao** desmarca nada — a contagem continua sobre a lista inteira. +- **Marcar todos e Limpar na tela Inicio**, que era a unica pagina sem eles. + +### Fase 7 - Distribuicao: auto-update, CI de release, portatil e assinatura (2026-09-14) + +#### Adicionado +- Auto-update com Velopack apontando para as releases do GitHub. Checagem silenciosa na + abertura, download em segundo plano, e a troca de versao so no proximo inicio ou quando + o usuario mandar. +- `VelopackApp.Build().Run()` como primeira linha do Main, antes de ler argumentos: o + Velopack roda o proprio exe para instalar e atualizar, e essa chamada precisa + interceptar. +- Secao "Versao do GameBoost" em Configuracoes: versao atual, botao Verificar e, quando + ha download pronto, "Instalar e reiniciar". +- Workflow `gameboost-release.yml`: dispara em tag `gameboost-v*`, confere que a tag bate + com o Directory.Build.props, testa, publica, assina (quando ha certificado), empacota + com o Velopack, monta o instalador e o portatil, gera SHA256SUMS e anexa tudo a release. +- Passo de assinatura condicional. Sem o segredo, a release sai sem assinatura e o CI + emite um aviso visivel, em vez de falhar a build inteira. +- Portatil montado no CI: o mesmo exe, mais um `portable.txt` explicando o que ele faz. +- Instalador com escolha entre instalar para todos os usuarios ou so para o atual. A + chave de autostart passou de HKCU para HKA, que resolve para a raiz certa nos dois casos. +- `docs/ASSINATURA.md`: o que foi apurado sobre certificado (o Brasil esta fora do Azure + Artifact Signing, EV nao pula mais o SmartScreen desde 2024, OV e o caminho viavel) e + como submeter falso positivo. +- `installer/NOTAS-DA-RELEASE.md`: o que baixar, e como conferir o SHA256 quando o + SmartScreen avisar. + +#### Corrigido +- A here-string do PowerShell (`@'...'@`) exige o terminador na coluna 0, e isso quebrava + o bloco YAML do workflow, que precisa de todo conteudo indentado. Trocada por array de + strings. +- O `.iss` tinha `#define AppVersion` fixo, entao o `/DAppVersion` do CI seria ignorado. + Agora e `#ifndef`. +- O `UseWindowsForms` da Fase 6 ligou os analisadores do WinForms, e o WFO0003 manda tirar + a configuracao de DPI do manifesto — regra que nao se aplica a um app WPF. Com o + `-warnaserror` do CI, isso quebraria a build de Release inteira. Suprimido com o motivo + escrito no csproj. + +#### Notas +- Modo portatil nao tem auto-update, e a tela diz isso: o Velopack atualiza uma instalacao + que ele mesmo montou, e um exe solto numa pasta nao tem o que atualizar. +- O teste do modo portatil nao pode exigir que o caminho fique fora de `%LOCALAPPDATA%`: + a pasta temporaria do Windows mora dentro dele. A invariante testada e nao cair na pasta + padrao do app. + +### Fase 7 - Distribuicao: auto-update, CI de release, portatil e assinatura (2026-09-14) + +#### Adicionado +- Auto-update com Velopack apontando para as releases do GitHub. Checagem silenciosa na + abertura, download em segundo plano, e a troca de versao so no proximo inicio ou quando + o usuario mandar. +- `VelopackApp.Build().Run()` como primeira linha do Main, antes de ler argumentos: o + Velopack roda o proprio exe para instalar e atualizar, e essa chamada precisa + interceptar. +- Secao "Versao do GameBoost" em Configuracoes: versao atual, botao Verificar e, quando + ha download pronto, "Instalar e reiniciar". +- Workflow `gameboost-release.yml`: dispara em tag `gameboost-v*`, confere que a tag bate + com o Directory.Build.props, testa, publica, assina (quando ha certificado), empacota + com o Velopack, monta o instalador e o portatil, gera SHA256SUMS e anexa tudo a release. +- Passo de assinatura condicional. Sem o segredo, a release sai sem assinatura e o CI + emite um aviso visivel, em vez de falhar a build inteira. +- Portatil montado no CI: o mesmo exe, mais um `portable.txt` explicando o que ele faz. +- Instalador com escolha entre instalar para todos os usuarios ou so para o atual. A + chave de autostart passou de HKCU para HKA, que resolve para a raiz certa nos dois casos. +- `docs/ASSINATURA.md`: o que foi apurado sobre certificado (o Brasil esta fora do Azure + Artifact Signing, EV nao pula mais o SmartScreen desde 2024, OV e o caminho viavel) e + como submeter falso positivo. +- `installer/NOTAS-DA-RELEASE.md`: o que baixar, e como conferir o SHA256 quando o + SmartScreen avisar. + +#### Corrigido +- A here-string do PowerShell (`@'...'@`) exige o terminador na coluna 0, e isso quebrava + o bloco YAML do workflow, que precisa de todo conteudo indentado. Trocada por array de + strings. +- O `.iss` tinha `#define AppVersion` fixo, entao o `/DAppVersion` do CI seria ignorado. + Agora e `#ifndef`. + +#### Notas +- Modo portatil nao tem auto-update, e a tela diz isso: o Velopack atualiza uma instalacao + que ele mesmo montou, e um exe solto numa pasta nao tem o que atualizar. +- O teste do modo portatil nao pode exigir que o caminho fique fora de `%LOCALAPPDATA%`: + a pasta temporaria do Windows mora dentro dele. A invariante testada e nao cair na pasta + padrao do app. + +### Fase 6 - Perfis por jogo, bandeja e onboarding (2026-09-14) + +#### Adicionado +- Perfis por jogo (secao 5.11): um arquivo por jogo em `profiles/*.json`. Um perfil diz o + que fazer quando o jogo abre e o que desfazer quando ele fecha; a reversao nao e + opcional nem configuravel. +- Deteccao automatica de jogo (secao 5.1): vigia os processos a cada 2 s numa thread de + prioridade baixa, cruzando nome conhecido, pasta de launcher e janela em tela cheia. +- Perfil novo **nunca** ativa sozinho. O convite aparece, e so quem marcar "ativa sozinho" + passa a ter o perfil aplicado sem perguntar (regra 3). +- Afinidade de CPU por perfil, desligada por padrao, recusando indice de nucleo que nao + existe na maquina atual. +- Resolucao de timer em 0,5 ms por perfil, desligada por padrao e com o texto dizendo que + vale menos do que a internet promete. +- Pagina Jogos: biblioteca dos launchers cruzada com os perfis. Medido: 14 jogos, + 974,2 GB. +- Icone na bandeja com o menu da secao 6 (Ativar Modo Game, Limpar RAM, Abrir, Sair). + Fechar a janela esconde na bandeja, com aviso na primeira vez; "Sair" sai de verdade. +- "Limpar RAM" como acao propria, com ganho medido antes e depois. Quando nao ha o que + liberar, o texto diz isso em vez de inventar um numero. +- Onboarding de tres telas na primeira abertura: reversivel, sem telemetria, e o que faz + diferenca de verdade. Nao vende FPS. +- Dois interruptores novos em Configuracoes: icone na bandeja e deteccao automatica. + +#### Corrigido +- `UseWindowsForms` (necessario para o NotifyIcon) injeta using global de + `System.Windows.Forms` e `System.Drawing` em todo arquivo, tornando `Control`, + `Application`, `Brush` e `MouseEventArgs` ambiguos com os tipos do WPF na aplicacao + inteira. Os usings implicitos foram removidos no csproj. +- O texto de estado vazio da pagina Jogos aparecia por cima da lista cheia: a + visibilidade dependia do Resumo, que nunca fica vazio. + +#### Notas +- A deteccao e por polling, nao por `Win32_ProcessStartTrace`. WMI em laco foi o que + estourou o orcamento de CPU na Fase 1; 2 segundos de atraso nao importam para algo que + o usuario vai confirmar num dialogo. +- Sair do app desfaz o perfil aplicado antes de encerrar. Sem isso, prioridade e tweaks + ficariam de pe depois que o processo sumiu. + +### Complemento da Fase 4 - aba Atualizacoes (winget) e aba Restos na tela (2026-09-14) + +Entrega retroativa: a secao 5.3 ganhou a aba de atualizacoes depois que a Fase 4 ja +estava commitada. Vem em commit proprio para o historico nao mentir sobre o que cada +fase entregou. + +#### Adicionado +- Aba "Atualizacoes" em cima do winget: lista o que tem versao nova, classificado e + agrupado. Medido: 35 atualizacoes, 4 de seguranca, 7 que se atualizam sozinhos. +- Classificacao por id: driver (bloqueado, o GameBoost nao instala driver), runtime, + "atualiza sozinho" e "o winget nao sabe a versao instalada". Cada grupo tras o texto + que explica por que tratar diferente. +- Apps que abrem arquivo vindo da internet (navegador, compactador, leitor de PDF, Java) + sao marcados como atualizacao de seguranca: neles ficar desatualizado nao e conforto. +- App aberto e detectado e a linha avisa "feche antes". +- Atualizacao uma a uma, com progresso por item. `winget upgrade --all` seria mais curto + e perderia o que importa: com `--all` um app que falha some no meio da saida. +- Codigos de saida do winget viram frase em portugues; o caminho do log detalhado aparece + quando algo falha. Historico em updates-history.json. +- Quando o winget nao existe, a aba explica o que ele e e abre a pagina do App Installer + na Microsoft Store. +- Aba "Restos de apps antigos" **na interface**. A varredura existia no Core desde a Fase + 4, mas nunca tinha sido ligada a uma tela. +- Pagina de Apps reorganizada em tres abas, cada uma com o seu proprio botao de acao. + +#### Corrigido +- O template de TabControl escrito para o tema escuro quebrava a arvore de automacao: + nenhum controle dentro das abas era exposto. Leitor de tela nao alcancaria nada, e a + navegacao por teclado perdia o destino. Faltava o `ContentPresenter` chamado + `PART_SelectedContentHost`, que e o nome que o `TabControlAutomationPeer` procura. +- O casamento de id do winget era por prefixo cru, entao "Google.Chrome" pegava + "Google.ChromeRemoteDesktopHost": o host de acesso remoto aparecia como navegador e + como atualizacao de seguranca. Agora exige fronteira de ponto. +- As abas do TabControl saiam ilegiveis: o estilo padrao do WPF usa fundo claro e texto + escuro, que sobre a superficie escura do GameBoost fica branco no branco. + +#### Notas +- A saida do `winget upgrade` nao tem formato estruturado: nem JSON, nem XML. E uma + tabela de largura fixa com cabecalho traduzido. A leitura e por **posicao de coluna**, + calculada a partir do cabecalho, o que funciona em qualquer idioma. +- Nada e pre-marcado, nem a atualizacao de seguranca: instalar versao nova pode quebrar + o que funcionava, e a escolha e de quem usa a maquina. + +### Fase 5 - Tweaks, servicos, rede com NAT e drivers (2026-09-14) + +#### Adicionado +- Pagina Tweaks (secoes 5.8 e 5.7): 12 ajustes do catalogo e 12 servicos do Windows na + mesma lista, agrupados por categoria. Cada ajuste tras o efeito real, a evidencia, o + risco e como desfazer. Medido: 24 itens, 1 ja ativo na maquina de teste. +- Nada e pre-marcado, nem o que o proprio GameBoost recomenda: o selo "recomendado" + aparece no texto e a caixa continua vazia (regra 3). +- Isolamento de nucleo (VBS) aparece so de leitura, com o custo declarado (5% a 15% + segundo testes publicos) e o botao que abre a tela da Microsoft. Mitigacoes de + Spectre/Meltdown nao entram nem como item bloqueado. +- SysMain, servicos do Xbox e Windows Update aparecem bloqueados **para explicar por que + nao mexer**: a informacao de que nao se deve desligar vale tanto quanto a sugestao de + desligar. +- Pagina Rede (secao 5.9) em tres blocos: "Posso jogar online?" com semaforo, "Velocidade" + e as tabelas de latencia, DNS e conexoes. +- Cliente STUN proprio (RFC 5389) com dois servidores por duas portas locais. Classifica o + NAT no vocabulario dos jogos: aberto, moderado, estrito. Medido na maquina real: aberto. +- Deteccao das causas: CGNAT pela faixa 100.64.0.0/10, NAT duplo por saltos privados no + traceroute, UPnP por SSDP, Teredo pelo registro e perfis do firewall. +- Teste de velocidade com 3 amostras e mediana, opt-in: gasta cerca de 100 MB, entao + depende de permissao explicita em Configuracoes, desligada por padrao. +- Reativacao do Teredo, reversivel, com ChangeRecord por chave. E a correcao do caso mais + comum de "Game Pass nao conecta": alguem desativou seguindo tutorial de otimizacao. +- Troca de DNS por adaptador, reversivel inclusive para "automatico (DHCP)", e reset de + Winsock com risco Alto e o aviso de que nao tem desfazer. +- Comparacao de DNS por consulta UDP montada a mao, porque `Dns.GetHostEntry` perguntaria + sempre ao resolvedor do sistema. +- Leitura do driver de video (secao 5.10) pelo registro, com traducao da versao da NVIDIA + para a de marketing (32.0.16.1664 vira 616.64) e a idade em meses. +- `docs/PORTAS.md`: portas por launcher e por jogo, o que fazer em CGNAT e em NAT duplo, e + onde fica UPnP em sete marcas de roteador. + +#### Corrigido +- O badge "Protegido" aparecia em ajuste que so precisava de elevacao. Sao coisas + diferentes: protegido e o GameBoost se recusando a mexer; precisar de admin some ao + reabrir elevado. `ActionItem` ganhou `RotuloBloqueio`. +- A leitura da hibernacao procurava `C:\hiberfil.sys`, e `File.Exists` devolve **false** + para aquele arquivo mesmo com a hibernacao ligada: a ACL barra a consulta sem elevacao. + O app diria "ja desligada" para todo mundo que abrisse sem ser administrador. Agora le o + registro. +- O driver de video listava o adaptador virtual do Hyper-V, carimbado com 21/06/2006, e a + tela anunciaria "seu driver tem 242 meses" numa maquina com driver do mes passado. +- A lista de quem usa a rede repetia o mesmo nome varias vezes, uma por PID. Agora agrupa + por nome e diz quantos processos sao. +- A barra de status da pagina Rede repetia a explicacao do NAT que ja tem bloco proprio, e + saia cortada no meio da frase. + +#### Notas +- O spec foi substituido no meio desta fase. Os conflitos entre o que ja estava escrito e o + texto novo estao listados em docs/DECISOES.md, com o que foi corrigido e o que ficou + pendente. +- A aba "Atualizacoes" via winget (secao 5.3) e da Fase 4, que ja foi entregue. Entra como + complemento retroativo depois desta fase, com commit proprio. +- Banda por processo nao e possivel como o spec descreve: `GetExtendedTcpTable` devolve + conexoes com o PID dono, nao contadores de trafego. A tela mostra conexoes e diz isso. + +### Fase 4 - Desinstalador e gerenciador de inicializacao (2026-09-14) + +#### Adicionado +- Inventario de aplicativos (secao 5.3) lendo registro (32 e 64 bits, maquina e usuario), + Microsoft Store e UserAssist. Medido: 217 apps, 113 da Store, 78 protegidos. +- Desinstalacao em lote, sempre pelo desinstalador do proprio app: QuietUninstallString, + depois `msiexec /x {GUID} /qn`, depois `Remove-AppxPackage`, e por ultimo o desinstalador + com janela. Limite de 10 minutos por app. Saidas 0, 3010 e 1605 contam como sucesso. +- Varredura de restos depois de cada desinstalacao, com remocao para a Lixeira. +- Aba "Restos de apps antigos": varre %APPDATA%, %LOCALAPPDATA% e %PROGRAMDATA% e lista + pastas sem app correspondente, com tamanho e data. Medido: 30 pastas, 3,9 GB. +- Gerenciador de inicializacao (secao 5.6): chaves Run, pastas Inicializar e o estado do + StartupApproved. Desativar grava o mesmo formato de 12 bytes do Gerenciador de Tarefas, + com ChangeRecord antes de cada escrita. Medido: 26 entradas, 4 bloqueadas, 8 sugeridas. +- Verificacao de assinatura Authenticode com WinVerifyTrust, para mostrar o fabricante. + +#### Corrigido +- O resumo dos apps anunciava 1812,4 GB numa maquina com 953 GB. Eram duas contas erradas: + app que declara InstallLocation generica (media a pasta inteira) e varios apps declarando + a MESMA pasta, contada uma vez por app. Agora sao 1044 GB para 2145 GB de disco. +- A lista vinha ordenada pelo tamanho DECLARADO no registro, medido so depois: o ARK, com + 309 GB reais, aparecia atras do AutoCAD, que declara 4 GB. +- App da Store cujo executavel mora em WindowsApps era acusado de "sem assinatura digital". + A ACL de la barra ate o administrador; nao dava para verificar, e agora o texto diz isso. +- O impacto no boot aparecia duas vezes na mesma linha. +- `WinVerifyTrust` rodava duas vezes por entrada de inicializacao. + +#### Notas +- Desinstalacao nao tem desfazer automatico, e a tela diz isso antes de confirmar. +- `Get-AppxPackage` so funciona com `-EncodedCommand` e com as duas saidas lidas: com + `-Command` o PowerShell reinterpreta pipes, e sem ler o stderr o buffer de progresso + CLIXML enche e o processo trava. Ver docs/DECISOES.md. +- Nada e pre-marcado nesta fase, em nenhuma das tres listas (regra 3). + +### Fase 3 - Analisador de espaco e biblioteca de jogos (2026-09-14) + +#### Adicionado +- Varredura de volume com FindFirstFileEx e FIND_FIRST_EX_LARGE_FETCH, paralelizada por + subarvore. Medido: 1.548.745 arquivos e 809,5 GB em 20,3 s, acima do volume do criterio + da secao 5.4 e abaixo do tempo. +- Treemap navegavel com algoritmo squarified, colorido por categoria, com clique para + descer e trilha para voltar. +- Top 100 arquivos e top 50 pastas, com abrir no Explorer e enviar para a Lixeira. +- Categorias inteligentes: jogos, videos, imagens, instaladores, caches, sistema, + documentos, codigo e musica. A pasta manda mais que a extensao. +- Arquivos especiais (hiberfil.sys, pagefile.sys, Windows.old) explicados em vez de so + listados, com o risco de cada um. +- Biblioteca de jogos (secao 5.11) lendo Steam, Epic, GOG e Xbox, com tamanho e quando foi + jogado pela ultima vez. Jogo grande e parado ha mais de 6 meses ganha selo. + +#### Notas +- A MFT crua nao foi usada: FSCTL_ENUM_USN_DATA nao devolve o tamanho do arquivo. Ver + docs/DECISOES.md. +- O contador de pastas inacessiveis reportava 40.871; o numero real e 543. +- A arvore consumia 1,1 GB de RAM por guardar o caminho completo em cada no; agora sao + 594 MB. + +### Fase 2 - Limpeza e ferramentas rapidas (2026-09-13) + +#### Adicionado +- Modulo Cleaner (secao 5.2): 20 alvos de limpeza, do %TEMP% ao shader cache, com + pagina no shell e o comando `--clean --preset seguro|completo`. +- Whitelist de leitura separada da de remocao: a varredura pode olhar Downloads para + dizer quanto ha de instalador velho, sem nunca ganhar permissao de apagar de la. +- Remocao de conteudo do usuario sempre pela Lixeira (SHFileOperation com FOF_ALLOWUNDO). +- Historico de limpeza em cleanup-history.json. +- Ferramentas rapidas (secao 5.13): reiniciar Explorer e driver de video, limpar cache + de DNS, esvaziar Lixeira, abrir Limpeza de Disco, criar ponto de restauracao, sfc e + DISM com saida ao vivo, teste de velocidade de disco e informacoes do sistema. + +#### Notas +- Cada alvo de limpeza diz o efeito colateral antes: shader cache avisa do engasgo, + prefetch avisa que raramente compensa. Ha teste exigindo advertencia em todo alvo de + risco medio ou alto. +- RevertAsync do Cleaner diz que nao ha reversao automatica, em vez de fingir que desfaz. +- `ipconfig /flushdns` virou DnsFlushResolverCache, e Checkpoint-Computer virou WMI + SystemRestore: regra 9, sem chamar executavel quando ha API. +- A pagina de Limpeza nao tem XAML proprio: herda a tela inteira do ModulePageViewModel. + +### Shell de navegacao (2026-09-13) + +- Menu lateral com as 11 entradas da secao 6, tema escuro mantido. +- ModulePageViewModel: padrao de pagina de modulo reutilizavel, com cabecalho e Varrer, + lista generica de ActionItem, rodape com "Aplicar N selecionados" e "Reverter". +- Paginas dos modulos ainda nao implementados explicam o que farao e em qual fase chegam. +- Configuracoes com abas Preferencias e Historico, lendo o state-backup. + +### Fase 1 - Diagnostico de gargalos e relatorio de saude (2026-09-13) + +#### Adicionado +- Modulo Bottleneck (secao 5.5): coleta a cada 1 s com buffer de 5 minutos, medindo CPU, + memoria, Standby List, GPU, disco, rede e temperatura. +- 17 regras de diagnostico como classes IFindingRule testaveis, cada uma escrevendo em + linguagem humana: "Chrome esta usando 38% da CPU", nao "cpu_threshold_exceeded". +- Deteccao de reset por atualizacao do Windows: guarda a build e avisa quando um update + desfez os ajustes. Nenhum concorrente faz isso. +- FindingEngine com debounce de 30 s, entao a lista nao pisca a cada segundo. +- Pagina Diagnostico: cinco medidores ao vivo com grafico de 60 s, top 10 processos e a + lista de achados com botao de acao. +- Modulo HealthReport (secao 5.12): nota 0 a 100 por area com os 5 principais achados, + exportavel em HTML (arquivo unico, sem link externo) e JSON. +- Pagina Inicio mostra a nota de saude e exporta o relatorio. +- `GameBoost.exe --report saida.html` na CLI, com versao texto e `--json`. + +#### Desempenho +- Custo da coleta: **0,115% de CPU, ciclo de 36 ms**, contra o limite de 1,5% da secao 5.5. + A primeira versao media 5,02% com ciclo de 1039 ms. Ver docs/DECISOES.md. + +#### Notas +- Nada de PerformanceCounter por nome: num Windows pt-BR os contadores do spec nao existem + com aquele nome. Onde ha API nativa, usa-se API nativa. +- Temperaturas ficam "indisponivel" nesta fase: a leitura boa exige driver de sensores. + Nunca aparece zero no lugar. + +### Fase 0 - Fundacao (2026-09-13) + +#### Adicionado +- Solucao dividida em `GameBoost.Core` (sem WPF, testavel), `GameBoost.App` (WPF), + `GameBoost.Cli` (parser e runner) e `GameBoost.Tests` (xUnit + NSubstitute). +- `SafetyGuard`: blindagem central consultada por todos os modulos. Listas de processos + criticos, anti-cheats, antivirus (WMI + estatica), caminhos e servicos protegidos. +- `RollbackEngine` e `StateBackup`: toda alteracao de sistema grava o valor anterior antes + de ser aplicada e tem reversao testada. Reversao em ordem inversa; falha em um item nao + interrompe os demais. +- `IGameBoostLogger` com log estruturado e rotacao (5 MB x 5 arquivos). +- `AppSettings` com `schemaVersion` e migradores em sequencia; arquivo antigo vira `.bak`. +- Modo portatil: `portable.txt` ao lado do exe move os dados para `.\data\`. +- Contrato `IModule` / `ActionItem` e tela generica de lista com checkbox, badge de risco e + botao "?" com o que faz, qual o risco e como desfazer. +- Modo Game portado para o contrato: varredura com blindagem, encerramento gracioso com + fallback forcado, limpeza de RAM (`EmptyWorkingSet` + purga da Standby List), plano de + energia, prioridade do jogo, silenciamento do sistema e pausa do Windows Update. +- Deteccao de jogo por nome conhecido, pasta de launcher e heuristica de consumo. +- CLI com a superficie da secao 8 do spec; `--scan` preserva o comportamento da v1. +- `--dry-run` funcional em todos os caminhos: mesmo relatorio, zero escrita. +- Modo somente leitura quando o app roda sem privilegios de administrador. +- Aviso na abertura quando o app foi fechado com o Modo Game ativo. +- CI no GitHub Actions: build, testes, publish e instalador. +- Documentacao: `CLAUDE.md`, `docs/PROTECAO.md`, `docs/TWEAKS.md`, `docs/DECISOES.md`, + `docs/QA.md`. + +#### Notas +- A v1.0.0 foi reconstruida a partir do `LEIA-ME.txt` porque o codigo-fonte original nao + existia mais. Ver `docs/DECISOES.md`. +- `--report` e `--clean` estao no parser mas respondem com a fase em que chegam. + +## [1.0.0] - 2026-09-03 + +Versao original. Modo Game com encerramento de processos, limpeza de RAM, plano de energia, +prioridade do jogo, silenciamento do sistema, snapshot e reversao, e `--scan`. +Codigo-fonte perdido. diff --git a/GameBoost/CLAUDE.md b/GameBoost/CLAUDE.md new file mode 100644 index 0000000..e0c17da --- /dev/null +++ b/GameBoost/CLAUDE.md @@ -0,0 +1,100 @@ +# CLAUDE.md — GameBoost + +Regras resumidas para quem escreve código neste repositório. Geradas a partir da seção 2 +do `GAMEBOOST_V2_SPEC.md`. **Valem para todo código novo, sem exceção.** + +--- + +## As 10 regras inegociáveis + +1. **Reversível ou não existe.** Toda alteração de sistema (registro, serviço, plano de + energia, tweak) grava o valor anterior num `ChangeRecord` em `state-backup.json` **antes** + de aplicar, e tem caminho de reversão testado em `RollbackEngineTests`. +2. **Nunca deletar sem lixeira ou confirmação.** Arquivos de usuário vão para a Lixeira + (`SHFileOperation` com `FOF_ALLOWUNDO`). Temporários de sistema podem ser apagados direto, + mas só das pastas da whitelist do módulo. +3. **Nada é pré-marcado se puder causar perda.** Jogos, launchers, IDEs com trabalho aberto e + apps de gravação aparecem na lista, nunca marcados. +4. **Honestidade sobre ganho.** Cada ação mostra o ganho **medido** (MB liberados, apps + fechados, segundos de boot). Proibido inventar número. Proibido "limpeza de registro". +5. **Sem telemetria, sem anúncios, sem upsell.** Nada sai da máquina sem o usuário clicar em + "Enviar diagnóstico". +6. **Blindagem primeiro.** `ISafetyGuard` é consultado por **todos** os módulos, não só pelo + Modo Game. +7. **Tudo em PT-BR.** Strings de UI em recurso para permitir EN depois. +8. **Log de tudo.** Toda ação grava linha estruturada em `gameboost.log` + (timestamp, módulo, ação, alvo, resultado) via `IGameBoostLogger`. +9. **Sem PowerShell quando houver API.** Win32/WMI/CIM via P/Invoke ou `System.Management`. + PowerShell só para o que não tem API razoável (`Get-AppxPackage`), sempre com + `-NoProfile -NonInteractive -ExecutionPolicy Bypass`. +10. **Nunca travar a UI.** Toda varredura roda em `Task` com `IProgress` e + `CancellationToken`. + +--- + +## Mapa de pastas + +``` +GameBoost/ +├── src/ +│ ├── GameBoost.Core/ Lógica pura, sem WPF. Testável. +│ │ ├── Abstractions/ Interfaces: IProcessService, IRegistryService, IFileSystem, IClock… +│ │ ├── Native/ P/Invoke centralizado (NativeMethods interno + Bridge público) +│ │ ├── Safety/ SafetyGuard, ProtectedProcesses/Paths/Services, AntivirusDetector +│ │ ├── State/ ChangeRecord, StateBackup, RollbackEngine +│ │ ├── Logging/ Logger estruturado, rotação 5 MB × 5 +│ │ ├── Settings/ AppPaths, AppSettings, SettingsStore, SettingsMigrator +│ │ ├── Services/ Implementações Windows das Abstractions +│ │ └── Modules/ +│ │ ├── IModule.cs, ActionItem.cs, RiskLevel.cs +│ │ └── GameMode/ GameModeModule, GameDetector, SystemSilencer, GameSession +│ ├── GameBoost.App/ WPF: Views, ViewModels, Converters, Themes +│ ├── GameBoost.Cli/ Parser e runner dos comandos de linha +│ └── GameBoost.Tests/ xUnit + NSubstitute +├── installer/GameBoost.iss +└── docs/ DECISOES.md, PROTECAO.md, TWEAKS.md, QA.md +``` + +--- + +## Contratos que todo módulo novo segue + +```csharp +public interface IModule +{ + string Id { get; } + Task ScanAsync(IProgress? progress, CancellationToken ct); + Task ApplyAsync(IReadOnlyList itemIds, bool dryRun, CancellationToken ct); + Task RevertAsync(IReadOnlyList changeIds, bool dryRun, CancellationToken ct); +} +``` + +Cada item acionável é um `ActionItem { Id, Categoria, Titulo, Descricao, Risco, +GanhoEstimado, PreMarcado, ComoDesfazer, Bloqueado, MotivoBloqueio }`. A UI é genérica: uma +lista de `ActionItem` com checkbox, badge de risco e botão "?". + +**Todo módulo precisa de `dryRun` funcional**: mesmo relatório, zero escrita. Existe teste +provando que os fakes não recebem chamadas de escrita. + +--- + +## Padrões + +- **MVVM** com CommunityToolkit.Mvvm (`[ObservableProperty]`, `[RelayCommand]`). +- **DI** com `Microsoft.Extensions.DependencyInjection`, montado em `CoreServices`. + Classe de módulo recebe interfaces, nunca `static`. +- **Elevação**: `requireAdministrator` no manifest. Sem admin o app entra em modo somente + leitura: varreduras funcionam, ações ficam desabilitadas com aviso. +- **Trimming**: proibido. Quebra WPF e reflexão. + +--- + +## Antes de abrir PR + +```powershell +dotnet build GameBoost.sln # 0 erros, 0 avisos +dotnet test GameBoost.sln # tudo verde +``` + +Qualquer decisão que fuja do `GAMEBOOST_V2_SPEC.md` vai para `docs/DECISOES.md` com data e +motivo. Tweak novo entra em `docs/TWEAKS.md`; lista de proteção nova, em `docs/PROTECAO.md`. diff --git a/GameBoost/Directory.Build.props b/GameBoost/Directory.Build.props new file mode 100644 index 0000000..ffe4cd8 --- /dev/null +++ b/GameBoost/Directory.Build.props @@ -0,0 +1,20 @@ + + + + 2.0.0 + GameBoost + Jonathan Vaz + Jonathan Vaz + Copyright (c) 2026 Jonathan Vaz + + latest + enable + enable + false + nullable + pt-BR + false + + false + + diff --git a/GameBoost/GAMEBOOST_V2_SPEC.md b/GameBoost/GAMEBOOST_V2_SPEC.md new file mode 100644 index 0000000..e85a774 --- /dev/null +++ b/GameBoost/GAMEBOOST_V2_SPEC.md @@ -0,0 +1,626 @@ +# GameBoost v2 · Especificação completa para o Claude Code + +> Documento de trabalho para o Claude Code evoluir o GameBoost de "booster de jogos" para "central de otimização e manutenção do PC gamer". Leia inteiro antes de escrever código. As regras da seção 2 valem para todo o projeto. + +--- + +## 0. Como usar este documento + +1. Abra o repositório do GameBoost (`src/GameBoost/GameBoost.csproj`, `installer/GameBoost.iss`). +2. Rode a Fase 0 (seção 12) antes de qualquer feature nova: refatorar para módulos, criar a infraestrutura compartilhada. +3. Implemente as fases na ordem. Cada fase termina com build limpo, testes passando e um commit. +4. Toda decisão que fugir deste documento deve ser registrada em `docs/DECISOES.md` com data e motivo. + +--- + +## 1. Estado atual (v1.0.0) + +**Stack**: C# / .NET 9 / WPF, publish self-contained single-file, instalador Inno Setup 6, requer admin (manifest `requireAdministrator`). + +**O que já existe e deve ser preservado:** + +| Recurso | Implementação atual | +|---|---| +| Modo Game (ativar/desativar) | Varredura de processos, tela de confirmação pré-marcada, encerramento gracioso (3 s) depois forçado | +| Reabertura de apps essenciais (★) | `session.json` guarda caminho + argumentos | +| Limpeza de RAM | `EmptyWorkingSet` em todos os processos + purga da Standby List (`NtSetSystemInformation` / `SystemMemoryListInformation`) | +| Plano de energia | Alto desempenho / Desempenho Máximo, restaura o anterior | +| Prioridade do jogo | Detecção do jogo e prioridade Alta (nunca Realtime) | +| Silenciar sistema | Notificações, Game DVR, Game Bar overlay, pausa Windows Update | +| Blindagem | Lista de processos críticos, tudo em `C:\Windows`, antivírus/EDR via WMI, drivers, outras sessões | +| Snapshot e reversão | `state-backup.json`, botão "Reverter tudo", aviso na abertura se ficou ativo, reversão no desinstalador | +| Diagnóstico | `GameBoost.exe --scan relatorio.txt` | +| Persistência | `%LOCALAPPDATA%\GameBoost\` (`settings.json`, `session.json`, `state-backup.json`, `gameboost.log`) | + +**Limitações conhecidas da v1:** +- Uma única função (Modo Game). Usuário instala, usa uma vez, esquece. +- Sem visão de "o que está pesando" na máquina. O usuário não sabe por que o PC está lento. +- Sem manutenção contínua (limpeza, inicialização, espaço em disco). +- Sem assinatura digital (SmartScreen e falsos positivos de antivírus). +- Sem auto-update. + +--- + +## 2. Regras inegociáveis (valem para todo código novo) + +1. **Reversível ou não existe.** Toda alteração de sistema (registro, serviço, plano de energia, tweak) grava o valor anterior em `state-backup.json` antes de aplicar e tem um caminho de reversão testado. +2. **Nunca deletar sem lixeira ou confirmação.** Arquivos de usuário vão para a Lixeira (`SHFileOperation` com `FOF_ALLOWUNDO`). Arquivos de sistema temporários podem ser deletados direto, mas só das pastas da whitelist (seção 5.2). +3. **Nada é pré-marcado se puder causar perda.** Jogos, launchers, IDEs com trabalho aberto, apps de gravação: aparecem, nunca vêm marcados. +4. **Honestidade sobre ganho.** Cada ação mostra o ganho real medido (MB liberados, apps fechados, segundos de boot economizados). Proibido inventar número. Proibido "limpeza de registro" (não traz ganho mensurável e é risco puro). +5. **Sem telemetria, sem anúncios, sem upsell.** Nada sai da máquina sem o usuário clicar em "Enviar diagnóstico". +6. **Blindagem primeiro.** As listas de proteção (seção 9) são aplicadas em todos os módulos, não só no Modo Game. +7. **Tudo em PT-BR**, com strings em arquivo de recursos (`Resources/Strings.pt-BR.resx`) para permitir EN depois. +8. **Log de tudo.** Toda ação grava linha estruturada em `gameboost.log` (timestamp, módulo, ação, alvo, resultado). +9. **Sem PowerShell quando houver API.** Prefira Win32/WMI/CIM via P/Invoke ou `System.Management`. PowerShell só para o que não tem API razoável (ex.: `Get-AppxPackage`), e sempre com `-NoProfile -NonInteractive -ExecutionPolicy Bypass`. +10. **Nunca travar a UI.** Toda varredura roda em `Task` com `IProgress` e `CancellationToken`. + +--- + +## 3. Pesquisa de mercado + +> Referência de concorrentes e do que os usuários pedem. Validado com fontes de 2026 (guias de otimização, reviews e fóruns) em 13/09/2026. Fontes principais listadas em 3.5. + +### 3.1 Concorrentes diretos (boosters de jogos) + +| Produto | Preço | Pontos fortes | Reclamações recorrentes dos usuários | +|---|---|---|---| +| **Razer Cortex** | Grátis | Ainda o "free all-rounder" mais citado em 2026: suspende serviços, limpa RAM, restaura ao sair, contador de FPS, launcher unificado | Anúncios e promoções embutidos, ganho mínimo em PC bom, relatos de conflito com apps e jogos, consumo próprio de recursos, exige conta | +| **Wise Game Booster** | Grátis | Leve, simples, gerencia serviços e processos | Interface datada, sem monitoramento, pouco mantido | +| **IObit Advanced SystemCare / Game Booster** | Freemium (~US$20/ano) | Suite completa (limpeza, startup, drivers, privacidade) | Bloatware, pop-ups agressivos, instala outros produtos IObit, "limpeza de registro" placebo | +| **Smart Game Booster** | Freemium | Overlay FPS, monitor de temperatura, auto-boost | Falsos positivos de antivírus, upsell constante | +| **Game Fire / GearUP Booster / Chris-PC Game Booster** | Freemium | Preset por jogo antes de abrir, restauração ao sair, teste antes/depois dentro do app (Game Fire e GearUP) | Pouco conhecidos, parte das funções paga | +| **Process Lasso** | Freemium | Regras persistentes por processo, ProBalance, afinidade de CPU. Sempre citado como "o que funciona de verdade" | Curva de aprendizado, não é pensado pra leigo | +| **Windows Game Mode + Xbox Game Bar** | Grátis (nativo) | Integração de sistema, sem instalação | Game Bar consome recursos, Game Mode faz pouco, sem controle fino | +| **AMD Adrenalin / NVIDIA App** | Grátis | Overlay, gravação, tweaks de driver | Só GPU, não cuida de processos/disco | + +### 3.2 Concorrentes indiretos (limpeza e manutenção) + +| Categoria | Referências | O que fazem bem | +|---|---|---| +| Limpeza | CCleaner (Piriform/Gen), BleachBit (open source), Wise Disk Cleaner, Windows Storage Sense | Presets por app (navegadores, Discord, Steam shader cache), agendamento | +| Análise de disco | **WizTree 4.30 (mar/2026)**: lê a MFT, varre SSD grande em segundos, trata hard links, treemap, top arquivos, busca rápida, export CSV e localizador de duplicados desde a v4. WinDirStat, TreeSize Free, SpaceSniffer | É a referência unânime em 2026 ("free, blindingly fast"). Não apaga nada sozinho, só mostra e abre no Explorer. Guias sempre listam os mesmos suspeitos: hiberfil.sys, Windows.old (20 GB+ após update), temp, jogos de 80 a 150 GB, backups velhos | +| Desinstalação | **Bulk Crap Uninstaller** (Apache 2.0, acha desinstaladores quebrados/ocultos, portáteis, Store, Windows Features, Chocolatey, apps de lojas de jogos), **Revo Uninstaller Free 2.7.0 (mai/2026)** com Hunter Mode pra apps da Store e restos melhorados, Geek Uninstaller | Varredura de restos após desinstalar (com aviso de que não é garantida), desinstalação silenciosa em lote, remoção de bloatware da Store | +| Inicialização | Autoruns (Sysinternals), Gerenciador de Tarefas, Startup Delayer | Mostrar impacto real no boot, tarefas agendadas, serviços | +| Gerência de processos | Process Lasso, Process Explorer | Regras por processo persistentes, afinidade de CPU, ProBalance | +| Tweaks | Chris Titus WinUtil, Winaero Tweaker, O&O ShutUp10++, Atlas OS / ReviOS | Tweaks documentados, reversíveis, com explicação de cada um | +| Monitoramento | HWiNFO64, MSI Afterburner + RTSS, LibreHardwareMonitor, LatencyMon | Temperaturas, clocks, throttling, DPC latency, overlay em jogo | +| Drivers | Driver Booster (IObit), DDU, Snappy Driver Installer | Detectar driver de GPU desatualizado (mas instalar driver automaticamente é risco) | + +### 3.3 O que os usuários realmente pedem (Reddit r/pcgaming, r/Windows11, r/pcmasterrace, Steam forums, reviews da Microsoft Store) + +**Querem:** +1. Saber **o que está pesando** o PC agora (o "por que está lento?"). +2. **Espaço em disco**: onde estão os GB perdidos (shader cache, Windows.old, downloads antigos, jogos que não jogam mais). +3. **Desinstalar de verdade**, inclusive bloatware da Store e apps que reaparecem. +4. **Inicialização limpa**: o PC demora pra ligar e não sabem o que desativar com segurança. +5. **Ganho de FPS honesto**: dizer claramente o que faz diferença (fechar Chrome/Discord overlay, plano de energia, driver de GPU, HAGS, Fullscreen Optimizations) e o que é placebo. +6. **Leve, sem anúncio, sem conta, sem assinatura**, de preferência portátil. +7. **Explicação em cada opção**: "o que isso faz, qual o risco, como desfazer". +8. **Não quebrar nada**: medo de tweak que trava Windows Update, some com o Wi-Fi, quebra a impressora. +9. **Perfis por jogo**: ao abrir Valorant faz X, ao abrir Cyberpunk faz Y. +10. **Temperatura e throttling**: saber se o FPS caiu porque a CPU/GPU está esquentando. + +**Odeiam:** +- Limpeza de registro, "otimização de DNS mágica", "acelerador de RAM" sem número. +- Pop-up de upgrade, instalação de toolbar, envio de dados. +- App que fecha o anti-cheat ou o próprio jogo. +- Alterações sem explicação e sem "desfazer". + +### 3.4 Consenso técnico de 2026 sobre o que realmente dá ganho + +Os guias mais sérios convergem: cerca de seis ajustes explicam quase todo o ganho real, o resto é placebo, já é padrão ou troca segurança por poucos %. + +1. **Plano de energia em Alto desempenho / Melhor desempenho** (evita clock baixo). +2. **Podar inicialização e apps em background** (junto com o item 1, são os dois de maior valor e os mais fáceis de reverter). +3. **Game Mode do Windows ligado** (versão 22H2+ ajuda e evita Windows Update durante o jogo; só desligar se houver conflito). +4. **Game Bar / gravação em segundo plano desligados.** +5. **Jogo em SSD** (lançamentos de 2025/2026 já exigem SSD e engasgam em HDD). +6. **Driver de GPU atualizado da fonte oficial** e monitor configurado na taxa de atualização certa (Windows volta pra 60 Hz depois de driver ou troca de cabo com frequência). + +Pontos que exigem teste, não regra: +- **HAGS**: obrigatório para DLSS Frame Generation; melhora frame pacing em GPUs recentes (RTX 30+, RX 6000+) com driver atual, mas causa stutter em CPUs antigas ou certas versões de driver. É "o único ajuste que você precisa testar em vez de só ligar". Custa até 1 GB de VRAM (atenção em placas de 8 GB). +- **VBS / Memory Integrity (Core Isolation)**: vem ligado por padrão no Windows 11 e os guias medem 5 a 15% de FPS em jogos CPU-bound (até 25% em casos extremos). É recurso de segurança contra malware de kernel. Consenso: desligar só em PC exclusivo de jogo, manter em máquina de trabalho/banco. Por isso o GameBoost **informa e mede, mas não desliga**: mostra o estado, o impacto estimado e o caminho nas Configurações do Windows. +- **Updates grandes do Windows (24H2, 26H2) resetam** Game Mode, HAGS, plano de energia, efeitos visuais e Game Bar. O GameBoost deve detectar isso e avisar "seu perfil foi desfeito por uma atualização". + +Placebo ou risco confirmados: limpeza de registro, "defrag" em SSD, updaters de driver automáticos, tweaks agressivos de serviço. + +### 3.5 Fontes consultadas (set/2026) +- Frozen Tweaks, Gear-Rank, SmoothFPS, Switchblade Gaming, TechFixGrid: guias de otimização Windows 11 2026 (VBS, HAGS, Game Mode, 26H2). +- iTechGuides, TechBloat, TechBre, Techlasi, Gitnux, Worldmetrics: rankings de boosters 2026. +- Neat Net Tricks e Swastiktechnology: reviews WizTree 2026; RottenWifi: BCU, Revo 2.7.0, Storage Sense. +- Steam Community e Tom's Guide: percepção de usuário sobre boosters ("snake oil", "bloatware em PC bem cuidado"). +- Microsoft Learn: SmartScreen reputation, opções de assinatura de código, Azure Artifact Signing. + +### 3.6 Posicionamento do GameBoost v2 + +**"O booster que mostra o que faz e desfaz tudo."** Diferenciais: +- Transparência total (cada botão explica antes, mede depois, reverte sempre). +- Zero anúncios, zero telemetria, PT-BR nativo. +- Diagnóstico de gargalo em linguagem humana ("seu Chrome está usando 40% da CPU com 62 abas"). +- Portátil opcional (mesmo exe roda sem instalar). +- **Prova de ganho**: medição antes/depois em cada ação (Game Fire e GearUP já fazem isso e os reviews valorizam muito). +- **Detector de reset pós-update**: nenhum concorrente avisa que o Windows desfez os ajustes. + +--- + +## 4. Arquitetura alvo + +### 4.1 Estrutura de pastas + +``` +GameBoost/ +├── src/ +│ ├── GameBoost.Core/ # Lógica pura, sem WPF. Testável. +│ │ ├── Abstractions/ # Interfaces (IProcessService, IRegistryService, IFileSystem, IClock...) +│ │ ├── Native/ # P/Invoke centralizado (Kernel32, Psapi, NtDll, Advapi32, Shell32, PowrProf) +│ │ ├── Safety/ # Blindagem: ProtectedProcesses, ProtectedPaths, ProtectedServices, AntiCheatList +│ │ ├── State/ # Snapshot/rollback (StateBackup, ChangeRecord, RollbackEngine) +│ │ ├── Logging/ # Logger estruturado (JSON lines + texto legível) +│ │ ├── Settings/ # Settings, perfis, migração de versão +│ │ └── Modules/ +│ │ ├── GameMode/ # (existente, refatorado) +│ │ ├── Cleaner/ # Limpeza de temporários e caches +│ │ ├── Uninstaller/ # Desinstalador Win32 + Store + restos +│ │ ├── DiskAnalyzer/ # Análise de espaço (MFT + fallback) +│ │ ├── Bottleneck/ # Monitor e diagnóstico de gargalos +│ │ ├── Startup/ # Inicialização, tarefas agendadas +│ │ ├── Services/ # Serviços do Windows +│ │ ├── Tweaks/ # Tweaks de jogos reversíveis +│ │ ├── Network/ # Velocidade, NAT (STUN), UPnP, Teredo, firewall +│ │ ├── Drivers/ # Verificação de drivers de GPU +│ │ ├── Profiles/ # Perfis por jogo +│ │ └── HealthReport/ # Relatório e pontuação +│ ├── GameBoost.App/ # WPF: Views, ViewModels, Converters, Themes +│ │ ├── Views/ +│ │ ├── ViewModels/ +│ │ ├── Controls/ # Treemap, Gauge, ProcessList, ConfirmDialog +│ │ ├── Themes/ # Dark.xaml (padrão), Light.xaml +│ │ └── Resources/Strings.pt-BR.resx +│ ├── GameBoost.Cli/ # Modo linha de comando (--scan, --clean, --report) +│ └── GameBoost.Tests/ # xUnit + NSubstitute +├── installer/GameBoost.iss +├── docs/ +│ ├── DECISOES.md +│ ├── TWEAKS.md # Catálogo de cada tweak: chave, valor, efeito, risco, fonte +│ ├── PORTAS.md # Portas por jogo/launcher para UPnP e firewall +│ └── PROTECAO.md # Listas de blindagem documentadas +├── CLAUDE.md # Regras resumidas para o Claude Code (gerar a partir da seção 2) +└── README.md +``` + +### 4.2 Padrões + +- **MVVM** com CommunityToolkit.Mvvm (`[ObservableProperty]`, `[RelayCommand]`). +- **DI** com `Microsoft.Extensions.DependencyInjection`. Toda classe de módulo recebe interfaces, nunca `static`. +- **Cada módulo expõe** `IModule` com: `ScanAsync(progress, ct)` → `ScanResult`, `ApplyAsync(selection, ct)` → `ApplyResult`, `RevertAsync(changeIds)`. +- **Cada item acionável** é um `ActionItem { Id, Categoria, Titulo, Descricao, Risco (Baixo/Medio/Alto), GanhoEstimado, PreMarcado, ComoDesfazer }`. A UI é genérica: uma lista de `ActionItem` com checkbox, badge de risco e botão "?" com a explicação. +- **Elevação**: manter `requireAdministrator`. Adicionar modo "somente leitura" quando rodar sem admin (varreduras funcionam, ações ficam desabilitadas com aviso). + +--- + +## 5. Módulos: especificação funcional + +Cada módulo abaixo tem: objetivo, fonte de dados/API, itens gerados, regras, UI, critérios de aceite. + +### 5.1 Modo Game (existente, melhorias) + +**Melhorias:** +- Detecção automática de jogo: monitorar criação de processos (WMI `Win32_ProcessStartTrace` ou polling a cada 2 s) e cruzar com (a) lista de jogos conhecidos, (b) executáveis dentro de `steamapps/common`, `Epic Games`, `Riot Games`, `Battle.net`, `GOG Galaxy/Games`, `XboxGames`, (c) janela em fullscreen exclusivo/borderless ocupando 100% da tela. Ao detectar, oferecer "Ativar Modo Game para ?" via notificação (ou ativar direto se o perfil do jogo mandar). +- Timer resolution: `NtSetTimerResolution(0.5 ms)` enquanto Modo Game ativo (Windows 11 já lida bem, mas em 10 ajuda). Reverter ao sair. +- Afinidade de CPU opcional para o jogo (perfil avançado): permitir reservar núcleos. Padrão desligado. +- Modo "Só o essencial": encerrar apenas processos com >2% CPU ou >300 MB RAM nos últimos 30 s (evita lista gigante). +- Exibir ganho: RAM livre antes/depois, nº de processos fechados, % CPU em background antes/depois. + +**Critérios de aceite:** ativar e desativar 10 vezes seguidas sem vazar estado; matar o app durante o modo ativo e reabrir mostra o aviso de restauração; jogo nunca aparece pré-marcado. + +### 5.2 Limpeza de arquivos temporários e caches + +**Objetivo:** liberar espaço com segurança, mostrando o que será apagado e quanto ocupa. + +**Categorias e alvos (whitelist, só estes caminhos):** + +| Categoria | Caminhos | Risco | Pré-marcado | +|---|---|---|---| +| Temp do usuário | `%TEMP%` (arquivos não bloqueados, >24 h) | Baixo | Sim | +| Temp do sistema | `C:\Windows\Temp` | Baixo | Sim | +| Prefetch | `C:\Windows\Prefetch\*.pf` mais antigos que 30 dias | Baixo | Não (afeta boot por alguns dias) | +| Windows Update cache | `C:\Windows\SoftwareDistribution\Download` (parar serviço `wuauserv` antes) | Baixo | Sim | +| Delivery Optimization | via `IDOManager` ou `Get-DeliveryOptimizationStatus`; simples: `Clear-DeliveryOptimizationCache` não existe, usar Limpeza de Disco API `cleanmgr /sageset` presets ou pasta `%WINDIR%\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Windows\DeliveryOptimization\Cache` | Baixo | Sim | +| Windows.old | `C:\Windows.old` (só se existir; usar `cleanmgr` handler "Previous Windows installation(s)" ou takeown + delete) | Médio | Não | +| Lixeira | `SHEmptyRecycleBin` | Médio | Não | +| Miniaturas / cache de ícones | `%LOCALAPPDATA%\Microsoft\Windows\Explorer\thumbcache_*.db`, `iconcache_*.db` | Baixo | Não | +| Logs do Windows | `C:\Windows\Logs\CBS\*.log` antigos, `%WINDIR%\Panther` (não), `C:\ProgramData\Microsoft\Windows\WER\ReportQueue` | Baixo | Sim (só WER) | +| Dumps de memória | `C:\Windows\Minidump`, `C:\Windows\MEMORY.DMP` | Baixo | Sim | +| Cache de navegadores | Chrome/Edge/Brave/Opera (`User Data\\Cache`, `Code Cache`, `GPUCache`), Firefox (`cache2`). NUNCA cookies, senhas, histórico | Baixo | Sim, só se navegador fechado | +| Discord | `%APPDATA%\discord\Cache`, `Code Cache`, `GPUCache` | Baixo | Sim, se fechado | +| Spotify | `%LOCALAPPDATA%\Spotify\Storage`, `Data` | Baixo | Não | +| Shader cache | NVIDIA: `%LOCALAPPDATA%\NVIDIA\DXCache`, `%LOCALAPPDATA%\NVIDIA\GLCache`, `%PROGRAMDATA%\NVIDIA Corporation\NV_Cache`; AMD: `%LOCALAPPDATA%\AMD\DxCache`, `GLCache`; DirectX: `%LOCALAPPDATA%\D3DSCache`; Steam: `Steam\steamapps\shadercache` | Médio (jogos vão engasgar ao recompilar) | Não, com explicação | +| Steam | `Steam\appcache\httpcache`, `Steam\logs`, `Steam\dumps` | Baixo | Sim | +| Epic / Riot / Battle.net | Logs e caches de cada launcher (documentar caminhos em `TWEAKS.md`) | Baixo | Sim | +| Instaladores esquecidos | `%USERPROFILE%\Downloads\*.exe|*.msi|*.zip` > 30 dias | Alto | Nunca. Apenas listar em "Sugestões" | + +**Regras:** +- Varredura calcula tamanho por categoria antes de mostrar. Exibir total "até X GB". +- Arquivos em uso (`IOException` sharing violation) são pulados e contados em "não foi possível remover N arquivos (em uso)". +- Cache de navegador/Discord só limpa se o processo não estiver rodando; senão, oferece "Fechar e limpar". +- Nunca tocar em: `AppData\Roaming` de jogos (saves), `Documents`, `Pictures`, `OneDrive`, qualquer pasta com `.git`. +- Guardar histórico: `cleanup-history.json` com data, categorias, MB liberados. +- Agendamento opcional: tarefa no Agendador (`schtasks`) semanal com `GameBoost.exe --clean --preset seguro`. + +**Critérios de aceite:** rodar limpeza duas vezes seguidas, segunda vez libera ~0; nenhum arquivo fora da whitelist é tocado (teste com `IFileSystem` fake registrando todos os caminhos); tamanho reportado bate com o `du` real com margem de 5%. + +### 5.3 Desinstalador de aplicativos + +**Objetivo:** listar tudo instalado (Win32, Store/UWP, portáteis detectados), permitir desinstalação em lote e limpar restos. + +**Fontes:** +- Registro: `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, `HKLM\SOFTWARE\WOW6432Node\...\Uninstall`, `HKCU\...\Uninstall`. Ler `DisplayName`, `DisplayVersion`, `Publisher`, `InstallDate`, `InstallLocation`, `EstimatedSize`, `UninstallString`, `QuietUninstallString`, `WindowsInstaller` (MSI → `msiexec /x {GUID} /qn`). +- Store/UWP: `Get-AppxPackage -AllUsers` via PowerShell (ou `PackageManager` da WinRT via `Windows.Management.Deployment`). Marcar `NonRemovable`, `IsFramework` e pacotes de sistema como protegidos. +- Uso real: cruzar com `HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist` (decodificar ROT13) e `LastWriteTime` dos executáveis para mostrar "último uso" e "nunca usado". +- Tamanho real: `EstimatedSize` do registro é pouco confiável; se `InstallLocation` existir, calcular tamanho da pasta em background. + +**Lista de bloatware conhecido (sugestão, nunca pré-marcado):** Candy Crush, Spotify (Store), TikTok, Xbox TCUI (não remover se for jogar), Microsoft Solitaire, 3D Viewer, Clipchamp, LinkedIn, McAfee/Norton trial, Booking.com, Disney+, Netflix (Store shortcut), OEM utilities (HP Wolf, Lenovo Vantage é útil, cuidado), Cortana, Feedback Hub, Get Help, Mixed Reality Portal. + +**Nunca desinstalar (protegido):** Visual C++ Redistributables, .NET Runtimes, DirectX, drivers de GPU/áudio/chipset, Microsoft Edge (WebView2 é dependência), Xbox Identity Provider e Xbox Game Services (anti-cheat e jogos do Game Pass dependem), antivírus ativo, qualquer app do publisher "Microsoft Corporation" da lista de frameworks. + +**Fluxo:** +1. Lista com busca, filtros (Store / Win32 / Grandes / Nunca usados / Bloatware sugerido), ordenação por tamanho e último uso. +2. Seleção múltipla → "Desinstalar N apps" → tela de confirmação com badge de risco. +3. Execução sequencial: preferir `QuietUninstallString`; se não houver, rodar `UninstallString` e mostrar aviso "este app abre o desinstalador próprio". Timeout de 10 min por app. +4. Após cada desinstalação, **varredura de restos**: pasta `InstallLocation` residual, `%APPDATA%\`, `%LOCALAPPDATA%\`, `%PROGRAMDATA%\`, chaves `HKCU\Software\` e `HKLM\Software\`. Só sugerir se o nome tiver match exato (nunca substring curta). Restos vão para a Lixeira / chave exportada em `.reg` antes de apagar. +5. Ponto de restauração do sistema opcional antes de lote grande (`Checkpoint-Computer` via PowerShell ou `SystemRestore` WMI), com aviso de que o Windows limita a 1 por 24 h por padrão. + +**Aba "Atualizações" (winget):** +- Verificar se o `winget` está disponível (`winget --version`; vem com o App Installer no Windows 10 1709+ e 11). Se não estiver, oferecer instalar pela Store (`ms-appinstaller:`). +- Listar desatualizados com `winget upgrade --include-unknown --accept-source-agreements --disable-interactivity` e parsear a saída (ou usar `--output json` quando disponível na versão instalada; detectar pela versão do winget). Colunas: nome, versão atual, versão nova, fonte (winget / msstore). +- Cruzar com a lista de instalados da aba principal pra mostrar tamanho e último uso ao lado. +- Ações: "Atualizar selecionados" (`winget upgrade --id --silent --accept-package-agreements --accept-source-agreements`) e "Atualizar tudo" (mesmo comando por item, em sequência, com progresso por app; evitar `--all` pra poder pular e mostrar erro por app). +- Regras: + - Nunca pré-marcar launchers e apps que se atualizam sozinhos (Steam, Epic, Discord, navegadores, Spotify, NVIDIA App): mostrar com tag "atualiza sozinho" e explicar que forçar pelo winget pode conflitar. + - Nunca atualizar drivers ou runtimes (Visual C++, .NET) sem o usuário marcar explicitamente; badge de risco Médio. + - Se um app estiver aberto, avisar "feche X antes" e oferecer fechar (reutilizando a lógica de encerramento gracioso do Modo Game). + - Apps com versão "desconhecida" (`--include-unknown`) ficam em grupo separado com aviso de que o winget não consegue comparar. + - Log de cada atualização em `updates-history.json`; se um update falhar, mostrar o código de erro do winget e link pro log em `%LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_*\LocalState\DiagOutputDir`. +- Finding no Diagnóstico: "N apps desatualizados (M de segurança: navegadores, Java, 7-Zip)". +- Agendamento opcional: checar semanalmente e notificar na bandeja, nunca atualizar sozinho. +- Também pode ser usado pra **desinstalar** apps que o winget conhece (`winget uninstall --id`), como alternativa quando o `UninstallString` estiver quebrado. + +**Critérios de aceite:** lista bate com "Aplicativos instalados" do Windows (±2 itens de diferença aceitável por apps ocultos); desinstalação silenciosa de um app MSI de teste funciona; restos de app removido são detectados; nenhum item protegido aparece selecionável. + +### 5.4 Analisador de espaço em disco + +**Objetivo:** responder "onde foram parar meus GB" em segundos, estilo WizTree. + +**Implementação:** +- **Caminho rápido (NTFS):** ler a MFT diretamente com `FSCTL_ENUM_USN_DATA` (`DeviceIoControl` em `\\.\C:`), reconstruir a árvore por `ParentFileReferenceNumber`, obter tamanho via `FSCTL_GET_RETRIEVAL_POINTERS` ou, mais simples, via `NtQueryDirectoryFile`/`FindFirstFileEx` com `FIND_FIRST_EX_LARGE_FETCH` só nos diretórios já enumerados. Alternativa aceitável na primeira versão: `FindFirstFileEx` recursivo paralelizado com `FIND_FIRST_EX_LARGE_FETCH`, que já é bem mais rápido que `DirectoryInfo`. +- **Fallback (FAT/exFAT/sem admin):** enumeração recursiva normal. +- Resultado em árvore com tamanho agregado, contagem de arquivos, extensão dominante. + +**UI:** +- **Treemap** (controle próprio em WPF, retângulos por tamanho, cor por categoria: jogos, vídeo, instaladores, cache, sistema, outros). Clique para descer nível, breadcrumb para voltar. +- **Top 100 maiores arquivos** e **Top 50 maiores pastas**. +- **Categorias inteligentes:** Jogos (pastas de launchers), Vídeos (>500 MB `.mp4/.mkv`), Instaladores (`.exe/.msi/.iso` em Downloads), Caches, Windows.old, Arquivos de hibernação (`hiberfil.sys`) e paginação (`pagefile.sys`) com explicação e opção "Desativar hibernação (`powercfg /h off`)" com badge de risco Médio. +- Ações por item: abrir no Explorer, mover para Lixeira, "Ignorar sempre". +- Duplicados (fase posterior): hash parcial (primeiros 64 KB + tamanho) e só depois hash completo dos candidatos. + +**Critérios de aceite:** varredura de 500 GB com 1 milhão de arquivos em < 30 s via MFT em SSD; treemap responde a clique em < 100 ms; nunca oferece apagar nada dentro de `C:\Windows`, `Program Files` (só via Desinstalador) ou pastas de saves. + +### 5.5 Diagnóstico de gargalos (o "por que está lento?") + +**Objetivo:** mostrar em tempo real e em linguagem simples o que está consumindo CPU, GPU, RAM, disco e rede, e quem é o culpado. + +**Coleta (intervalo 1 s, buffer de 5 min em memória):** +- **CPU:** total e por processo (`PerformanceCounter` ou delta de `Process.TotalProcessorTime`; preferir `NtQuerySystemInformation(SystemProcessInformation)` que traz tudo numa chamada). Frequência atual vs. base (`Win32_Processor.CurrentClockSpeed` é lento; usar contador `\Processor Information(_Total)\% Processor Performance`). +- **RAM:** `GlobalMemoryStatusEx`, Standby List, Commit, por processo (Working Set privado). +- **GPU:** contadores `\GPU Engine(*)\Utilization Percentage` e `\GPU Adapter Memory(*)\Dedicated Usage` (disponíveis no Windows 10 1709+). Por processo via instância do contador (`pid_XXXX_luid...`). Temperatura e clocks via **LibreHardwareMonitorLib** (NuGet, MPL 2.0) para NVIDIA/AMD/Intel. +- **Disco:** `\PhysicalDisk(*)\% Idle Time`, fila, MB/s; por processo via ETW (`Microsoft.Diagnostics.Tracing.TraceEvent`, kernel provider `DiskIO`) ou, mais simples, `Process` IO counters delta (`GetProcessIoCounters`). +- **Rede:** `\Network Interface(*)\Bytes Total/sec`; por processo via `GetExtendedTcpTable` + ETW `TcpIp` provider (fase posterior). +- **Temperaturas:** CPU package, GPU core, NVMe (LibreHardwareMonitor). Detectar **throttling**: CPU com `% Processor Performance` < 80% enquanto uso > 70% e temperatura > 90 °C. +- **Latência DPC/ISR** (opcional, avançado): ETW kernel `DPC`/`Interrupt` events para apontar driver problemático (estilo LatencyMon). Marcar como experimental. + +**Diagnóstico automático (regras, cada uma gera um `Finding` com texto humano e ação sugerida):** + +| Regra | Texto exemplo | Ação sugerida | +|---|---|---| +| Processo > 25% CPU por 30 s fora do jogo | "Chrome está usando 38% da CPU (62 abas abertas)" | Fechar / adicionar ao Modo Game | +| RAM disponível < 10% | "Só 1,2 GB de RAM livre. Discord + Chrome usam 4,1 GB" | Limpar RAM / fechar apps | +| Standby List > 40% da RAM | "6 GB de RAM presos em cache do sistema" | Purgar Standby | +| Disco do sistema > 90% ocupado | "C: com 94% de uso. SSD cheio fica lento" | Abrir Analisador de Disco | +| Disco com % Idle < 20% por 30 s | "Seu disco está no limite. Windows Search está indexando" | Pausar indexação / ver processo | +| GPU > 95% e FPS baixo | "GPU no máximo. Reduza resolução/qualidade ou ative DLSS/FSR" | Informativo | +| CPU > 95% e GPU < 60% em jogo | "Gargalo de CPU: o processador não alimenta a placa de vídeo" | Fechar apps / prioridade | +| Throttling térmico | "CPU a 97 °C reduzindo clock. Limpe o cooler / ajuste curva" | Informativo | +| Plano de energia balanceado em jogo | "Plano Balanceado ativo durante o jogo" | Ativar Alto desempenho | +| Driver de GPU > 6 meses | "Driver NVIDIA de fev/2026. Nova versão disponível" | Abrir página oficial | +| Muitos apps na inicialização | "23 programas iniciam com o Windows (boot estimado +48 s)" | Abrir Inicialização | +| Windows Search / Defender / Update usando disco | "Windows Defender fazendo varredura completa" | Informativo (nunca desativar Defender) | +| Xbox Game Bar / Game DVR ligado | "Gravação em segundo plano ativa" | Desativar (reversível) | +| Build do Windows mudou desde a última execução e tweaks/perfil não batem mais | "A atualização 26H2 desfez 4 ajustes seus" | Reaplicar perfil | +| Monitor a 60 Hz sendo capaz de mais (`EnumDisplaySettings`) | "Seu monitor de 165 Hz está em 60 Hz" | Abrir configurações de vídeo | +| VBS / Memory Integrity ativo | "Isolamento do núcleo ativo (custo típico 5 a 15% em jogos CPU-bound)" | Informativo com link | +| HDD como disco do jogo | "Jogo instalado em HDD. Mova para SSD" | Informativo | +| Memória em single channel | Via `Win32_PhysicalMemory` contagem de pentes | Informativo | + +**UI:** painel com 5 medidores (CPU, GPU, RAM, Disco, Rede) + temperatura, gráfico de 60 s, tabela "Top 10 processos" por recurso selecionado, e lista de `Findings` ordenada por impacto. Modo compacto "sempre no topo" (janela pequena) para deixar ao lado do jogo em segundo monitor. + +**Critérios de aceite:** coleta consome < 1,5% de CPU e < 60 MB de RAM; funciona sem LibreHardwareMonitor (temperaturas ficam "indisponível"); Findings não repetem a cada segundo (debounce de 30 s). + +### 5.6 Gerenciador de inicialização + +**Fontes:** `HKCU/HKLM\...\Run`, `RunOnce`, pasta Startup do usuário e comum, `HKLM\...\Explorer\StartupApproved` (estado ativado/desativado igual ao Gerenciador de Tarefas), Tarefas Agendadas com gatilho "no logon" (`Microsoft.Win32.TaskScheduler` NuGet ou `schtasks /query /xml`), serviços com início Automático de terceiros. + +**Dados por item:** nome, publisher, caminho, assinado digitalmente (sim/não, via `WinVerifyTrust` ou `X509Certificate.CreateFromSignedFile`), impacto no boot (ler `HKCU\...\Explorer\StartupApproved` não traz impacto; estimar via tamanho do exe + nº de DLLs importadas, ou usar dados de `Windows Performance` se disponíveis; mostrar como Baixo/Médio/Alto estimado). + +**Ações:** Desativar (gravar em `StartupApproved` com o mesmo formato do Gerenciador de Tarefas, para o Windows mostrar coerente), Atrasar (criar tarefa agendada com delay de N s e desativar a entrada original), Abrir local, Ver no Desinstalador. + +**Protegidos:** drivers de áudio/vídeo, antivírus, OneDrive (avisar), Bluetooth/touchpad OEM, SecurityHealth. Sugerir desativar: Spotify, Discord (se o usuário quiser abrir manualmente), Steam/Epic/EA app, Teams, Adobe Updater, iTunes Helper, Cortana. + +### 5.7 Serviços do Windows + +Lista de serviços com filtro "terceiros" e "otimizáveis para jogos". Tweaks reversíveis (guardar `StartType` anterior): + +| Serviço | Ação sugerida | Risco | +|---|---|---| +| `SysMain` (Superfetch) | Desativar só em SSD com <16 GB RAM? Não. Manter. Apenas explicar | Médio | +| `WSearch` | Manual (busca do Explorer fica lenta) | Baixo | +| `DiagTrack` (telemetria) | Desativar | Baixo | +| `XblAuthManager`, `XblGameSave`, `XboxNetApiSvc` | Manter se usa Game Pass / anti-cheat da MS. Explicar | Médio | +| `Fax`, `RemoteRegistry`, `WMPNetworkSvc`, `MapsBroker` | Desativar | Baixo | +| `Spooler` | Manual se não tem impressora | Baixo | +| `wuauserv` | Nunca desativar. Só pausar dentro do Modo Game (já existe) | Alto | +| Serviços de antivírus, `WinDefend`, `SecurityHealthService` | Protegido | Alto | + +### 5.8 Tweaks de jogos (catálogo documentado em `docs/TWEAKS.md`) + +Cada tweak: chave/valor, efeito, evidência, risco, reversão. Todos desligados por padrão, aplicados individualmente, com snapshot. + +| Tweak | Onde | Efeito real | Risco | +|---|---|---|---| +| Hardware-Accelerated GPU Scheduling (HAGS) | `HKLM\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\HwSchMode = 2` (reboot) | Obrigatório para DLSS Frame Gen; melhora frame pacing em RTX 30+/RX 6000+ com driver atual, piora em CPU antiga. Usa até 1 GB de VRAM. Oferecer "Testar com/sem" com benchmark rápido | Médio | +| Game Mode do Windows | `HKCU\Software\Microsoft\GameBar\AutoGameModeEnabled` | Positivo no 22H2+ (bloqueia Update durante o jogo). Recomendar ligado; desligar só se der conflito com streaming | Baixo | +| Game DVR / Game Bar | `HKCU\System\GameConfigStore\GameDVR_Enabled = 0`, `HKCU\Software\Microsoft\GameBar\UseNexusForGameBarEnabled = 0` | Reduz overhead de gravação | Baixo | +| Fullscreen Optimizations por exe | `HKCU\System\GameConfigStore` `GameDVR_FSEBehaviorMode`, e `HKCU\...\AppCompatFlags\Layers` `DISABLEDXMAXIMIZEDWINDOWEDMODE` por jogo | Depende do jogo; oferecer por perfil | Baixo | +| Multi-Plane Overlay (MPO) | `HKLM\SOFTWARE\Microsoft\Windows\Dwm\OverlayTestMode = 5` | Resolve stutter/flicker em alguns setups NVIDIA | Médio | +| Nagle (TcpAckFrequency, TCPNoDelay) | `HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\` | Ganho marginal em jogos com muitos pacotes pequenos; a maioria já usa UDP. Explicar honestamente | Baixo | +| Network Throttling Index | `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\NetworkThrottlingIndex = 0xFFFFFFFF` | Marginal | Baixo | +| SystemResponsiveness | mesma chave, `SystemResponsiveness = 0` | Prioriza foreground | Baixo | +| Games task priority | `...\SystemProfile\Tasks\Games` `GPU Priority = 8`, `Priority = 6`, `Scheduling Category = High` | Marginal | Baixo | +| Mouse: desativar aceleração | `HKCU\Control Panel\Mouse` `MouseSpeed=0`, `MouseThreshold1/2=0` | Consistência de mira | Baixo | +| Power throttling off para o jogo | `powercfg /setacvalueindex ... PERFBOOSTMODE` e `HKLM\...\Power\PowerThrottling\PowerThrottlingOff` | Evita clock baixo em notebook | Baixo | +| Desativar hibernação | `powercfg /h off` | Libera espaço = tamanho da RAM, perde Inicialização Rápida | Médio | +| Ultimate Performance plan | `powercfg /duplicatescheme e9a42b02-...` | Já existe no Modo Game | Baixo | +| Visual effects "melhor desempenho" | `HKCU\...\Explorer\VisualEffects` | Ajuda só em máquina muito fraca; afeta UX | Baixo | +| Timer resolution | Já no Modo Game | Baixo | +| Core Isolation / VBS (Memory Integrity) | **Não alterar.** Detectar via `msinfo32`/WMI `Win32_DeviceGuard` e mostrar Finding: "VBS ativo, custo medido pela comunidade de 5 a 15% em jogos CPU-bound". Botão abre Configurações → Segurança do Windows → Isolamento do núcleo. Explicar o trade-off (PC só de jogo vs. máquina de trabalho/banco) | Alto | +| Spectre/Meltdown mitigations | **Nunca.** | Alto | + +### 5.9 Rede: velocidade, NAT e conectividade para jogar online + +**Objetivo:** responder "minha internet está boa pra jogar?" e "por que não consigo entrar na partida/festa do meu amigo?" (NAT estrito), corrigindo o que for do Windows e apontando com precisão o que é do roteador ou da operadora. + +**5.9.1 Teste de velocidade** +- Download/upload contra endpoints públicos (Cloudflare `speed.cloudflare.com/__down?bytes=N` e `__up`; fallback: arquivos de teste de CDNs conhecidas). Rodar 3 amostras de 10 s, reportar mediana. Nunca usar API da Ookla (termos restritos). +- Ping/jitter/perda (ICMP `Ping` + TCP connect quando ICMP bloqueado) para: gateway, 1.1.1.1, 8.8.8.8 e servidores de jogo cadastrados pelo usuário (lista inicial com regiões de Riot, Valve, Blizzard, Epic, EA em São Paulo). +- Resultado com semáforo: ping < 30 ms verde, 30 a 80 amarelo, > 80 vermelho; jitter > 15 ms e perda > 1% viram Finding. +- Histórico em `network-history.json` pra mostrar "sua conexão piorou à noite". + +**5.9.2 Detecção do tipo de NAT (STUN, RFC 5389/5780)** +- Cliente STUN próprio em UDP (poucas centenas de linhas): enviar Binding Request para 2 servidores públicos (ex.: `stun.l.google.com:19302`, `stun.cloudflare.com:3478`) por 2 portas locais e comparar os endereços mapeados. Classificar: + - **Aberto** (full cone / sem NAT): mesmo mapeamento em todos os testes e recebe de qualquer origem. + - **Moderado** (restricted / port-restricted cone): mapeamento estável, mas só recebe de quem já falou. + - **Estrito** (simétrico): mapeamento muda por destino. É o que impede duas pessoas com NAT estrito de se conectarem. +- Exibir no mesmo vocabulário que Xbox/Call of Duty/Discord usam (Open/Moderate/Strict) e explicar em uma frase o que cada um bloqueia. + +**5.9.3 Detecção das causas** + +| Verificação | Como | Finding | +|---|---|---| +| **CGNAT** da operadora | IP WAN do roteador (via UPnP `GetExternalIPAddress` ou página do gateway) na faixa `100.64.0.0/10`, ou diferente do IP visto pelo STUN/`api.ipify.org` | "Sua operadora usa CGNAT. Nenhum ajuste no PC ou no roteador resolve NAT estrito. Peça IP público (às vezes chamado de IP fixo) ou use IPv6" | +| **NAT duplo** | `tracert` para 1.1.1.1: dois ou mais saltos iniciais com IP privado (10/8, 172.16/12, 192.168/16) | "Dois roteadores em cascata. Coloque o modem da operadora em modo bridge ou use só um deles" | +| **UPnP** | Descoberta SSDP (`M-SEARCH` para `239.255.255.250:1900`, `urn:schemas-upnp-org:device:InternetGatewayDevice:1`), depois `AddPortMapping` de teste e `DeletePortMapping` | "UPnP desligado no roteador: os jogos não conseguem abrir portas sozinhos" | +| **Teredo / rede Xbox** | `netsh interface teredo show state` (estado `qualified` é o bom), serviço `XblAuthManager`/`XboxNetApiSvc`, e a leitura que o Windows mostra em Configurações → Jogos → Rede Xbox | "Teredo bloqueado. Festas e jogos que usam a rede Xbox no PC não vão conectar" | +| **IPv6** | Interface tem endereço global e `ping -6` para `2606:4700:4700::1111` responde | Informativo: "IPv6 ativo, isso contorna o CGNAT em jogos que suportam" | +| **Firewall do Windows** | `INetFwPolicy2` (COM) buscando regras para o exe do jogo detectado; verificar perfil ativo (Público vs Privado) | "Sua rede está marcada como Pública e o jogo não tem regra de entrada" | +| **Wi-Fi** | `WlanQueryInterface`: banda, sinal, canal, taxa de conexão | "Wi-Fi 2,4 GHz com sinal de 40%. Para competitivo, use cabo ou 5 GHz" | +| **Adaptador** | Velocidade de link (`Win32_NetworkAdapter.Speed`), duplex, driver com mais de 2 anos | "Placa de rede negociou 100 Mbps num plano de 500" | +| **DNS** | Tempo de resolução com o DNS atual vs Cloudflare/Google/Quad9 | Tabela comparativa, o usuário escolhe | +| **Banda em uso** | `GetExtendedTcpTable`/`GetExtendedUdpTable` + bytes por processo | "Steam baixando a 45 MB/s enquanto você joga" | + +**5.9.4 Correções que o app aplica (todas com snapshot e "Desfazer")** + +| Ação | Comando/API | Risco | +|---|---|---| +| Ativar Teredo e serviço de rede Xbox | `netsh interface teredo set state type=enterpriseclient servername=win1910.ipv6.microsoft.com` (padrão `default` como primeira tentativa), `Start-Service XboxNetApiSvc` | Baixo | +| Criar regra de firewall pro jogo detectado (entrada e saída, TCP/UDP) | `INetFwRule` via COM | Baixo | +| Marcar rede atual como Privada | `Set-NetConnectionProfile -NetworkCategory Private` | Baixo (explicar que não é pra fazer em Wi-Fi público) | +| Abrir portas via UPnP para o jogo em execução (tabela de portas por jogo em `docs/PORTAS.md`) | `AddPortMapping` com lease de 24 h, removido ao sair do jogo | Baixo | +| Limpar DNS, renovar IP | `ipconfig /flushdns`, `/release`, `/renew` | Baixo | +| Resetar Winsock e pilha TCP/IP | `netsh winsock reset`, `netsh int ip reset` (exige reinício) | Médio | +| Trocar DNS do adaptador | `SetDNSServerSearchOrder` (WMI) | Baixo | +| Desativar Nagle na interface do jogo | Ver 5.8 | Baixo | + +**5.9.5 O que o app não corrige (e como orienta)** +- **CGNAT**: mostrar o nome da operadora (via ASN do IP público, tabela estática dos principais ASNs BR) e um texto pronto pra copiar no chat de suporte pedindo IP público, além de sugerir IPv6 e, como paliativo, serviços de roteamento (ExitLag, WTFast, GearUP) com a ressalva de que custam por mês e não resolvem CGNAT em todo jogo. +- **Roteador**: botão "Abrir configuração do roteador" (gateway em `http://` ou `https://`), lista das portas do jogo detectado e passo a passo por marca (TP-Link, Intelbras, Huawei, ZTE, Mercusys, Asus, D-Link) pra ligar UPnP, abrir portas ou DMZ pro IP do PC. Recomendar reservar IP fixo no DHCP antes de abrir porta. +- **NAT duplo**: instruções de modo bridge por marca e aviso de que isso é feito no modem da operadora. + +**UI:** página Rede com três blocos: "Velocidade" (gráfico e histórico), "Posso jogar online?" (NAT com semáforo grande, lista de causas encontradas, botão "Corrigir o que é do Windows" e cartões de orientação pro resto) e "Ajustes" (DNS, firewall, tabela de portas). + +**Critérios de aceite:** classificação de NAT bate com o que o Xbox app / Call of Duty mostram na mesma rede; CGNAT detectado corretamente em conexão com IP 100.64.x.x; todas as correções revertem; nenhuma alteração de roteador é tentada além de UPnP com lease. + +### 5.10 Drivers de GPU + +- Detectar fabricante e versão (`Win32_VideoController.DriverVersion`, `DriverDate`) e traduzir para versão "de marketing" (NVIDIA: últimos 5 dígitos do DriverVersion → `5xx.xx`). +- Comparar com versão mais recente conhecida via arquivo JSON estático atualizado a cada release do GameBoost e, se web permitida pelo usuário, endpoint oficial (NVIDIA tem API não documentada; AMD/Intel via página). Sem web: só "seu driver tem N meses". +- **Nunca instalar driver.** Abrir a página oficial. Opcional: link para DDU com explicação de quando usar. + +### 5.11 Perfis por jogo + +- Perfil = `{ executavel, nome, acoes: [modo game on, plano energia X, prioridade Y, afinidade Z, tweaks por exe, apps a fechar, apps a reabrir], auto: bool }`. +- Detecção via 5.1. Ao fechar o jogo, reverter automaticamente. +- Biblioteca: varrer launchers para listar jogos instalados (Steam `libraryfolders.vdf` + `appmanifest_*.acf`; Epic `C:\ProgramData\Epic\EpicGamesLauncher\Data\Manifests\*.item`; GOG registry; Xbox `.GamingRoot`), mostrar tamanho e último jogado (para sugerir desinstalar jogos parados, que é onde estão os GB de verdade). + +### 5.12 Relatório de saúde e pontuação + +- Executa todas as varreduras em modo leitura e gera nota 0 a 100 por área (Desempenho, Espaço, Inicialização, Configuração para jogos) com os 5 principais `Findings`. +- Exportar `relatorio.html` (para o usuário mandar para quem dá suporte) e `relatorio.json`. +- `GameBoost.exe --report saida.html` na CLI. + +### 5.13 Ferramentas rápidas (atalhos de coisas que o usuário procura) + +- Reiniciar Explorer, reiniciar driver de vídeo (`Win+Ctrl+Shift+B` programático via `SendInput`), limpar cache DNS, esvaziar Lixeira, abrir Limpeza de Disco do Windows, criar ponto de restauração, verificar integridade (`sfc /scannow` e `DISM /RestoreHealth` com console embutido), teste de velocidade de disco (leitura/escrita sequencial 1 GB em arquivo temporário), informações do sistema (CPU, GPU, RAM canais/velocidade, discos e tipo, versão do Windows, BIOS) com botão copiar. + +--- + +## 6. UI / UX + +- **Tema escuro padrão**, acento único (manter identidade atual do GameBoost). Fluent-like, sem gradientes exagerados. +- **Navegação lateral** com ícones: Início (Modo Game + pontuação + findings), Diagnóstico, Limpeza, Espaço, Apps, Inicialização, Jogos (perfis), Tweaks, Rede, Ferramentas, Configurações. +- **Padrão de tela de módulo:** cabeçalho com botão "Varrer" e resumo (ex.: "12,4 GB podem ser liberados"), lista de `ActionItem` agrupada por categoria com checkbox, badge de risco (verde/amarelo/vermelho), botão "?" que abre painel lateral com "O que faz / Risco / Como desfazer", rodapé fixo com "Aplicar N selecionados" e "Reverter". +- **Tela de confirmação única e genérica** (reusar a do Modo Game) para tudo que altera o sistema. +- **Histórico** (Configurações → Histórico): toda ação aplicada, com botão "Desfazer" individual enquanto a reversão for possível. +- **Bandeja do sistema**: ícone com menu (Ativar Modo Game, Limpar RAM, Abrir, Sair). Opção "iniciar minimizado com o Windows" (desligada por padrão, e o GameBoost aparece na própria lista de inicialização, com honestidade). +- **Acessibilidade**: fontes escaláveis, navegação por teclado, contraste AA. +- **Onboarding** na primeira abertura: 3 telas explicando "reversível / sem telemetria / o que faz diferença de verdade" e oferecendo o Relatório de Saúde inicial. + +--- + +## 7. Dados e persistência + +Tudo em `%LOCALAPPDATA%\GameBoost\`: + +| Arquivo | Conteúdo | Formato | +|---|---|---| +| `settings.json` | Preferências, tema, whitelist, essenciais, agendamentos, `schemaVersion` | JSON | +| `profiles/*.json` | Um perfil por jogo | JSON | +| `state-backup.json` | Pilha de `ChangeRecord { Id, Modulo, Tipo (Registry/Service/Power/File/Startup), Alvo, ValorAnterior, ValorNovo, Data, Revertido }` | JSON | +| `session.json` | Sessão do Modo Game | JSON | +| `history/*.json` | Histórico de ações por dia | JSON | +| `cache/disk-scan-.bin` | Último resultado da varredura de disco (para abrir instantâneo) | Binário | +| `gameboost.log` | Log texto rotacionado (5 MB × 5 arquivos) | Texto | +| `backups/*.reg` | Export de chaves antes de remover restos | REG | + +Migração: ao abrir, se `schemaVersion` < atual, rodar migradores em sequência. Nunca apagar arquivo antigo, renomear para `.bak`. + +Modo portátil: se existir `portable.txt` ao lado do exe, usar `.\data\` em vez de `%LOCALAPPDATA%`. + +--- + +## 8. CLI (`GameBoost.Cli` ou argumentos no exe principal) + +``` +GameBoost.exe --scan [arquivo] # relatório de varredura do Modo Game (já existe) +GameBoost.exe --report saida.html # relatório de saúde completo +GameBoost.exe --clean --preset seguro # limpeza com preset (seguro | completo) +GameBoost.exe --clean --categorias temp,wu,wer +GameBoost.exe --revert-all # reverte tudo do state-backup +GameBoost.exe --gamemode on|off +GameBoost.exe --dry-run # combina com qualquer comando, não altera nada +``` + +Saída em texto e `--json`. + +--- + +## 9. Blindagem (documentar em `docs/PROTECAO.md`) + +Aplicada por `SafetyGuard` central, consultado por todos os módulos. + +- **Processos:** lista da v1 + anti-cheats (`EasyAntiCheat`, `BEService`, `vgc`, `vgtray` (Vanguard), `FACEIT`, `PnkBstr`, `XignCode`, `GameGuard`, `nProtect`, `Ricochet`, `EAAntiCheat`), launchers nunca pré-marcados, `RTSS`, `MSIAfterburner` (usuário pode querer), overlays de captura (aviso). +- **Caminhos:** `C:\Windows` (exceto whitelist de limpeza), `Program Files*` (exceto via Desinstalador), pastas de usuário (`Documents`, `Pictures`, `Videos`, `Desktop`, `OneDrive`), qualquer pasta contendo `.git`, `saves`, `Saved Games`, `%APPDATA%` de jogos. +- **Serviços:** núcleo do Windows, antivírus, `wuauserv` (só pausa), rede (`Dhcp`, `Dnscache`, `nsi`, `NlaSvc`), áudio (`Audiosrv`, `AudioEndpointBuilder`), `Themes`, `DcomLaunch`, `RpcSs`, `Winmgmt`, `EventLog`, `Schedule`, `TrustedInstaller`, `CryptSvc`, `BFE`, `mpssvc`. +- **Registro:** nunca tocar em `HKLM\SYSTEM\CurrentControlSet\Services\*` além de `Start`, nunca em `Winlogon`, `Image File Execution Options`, `Policies`. +- **Apps:** ver 5.3. +- **Tweaks proibidos:** VBS/Core Isolation, mitigações de CPU, Defender, UAC, SmartScreen, assinatura de drivers. + +Teste obrigatório: `SafetyGuardTests` com casos para cada lista. + +--- + +## 10. Build, distribuição e atualização + +- `dotnet publish -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishReadyToRun=true` (ReadyToRun reduz tempo de abertura). +- Trimming: **não** habilitar (WPF e reflexão quebram). +- Versão semântica em `Directory.Build.props`, propagada para Inno Setup. +- **Assinatura de código (validado set/2026):** + - **Azure Artifact Signing** (ex-Trusted Signing, a partir de US$ 9,99/mês) é a opção barata da Microsoft, mas para **desenvolvedor individual só aceita EUA e Canadá**; organizações precisam estar em EUA/Canadá/UE/UK e outros poucos países. **Brasil está fora**, tanto PF quanto PJ. Não contar com isso. + - **Certificado OV** de uma CA (Certum, Sectigo, DigiCert, GlobalSign): caminho viável para o Brasil. Desde jun/2023 a chave privada precisa ficar em token USB ou HSM em nuvem (a CA fornece). Faixa de US$ 70 a 300/ano. Certum costuma ser a mais barata para dev individual. + - **EV não pula mais o SmartScreen** desde 2024: OV e EV passam pelo mesmo processo de reputação. Não vale pagar EV. + - **Microsoft Store (MSIX)**: a Microsoft reassina o pacote e o SmartScreen nunca aparece. Exige `runFullTrust` e o app precisa passar na certificação (o Modo Game e as ações de sistema podem gerar perguntas). Vale como meta de médio prazo. + - Independente da opção: reputação só acumula com downloads, então assinar sempre com a mesma identidade e submeter cada release ao Microsoft Security Intelligence para reduzir falsos positivos. +- **Auto-update:** Velopack (sucessor do Squirrel, .NET moderno) apontando para GitHub Releases. Checar na abertura, baixar em background, aplicar no próximo início. Configurável. Sem web, apenas mostrar "versão X disponível" se o usuário clicar "Verificar". +- **Portátil:** o mesmo exe publicado, zipado, com `portable.txt`. +- **CI:** GitHub Actions `windows-latest`: build, testes, publish, Inno Setup, anexar ao Release. Gerar `SHA256SUMS.txt`. +- **Instalador:** manter Inno Setup. Adicionar página de opções: atalho na bandeja ao iniciar, associar tarefa de limpeza semanal (desligada por padrão), instalar para todos os usuários vs. só atual. + +--- + +## 11. Testes + +- **Unit (xUnit):** `SafetyGuard`, `RollbackEngine` (aplicar → reverter → estado igual), parsers (registro Uninstall, `libraryfolders.vdf`, UserAssist ROT13, versão de driver NVIDIA), regras de `Bottleneck` (dado um snapshot de métricas, gera os Findings esperados), whitelist do Cleaner (todo caminho gerado pertence à whitelist). +- **Integração (rodam só em Windows, tag `[Trait("Category","Windows")]`):** limpeza real em pasta temp sintética; leitura de MFT em volume real (só leitura); desinstalação de um MSI de teste (criar com WiX ou usar `msiexec` em pacote conhecido); startup toggle em chave `HKCU` fake. +- **Manual (checklist em `docs/QA.md`):** SmartScreen, antivírus, máquina com 8 GB, notebook com iGPU + dGPU, usuário sem admin, Windows 10 22H2 e Windows 11 24H2+, disco FAT32 externo, jogo com Vanguard rodando (nada pode ser fechado). +- **Dry-run** em todos os módulos: `--dry-run` produz o mesmo relatório sem tocar em nada; teste garante que `IFileSystem`/`IRegistry` fakes não recebem chamadas de escrita. + +--- + +## 12. Fases de implementação (ordem obrigatória) + +Cada fase: branch própria, PR, changelog em `CHANGELOG.md`. + +### Fase 0 · Fundação (refatorar sem mudar comportamento) +- Separar `GameBoost.Core` (sem WPF) de `GameBoost.App`. +- Extrair `SafetyGuard`, `RollbackEngine`, `Logger`, `Settings` com `schemaVersion`. +- Criar `IModule` / `ActionItem` e portar o Modo Game para esse contrato. +- Criar a tela genérica de lista de `ActionItem` + confirmação. +- Configurar DI, CommunityToolkit.Mvvm, xUnit, GitHub Actions. +- Gerar `CLAUDE.md` com a seção 2 e o mapa de pastas. +- **Pronto quando:** app faz exatamente o que a v1 fazia, com testes de `SafetyGuard` e `RollbackEngine` passando. + +### Fase 1 · Diagnóstico de gargalos (5.5) + Relatório (5.12) +Motivo: é o que faz o usuário abrir o app todo dia e entender o valor do resto. + +### Fase 2 · Limpeza (5.2) + Ferramentas rápidas (5.13) + +### Fase 3 · Analisador de disco (5.4) + Biblioteca de jogos (parte de 5.11) + +### Fase 4 · Desinstalador (5.3) + Inicialização (5.6) + +### Fase 5 · Tweaks (5.8) + Serviços (5.7) + Rede completa com NAT (5.9) + Drivers (5.10) + +### Fase 6 · Perfis por jogo com detecção automática (5.1 + 5.11), bandeja, onboarding + +### Fase 7 · Distribuição: Velopack, CI completo, portátil, assinatura, submissão de falsos positivos + +### Backlog (depois da v2.0) +- Overlay de FPS in-game (integração com RTSS via shared memory, ou hook próprio em D3D11/12, que é trabalho grande e risco com anti-cheat; preferir RTSS). +- Duplicados e arquivos grandes esquecidos com sugestão de compressão NTFS (`compact /c /exe:xpress8k` em pastas de jogos, que economiza 20 a 40% sem perda de FPS em SSD). +- Curva de ventoinha (fora de escopo, depende de hardware). +- Versão EN e loja Microsoft Store (MSIX com `runFullTrust`). +- Benchmark rápido antes/depois (rodar 30 s de carga sintética e comparar) para provar ganho. + +--- + +## 13. Prompts sugeridos para o Claude Code (um por fase) + +**Fase 0** +> Leia `GAMEBOOST_V2_SPEC.md` inteiro. Execute a Fase 0: refatore o projeto para a estrutura da seção 4.1 sem alterar nenhum comportamento visível. Crie `SafetyGuard`, `RollbackEngine`, `Logger` e `Settings` conforme seções 2, 7 e 9. Porte o Modo Game para `IModule`/`ActionItem`. Adicione xUnit com testes para `SafetyGuard` e `RollbackEngine`. Gere `CLAUDE.md` com as regras da seção 2. Ao final, `dotnet build` e `dotnet test` limpos, e liste em `docs/DECISOES.md` qualquer desvio. + +**Fase 1** +> Implemente o módulo `Bottleneck` (seção 5.5) e `HealthReport` (5.12). Coleta a cada 1 s com buffer de 5 min, contadores de CPU/GPU/RAM/disco/rede, LibreHardwareMonitorLib opcional para temperaturas. Implemente todas as regras de diagnóstico da tabela como classes `IFindingRule` testáveis. Tela com 5 medidores, gráfico de 60 s, top 10 processos e lista de Findings. Exporte `relatorio.html`. Meça e garanta < 1,5% CPU. + +**Fases seguintes:** mesmo padrão: "Implemente o módulo X conforme seção 5.Y, respeitando seções 2 e 9, com testes das regras, dry-run funcional e entrada no `docs/TWEAKS.md` ou `docs/PROTECAO.md` quando aplicável." + +--- + +## 14. Glossário rápido (para textos da UI) + +- **Standby List:** memória que o Windows guarda "por precaução" com dados já usados. Liberar ajuda quando falta RAM, não aumenta FPS por si só. +- **Shader cache:** arquivos que o jogo gera para não recompilar efeitos. Apagar libera espaço, mas o jogo engasga nas primeiras horas até recriar. +- **HAGS:** deixa a placa de vídeo gerenciar a própria fila de trabalho. Ajuda em alguns casos, atrapalha em outros. Teste com e sem. +- **Throttling:** o processador reduz a velocidade para não superaquecer. Sinal de cooler sujo ou pasta térmica velha. +- **Gargalo de CPU:** o processador não consegue preparar quadros rápido o suficiente; a placa de vídeo fica esperando. + +--- + +*Documento gerado em 13/09/2026 para o projeto GameBoost, com pesquisa de mercado validada por fontes web na mesma data. Autor do produto: Jonathan Vaz.* diff --git a/GameBoost/GameBoost.sln b/GameBoost/GameBoost.sln new file mode 100644 index 0000000..7731860 --- /dev/null +++ b/GameBoost/GameBoost.sln @@ -0,0 +1,84 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameBoost.Core", "src\GameBoost.Core\GameBoost.Core.csproj", "{67F62495-3B0A-4309-BC87-D97EABE2D2A7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameBoost.Cli", "src\GameBoost.Cli\GameBoost.Cli.csproj", "{94138042-896D-4F31-BAED-E287520B18A7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameBoost.Tests", "src\GameBoost.Tests\GameBoost.Tests.csproj", "{984E3716-662D-4E8C-8B6F-B81C33CDF790}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameBoost.App", "src\GameBoost.App\GameBoost.App.csproj", "{CAF682AC-B64C-483C-9C80-533990BC4F24}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Debug|x64.ActiveCfg = Debug|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Debug|x64.Build.0 = Debug|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Debug|x86.Build.0 = Debug|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Release|Any CPU.Build.0 = Release|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Release|x64.ActiveCfg = Release|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Release|x64.Build.0 = Release|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Release|x86.ActiveCfg = Release|Any CPU + {67F62495-3B0A-4309-BC87-D97EABE2D2A7}.Release|x86.Build.0 = Release|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Debug|x64.ActiveCfg = Debug|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Debug|x64.Build.0 = Debug|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Debug|x86.Build.0 = Debug|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Release|Any CPU.Build.0 = Release|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Release|x64.ActiveCfg = Release|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Release|x64.Build.0 = Release|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Release|x86.ActiveCfg = Release|Any CPU + {94138042-896D-4F31-BAED-E287520B18A7}.Release|x86.Build.0 = Release|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Debug|Any CPU.Build.0 = Debug|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Debug|x64.ActiveCfg = Debug|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Debug|x64.Build.0 = Debug|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Debug|x86.ActiveCfg = Debug|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Debug|x86.Build.0 = Debug|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Release|Any CPU.ActiveCfg = Release|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Release|Any CPU.Build.0 = Release|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Release|x64.ActiveCfg = Release|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Release|x64.Build.0 = Release|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Release|x86.ActiveCfg = Release|Any CPU + {984E3716-662D-4E8C-8B6F-B81C33CDF790}.Release|x86.Build.0 = Release|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Debug|x64.ActiveCfg = Debug|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Debug|x64.Build.0 = Debug|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Debug|x86.ActiveCfg = Debug|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Debug|x86.Build.0 = Debug|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Release|Any CPU.Build.0 = Release|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Release|x64.ActiveCfg = Release|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Release|x64.Build.0 = Release|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Release|x86.ActiveCfg = Release|Any CPU + {CAF682AC-B64C-483C-9C80-533990BC4F24}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {67F62495-3B0A-4309-BC87-D97EABE2D2A7} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {94138042-896D-4F31-BAED-E287520B18A7} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {984E3716-662D-4E8C-8B6F-B81C33CDF790} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {CAF682AC-B64C-483C-9C80-533990BC4F24} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + EndGlobalSection +EndGlobal diff --git a/GameBoost/README.md b/GameBoost/README.md new file mode 100644 index 0000000..f96a1a1 --- /dev/null +++ b/GameBoost/README.md @@ -0,0 +1,199 @@ +# GameBoost + +**O booster que mostra o que faz e desfaz tudo.** + +Central de otimização e manutenção para PC gamer no Windows 10 e 11. Sem anúncio, sem conta, +sem telemetria, sem assinatura. PT-BR nativo. + +> **Estado:** v2.0.0 em desenvolvimento — Fases 0 a 3 concluídas. +> Modo Game, diagnóstico, relatório de saúde, limpeza, ferramentas rápidas e analisador de +> espaço funcionais; os demais módulos chegam nas fases seguintes. + +--- + +## O que faz hoje + +**Modo Game.** Encerra os aplicativos que você confirmar, libera memória, aplica o plano de +energia de alto desempenho, sobe a prioridade do jogo e silencia notificações, gravação em +segundo plano e Windows Update. Ao desligar, devolve tudo ao estado anterior e reabre os +apps marcados com ★. + +Três coisas que ele não faz, por princípio: + +- **Não fecha o jogo nem o anti-cheat.** Vanguard, EasyAntiCheat, BattlEye e companhia nem + aparecem selecionáveis. +- **Não mata nada sem pedir.** Todo app recebe primeiro o pedido de fechamento normal, com + 3 segundos para salvar. A tela de confirmação aparece antes de qualquer coisa ser fechada. +- **Não inventa número.** O ganho mostrado é o medido: RAM livre antes e depois, apps + fechados, alterações aplicadas. + +**Diagnóstico de gargalos.** Cinco medidores ao vivo (CPU, GPU, memória, disco, rede) com +gráfico dos últimos 60 segundos, os 10 processos que mais consomem, e uma lista de achados +em português claro: *"Chrome está usando 38% da CPU"*, *"seu monitor de 165 Hz está em +60 Hz"*, *"a atualização 26H2 desfez 4 ajustes seus"*. A coleta custa 0,1% de CPU. + +**Relatório de saúde.** Nota de 0 a 100 por área (Desempenho, Espaço, Inicialização, +Configuração para jogos) com os cinco principais achados, exportável como página HTML única +para mandar a quem dá suporte. + +**Limpeza.** Vinte alvos, do `%TEMP%` ao shader cache, cada um mostrando quanto ocupa e +qual o efeito colateral antes de você marcar. Arquivos seus vão para a Lixeira; navegador +aberto bloqueia o próprio cache; `Downloads` só é listado, nunca tocado. + +**Ferramentas rápidas.** Reiniciar o Explorer ou o driver de vídeo, limpar o cache de DNS, +criar ponto de restauração, rodar `sfc` e `DISM` com a saída ao vivo, medir a velocidade do +disco e copiar as informações do sistema para mandar a quem dá suporte. + +**Onde está o seu espaço.** Varre o disco inteiro em cerca de 20 segundos e mostra num +treemap onde foram parar os GB. Lista os 100 maiores arquivos, as 50 maiores pastas e a sua +biblioteca de jogos — com o tamanho de cada um e quando você jogou pela última vez. Explica o +que são `hiberfil.sys` e `pagefile.sys` em vez de só mostrar o tamanho. Não apaga nada: abre +no Explorer ou manda para a Lixeira, com confirmação. + +**Aplicativos instalados.** Lista tudo que está instalado — registro, Microsoft Store e o +que o Windows registra de uso — com o tamanho medido de verdade, não o declarado no +registro, e quando você usou pela última vez. Desinstala em lote, sempre chamando o +desinstalador do próprio app, e depois procura o que ficou para trás. Uma aba à parte varre +`%APPDATA%`, `%LOCALAPPDATA%` e `%PROGRAMDATA%` atrás de pastas de programas que você já +desinstalou. Runtimes, drivers e antivírus aparecem bloqueados, com o motivo. **Nada vem +marcado** — nem o bloatware mais óbvio, nem o resto mais antigo. + +**Atualizações.** Uma aba da mesma tela mostra o que tem versão nova, em cima do winget, +o gerenciador de pacotes da própria Microsoft — quem baixa e confere assinatura é ela, não +o GameBoost. Navegador, compactador e Java desatualizados aparecem marcados como +**atualização de segurança**, porque neles ficar para trás não é questão de conforto. +Driver aparece bloqueado: o GameBoost não instala driver, nem pelo winget. O que se +atualiza sozinho (Steam, Discord, navegadores) vem com a tag e a explicação de por que +forçar pode brigar com o atualizador do próprio app. + +**O que abre com o Windows.** Chaves `Run`, pastas Inicializar e o estado real de cada item, +lido de onde o Gerenciador de Tarefas lê. Mostra o fabricante, se o executável é assinado e +uma estimativa grosseira do peso no boot — declarada como grosseira, porque vem do tamanho +do arquivo e não de medição. Desativar grava exatamente o mesmo formato do Gerenciador de +Tarefas, e dá para reverter aqui, lá ou os dois. + +**Ajustes do Windows.** Doze tweaks de jogo e doze serviços do Windows na mesma lista, cada +um com o efeito real escrito sem promessa: quando um ajuste é marginal, está escrito que é +marginal. O isolamento de núcleo aparece explicado e **não** é alterado — essa escolha é +sua, na tela da Microsoft. SysMain, os serviços do Xbox e o Windows Update aparecem +bloqueados para explicar por que toda lista de otimização da internet erra ao mandar +desligar. Nada vem marcado, nem o que o próprio GameBoost recomenda. + +**Rede.** Responde duas perguntas: "minha internet está boa pra jogar?" e "por que eu não +entro na partida do meu amigo?". Mede latência, jitter e perda, compara o seu DNS com +Cloudflare, Google e Quad9, e descobre o tipo do seu NAT com um cliente STUN próprio — +aberto, moderado ou estrito, no mesmo vocabulário que o jogo usa. Quando é estrito, diz +**por quê**: CGNAT da operadora, dois roteadores em série, UPnP desligado, Teredo +desativado ou firewall. Corrige o que é do Windows, com desfazer, e para o resto abre +[docs/PORTAS.md](docs/PORTAS.md) com as portas de cada jogo e onde fica cada coisa em sete +marcas de roteador. O teste de velocidade é um botão à parte e desligado por padrão, porque +gasta 100 MB da sua franquia. + +**Perfis por jogo.** O GameBoost reconhece quando um jogo abre e pergunta se quer ativar o +Modo Game para ele. Cada jogo pode ter o seu perfil — prioridade, núcleos, tweaks, apps a +fechar — aplicado ao entrar e **desfeito ao sair**, sem exceção. Perfil novo nunca ativa +sozinho: só passa a agir sem perguntar quem você marcar como automático. + +**Bandeja e primeira abertura.** Ícone ao lado do relógio com Ativar Modo Game, Limpar RAM, +Abrir e Sair. Fechar a janela esconde na bandeja, e ele avisa isso na primeira vez — +programa que some sem dizer para onde foi é o motivo de as pessoas irem no Gerenciador de +Tarefas. Na primeira abertura, três telas explicando o contrato: tudo é reversível, nada +sai daqui, e o que faz diferença de verdade. + +## Como instalar + +| Arquivo | Para quem | +|---|---| +| `GameBoost-Setup-*.exe` | A maioria. Instala, cria atalho e habilita a atualização automática | +| `GameBoost-*-portatil.zip` | Pen drive ou máquina de terceiro. Guarda os dados ao lado do executável e não se atualiza sozinho | +| `SHA256SUMS.txt` | Conferir que o download veio inteiro | + +O instalador pergunta se quer instalar para todos os usuários ou só para você, e +oferece — desmarcados — o atalho na área de trabalho, iniciar com o Windows +minimizado na bandeja, e uma limpeza semanal agendada. + +Se o SmartScreen avisar, é porque esta versão pode não estar assinada: **Mais +informações → Executar assim mesmo**. O porquê está em +[docs/ASSINATURA.md](docs/ASSINATURA.md), junto com o que foi apurado sobre +certificado de código para quem está no Brasil. + +--- + +## Reversibilidade + +Antes de alterar qualquer coisa, o GameBoost grava o valor anterior em +`state-backup.json`. Isso vale para registro, serviços e plano de energia. + +- O botão **Reverter tudo** está sempre visível. +- Se o app travar ou o PC reiniciar com o Modo Game ativo, a próxima abertura avisa e + oferece a restauração. +- O desinstalador roda a reversão antes de remover o app. + +Sem isso, um travamento com o Windows Update pausado deixaria a máquina sem atualizações +indefinidamente. + +--- + +## Linha de comando + +``` +GameBoost.exe abre a interface +GameBoost.exe --scan [arquivo] relatório de varredura, não encerra nada +GameBoost.exe --report saida.html relatório de saúde completo (HTML ou JSON) +GameBoost.exe --clean --preset seguro limpeza (Fase 2) +GameBoost.exe --revert-all reverte tudo do state-backup +GameBoost.exe --gamemode on|off liga ou desliga o Modo Game +``` + +Modificadores: `--dry-run` (combina com qualquer comando, não altera nada), `--json`, +`--help`. + +## Onde ficam os dados + +`%LOCALAPPDATA%\GameBoost\` — `settings.json`, `session.json`, `state-backup.json`, +`gameboost.log` (rotacionado, 5 MB × 5). + +**Modo portátil:** criando um `portable.txt` ao lado do executável, tudo passa a ficar em +`.\data\`. + +--- + +## Compilar + +Requisitos: .NET 9 SDK e, para o instalador, Inno Setup 6. + +```powershell +dotnet build GameBoost.sln +dotnet test GameBoost.sln + +# Executável final (self-contained, arquivo único) +dotnet publish src\GameBoost.App\GameBoost.App.csproj -c Release -o publish + +# Instalador +& "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe" installer\GameBoost.iss +``` + +O app exige privilégios de administrador (encerrar processos, controlar serviços e purgar a +Standby List). Sem elevação ele abre em modo somente leitura: as varreduras funcionam e as +ações ficam desabilitadas. + +## Avisos conhecidos + +- **SmartScreen.** O instalador ainda não é assinado. Ver seção 10 do spec para o caminho de + assinatura (certificado OV; Azure Artifact Signing não atende o Brasil). +- **Antivírus.** Encerrar processos e elevar privilégios é o mesmo comportamento que um + malware teria — alguns antivírus reclamam de qualquer booster. + +## Documentação + +| Arquivo | Conteúdo | +|---|---| +| [CLAUDE.md](CLAUDE.md) | Regras obrigatórias para código novo | +| [docs/PROTECAO.md](docs/PROTECAO.md) | Listas de blindagem, documentadas | +| [docs/TWEAKS.md](docs/TWEAKS.md) | Catálogo de tweaks com efeito real e risco | +| [docs/DECISOES.md](docs/DECISOES.md) | Decisões que fugiram do spec, com motivo | +| [docs/QA.md](docs/QA.md) | Checklist de teste manual | + +--- + +Autor: Jonathan Vaz. diff --git a/GameBoost/docs/ASSINATURA.md b/GameBoost/docs/ASSINATURA.md new file mode 100644 index 0000000..c621e89 --- /dev/null +++ b/GameBoost/docs/ASSINATURA.md @@ -0,0 +1,115 @@ +# Assinatura de código e falso positivo + +O que foi apurado em setembro de 2026 sobre assinar o GameBoost, e o que fazer +enquanto ele não está assinado. + +Isto importa mais para este programa do que para a média. Um app que encerra +processos, mexe no registro, altera serviços e purga a Standby List tem o perfil +de comportamento que antivírus heurístico marca. Sem assinatura, o SmartScreen +avisa no primeiro download e uma parte das pessoas desiste ali. + +--- + +## As opções, e por que a escolha é essa + +### Azure Artifact Signing (ex-Trusted Signing) — **fora do Brasil** + +É a opção barata da Microsoft, a partir de US$ 9,99/mês, e seria a escolha óbvia +se desse. + +Não dá. Para **desenvolvedor individual** a Microsoft só aceita Estados Unidos e +Canadá. Para **organização**, a lista é maior — EUA, Canadá, União Europeia, +Reino Unido e mais alguns — e **o Brasil não está nela**, nem como pessoa física +nem como jurídica. + +Não é questão de preço nem de burocracia: é elegibilidade geográfica. Não vale +gastar tempo tentando. + +### Certificado OV de uma CA — **o caminho viável** + +Certum, Sectigo, DigiCert, GlobalSign. Faixa de US$ 70 a 300 por ano; a Certum +costuma ser a mais barata para desenvolvedor individual. + +Desde junho de 2023 a chave privada **não pode mais ficar num arquivo `.pfx` no +seu disco**: ela tem que morar em token USB ou HSM em nuvem, e a CA fornece um +dos dois. Isso muda o CI — assinar deixa de ser "coloca o segredo e roda" e passa +a exigir ou um runner com o token plugado, ou a API do HSM da CA. + +O `gameboost-release.yml` tem o passo de assinatura preparado para o caso `.pfx`, +que serve para HSM em nuvem com credencial exportável e para certificado de teste. +Para token USB físico, o passo precisa ser trocado pelo cliente da CA. + +### EV — **não vale mais a pena** + +Até 2024 o certificado EV pulava o SmartScreen na hora. **Não pula mais.** Desde +então OV e EV passam pelo mesmo processo de reputação, e a diferença de preço +deixou de comprar alguma coisa. + +Se alguém sugerir EV "porque tira o aviso", essa informação está desatualizada. + +### Microsoft Store (MSIX) — **meta de médio prazo** + +A Microsoft reassina o pacote, e o SmartScreen nunca aparece. É o caminho mais +limpo do ponto de vista do usuário. + +Custa passar na certificação, e este app tem tudo para gerar pergunta: o Modo +Game encerra processos de terceiros, o desinstalador roda executáveis de +terceiros, e vários módulos escrevem em `HKLM`. Precisa de `runFullTrust`, e +ainda assim pode receber pedido de justificativa. + +Vale tentar depois que a v2 estiver estável, não antes. + +--- + +## Reputação: o que realmente tira o aviso + +Assinar não zera o SmartScreen. O que zera é **reputação acumulada**, e ela +depende de duas coisas: + +1. **Sempre a mesma identidade.** Trocar de certificado ou de nome de publicador + reinicia a contagem. Se um dia o certificado for renovado, renovar com a mesma + entidade, não com outra. +2. **Volume de downloads sem incidente.** Não há atalho. É tempo. + +--- + +## Submeter falso positivo + +Quando um antivírus marcar o GameBoost, o caminho é submeter a amostra, não pedir +ao usuário para desativar a proteção. + +**Microsoft Defender** — [microsoft.com/wdsi/filesubmission](https://www.microsoft.com/en-us/wdsi/filesubmission) + +Escolher "Software developer", anexar o `.exe`, e no campo de justificativa +descrever o que o programa faz e por quê. Vale citar, especificamente: + +- Ele encerra processos porque é um otimizador de jogos e essa é a função + principal, sempre com confirmação do usuário. +- Ele grava em `HKLM\SYSTEM\CurrentControlSet\Control\GraphicsDrivers` e chaves + vizinhas porque aplica ajustes documentados pela própria Microsoft. +- Ele chama `NtSetSystemInformation` para purgar a Standby List. +- Todo o código está aberto em `github.com/jvaz-DEVENG/showcase`. + +A resposta costuma vir em 24 a 72 horas. + +**Outros antivírus** — cada um tem o próprio formulário. Os mais comuns em +máquina brasileira, em ordem de aparição: Kaspersky, Avast/AVG, Bitdefender, +McAfee, Norton. Todos aceitam submissão de desenvolvedor. + +**Depois de cada release**, submeter de novo: a assinatura muda a cada build, e o +hash novo não herda a liberação do anterior. + +--- + +## O que o usuário vê hoje + +Sem certificado, no primeiro download: + +> O Windows protegeu o computador. +> O Microsoft Defender SmartScreen impediu a inicialização de um aplicativo não +> reconhecido. + +Para rodar mesmo assim: **Mais informações → Executar assim mesmo**. + +O `SHA256SUMS.txt` sai em toda release justamente para isso: quem quiser conferir +que baixou o arquivo certo tem como, sem depender de assinatura nenhuma. diff --git a/GameBoost/docs/DECISOES.md b/GameBoost/docs/DECISOES.md new file mode 100644 index 0000000..70e766d --- /dev/null +++ b/GameBoost/docs/DECISOES.md @@ -0,0 +1,769 @@ +# Decisões de projeto + +Registro de toda decisão que fugiu do `GAMEBOOST_V2_SPEC.md`, com data e motivo. + +--- + +## 2026-09-13 — A Fase 0 virou reconstrução, não refatoração + +**Contexto.** A Fase 0 do spec diz "refatore o projeto para a estrutura da seção 4.1 **sem +alterar nenhum comportamento visível**". Isso pressupõe o código-fonte da v1.0.0. + +**O que existia.** Apenas o binário: `GameBoost.rar` com `GameBoost.exe` (68 MB, +self-contained, compilado em 03/09/2026), `LEIA-ME.txt` e o desinstalador Inno. Nenhum +`.csproj`, `.sln`, `.iss` ou repositório git da v1 em nenhum disco da máquina. + +**Decisão.** Reconstruir a v1 do zero já dentro da arquitetura alvo da seção 4.1, usando o +`LEIA-ME.txt` da v1 e a seção 1 do spec como especificação do comportamento a reproduzir. + +**Consequência.** Não há garantia de paridade byte-a-byte com a v1 — só de paridade +funcional com o que estava documentado. O checklist de QA (`docs/QA.md`) lista o que precisa +ser validado à mão contra o binário antigo. + +--- + +## 2026-09-13 — CLI é biblioteca, não executável separado + +**Spec.** A seção 4.1 lista `GameBoost.Cli/` como projeto; a seção 8 diz +"`GameBoost.Cli` ou argumentos no exe principal". + +**Decisão.** `GameBoost.Cli` é uma **biblioteca** (`CommandLineOptions` + `CommandRunner`) +consumida pelo `GameBoost.App`. O `App.OnStartup` faz o parse dos argumentos e, se for um +comando de linha, anexa ao console do processo pai (`AttachConsole(-1)`), executa e sai. + +**Motivo.** A v1 expunha `GameBoost.exe --scan relatorio.txt`. Dois executáveis quebrariam +esse contrato, dobrariam o tamanho do publish self-contained e obrigariam o instalador a +distribuir os dois. Uma biblioteca preserva o comando da v1 e ainda deixa a lógica testável +sem levantar processo. + +--- + +## 2026-09-13 — `AppSettings.SchemaVersion` começa em 0, não na versão atual + +**Decisão.** O inicializador do campo foi removido; a propriedade começa em `0` e o +`SettingsStore.Save` carimba `VersaoAtual` na gravação. + +**Motivo.** Com `= VersaoAtual` no campo, um `settings.json` da v1 — que não gravava +`schemaVersion` — desserializava como se já fosse da versão corrente, e o migrador **nunca +rodava**. O bug foi pego pelo teste +`Settings_da_v1_sem_schema_version_e_migrado_e_o_original_vira_bak`. + +--- + +## 2026-09-13 — Prioridade do jogo não gera ChangeRecord + +**Decisão.** Subir a prioridade do processo do jogo para Alta é registrado no log, mas não +gera entrada em `state-backup.json`. + +**Motivo.** Prioridade de processo é estado volátil: morre junto com o processo e não +sobrevive a reboot. Um `ChangeRecord` pendente apontando para um PID que não existe mais só +poluiria a lista de reversão. A regra 1 continua valendo para tudo que **persiste** +(registro, serviço, plano de energia). + +--- + +## 2026-09-13 — Processos críticos não aparecem na lista da UI + +**Spec.** A seção 4.2 prevê itens protegidos aparecendo bloqueados, não selecionáveis. + +**Decisão.** Itens protegidos por `ProcessoCritico`, `DentroDoWindows`, `OutraSessao` e +`ProprioApp` são **omitidos** da lista. Anti-cheats e antivírus **aparecem**, bloqueados, +com a explicação. + +**Motivo.** Incluir todos encheria a tela com dezenas de `svchost` que o usuário nunca deve +tocar, escondendo o que importa. Anti-cheat e antivírus continuam visíveis de propósito: é +informação útil ("o Vanguard está rodando"), e ver que o app se recusa a tocar neles é o que +constrói confiança. + +--- + +## 2026-09-13 — Escrita de arquivo com retry na troca atômica + +**Decisão.** `WindowsFileSystem.WriteAllText` grava `.tmp` e faz `File.Move(overwrite)` com +até 5 tentativas e espera progressiva. + +**Motivo.** O antivírus desta máquina segura o handle do `.tmp` por alguns milissegundos +depois da escrita, fazendo a troca atômica falhar com `IOException` de forma intermitente. +Sem o retry, `state-backup.json` poderia não ser gravado exatamente no momento mais crítico: +logo antes de alterar o sistema. + +--- + +## 2026-09-13 — ReadyToRun dobra o tamanho do executável + +**Medido.** O publish self-contained single-file da v2 saiu com **138 MB**, contra 68 MB do +`GameBoost.exe` da v1.0.0. + +**Causa.** `PublishReadyToRun=true`, pedido pela seção 10 do spec para reduzir o tempo de +abertura. Ele embute código nativo pré-compilado junto do IL. + +**Decisão.** Manter ligado, como o spec pede, e medir o tempo de abertura com e sem antes de +decidir de vez. Trimming continua proibido (quebra WPF), então não há como recuperar o +tamanho por esse caminho. + +**A conferir na Fase 7:** se o ganho de abertura não justificar +70 MB de download, desligar +ReadyToRun é a alternativa óbvia — o instalador comprimido sofre menos com isso que o +download do portátil. + +--- + +## 2026-09-13 — Fase 1: nada de PerformanceCounter por nome traduzido + +**Problema.** A seção 5.5 sugere contadores como `\Processor Information(_Total)\% Processor Performance`. +Num Windows **pt-BR esse contador não existe** — chama-se "Informações do Processador". +`PerformanceCounter` exige o nome no idioma do sistema, então o código do spec falharia +nesta máquina e em qualquer Windows não-inglês. + +**Decisão.** Onde há API nativa, usar API nativa: `NtQuerySystemInformation` para CPU, +Standby List e processos; `GlobalMemoryStatusEx` para memória; `DriveInfo` para disco; +`System.Net.NetworkInformation` para rede. Todas devolvem números, não texto. + +Para GPU não existe equivalente público, então ali se usa PDH — mas via +`PdhAddEnglishCounter`, que aceita o nome em inglês em qualquer idioma. + +--- + +## 2026-09-13 — A coleta custava 5% de CPU; três correções a levaram a 0,1% + +O critério de aceite da seção 5.5 é **menos de 1,5% de CPU**. A primeira versão media +**5,02%**, com ciclo de **1039 ms** — mais lenta que o próprio intervalo de coleta. + +| O que estava errado | Correção | Resultado | +|---|---|---| +| Um `PerformanceCounter` por instância de GPU; cada `NextValue()` é uma consulta separada | Uma consulta PDH com curinga cobre todas as instâncias de uma vez | ciclo 1039 ms → 36 ms | +| `Process.GetProcesses()` + `TotalProcessorTime` abria um handle por processo, ~400 vezes por segundo | `NtQuerySystemInformation(SystemProcessInformation)`: tudo numa chamada, sem handle | — | +| Detecção de jogo chamava WMI `Win32_Process` a cada 10 s | Detecta pelo nome nos processos que a coleta já leu | — | +| Interfaces de rede reenumeradas a cada segundo | Lista revalidada a cada 30 s | — | + +**Medição final:** 0,115% de CPU, ciclo de 36 ms, app inteiro em 0,61%. + +### A medição do custo também estava errada + +O primeiro cálculo usava `Stopwatch`, que mede **tempo de parede**: um ciclo parado +esperando WMI parecia caro sem ter gasto CPU. Foi trocado por `GetThreadTimes`, que mede o +tempo de CPU realmente consumido. + +Isso exigiu tirar o laço do `Task.Delay`: com `await`, a continuação volta em **outra** +thread do pool e a medição compara threads diferentes. O monitor passou a usar uma thread +dedicada, com prioridade abaixo do normal — o que também garante que ele ceda lugar ao jogo. + +Medir errado o próprio custo é exatamente o tipo de número inventado que a regra 4 proíbe. + +--- + +## 2026-09-13 — Temperaturas ficam indisponíveis nesta fase + +**Spec.** A seção 5.5 pede LibreHardwareMonitorLib para temperaturas de CPU e GPU. + +**Decisão.** A Fase 1 usa apenas WMI `MSAcpi_ThermalZoneTemperature`, que nesta máquina +responde "Sem suporte". A UI mostra **"indisponível"**, nunca zero. + +**Motivo.** LibreHardwareMonitorLib carrega um driver em modo kernel para ler os sensores. +Isso é uma decisão de segurança e de distribuição (assinatura, antivírus, SmartScreen) grande +demais para entrar junto com o resto da Fase 1. A regra de throttling térmico já está escrita +e testada: ela simplesmente não dispara enquanto não houver leitura. + +--- + +## 2026-09-13 — `AoEntrar` no binding, não no comando de navegação + +O menu lateral troca de página pelo binding de `SelectedItem`, que **nunca passa pelo +comando** `Navegar`. Com o gancho no comando, o Diagnóstico não começava a coletar e o +Histórico não recarregava. O gancho foi para `OnPaginaAtualChanged`, que roda em qualquer +caminho de navegação. Descoberto porque o log não registrava o início da coleta. + +--- + +## 2026-09-13 — Fase 3: sem leitura crua da MFT, e não foi preciso + +**Spec.** A seção 5.4 pede leitura da MFT via `FSCTL_ENUM_USN_DATA`, com o critério de +**500 GB e 1 milhão de arquivos em menos de 30 s**. + +**Problema com o caminho sugerido.** `FSCTL_ENUM_USN_DATA` devolve `USN_RECORD`, que traz +nome, referência do pai e atributos — mas **não traz o tamanho do arquivo**. Para os +tamanhos seria preciso parsear os registros da MFT crus, lendo os atributos `$DATA` a mão. É +o que o WizTree faz, e é bem mais trabalho e risco do que a seção sugere. + +**Decisão.** A alternativa que o próprio spec autoriza: `FindFirstFileEx` com +`FIND_FIRST_EX_LARGE_FETCH` e `FindExInfoBasic`, paralelizado por subárvore com uma fila de +trabalho. O tamanho já vem no resultado da enumeração, sem uma chamada extra por arquivo. + +**Medido nesta máquina** (disco do sistema, NVMe de 953 GB): + +``` +1.548.745 arquivos, 809,5 GB somados, em 20,3 s +``` + +**Um milhão e meio de arquivos e 809 GB em 20 s** — acima do volume do critério e abaixo do +tempo. A MFT crua fica para quando houver uma máquina onde isto não seja suficiente. + +### O contador de pastas inacessíveis estava mentindo + +A primeira versão contava **40.871 pastas "sem acesso"**. O número real é **543**: eu estava +contando toda pasta vazia como inacessível, porque `FindFirstFileEx` devolve lista vazia nos +dois casos. Agora o código separa pelos códigos de erro do Windows +(`ERROR_FILE_NOT_FOUND`, `ERROR_PATH_NOT_FOUND`, `ERROR_NO_MORE_FILES` não são falta de +permissão). Reportar 40 mil problemas onde há 543 é exatamente o número inventado que a +regra 4 proíbe. + +### Outras decisões da varredura + +- **Reparse points são ignorados.** Junção e link simbólico apontam para outro lugar: segui-los + contaria o mesmo espaço duas vezes e pode virar laço infinito. +- **Prefixo `\?\`** em todo caminho, senão a varredura para nos 260 caracteres — o que + acontece em qualquer `node_modules` ou pasta de build. +- **`Consolidar` é iterativo**, não recursivo: há um teste com 20 mil níveis de profundidade, + que estouraria a pilha numa versão recursiva. + +--- + +## 2026-09-14 — A árvore do disco custava 1,1 GB de RAM + +Depois da primeira varredura pela interface, o app estava com **1118 MB**. A causa era o +`DiskNode` guardar o **caminho completo** em cada nó: numa árvore de 1,5 milhão de arquivos, +com caminhos de 60 a 100 caracteres, isso sozinho passa de 240 MB, mais o overhead de cada +string. + +O caminho já está na estrutura — é a sequência de nomes até a raiz. Agora só a raiz guarda um +caminho completo, e os demais montam o seu subindo pelos pais. + +**Medido: 594 MB**, contra 1118 MB. Redução de 47%. + +Ao fazer isso apareceu uma armadilha: o scanner montava o caminho de cada filho a partir de +`pasta.Caminho`, o que passou a subir a árvore inteira **por arquivo** e tornaria a varredura +quadrática na profundidade. O caminho da pasta passou a ser calculado uma vez por pasta, não +por arquivo. O construtor foi separado em dois — um para a raiz, um para filho — para que +esse erro não seja possível de escrever de novo. + +**Ainda são 594 MB** para 1,5 milhão de nós, e não vale esconder isso: uma versão futura pode +guardar a árvore achatada em arrays em vez de objetos ligados. Por ora o custo aparece só +enquanto a página de Espaço está aberta. + +--- + +## 2026-09-14 — Fase 4: `Get-AppxPackage` devolvia zero numa máquina com 142 pacotes + +Duas causas somadas, e cada uma sozinha já bastava para zerar a lista. + +A primeira: `powershell -Command "..."` passa pela análise de linha de comando do próprio +PowerShell, que reinterpreta pipe e cifrão antes de o script existir. A correção é +`-EncodedCommand` com o script em Base64 UTF-16LE — o texto chega intacto. + +A segunda é mais traiçoeira. O `stderr` estava redirecionado e ninguém lia. O PowerShell +escreve o progresso ali em CLIXML; o buffer do pipe encheu, o processo parou de escrever à +espera de alguém ler, e morreu no timeout de 30 s sem uma linha de erro. Agora as **duas** +saídas são lidas em paralelo, e o script começa com `$ProgressPreference = 'SilentlyContinue'`. + +Vale para qualquer processo filho: redirecionar um fluxo obriga a lê-lo. + +## 2026-09-14 — O casamento exato de nomes acusava app instalado como resto + +A primeira varredura de restos devolveu 53 pastas e 26 GB, e entre elas estavam +`BraveSoftware`, `EpicGamesLauncher` e uma pasta `Programs` de 9 GB. Os dois primeiros estão +instalados — só escrevem a pasta sem os espaços do nome. `Programs` é guarda-chuva: tem +vários apps ativos dentro, não é resto de nada. + +A comparação passou a normalizar (fora espaço e pontuação, tudo minúsculo) e a aceitar +prefixo a partir de 5 caracteres, e as pastas guarda-chuva entraram na lista de ignoradas. +Caiu para 30 pastas e 3,9 GB. + +O corte de 5 caracteres é arbitrário e foi escolhido por um motivo assimétrico: **deixar de +listar um resto custa espaço em disco; apontar um app ativo como resto custa os dados do +usuário.** Quando errar é inevitável, errar para o lado barato. + +## 2026-09-14 — O resumo dos apps anunciava 1812 GB num disco de 953 GB + +A tela ficou pronta, rodou na máquina real e disse "217 aplicativos, 1812,4 GB no total". +Nenhum teste pegaria: cada tamanho individual estava certo. O que estava errado era somar. + +Dois erros diferentes, os dois só visíveis no total: + +1. Instalador que grava `InstallLocation` apontando para `C:\Program Files` ou para a raiz + do disco. Medir aquela pasta atribui o disco inteiro a um programa só. Agora pasta + genérica é recusada e o app fica sem tamanho medido, que é a resposta honesta. +2. Vários apps declarando a **mesma** pasta — componentes de uma suíte, por exemplo. A soma + contava a pasta uma vez por app. Agora agrupa por pasta antes de somar. + +Deu 1044 GB para 2145 GB de disco. O teste que trava isso não confere um número exato: ele +compara o total com a capacidade real dos discos e recusa qualquer app que sozinho responda +por mais de um terço. É o tipo de invariante que sobrevive à máquina mudar. + +No mesmo teste apareceu um terceiro erro. A lista vinha ordenada pelo tamanho **declarado no +registro**, porque o inventário ordena antes de a medição acontecer. O ARK, com 309 GB +reais, aparecia atrás do AutoCAD, que declara 4 GB — exatamente o contrário do que a tela +serve para mostrar. + +## 2026-09-14 — "Sem assinatura digital" era acusação falsa + +O Teams aparecia na inicialização com a observação "sem assinatura digital". O executável +dele mora em `WindowsApps`, cuja ACL barra leitura até para o administrador: o +`WinVerifyTrust` não reprovou a assinatura, ele nunca conseguiu abrir o arquivo. + +`Assinado` virou tri-estado (`bool?`): verdadeiro, falso e **não deu para verificar**. São +coisas diferentes e a tela agora diz qual é qual. Atalho da pasta Inicializar não fala de +assinatura nenhuma — o `.lnk` aponta para outro lugar e nunca é assinado, então dizer que +ele não tem assinatura não diria nada sobre o programa. + +--- + +## 2026-09-14 — O spec foi atualizado no meio da Fase 5: o que conflita + +O `GAMEBOOST_V2_SPEC.md` foi substituído por uma versão nova enquanto a Fase 5 +estava sendo escrita. Duas seções mudaram de tamanho: a **5.9 (Rede)** virou um +módulo inteiro com NAT, e a **5.3 (Desinstalador)** ganhou uma aba de +atualizações via `winget` — esta última atribuída à Fase 4, que já foi entregue. + +Antes de tocar em código, o que já existe e diverge do texto novo: + +### Onde o arquivo estava + +O spec novo não chegou ao repositório: o `GAMEBOOST_V2_SPEC.md` de lá continuava +sendo o de 13/09, e as duas versões novas estavam em `C:\Users\User\Downloads` +como `GAMEBOOST_V2_SPEC (1).md` e `(2).md`. A `(2)`, de 14/09 12:20, é a única +com as duas mudanças (8 menções a `winget`, 4 a `STUN`); foi ela que entrou no +repositório. + +### 5.9 — o que já estava escrito e diverge + +| O que existe | O que o spec novo pede | Situação | +|---|---|---| +| NAT em 5 categorias próprias (sem NAT, aberto, simétrico, CGNAT, duplo) | 3 categorias no vocabulário do Xbox: **Aberto / Moderado / Estrito** | Corrigido nesta fase | +| STUN de **uma** porta local para 2 servidores | 2 servidores **por 2 portas locais** | Corrigido nesta fase | +| Semáforo do ping em 20/50/100 ms | 30 / 80 ms | Corrigido nesta fase | +| Teste de velocidade: um download de 25 MB | 3 amostras de 10 s, reportar a **mediana** | Corrigido nesta fase | +| Aviso de jitter a partir de 5 ms no gateway | Finding com jitter > 15 ms e perda > 1% | Os dois convivem: ver nota abaixo | +| DNS trocado por `netsh` | `SetDNSServerSearchOrder` via WMI | Fica como está: ver nota abaixo | +| Banda por processo: recusada, mostra contagem de conexões | bytes por processo via `GetExtendedTcpTable`/`GetExtendedUdpTable` | Não é possível como descrito: ver nota abaixo | + +**Jitter.** O spec define o Finding em 15 ms, e está certo para a conexão como um +todo. O aviso de 5 ms que já existia é outra coisa e continua: ele mede o jitter +até o **próprio roteador**, onde 5 ms já é anormal e aponta problema dentro de +casa. São duas medidas diferentes com dois limiares diferentes, não uma +contradição. + +**DNS por `netsh`, não por WMI.** `SetDNSServerSearchOrder` pertence à +`Win32_NetworkAdapterConfiguration`, congelada desde o Windows 8, e falha em +adaptador configurado por DHCP em parte das máquinas. O `netsh` é o caminho que o +próprio Windows usa, e é ele que permite voltar ao estado "automático (DHCP)" — +que o método WMI não expressa. A reversão para automático é justamente o caso +mais fácil de perder. + +**Bytes por processo.** `GetExtendedTcpTable` devolve **conexões com o PID dono**, +não contadores de tráfego. Não existe contador por processo de rede no Windows +sem ETW, e um consumidor de ETW rodando continuamente estoura o teto de 1,5% de +CPU da regra 9. O que a tela faz é o que dá para afirmar: quantas conexões cada +processo mantém e para onde. O texto na tela diz isso com todas as letras, em vez +de apresentar contagem de conexão como se fosse velocidade. + +### 5.9 — o que o spec novo pede e ainda não existe + +Fica como pendência declarada desta fase, não como coisa feita: + +- Histórico em `network-history.json` ("sua conexão piorou à noite"). +- Ping para servidores de jogo por região (Riot, Valve, Blizzard, Epic, EA em + São Paulo). +- UPnP hoje é só descoberta SSDP. O `AddPortMapping` de teste, e a abertura de + porta com lease de 24 h removida ao sair do jogo, dependem do detector de jogo + em execução, que é da Fase 6. +- Firewall: hoje lê o estado dos perfis pelo registro. `INetFwPolicy2` via COM + para procurar regra do executável do jogo, e `Set-NetConnectionProfile` para + marcar a rede como Privada, também dependem do jogo detectado. +- Checagem de IPv6 e de velocidade de link do adaptador. +- Tabela de ASN para nomear a operadora, texto pronto para o suporte e passo a + passo por marca de roteador. + +### 5.3 — a aba "Atualizações" (winget) é da Fase 4, que já foi entregue + +O spec novo põe a aba de atualizações via `winget` na seção 5.3, e a seção 12 +mantém a 5.3 na **Fase 4**, que foi fechada e commitada antes desta versão do +documento existir. + +Ela **não** foi feita, e não vai ser embutida na Fase 5 fingindo que sempre +esteve lá. Entra como a primeira coisa depois que a Fase 5 fechar, com o commit +dizendo que é complemento retroativo da Fase 4. Misturar as duas deixaria o +histórico mentindo sobre o que cada fase entregou. + +Na mesma seção 5.3 há outros dois pontos ainda não atendidos: + +- A varredura de restos cobre pastas, mas não as chaves `HKCU\Software\` e + `HKLM\Software\`, nem a exportação para `.reg` antes de apagar. +- O ponto de restauração antes de um lote grande existe em Ferramentas, mas não é + oferecido dentro do fluxo de desinstalação. + +--- + +## 2026-09-14 — A saída do winget não tem formato para máquina ler + +`winget upgrade` não tem `--output json`, nem XML, nem nada estruturado. O que +existe é uma tabela de largura fixa desenhada para humano. + +O jeito óbvio de ler seria procurar a coluna "Available". Ele quebra na primeira +máquina em outro idioma: em português o cabeçalho é "Disponível", e nesta máquina +o winget imprimiu os cabeçalhos em inglês enquanto traduzia os **nomes** dos +pacotes ("Subsistema do Windows para Linux"). Depender de qualquer palavra seria +depender de uma combinação que nem é consistente dentro da mesma saída. + +A leitura é por **posição de coluna**: acha-se a régua de tracinhos, o cabeçalho é +a linha acima dela, e as colunas começam onde há caractere visível logo depois de +um espaço. Nenhuma palavra é lida. Sobre isso vêm duas defesas: id com espaço no +meio é descartado (sinal de que a fatia caiu errado), e a fonte precisa ser uma +das que o winget conhece. + +O teste que trava isso passa a mesma tabela com cabeçalho em português. Se o +parser voltar a depender de idioma, ele devolve zero e o teste falha. + +## 2026-09-14 — Um template de TabControl apagou a árvore de acessibilidade + +As abas da página de Apps saíram ilegíveis no tema escuro: o `TabItem` padrão do +WPF ainda usa o visual do Windows Classic, com fundo claro e texto escuro. Reescrevi +o `ControlTemplate` do `TabItem` e do `TabControl`. + +O script de captura de tela parou de achar os botões. A investigação mostrou algo +pior que um problema de teste: **a automação não enxergava nenhum controle dentro +das abas**. As três abas apareciam; da aba para dentro, árvore vazia. + +A causa é que o `TabControlAutomationPeer` do WPF procura um `ContentPresenter` +chamado exatamente `PART_SelectedContentHost` para expor o conteúdo da aba +selecionada. O meu tinha `ContentSource="SelectedContent"` e nenhum nome, então +renderizava certo e sumia da automação. + +Isso vale registrar por dois motivos. O primeiro é que um leitor de tela não +alcançaria botão nenhum dessa página — regra de acessibilidade da seção 6. +O segundo é o método: o defeito só apareceu porque **a mesma automação que um +leitor de tela usa** é a que dirige os testes de tela. Um teste que clicasse por +coordenada teria passado. + +## 2026-09-14 — Prefixo de id não é fronteira + +A classificação das atualizações casava o id do winget por prefixo cru. Na tela +real, "Chrome Remote Desktop Host" apareceu marcado como **atualização de +segurança** e como navegador que se atualiza sozinho: o id dele é +`Google.ChromeRemoteDesktopHost`, e `StartsWith("Google.Chrome")` é verdadeiro. + +O id do winget é hierárquico e separado por ponto. O casamento passou a exigir +fronteira: ou igualdade, ou o caractere seguinte é um ponto. Padrão terminado em +ponto (`AMD.`, `Intel.`, `RiotGames.`) continua valendo para a família inteira, e +isso é proposital. + +O mesmo erro pegaria `Git.GitLFS` como se fosse o `Git.Git`, e +`Microsoft.EdgeWebView2Runtime` como se fosse o navegador Edge. Os três casos +estão no teste. + +--- + +## 2026-09-14 — Detecção de jogo por polling, não por WMI + +A seção 5.1 sugere `Win32_ProcessStartTrace`, que entrega o evento no instante em +que o processo nasce. A alternativa é listar processos de tempos em tempos. + +Ficou o polling de 2 segundos, e o motivo é medido, não teórico: na Fase 1 a +coleta de métricas chegou a **5,02% de CPU**, e o WMI era a maior parcela +daquilo. O `ProcessStartTrace` exige uma consulta WMI viva o tempo todo — é +exatamente a forma que saiu de lá. Listar processos por +`NtQuerySystemInformation` custa microssegundos. + +Dois segundos de atraso não importam para isto. O que acontece ao detectar é uma +pergunta num diálogo; ninguém percebe a diferença entre responder no segundo 0 e +no segundo 2. Se um dia houver um caso que precise do instante exato, o +argumento muda. + +## 2026-09-14 — `UseWindowsForms` contaminou a aplicação inteira + +O WPF não tem ícone de bandeja. O caminho normal é o `NotifyIcon` do WinForms, +que exige `true` no csproj. + +O que esse switch faz, além de referenciar a biblioteca, é injetar `global using +System.Windows.Forms` e `global using System.Drawing` em **todo arquivo do +projeto**. `Control`, `Application`, `Brush` e `MouseEventArgs` existem nos dois +mundos, então metade da aplicação parou de compilar por ambiguidade — inclusive +o `Treemap` e o `MiniGrafico`, que não têm nada a ver com bandeja. + +A correção é remover só os usings implícitos, mantendo a referência: + +```xml + + + + +``` + +O arquivo da bandeja declara os dele explicitamente. O resto da aplicação +continua vendo só o WPF. + +## 2026-09-14 — O timer de 0,5 ms não faz o que dizem que faz + +`NtSetTimerResolution(0.5 ms)` é receita fixa de todo guia de otimização. Desde o +Windows 10 2004 o pedido é **por processo**: o GameBoost pedir 0,5 ms afeta o +GameBoost, não o jogo. O que sobra é o sistema honrar a menor resolução pedida +por alguém para temporizadores globais, e é daí que vem o ganho residual que +ainda aparece no Windows 10. + +Ele entrou no perfil porque é reversível e não custa nada, fica **desligado por +padrão**, e o texto na tela não promete FPS. É o mesmo tratamento dado ao +`SystemResponsiveness` e ao `NetworkThrottlingIndex` na Fase 5: marginal escrito +como marginal. + +--- + +## 2026-09-14 — Assinatura de código: o Brasil está fora da opção barata + +O caminho óbvio seria o Azure Artifact Signing, ex-Trusted Signing, a US$ 9,99 +por mês. Ele não está disponível aqui: para desenvolvedor individual a Microsoft +só aceita Estados Unidos e Canadá, e para organização a lista maior também não +inclui o Brasil. Não é preço nem papelada, é elegibilidade geográfica. + +Sobra o certificado OV de uma CA — Certum, Sectigo, DigiCert —, entre US$ 70 e +300 por ano. Desde junho de 2023 a chave privada precisa ficar em token USB ou +HSM em nuvem, o que muda o CI: assinar deixa de ser "põe o segredo e roda". + +O que **não** vale é EV. Até 2024 ele pulava o SmartScreen na hora; desde então +OV e EV passam pelo mesmo processo de reputação, e a diferença de preço parou de +comprar alguma coisa. É informação que ainda circula desatualizada. + +Por isso o passo de assinatura do workflow é **condicional**. Sem o segredo, a +release sai sem assinatura e o CI emite um aviso visível. Falhar a build inteira +por falta de um certificado que ainda não existe impediria de publicar qualquer +coisa. Detalhes em docs/ASSINATURA.md. + +## 2026-09-14 — A here-string do PowerShell é incompatível com bloco YAML + +O passo que monta o `portable.txt` usava uma here-string (`@'...'@`). O +PowerShell exige o terminador `'@` no começo da linha, sem espaço antes. O YAML +exige que todo conteúdo de um bloco `run: |` fique indentado. + +As duas regras não coexistem: o `'@` em coluna 0 encerra o bloco YAML no meio do +script, e o arquivo deixa de ser YAML válido. O erro não aparece ao escrever — +aparece quando o GitHub recusa o workflow. + +Trocado por um array de strings passado ao `Set-Content`, que faz o mesmo e +respeita a indentação. Vale como regra geral para qualquer script PowerShell +dentro de workflow. + +Vale registrar também **como** isso foi pego: validando o YAML com um parser +antes do commit, e não confiando na leitura. O mesmo cuidado achou que o +`build.yml` continuava válido. + +--- + +## 2026-09-14 — Seis bugs em três passos de QA manual + +A primeira rodada de QA à mão, com o binário elevado na máquina real, achou seis +problemas nos três primeiros passos do checklist. Nenhum deles tinha aparecido em +304 testes automatizados nem nas capturas de tela. + +Os dois piores eram de **ciclo de vida**, não de lógica: + +**A sessão nunca era encerrada.** `ReverterTudo` desfazia tudo e limpava a tela, +mas não tocava no `session.json`. O arquivo continuava dizendo `Ativo: true` para +sempre, e o aviso de "fechado com o Modo Game ainda ativo" voltava em toda +abertura — com zero pendências. Nenhum teste pegaria: o estado do registro estava +perfeito, e é isso que os testes conferem. + +**`Dispose` chamado duas vezes.** O `App.OnExit` descartava o `GameWatcher`, e o +`ServiceProvider.Dispose()` logo abaixo descartava de novo, porque o container +descarta todo singleton `IDisposable`. A segunda chamada caía num +`CancellationTokenSource` já descartado. A exceção escapava porque o +`ServiceProvider.Dispose()` estava **fora** do `try`. + +A lição que fica: quem escreve `Dispose` não controla quantas vezes ele é chamado. +Aguentar a segunda chamada em silêncio é obrigação, não gentileza. + +Os outros quatro eram de **interface dizendo uma coisa e fazendo outra**: + +- O rodapé anunciava "N aguardando na lista de restauração" sem existir lista. +- O resumo dizia "38 apps marcados" com um único item marcado, porque era o texto + da varredura e não acompanhava a seleção. +- Faltava marcar/desmarcar todos na tela Início. +- Faltava busca em qualquer lista. + +Os dois últimos vieram como sugestão de quem estava testando, não como defeito. A +busca, em particular, já estava na seção 5.3 do spec e tinha passado despercebida +por sete fases. + +**O que isso diz sobre o método.** As capturas de tela das fases anteriores +mostravam cada tela funcionando, e todas estavam certas. O que elas não mostram é +a **sequência**: abrir, agir, fechar, reabrir. Foi aí que os seis apareceram. + +--- + +## 2026-09-14 — Medir banda por tamanho fixo mede a rajada, não a linha + +O spec pedia "3 amostras de 10 s". Eu troquei por 25 MB de transferência +cronometrada, achando que era equivalente e mais previsível para quem tem +franquia. Deixei isso escrito como se fosse uma melhoria. + +Não é equivalente. Numa fibra doméstica a operadora deixa passar bem acima do +contratado por um ou dois segundos. Uma transferência que termina em 0,6 segundo +mede **só essa rajada**. + +O sinal de que algo estava errado foi o número não bater com um teste dedicado. +A investigação mostrou o problema de forma direta — mesma linha, mesmo minuto: + +| Tamanho | Tempo | Upload medido | +|---|---|---| +| 8 MB | 0,51 s | 131,5 Mbps | +| 25 MB | 0,59 s | 354,7 Mbps | +| 50 MB | 1,53 s | 274,9 Mbps | + +Três respostas diferentes para a mesma pergunta. Um teste cujo resultado depende +do tamanho escolhido não está medindo a linha. + +A correção é medir por **tempo** (8 s) e descartar os 2 primeiros segundos, onde +moram a rajada e o slow start do TCP. O teto de 120 MB por amostra continua +protegendo a franquia. + +### E um erro meu de leitura, no meio disso + +Ao comparar com o Speedtest, eu vi 380 Mbps de upload contra 73 e afirmei que 380 +era impossível e 73 era o número real. Estava errado: aquele servidor específico +(em Cotia) é que limitava. Trocando de servidor, o mesmo Speedtest deu **264 +Mbps**, e o ping caiu de 361 ms para 4 ms. + +A medição corrigida dá 224 Mbps — 15% abaixo dos 264, que é o esperado para um +teste de um servidor só e uma conexão só. + +A lição vale para o texto da tela, e ela foi escrita lá: **um teste dedicado +também erra** quando o servidor escolhido está congestionado. Dizer "o Speedtest +é a verdade" seria tão impreciso quanto o defeito original. + +## 2026-09-14 — A tela mostrava o estado de antes depois de agir + +Quem testava perguntou se estava certo o badge de um serviço dizer "automático" +depois da reversão. Estava — mas a pergunta revelou o problema: logo **depois de +aplicar**, com o serviço já em Manual, o badge também dizia "automático". + +A lista guarda o resultado da varredura e não é refeita depois de agir. Na +prática: a pessoa aplica, olha a linha, e parece que nada aconteceu. + +Refazer a varredura sozinho resolve onde ela é barata. Em Tweaks são +milissegundos, e passou a ser automático. Na lista de apps instalados leva quase +um minuto, e revarrer sozinho travaria a tela logo depois de uma ação — ali a +tela avisa que a lista mostra o estado de antes. + +A regra que fica: **ou a tela mostra o estado atual, ou diz que não está +mostrando.** O que não pode é apresentar um estado velho como se fosse o de +agora. + +--- + +## 2026-09-14 — Um `Pack = 1` apagou a Lixeira do aplicativo inteiro + +Ao confirmar o envio de uma pasta para a Lixeira, o GameBoost fechou. Sem caixa +de erro, sem exceção no log — o log simplesmente parava na linha anterior. O +Visualizador de Eventos do Windows mostrou o motivo: + +``` +Código de exceção: 0xc0000005 +Nome do módulo com falha: GameBoost.exe +``` + +Violação de acesso. Crash nativo, não exceção .NET — por isso os três handlers +globais não pegaram nada. + +A causa estava numa linha: + +```csharp +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)] +private struct SHFILEOPSTRUCT +``` + +Em x64 os campos dessa struct são alinhados em 8 bytes. Com `Pack = 1` o .NET +remove o preenchimento, e o `pFrom` passa a ficar no deslocamento 12. O `shell32` +continua lendo do 16 — pega a metade alta de um ponteiro colada na metade baixa +do seguinte, e usa aquilo como endereço de memória. + +### O que isso diz sobre o que não foi testado + +**A Lixeira nunca funcionou.** Limpeza, Espaço e Restos chamam a mesma função. +Três módulos, três fases, e em todos eles a ação de "mandar para a Lixeira" +derrubaria o aplicativo. + +O `docs/QA.md` tinha o item desde a Fase 2: + +``` +- [ ] Enviar um arquivo para a Lixeira pela página e conferir que dá para restaurar +``` + +Ficou sem marcar por três fases. E não era um detalhe cosmético esperando na +fila: era a única forma de descobrir um crash garantido. + +Marshalling de P/Invoke não quebra a compilação e não aparece em teste que use +fake. O teste novo chama a função de verdade, com um arquivo de verdade. É o +único jeito. + +--- + +## 2026-09-14 — A detecção funcionava; era tudo depois dela que não + +O QA do bloco de perfis achou seis problemas, e o padrão entre eles diz mais que +cada um isolado. + +A **detecção** estava certa desde a Fase 6. O log registrava exatamente o que o +spec pede: + +``` +Watcher | Warframe.x64 | executavel dentro de uma pasta de launcher, + confiança 80, tela cheia True +``` + +O que não existia era o resto do caminho: + +1. O convite era disparado num evento **sem nenhum assinante**. Nascia e morria + na mesma linha. +2. Aceitar o convite só navegava para outra tela. O `ProfileRunner` — a classe + inteira escrita para aplicar o perfil — nunca era chamada por ninguém. +3. Fechar o jogo também não chamava o `ProfileRunner`, mas a mensagem afirmava + que "tudo o que o GameBoost alterou foi desfeito". + +Três elos de uma corrente de quatro. O primeiro funcionava perfeitamente e os +outros três não existiam — e nada disso aparece em teste unitário, porque cada +peça isolada faz o que promete. O `ProfileRunner` tem teste e passa. O que não +tinha teste era **alguém chamando o ProfileRunner**. + +### O falso positivo, e por que o filtro de memória não bastou + +Numa sessão real o app anunciou "Agent está rodando. Ativar o Modo Game?". + +`Agent.exe` é o atualizador do Battle.net, e mora em +`C:\ProgramData\Battle.net\Agent\Agent.9775\`. A pasta bate com a lista de +launchers, o que dá confiança 80. + +Existia um filtro: dentro de pasta de launcher, só conta como jogo quem usa mais +de 300 MB. Parecia suficiente — o Agent usa 32 MB parado. Só que ele **engorda +enquanto baixa atualização**, e naquele momento passou do limite. + +Filtro de memória não distingue jogo de instalador; distingue processo grande de +processo pequeno. O que separa de verdade é o nome: jogo tem nome próprio, +infraestrutura tem nome de função. "Agent", "Launcher", "Updater" e "Helper" +descrevem o que o programa faz — e dentro da pasta de um launcher, isso é sempre +a maquinaria dele. + +O teste novo passa 2 GB de RAM de propósito, para provar que o filtro de nome +vale mesmo quando o processo está gordo. + +### Um texto que negava o que tinha acontecido + +Quando o jogo fecha antes de o Desfazer rodar, não sobra prioridade para +restaurar: ela morre junto com o processo. Isso é o esperado. + +Mas o aviso dizia **"Não havia perfil aplicado"** — e havia. A frase negava um +fato para descrever uma consequência. Agora ela explica o que aconteceu de +verdade: o perfil saiu junto com o jogo. + +--- + +## Pendências conhecidas desta fase + +- `--clean` (seção 5.2) responde com "chega na Fase 2" e código de saída 3. Está no parser + porque a superfície da CLI da seção 8 precisa existir inteira desde já. + `--report` foi implementado na Fase 1. +- `Resources/Strings.pt-BR.resx` ainda não existe: as strings estão no código. A regra 7 diz + que devem sair para recurso; fica para quando houver a segunda tela, para não criar um + arquivo de recursos com meia dúzia de entradas. +- Detecção automática de jogo por `Win32_ProcessStartTrace` (seção 5.1) não foi feita: a + detecção atual é sob demanda, durante a varredura. Entra na Fase 6, junto com os perfis. +- Findings de uso de disco e rede **por processo** (seção 5.5) dependem de ETW e ficaram de + fora; as regras que existem usam CPU, memória e os fatos do sistema. +- Latência DPC/ISR, marcada como experimental no spec, não foi implementada. +- Timer resolution: o P/Invoke existe (`NativeMethodsBridge.SetTimerResolution`) mas não é + chamado pelo Modo Game ainda. Entra na Fase 6. diff --git a/GameBoost/docs/PORTAS.md b/GameBoost/docs/PORTAS.md new file mode 100644 index 0000000..55070fc --- /dev/null +++ b/GameBoost/docs/PORTAS.md @@ -0,0 +1,213 @@ +# Portas por jogo e por launcher + +Referência usada pela página **Rede** quando o diagnóstico encontra NAT estrito e +o roteador não tem UPnP. É a lista que o usuário precisa ter na mão para abrir +porta à mão, e o texto de cada seção diz o que abrir e o que **não** adianta +abrir. + +> **Antes de abrir qualquer porta:** reserve um IP fixo para este PC no DHCP do +> roteador. Sem isso o endereço muda no próximo boot e a regra passa a apontar +> para outra máquina da casa — normalmente a TV. + +> **Abrir porta não resolve CGNAT nem NAT duplo.** Se o diagnóstico apontou um +> desses, a porta aberta no seu roteador morre no equipamento de cima. Veja as +> seções no fim deste arquivo. + +--- + +## O que o GameBoost faz e o que ele não faz + +| | | +|---|---| +| Detecta o tipo de NAT (STUN) | Sim | +| Detecta CGNAT, NAT duplo, UPnP, Teredo e firewall | Sim | +| Reativa o Teredo (reversível) | Sim | +| Abre porta via UPnP com prazo de 24 h | Previsto para a Fase 6, junto com o detector de jogo | +| Configura o roteador | **Não.** O roteador não é dele; uma regra escrita lá não tem como ser desfeita se o app sumir | +| Coloca o modem em bridge | **Não.** Isso derruba a internet da casa até a nova configuração subir | + +--- + +## Launchers e plataformas + +Estas são as portas que valem para **todos** os jogos da plataforma. Abrir só +estas já resolve boa parte dos casos de "não entro na festa". + +### Steam + +| Porta | Protocolo | Para quê | +|---|---|---| +| 27015 – 27030 | UDP | Tráfego de jogo e consulta de servidor | +| 27015 – 27030 | TCP | Servidor dedicado | +| 27036 – 27037 | UDP e TCP | Remote Play e transmissão na rede local | +| 4380 | UDP | Tráfego de jogo antigo (Source) | +| 3478, 4379, 4380 | UDP | Voz do Steam | + +### Epic Games + +| Porta | Protocolo | Para quê | +|---|---|---| +| 5222 | TCP | Presença e convite de amigo | +| 5795 – 5847 | UDP | Tráfego de jogo (Fortnite e outros) | +| 443, 80 | TCP | Download e autenticação | + +### Battle.net (Blizzard) + +| Porta | Protocolo | Para quê | +|---|---|---| +| 1119 | TCP e UDP | Battle.net (chat, grupo, presença) | +| 3724 | TCP | World of Warcraft | +| 6112 – 6114 | TCP e UDP | Jogos antigos e Overwatch | +| 80, 443 | TCP | Cliente e download | + +### Xbox / Game Pass no PC + +| Porta | Protocolo | Para quê | +|---|---|---| +| 3074 | UDP e TCP | Rede Xbox Live | +| 88 | UDP | Autenticação | +| 500, 3544, 4500 | UDP | **Teredo** — sem estas, festa e multiplayer não conectam | +| 53 | UDP e TCP | DNS | + +> O caso do Game Pass é o mais comum de todos: o multiplayer não conecta porque +> alguém desativou o Teredo seguindo um tutorial de "otimização de rede". O +> GameBoost detecta isso e reativa, com desfazer. Não precisa abrir porta +> nenhuma para resolver esse caso. + +### Riot (League of Legends, Valorant) + +| Porta | Protocolo | Para quê | +|---|---|---| +| 5000 – 5500 | UDP | Tráfego de partida | +| 8393 – 8400 | TCP | Cliente | +| 2099, 5222, 5223 | TCP | Chat e plataforma | + +### EA / Origin / EA App + +| Porta | Protocolo | Para quê | +|---|---|---| +| 3659 | UDP | Tráfego de jogo | +| 9988, 20000 – 20100 | UDP | Matchmaking | +| 42127 | TCP | Cliente | +| 1024 – 1124 | TCP | Download | + +### GOG Galaxy + +Não exige porta aberta: usa relay por padrão. Se um jogo específico da GOG pedir +porta, ela é do jogo, não da plataforma. + +### Discord + +| Porta | Protocolo | Para quê | +|---|---|---| +| 50000 – 65535 | UDP | Voz | +| 443, 80 | TCP | Aplicativo | + +> Discord com voz falhando e NAT estrito no diagnóstico são o mesmo problema. +> Quando não dá para abrir porta, a opção "Qualidade de serviço com prioridade +> alta" **desligada** nas configurações de voz do Discord costuma ajudar, porque +> alguns roteadores tratam mal o pacote marcado. + +--- + +## Jogos com porta própria + +Abrir a porta do launcher normalmente basta. Estes pedem porta própria quando +você quer **hospedar**. + +| Jogo | Portas | Protocolo | +|---|---|---| +| Minecraft (Java, servidor) | 25565 | TCP e UDP | +| Minecraft (Bedrock) | 19132 – 19133 | UDP | +| Terraria | 7777 | TCP | +| ARK: Survival Evolved | 7777 – 7778, 27015 | UDP | +| Valheim | 2456 – 2458 | UDP | +| Rust | 28015 – 28016 | UDP | +| Call of Duty (Warzone, MW) | 3074, 27014 – 27050 | UDP e TCP | +| Counter-Strike 2 | 27015 – 27020, 27031 – 27036 | UDP | +| Palworld | 8211 | UDP | +| Satisfactory | 7777, 15000, 15777 | UDP | + +--- + +## CGNAT: por que abrir porta não adianta + +A operadora divide um endereço público entre vários assinantes. O NAT que +atrapalha está no equipamento dela, e você não tem acesso a ele. O sinal é o IP +de WAN do seu roteador estar na faixa `100.64.x.x` a `100.127.x.x`, ou ser +diferente do IP que a internet enxerga. + +O que funciona, em ordem de esforço: + +1. **Pedir IP público à operadora.** Em várias é gratuito e resolve por telefone; + em outras é um adicional mensal. O nome varia: "IP público", "IP fixo", "IP + válido", "saída do CGNAT". +2. **IPv6.** Jogos que suportam IPv6 passam por cima do CGNAT, porque cada + máquina tem endereço próprio. A maioria das operadoras brasileiras já entrega + IPv6; verifique se está ligado no roteador. +3. **Serviço de roteamento** (ExitLag, WTFast, GearUP). Custa por mês, e não + resolve em todo jogo — só nos que aceitam o túnel. É paliativo, não solução. + +O que **não** funciona: abrir porta, ligar UPnP, DMZ, trocar de DNS, resetar o +Winsock, trocar o cabo. Nenhum deles toca no equipamento da operadora. + +--- + +## NAT duplo: dois roteadores em série + +Acontece quando o modem da operadora também roteia e você ligou o seu roteador +nele. O `tracert` mostra dois ou mais saltos com IP privado antes do primeiro +público. + +Três saídas, da melhor para a pior: + +1. **Modo bridge no modem da operadora.** Ele passa a ser só modem e o seu + roteador recebe o IP público direto. É a solução correta. Feito na + configuração do modem, e às vezes só o suporte da operadora consegue ligar. +2. **DMZ no modem apontando para o IP do seu roteador.** Todo tráfego de entrada + passa a ir para o seu roteador. Funciona, mas expõe o seu roteador inteiro. +3. **Usar só um dos dois.** Se o modem da operadora tem Wi-Fi decente, desligar o + seu roteador resolve o NAT duplo de graça. + +--- + +## Marcas de roteador: onde fica cada coisa + +O endereço é quase sempre `192.168.0.1`, `192.168.1.1` ou `192.168.15.1` — a +página Rede mostra o do seu. O usuário e a senha padrão costumam estar numa +etiqueta embaixo do aparelho. + +| Marca | UPnP | Redirecionamento de porta | +|---|---|---| +| TP-Link | Avançado → NAT Forwarding → UPnP | Avançado → NAT Forwarding → Virtual Servers | +| Intelbras | Avançado → Encaminhamento → UPnP | Avançado → Encaminhamento → Servidores Virtuais | +| Huawei | Avançado → NAT → UPnP | Avançado → NAT → Mapeamento de Portas | +| ZTE | Aplicação → UPnP | Aplicação → Encaminhamento de Portas | +| Mercusys | Avançado → NAT Forwarding → UPnP | Avançado → NAT Forwarding → Servidores Virtuais | +| Asus | WAN → Internet Connection → Enable UPnP | WAN → Virtual Server / Port Forwarding | +| D-Link | Advanced → Advanced Network → UPnP | Advanced → Port Forwarding | + +--- + +## Quando o problema é o firewall do Windows, não o roteador + +Dois casos, e os dois aparecem no diagnóstico: + +- **Rede marcada como Pública.** O Windows bloqueia entrada por padrão em rede + pública. Em casa, a rede deve estar como Privada. +- **O jogo não tem regra de entrada.** Acontece quando o jogo foi instalado + copiando a pasta, ou quando alguém clicou em "Cancelar" no aviso do firewall + na primeira execução. + +O GameBoost hoje lê e informa os dois. Criar a regra do jogo e trocar o perfil da +rede dependem de saber qual jogo está aberto, o que entra na Fase 6 junto com o +detector de jogo. + +--- + +## Fontes + +As portas vêm da documentação de suporte de cada plataforma (Steam, Epic, +Blizzard, Riot, EA, Xbox) consultadas em setembro de 2026. Elas mudam pouco, mas +mudam: quando um jogo parar de bater com esta tabela, a fonte é a página de +suporte do próprio jogo, não um fórum. diff --git a/GameBoost/docs/PROTECAO.md b/GameBoost/docs/PROTECAO.md new file mode 100644 index 0000000..bd5a0db --- /dev/null +++ b/GameBoost/docs/PROTECAO.md @@ -0,0 +1,228 @@ +# Blindagem + +Documentação das listas de proteção da seção 9 do spec. Toda lista aqui tem teste +correspondente em `SafetyGuardTests`. **Se algum desses testes cair, o app não pode ser +publicado.** + +Ponto único de consulta: `ISafetyGuard`, implementado por +[`SafetyGuard`](../src/GameBoost.Core/Safety/SafetyGuard.cs). Nenhum módulo decide sozinho o +que pode tocar. + +--- + +## 1. Processos + +### 1.1 Núcleo do Windows — omitidos da lista + +`ProtectedProcesses.Criticos`. Encerrar qualquer um derruba a sessão ou o sistema. + +`system`, `idle`, `registry`, `memory compression`, `secure system`, `smss`, `csrss`, +`wininit`, `winlogon`, `services`, `lsass`, `lsaiso`, `svchost`, `spoolsv`, `dwm`, +`explorer`, `fontdrvhost`, `sihost`, `taskhostw`, `ctfmon`, `runtimebroker`, +`shellexperiencehost`, `startmenuexperiencehost`, `searchhost`, `searchindexer`, `audiodg`, +`conhost`, `dllhost`, `wudfhost`, `wmiprvse`, `trustedinstaller`, `tiworker`, `logonui`, +`userinit`, `dashost`, `applicationframehost`, `textinputhost`, `systemsettings`, +`backgroundtaskhost` + +Além da lista: **qualquer PID ≤ 4** e **qualquer executável dentro de `C:\Windows`**. + +> A comparação de caminho é por segmento. `C:\WindowsApps` **não** conta como dentro de +> `C:\Windows` — há teste para isso. + +### 1.2 Anti-cheats — aparecem bloqueados + +`ProtectedProcesses.AntiCheats`. Fechar derruba o jogo e pode gerar banimento. + +`easyanticheat` (+ `_eos`, `_setup`), `beservice`, `bedaisy`, `battleye`, `vgc`, `vgtray`, +`vgk` (Vanguard), `faceitservice`, `faceit`, `pnkbstra/b/k` (PunkBuster), `xigncode`, +`xhunter1`, `gameguard`, `gamemon`, `npggnt`, `nprotect`, `ricochet`, `eaanticheat`, +`anticheatexpert`, `ace-base`, `ace-guard`, `mhyprot`, `sgguard`, `steamservice` + +### 1.3 Antivírus e EDR — aparecem bloqueados + +Detecção em duas camadas: + +1. **WMI** `root\SecurityCenter2` (`AntiVirusProduct`, `AntiSpywareProduct`, + `FirewallProduct`) → nome do executável assinado. É a fonte de verdade. +2. **Lista estática** `ProtectedProcesses.Seguranca`, rede de segurança para quando o WMI + falhar ou o produto não se registrar: Defender, Kaspersky, Avast/AVG, Bitdefender, + McAfee, Norton, ESET, Sophos, Carbon Black, CrowdStrike, SentinelOne, Cylance, Trend + Micro, Webroot, Malwarebytes. + +### 1.4 Nunca pré-marcados — aparecem desmarcados (regra 3) + +`ProtectedProcesses.NuncaPreMarcados`. O usuário pode marcar à mão; o app nunca marca por +ele. + +| Grupo | Exemplos | +|---|---| +| Launchers | Steam, Epic, Battle.net, Riot Client, GOG Galaxy, EA Desktop, Origin, Ubisoft Connect, Xbox | +| Overlay e monitoramento | RTSS, RivaTuner, MSI Afterburner | +| Gravação e streaming | OBS, Streamlabs, XSplit, NVIDIA Share, Radeon Software | +| IDEs e editores | Visual Studio, VS Code, Rider, IntelliJ, PyCharm, WebStorm | +| Criação | Photoshop, Illustrator, Premiere, After Effects | +| Office | Excel, Word, PowerPoint, Outlook | +| Virtualização | VMware, VirtualBox, Docker Desktop | +| Sincronização de nuvem | OneDrive, OneDrive.Sync.Service, FileCoAuth, Dropbox, Google Drive, MEGAsync, Syncthing, Nextcloud | + +**Instaladores e atualizadores em execução** também nunca vêm marcados. A detecção é por +padrão de nome (`setup`, `install`, `updater`, `update`, `upgrade`, `patch`, `msiexec`, +`redist`, `unins`, ou terminando em `.tmp`), e não por lista: instalador novo aparece toda +semana. Encerrar um no meio do trabalho corrompe a instalação. + +Além da lista: **qualquer executável dentro de pasta de jogo** — +`\steamapps\common\`, `\Epic Games\`, `\Riot Games\`, `\Battle.net\`, +`\GOG Galaxy\Games\`, `\XboxGames\`, `\EA Games\`, `\Origin Games\`, `\Ubisoft\`. + +### 1.5 Listas do usuário + +`settings.json` aceita `NuncaEncerrar` e `SempreEncerrar`. + +**`SempreEncerrar` não vence a blindagem.** Colocar `lsass` nessa lista não libera nada — há +teste garantindo. Ela só supera a lista de "nunca pré-marcados": quem quer que a Steam venha +marcada pode pedir isso. + +--- + +## 2. Caminhos + +`CheckPath(caminho, whitelist)` só libera se o caminho estiver **dentro** de algum item da +whitelist do módulo. **Whitelist vazia não libera nada.** O caminho é normalizado com +`Path.GetFullPath` antes de qualquer comparação, então `..\` não escapa. + +Bloqueados mesmo dentro da whitelist — se o segmento aparecer em **qualquer nível**: + +`.git`, `onedrive`, `dropbox`, `google drive`, `icloud`, `saves`, `savegames`, +`saved games`, `savedgames`, `my games` + +Pastas pessoais (`Documentos`, `Imagens`, `Vídeos`, `Música`, `Área de Trabalho`, +`Favoritos`) são bloqueadas a menos que a própria whitelist do módulo aponte para dentro +delas — o que exige decisão explícita de quem escreveu o módulo. + +Pastas de sistema (`C:\Windows`, `Program Files`, `Program Files (x86)`, `System32`, +`SysWOW64`) só são alcançáveis pela whitelist explícita de limpeza da seção 5.2, e +`Program Files` só via Desinstalador. + +--- + +## 3. Serviços + +`ProtectedServices.Intocaveis` — não podem ser parados nem ter o `StartType` alterado: + +| Grupo | Serviços | +|---|---| +| Núcleo | `DcomLaunch`, `RpcSs`, `RpcEptMapper`, `Winmgmt`, `EventLog`, `Schedule`, `TrustedInstaller`, `CryptSvc`, `PlugPlay`, `Power`, `ProfSvc`, `SamSs`, `LSM`, `Themes`, `UserManager`, `gpsvc`, `BrokerInfrastructure`, `SystemEventsBroker` | +| Rede | `Dhcp`, `Dnscache`, `nsi`, `NlaSvc`, `netprofm`, `WlanSvc`, `LanmanWorkstation` | +| Áudio | `Audiosrv`, `AudioEndpointBuilder` | +| Segurança | `WinDefend`, `SecurityHealthService`, `wscsvc`, `Sense`, `mpssvc`, `BFE` | +| Update | `wuauserv` | + +`ProtectedServices.PausaveisTemporariamente` — podem ser **parados** durante o Modo Game, +nunca desabilitados: `wuauserv`, `UsoSvc`, `DoSvc`, `BITS`. + +A parada grava `ChangeRecord` com `estavaRodando=true`, então o serviço volta no +desligamento do Modo Game, no "Reverter tudo", na próxima abertura após um travamento e no +desinstalador. + +--- + +## 4. Registro + +- Nunca tocar em `HKLM\SYSTEM\CurrentControlSet\Services\*` além de `Start`. +- Nunca tocar em `Winlogon`, `Image File Execution Options`, `Policies`. +- Toda escrita passa por `ChangeRecord` com `root`, `kind` e `ValorAnteriorExistia`. Quando + o valor **não existia**, reverter significa **apagar**, não gravar zero — há teste. + +--- + +## 5. Tweaks proibidos + +Nunca alterados pelo GameBoost, em nenhuma fase: + +- **VBS / Core Isolation (Memory Integrity)** — só informar e medir, com link para as + Configurações do Windows. É segurança contra malware de kernel; a decisão é do usuário. +- **Mitigações de Spectre/Meltdown** +- **Windows Defender**, **UAC**, **SmartScreen**, **assinatura de drivers** +- **Limpeza de registro** — sem ganho mensurável, risco puro (regra 4) + +--- + +## 6. O que **não** é jogo, mesmo morando na pasta da Steam + +`GameDetector.NaoSaoJogos`. Sem esta lista, o Wallpaper Engine era eleito "o jogo" só por +estar em `steamapps\common` — e o Modo Game subia a prioridade de CPU do papel de parede em +vez da do jogo de verdade. Foi encontrado no primeiro teste com privilégios numa máquina real. + +`wallpaper64`, `wallpaper32`, `wallpaperservice64`, `webwallpaper32`, `vrmonitor`, +`vrserver`, `vrcompositor`, `vrdashboard`, `steamvr`, `aseprite`, `blender`, `krita`, +`obs64`, `3dsmax`, `unity`, `unityhub`, `rpcs3`, `pcsx2`, `dolphin`, `retroarch`, +`steamwebhelper`, `gameoverlayui`, `steamerrorreporter` + +Além da lista, a heurística de pasta ficou mais exigente: um executável desconhecido dentro +de pasta de launcher só é considerado jogo se ocupar **mais de 300 MB** de memória. +Utilitário não tem porte de jogo. + +--- + +## 7. Desinstalador: o que nunca sai + +`UninstallerProtection`. Um app que case com qualquer destes trechos de nome aparece na lista +**bloqueado**, com o motivo visível. + +| Grupo | Por quê | +|---|---| +| Visual C++ Redistributable, `vc_redist` | Outros programas param de abrir sem o runtime | +| .NET Runtime, Desktop Runtime, ASP.NET Core, .NET SDK | Programas em .NET param de abrir | +| DirectX | Jogos param de abrir | +| Microsoft Edge, WebView2 | Muitos aplicativos usam o WebView2 para desenhar a interface | +| Xbox Identity Provider, Gaming Services | Game Pass e anti-cheats da Microsoft dependem | +| NVIDIA, AMD Software, Radeon, Intel, Realtek, chipset, áudio | Componentes de driver | +| Windows SDK, Visual Studio | Remova pelo instalador oficial | +| Atualizações e componentes de sistema | Não são aplicativos | +| **Antivírus** (Kaspersky, Avast, AVG, Bitdefender, McAfee, Norton, ESET, Sophos, Malwarebytes, Trend Micro, F-Secure, Panda, CrowdStrike, SentinelOne, Webroot, Carbon Black) | Remover daqui deixaria a máquina desprotegida | +| Pacotes da Store marcados `NonRemovable` pelo Windows | O próprio Windows recusa | + +**Bloatware conhecido** (Candy Crush, Solitaire, 3D Viewer, Clipchamp, LinkedIn, TikTok, +Feedback Hub, Get Help, Your Phone, utilitários OEM e afins) aparece **sugerido**, com a caixa +**vazia**. O que é bloat para um é ferramenta para outro — a decisão é do usuário (regra 3). + +--- + +## 8. Restos de apps antigos: pastas nunca listadas + +`LeftoverScanner.Ignoradas`. A varredura de `%APPDATA%`, `%LOCALAPPDATA%` e `%PROGRAMDATA%` +nunca inclui estas pastas, mesmo sem app correspondente: + +**Microsoft e Windows:** `Microsoft`, `MicrosoftEdge`, `Microsoft Corporation`, `Windows`, +`WindowsApps`, `OneDrive`, `Microsoft OneDrive`, `Packages`, `Temp`, `Tmp`, `Cache`, `Comms`, +`ConnectedDevicesPlatform`, `D3DSCache`, `ElevatedDiagnostics`, `Publisher Cache`, +`VirtualStore`, `WebCacheLock`, `IconCache`, `Package Cache`, `Installer`, `Assembly`, +`Diagnostics`, `History`, `Application Data`, `AppData`, `ProgramData`, `UsoShared`, +`UsoPrivate`, `WindowsHolographicDevices`, `Desktop`, `Start Menu`, `ssh` + +**Guarda-chuva** (contêm vários apps ativos dentro): `Programs`, `Apps`, `Local`, `Roaming`, +`LowLevel`, `Virtual Machines` + +**Hardware e driver:** `NVIDIA`, `NVIDIA Corporation`, `AMD`, `ATI`, `Intel`, +`IntelGraphicsProfiles`, `Realtek`, `Logishrd`, `Logitech`, `Razer`, `Corsair`, `SteelSeries`, +`Synaptics`, `ELAN`, `Qualcomm`, `Broadcom` + +**Compartilhadas:** `Adobe`, `Common Files`, `Oracle`, `Java`, `Sun`, `Mozilla`, `Google`, +`Chromium`, `Chrome`, `CrashDumps`, `crashpad`, `CrashReports`, `Sentry`, `SquirrelTemp`, +`node`, `npm`, `npm-cache`, `yarn`, `pip`, `nuget`, `.nuget`, `dotnet`, `JetBrains`, +`VisualStudio`, `VSCode`, `Code`, `GitHub`, `Git`, `Docker`, `Kaspersky Lab`, `ESET`, +`Avast Software` + +Além da lista: pastas começando com ponto, e qualquer pasta abaixo de 1 MB. + +### Por que o casamento não é só exato + +A primeira versão comparava nomes exatos e acusou `BraveSoftware` e `EpicGamesLauncher` como +restos — os dois **estão instalados**, só escrevem a pasta sem os espaços do nome. A +comparação passou a normalizar espaços e pontuação e a aceitar prefixo a partir de 5 +caracteres. Isso derrubou o resultado de **53 pastas e 26 GB** para **30 pastas e 3,9 GB** +nesta máquina. + +**Falso positivo aqui é pior que falso negativo:** deixar de listar um resto custa espaço, +apontar um app ativo como resto custa os dados do usuário. Por isso, e porque a heurística +nunca será perfeita, **nada é pré-marcado e a remoção vai para a Lixeira**. diff --git a/GameBoost/docs/QA.md b/GameBoost/docs/QA.md new file mode 100644 index 0000000..e6fb1f0 --- /dev/null +++ b/GameBoost/docs/QA.md @@ -0,0 +1,270 @@ +# Checklist de QA manual + +O que os testes automatizados não cobrem. Nenhuma release sai sem isto. + +Legenda: `[ ]` não testado · `[x]` passou · `[!]` falhou, ver observação. + +--- + +## Fase 0 — paridade com a v1.0.0 + +A v1 foi reconstruída a partir do `LEIA-ME.txt` (ver `DECISOES.md`), então a paridade +funcional precisa ser conferida à mão contra o binário antigo. + +- [x] `GameBoost.exe --scan relatorio.txt` gera relatório e **não encerra nada** +- [x] `GameBoost.exe --scan` sem argumento imprime no terminal que chamou +- [x] Ativar o Modo Game abre a tela de confirmação **antes** de fechar qualquer coisa, + e lista só o que está marcado +- [x] Um app com trabalho não salvo (Bloco de Notas com texto) não perde nada. O Bloco de + Notas do Windows 11 persiste sozinho e por isso não pergunta; o texto volta intacto +- [x] Apps marcados com ★ reabrem sozinhos ao desligar +- [x] Apps não marcados aparecem na lista de restauração com botão individual +- [x] Plano de energia volta ao anterior ao desligar +- [x] Windows Update volta a rodar ao desligar (`Get-Service wuauserv`) +- [x] Game DVR e Game Bar voltam aos valores originais (conferir no `regedit`) + +## Reversão e recuperação + +- [x] Ativar o Modo Game, **matar o GameBoost pelo Gerenciador de Tarefas**, reabrir: + aparece o aviso "foi fechado com o Modo Game ainda ativo" com o botão Restaurar agora +- [ ] Ativar o Modo Game, **reiniciar o PC**, abrir: mesmo aviso +- [ ] "Reverter tudo" funciona a qualquer momento, mesmo sem Modo Game ativo +- [ ] Rodar o desinstalador com o Modo Game ativo reverte antes de remover +- [ ] Reverter duas vezes seguidas não reaplica nada +- [ ] Ativar e desativar **10 vezes seguidas** sem vazar estado + (`state-backup.json` termina sem pendências) + +## Blindagem — o teste que não pode falhar + +- [ ] **Com Valorant aberto (Vanguard rodando)**: nem `vgc`, nem `vgtray`, nem `vgk` + aparecem selecionáveis. Ativar o Modo Game **não derruba o jogo** +- [ ] Com jogo da Steam aberto: o jogo aparece na categoria "Jogo", bloqueado +- [ ] Steam, Epic e Battle.net aparecem **desmarcados** +- [ ] OBS gravando aparece desmarcado +- [ ] Nenhum processo do antivírus aparece selecionável + +## Fase 1 — diagnóstico e relatório + +- [x] Página Diagnóstico: os cinco medidores se movem e o gráfico de 60 s preenche +- [x] GPU aparece com valor numa máquina com placa dedicada (medido: 14%, 1,9 GB de VRAM) +- [x] Temperatura aparece como "indisponível", nunca como 0 °C +- [ ] Sair da página Diagnóstico para a coleta (conferir no log a linha de custo) +- [ ] Achados não piscam: um que apareceu continua na tela por pelo menos 30 s +- [ ] Botão de ação de cada achado leva ao lugar certo (Segurança do Windows, vídeo, energia) +- [x] `--report saida.html` gera arquivo que abre em qualquer navegador, sem link externo +- [x] `--report saida.json` gera JSON válido +- [x] Custo da coleta abaixo de 1,5% de CPU em máquina fraca (medido: 0,115% com 20 núcleos) +- [ ] Numa máquina bem cuidada, a nota fica acima de 90 e quase não há achados + +## Fase 2 — limpeza e ferramentas + +- [x] Varredura de limpeza mede o disco real (medido: 22,3 GB encontrados, 4,8 GB no preset seguro) +- [x] Shader cache e prefetch aparecem desmarcados, com a advertência visível +- [x] `Downloads` aparece bloqueado, com "o GameBoost nunca remove arquivo daqui" +- [x] Cache de navegador aberto aparece bloqueado, pedindo para fechar +- [x] `--clean --preset seguro --dry-run` não remove nada +- [x] Teste de velocidade de disco apaga o arquivo de 1 GB, inclusive se falhar +- [x] Informações do sistema leem dados reais da máquina +- [ ] Limpeza real: rodar duas vezes seguidas, segunda libera ~0 +- [ ] Reiniciar o Explorer devolve a barra de tarefas +- [ ] Reiniciar o driver de vídeo não derruba jogo aberto +- [ ] Ponto de restauração aparece na Restauração do Sistema do Windows +- [ ] `sfc /scannow` mostra a saída ao vivo e não parece travado + +## Fase 3 — analisador de espaço + +- [x] Varredura do disco do sistema (medido: 1.548.745 arquivos, 809,5 GB, em 18 a 20 s) +- [x] Soma bate com o espaço ocupado, e o que falta é declarado no rodapé +- [x] Treemap desenha proporcional e colorido por categoria +- [x] Biblioteca lê Steam e Epic com tamanho e último jogo (14 jogos, 974 GB) +- [x] Contador de pastas inacessíveis reporta o número real (543, não 40.871) +- [x] Memória com a árvore aberta (medido: 594 MB para 1,5 milhão de nós) +- [ ] Clicar num bloco do treemap desce um nível, e Subir volta +- [x] Enviar um arquivo para a Lixeira pela página e conferir que dá para restaurar +- [ ] Varredura de um segundo volume (D:) +- [ ] Cancelar a varredura no meio deixa a interface utilizável + +## Fase 4 — desinstalador e inicialização + +- [x] Inventário lê a máquina real (medido: 217 apps, 113 da Store, 78 protegidos) +- [x] Nada vem pré-marcado, nem o bloatware mais óbvio (rodapé abre em "Aplicar 0 selecionados") +- [x] IDEs e apps de desenvolvimento aparecem na lista e **nunca** pré-marcados (regra 3) +- [x] Runtimes, drivers e antivírus aparecem bloqueados com o motivo visível +- [x] Total declarado é plausível (1044 GB para 2145 GB de disco; antes dizia 1812 GB) +- [x] Lista ordenada pelo tamanho **medido**, não pelo declarado no registro +- [x] Aba "Restos de apps antigos" sem falso positivo óbvio (30 pastas, 3,9 GB) +- [x] Inicialização lê registro e pastas (medido: 26 entradas, 4 bloqueadas, 8 sugeridas) +- [x] O número bate com o que o módulo de Diagnóstico informa +- [x] Dry-run da inicialização não escreve `ChangeRecord` nenhum +- [x] App da Store em WindowsApps não é acusado de "sem assinatura digital" +- [x] Desinstalar um app de verdade (medido: Call of Duty, 180,3 GB; 217→216 apps, 1044→863,7 GB) +- [ ] Desinstalar um app da Store (`Remove-AppxPackage`) +- [x] Desinstalar um app cujo desinstalador abre janela própria (o GameBoost espera, não trava) +- [x] Desativar um item e conferir no Gerenciador de Tarefas (medido: WallpaperEngine, + byte 0x03 + FILETIME, "Desabilitado" nos dois lados) +- [x] Reverter a desativação e conferir que o programa volta a abrir no boot +- [x] Enviar uma pasta de resto para a Lixeira (medido: qwen-updater, 119 MB, restaurável) + +## Complemento da Fase 4 — aba Atualizações (winget) + +- [x] winget detectado e versão lida (medido: 1.29.290) +- [x] Tabela lida por posição, não por idioma (teste com cabeçalho em português) +- [x] Lista bate com `winget upgrade` rodado à mão (medido: 35 atualizações) +- [x] Nenhum id sai com espaço no meio; toda fonte é winget ou msstore +- [x] Driver aparece bloqueado com o motivo +- [x] Runtime e "atualiza sozinho" aparecem com risco médio e explicação +- [x] Atualização de segurança marcada só em quem abre arquivo da internet +- [x] "Chrome Remote Desktop Host" **não** é marcado como navegador +- [x] App aberto é detectado e a linha avisa "feche antes" +- [x] Nada pré-marcado, nem a atualização de segurança +- [x] Abas legíveis no tema escuro +- [x] Controles dentro das abas visíveis para a automação (leitor de tela) +- [x] Aba "Restos de apps antigos" na interface (medido: 30 pastas, 3,8 GB) +- [x] Atualizar um app de verdade (medido: 7-Zip 24.09 → 26.03) +- [ ] Atualizar um app que está aberto e conferir a mensagem de erro +- [ ] Atualizar um app que falha e conferir o código traduzido e o link do log +- [x] Conferir o `updates-history.json` depois de uma atualização real +- [ ] Máquina sem winget: conferir o cartão e o botão da Store +- [ ] Windows em inglês: conferir que a tabela continua sendo lida + +## Fase 5 — tweaks, serviços, rede e drivers + +- [x] Catálogo lê o estado real de cada ajuste (medido: 24 itens, 1 já ativo) +- [x] Nada pré-marcado, nem o que está marcado como recomendado +- [x] VBS aparece bloqueado e só informativo +- [x] Windows Update e Defender aparecem bloqueados com o motivo +- [x] Ajuste que só precisa de elevação diz "Precisa de admin", não "Protegido" +- [x] Estado da hibernação lido do registro, correto sem elevação +- [x] Dry-run dos tweaks não escreve ChangeRecord nenhum +- [x] NAT classificado como aberto/moderado/estrito (medido: aberto, semáforo verde) +- [x] STUN devolve endereço público de verdade, nunca um privado +- [x] CGNAT, NAT duplo, UPnP, Teredo e firewall detectados e explicados +- [x] Teste de velocidade recusa sair para a rede sem a permissão, e diz por quê +- [x] Driver de vídeo lido com versão de marketing (medido: GTX 1660 Ti, 616.64, 0 meses) +- [x] Adaptador virtual do Hyper-V não entra na lista de placas +- [x] Aplicar um tweak de HKCU elevado e conferir a chave (mouse: 1/6/10 viraram 0/0/0) +- [x] Reverter tweak cujo valor **não existia**: `VisualFXSetting` foi apagado, não zerado +- [x] Mudar um serviço para Manual e conferir (WSearch: Automatic → Manual) +- [x] Reverter o serviço e conferir que o estado de execução também volta +- [ ] Trocar o DNS do adaptador e reverter para automático (DHCP) +- [ ] Reativar o Teredo numa máquina onde ele está desligado e conferir `netsh interface teredo show state` +- [x] Teste de velocidade comparado com teste dedicado (medido: -15%, dentro do esperado) +- [ ] Máquina em CGNAT: conferir que o diagnóstico acusa e não sugere abrir porta +- [ ] Máquina com NAT duplo: conferir a contagem de saltos privados +- [ ] Comparar a classificação de NAT com o que o app do Xbox mostra na mesma rede +- [ ] Máquina no Wi-Fi: conferir SSID, padrão e banda + +## Fase 6 — perfis, bandeja e onboarding + +- [x] Perfil grava e lê de volta, com afinidade e timer +- [x] Perfil novo nunca vem com "ativa sozinho" marcado +- [x] Afinidade recusa índice de núcleo que não existe na máquina (medido: 20 núcleos) +- [x] Afinidade com lista vazia devolve todos os núcleos +- [x] Varredura de janelas para tela cheia responde rápido (medido: 3 ms) +- [x] Biblioteca cruza com perfis (medido: 14 jogos, 974,2 GB, 0 com perfil) +- [x] Ícone na bandeja aparece e o vigia inicia (confirmado no log) +- [x] Onboarding aparece na primeira abertura e é alcançável pela automação +- [x] Abrir um jogo de verdade e conferir o convite (medido: Warframe, confiança 80, tela cheia) +- [x] Aceitar o convite e conferir que o perfil é aplicado (prioridade Normal → AboveNormal) +- [x] Fechar o jogo e conferir o comportamento (prioridade morre com o processo; o texto diz isso) +- [ ] Marcar "ativa sozinho" e conferir que o próximo boot do jogo não pergunta +- [x] Recusar o convite e conferir que ele não volta na mesma sessão +- [ ] Matar o GameBoost com perfil aplicado e conferir o aviso na reabertura +- [x] Fechar a janela e conferir que o app fica na bandeja, com o aviso uma vez só +- [x] "Sair" da bandeja encerra o processo de verdade +- [ ] "Limpar RAM" pela bandeja com o app minimizado +- [ ] Onboarding marcado como concluído não volta na segunda abertura +- [x] Anti-cheat: o jogo aparece na lista do Modo Game como **Protegido**, com "Este parece + ser o jogo", risco Alto e caixa desabilitada +- [x] Atualizador de launcher (Agent do Battle.net) **não** é detectado como jogo +- [ ] Perfil com afinidade num jogo real: conferir no Gerenciador de Tarefas + +## Fase 7 — distribuição + +- [x] Modo portátil grava tudo em `.\data\` ao lado do exe (medido com o binário real) +- [x] Modo portátil não deixa nada em `%LOCALAPPDATA%\GameBoost` +- [x] Serviço de atualização sempre explica por que não pode verificar +- [x] YAML do workflow de release é válido (14 passos) +- [x] `#ifndef AppVersion` deixa o CI passar a versão +- [ ] Rodar o workflow de release por `workflow_dispatch` e conferir os artefatos +- [ ] Instalar pelo `.exe` gerado e conferir o atalho e a entrada em Programas +- [ ] Instalar "só para o usuário atual" sem senha de administrador +- [ ] Instalar "para todos" e conferir que o autostart foi para HKLM +- [ ] Desinstalar e conferir que não sobra chave de autostart +- [ ] Marcar a limpeza semanal e conferir a tarefa em `taskschd.msc` +- [ ] Publicar uma release e conferir que o auto-update encontra a versão nova +- [ ] Aplicar a atualização e conferir que a versão mudou +- [ ] Anotar exatamente o que o SmartScreen diz no primeiro download +- [ ] Conferir se o Defender bloqueia o instalador ou a execução +- [ ] Submeter ao Microsoft Security Intelligence se houver falso positivo + +## Ambiente + +- [x] **Usuário sem admin**: varreduras funcionam, botões de ação desabilitados com aviso + visível de modo somente leitura +- [ ] Windows 10 22H2 +- [ ] Windows 11 24H2 ou mais novo +- [ ] Máquina com 8 GB de RAM (é onde a limpeza de RAM rende de verdade) +- [ ] Notebook com iGPU + dGPU +- [ ] Dois usuários logados: processos da outra sessão não aparecem +- [ ] Monitor 4K e escala de 150% (DPI per-monitor v2) + +## Distribuição + +- [ ] SmartScreen: anotar exatamente o que aparece no primeiro download +- [ ] Windows Defender não bloqueia o instalador nem a execução +- [ ] Submeter ao Microsoft Security Intelligence se houver falso positivo +- [ ] Instalação limpa em máquina sem .NET instalado (publish é self-contained) +- [ ] Desinstalação não deixa `%LOCALAPPDATA%\GameBoost` com dados do usuário sem avisar + +## Desempenho + +- [ ] Varredura completa em menos de 5 s numa máquina com ~250 processos +- [ ] A UI não congela durante a varredura (o botão Cancelar responde) +- [ ] O app ocioso consome menos de 60 MB de RAM + +--- + +## Observações de execução + +| Data | Item | Resultado | +|---|---|---| +| 14/09/2026 | QA manual bloco 6 (perfis, deteccao, bandeja) | **6 bugs achados**: a deteccao funcionava, o resto nao | +| 14/09/2026 | QA manual bloco 5 (inicializacao) | **1 bug achado**: o Reverter nao revertia | +| 14/09/2026 | QA manual bloco 4 (apps, restos, winget) | **1 crash achado e corrigido**: a Lixeira derrubava o app | +| 14/09/2026 | QA manual blocos 2 e 3 (tweaks, servicos, rede) | **4 bugs achados e corrigidos**, depois passou | +| 14/09/2026 | QA manual bloco 1 (reversibilidade) elevado, na maquina real | **6 bugs achados e corrigidos**, depois passou | +| 14/09/2026 | Modo portatil com o binario real: data\ ao lado do exe | Passou | +| 14/09/2026 | Perfis, afinidade, tela cheia, bandeja e onboarding | Passou apos 2 correcoes | +| 14/09/2026 | Telas de Jogos e onboarding renderizadas | Passou | +| 14/09/2026 | winget: 35 atualizacoes lidas e classificadas | Passou apos 3 correcoes | +| 14/09/2026 | Abas de Apps (Instalados, Atualizacoes, Restos) renderizadas | Passou | +| 14/09/2026 | Tweaks, servicos, NAT, velocidade e driver de video (nao elevado) | Passou apos 4 correcoes | +| 14/09/2026 | Telas de Tweaks e Rede renderizadas e conferidas | Passou | +| 14/09/2026 | Inventario de apps, restos, inicializacao (nao elevado) | Passou. 217 apps, 30 restos, 26 entradas | +| 14/09/2026 | Telas de Apps e Inicializacao renderizadas e conferidas | Passou apos 4 correcoes | +| 13/09/2026 | Ciclo real ativar → desligar, com privilégios | **Passou.** Ver detalhe abaixo | +| 13/09/2026 | `--scan`, `--report` (HTML, JSON e texto) no binário de produção | Passou | +| 13/09/2026 | Custo da coleta | 0,115% de CPU, ciclo de 36 ms, em 20 núcleos | +| 13/09/2026 | Telas Início e Diagnóstico | Passou, depois de corrigir um crash de XAML | + +### Ciclo real de reversibilidade — 13/09/2026 + +Executado com o binário de produção (`requireAdministrator`), com todos os 129 processos da +máquina em `NuncaEncerrar` e um único alvo descartável compilado para o teste. + +| Item | Antes | Com o Modo Game ativo | Depois de desligar | +|---|---|---|---| +| `GameDVR_Enabled` | `1` | `0` | `1` | +| `UseNexusForGameBarEnabled` | não existia | `0` | **apagado** | +| `ShowStartupPanel` | não existia | `0` | **apagado** | +| `ToastEnabled` | não existia | `0` | **apagado** | +| Plano de energia | Equilibrado | Alto desempenho | Equilibrado | +| `wuauserv` | parado | parado (não foi tocado) | parado | +| App marcado com ★ | rodando | encerrado | **reaberto** | + +Os três valores que **não existiam** foram apagados na reversão, não zerados — que é a +diferença entre reverter e deixar um rastro. `state-backup.json` terminou com **0 pendências**. + +Ganho medido na ativação: RAM livre de 1,3 GB para 3,9 GB, com 13,2 GB retirados do working +set de 462 processos. diff --git a/GameBoost/docs/TWEAKS.md b/GameBoost/docs/TWEAKS.md new file mode 100644 index 0000000..374e412 --- /dev/null +++ b/GameBoost/docs/TWEAKS.md @@ -0,0 +1,174 @@ +# Catálogo de tweaks + +Cada tweak: chave, valor, efeito real, evidência, risco e reversão. Todos desligados por +padrão, aplicados individualmente, sempre com snapshot em `state-backup.json`. + +**A coluna "efeito real" é honesta por obrigação (regra 4).** Se um tweak é marginal, está +escrito que é marginal. + +--- + +## Implementados na Fase 5 (página Tweaks) + +Catálogo completo da seção 5.8, aplicado item a item, cada um com ChangeRecord +antes da escrita. **Nada vem pré-marcado**, nem o que está marcado como +recomendado: recomendação é informação, não consentimento. + +### Jogos + +| Tweak | Chave | Valor | Efeito real | Risco | +|---|---|---|---|---| +| Agendamento de GPU por hardware (HAGS) | `HKLM\SYSTEM\CurrentControlSet\Control\GraphicsDrivers` → `HwSchMode` | `2` | Obrigatório para o Frame Generation do DLSS 3. Em RTX 30+ e RX 6000+ com driver atual costuma melhorar a regularidade dos quadros; em CPU antiga pode piorar. Usa até 1 GB de VRAM. **Exige reinício** | Médio | +| Modo Jogo do Windows | `HKCU\Software\Microsoft\GameBar` → `AutoGameModeEnabled` | `1` | No 22H2+ segura o Windows Update e a instalação de driver durante o jogo, que é o ganho concreto. Recomendado | Baixo | +| Gravação em segundo plano (Game DVR) | `HKCU\System\GameConfigStore` → `GameDVR_Enabled` e `HKCU\Software\Microsoft\GameBar` → `UseNexusForGameBarEnabled` | `0` | Desliga a gravação contínua. Ganho real em máquina modesta, pequeno em PC forte. Perde o atalho de gravar os últimos 30 s | Baixo | +| Prioridade da categoria Jogos | `HKLM\...\SystemProfile\Tasks\Games` → `GPU Priority`=8, `Priority`=6, `Scheduling Category`=High | — | **Marginal.** Nenhuma medição pública consistente mostra ganho em hardware atual. Está no catálogo por ser reversível e sem custo, não por render FPS | Baixo | +| Desativar Multi-Plane Overlay (MPO) | `HKLM\SOFTWARE\Microsoft\Windows\Dwm` → `OverlayTestMode` | `5` | Só vale com o sintoma: piscada preta, tremulação ou stutter na área de trabalho, comum em NVIDIA com dois monitores. **Não dá FPS.** Exige reinício | Médio | + +### Sistema + +| Tweak | Chave | Valor | Efeito real | Risco | +|---|---|---|---|---| +| Reserva de CPU para tarefas de fundo | `HKLM\...\SystemProfile` → `SystemResponsiveness` | `0` | **Marginal.** Devolve ao primeiro plano os 20% que o Windows reserva para multimídia de fundo. Pode causar engasgo em gravação e transmissão — o padrão 20 existe para proteger áudio | Baixo | +| Efeitos visuais no melhor desempenho | `HKCU\...\Explorer\VisualEffects` → `VisualFXSetting` | `2` | Ajuda só em máquina fraca ou sem GPU dedicada. Não afeta jogo em tela cheia, que nem passa pelo DWM | Baixo | + +### Rede + +| Tweak | Chave | Valor | Efeito real | Risco | +|---|---|---|---|---| +| Desativar limite de tráfego multimídia | `HKLM\...\SystemProfile` → `NetworkThrottlingIndex` | `0xFFFFFFFF` | **Marginal.** O limite de 10 pacotes/ms foi criado para placas de rede de 2007 e praticamente não morde em hardware atual | Baixo | + +### Mouse e teclado + +| Tweak | Chave | Valor | Efeito real | Risco | +|---|---|---|---|---| +| Desativar aceleração do mouse | `HKCU\Control Panel\Mouse` → `MouseSpeed`, `MouseThreshold1`, `MouseThreshold2` | `0` | Consistência de mira, não desempenho. Jogos que usam Raw Input já ignoram isto. Vai atrapalhar quem está acostumado com a aceleração ligada | Baixo | + +### Energia + +| Tweak | Onde | Efeito real | Risco | +|---|---|---|---| +| Desativar limitação de energia do processador | `HKLM\SYSTEM\CurrentControlSet\Control\Power\PowerThrottling` → `PowerThrottlingOff` = 1 | Faz diferença em notebook, onde a limitação é agressiva. Em desktop na tomada, quase nada. Custa consumo e temperatura | Baixo | +| Desativar hibernação | `powercfg /h off` | Libera em disco o equivalente à RAM instalada. Perde hibernação e Inicialização Rápida, e o boot fica alguns segundos mais lento. Vale por espaço, não por desempenho | Médio | + +### Segurança: aparece, explica e não mexe + +| Item | Por quê | +|---|---| +| Isolamento de núcleo (VBS) | Aparece **só de leitura**. As medições da comunidade põem o custo entre 5% e 15% em jogos limitados por CPU, e em troca é o que impede um driver malicioso de ler a memória do sistema. Num PC que também é banco e trabalho, desligar é troca ruim; num PC só de jogo é escolha defensável — do dono, na tela da Microsoft. O botão abre Segurança do Windows | +| Mitigações de Spectre/Meltdown | **Não estão no catálogo, nem bloqueadas.** Item na tela vira ideia na cabeça de alguém. Desligar rende alguns por cento em CPU antiga e abre um buraco de segurança real | + +### Como o estado é lido + +Um tweak "desligado" quase nunca significa valor zero: na maioria das chaves +significa **valor ausente**, com o Windows assumindo o padrão. Por isso a +reversão apaga o valor quando ele não existia antes, em vez de gravar zero — +confundir os dois é o que faz ferramenta de tweak deixar lixo que nunca sai. + +A hibernação é lida do registro (`HibernateEnabled`, com `HibernateEnabledDefault` +como fallback), não pela existência de `C:\hiberfil.sys`: aquele arquivo é de +sistema e `File.Exists` devolve falso sem elevação, o que faria o app dizer "já +desligada" para quem abriu sem ser administrador. + +--- + +## Serviços do Windows (seção 5.7, Fase 5) + +Régua diferente da dos tweaks: um serviço desligado por engano não tira alguns +FPS, tira a impressora, a busca ou o Game Pass. + +### Dá para desativar + +| Serviço | Sugestão | O que você perde | +|---|---|---| +| `DiagTrack` | Desativado | Nada de sistema. O motivo de desligar é privacidade, não FPS | +| `MapsBroker` | Desativado | Mapas offline do aplicativo Mapas | +| `RemoteRegistry` | Desativado | Nada. Já vem desativado no Windows doméstico | +| `WMPNetworkSvc` | Desativado | Compartilhar a biblioteca do Media Player na rede | +| `Fax` | Desativado | Fax por modem | +| `WSearch` | Manual | A busca do Iniciar e do Explorer fica lenta. Vale em HDD; em SSD o índice compensa | +| `Spooler` | Manual | Impressão, **inclusive salvar em PDF** | + +### Aparece para explicar por que **não** mexer + +| Serviço | Por quê | +|---|---| +| `SysMain` | Toda lista de otimização manda desligar, e está errada. Em SSD ele quase não faz leitura antecipada; o que faz hoje é gerenciar memória. Desligar não devolve RAM, devolve cache — e o Windows usa RAM livre para cache de qualquer jeito | +| `XblAuthManager`, `XboxNetApiSvc` | Sem eles o Game Pass não abre e alguns anti-cheat falham. Parados não custam nada; desligados quebram o Game Pass | +| `wuauserv` | Nunca desativar. Máquina sem atualização de segurança é problema maior que qualquer FPS. O Modo Game já pausa o Update durante o jogo e religa depois | +| `WinDefend`, `SecurityHealthService` | O GameBoost não desliga antivírus, nem o do Windows nem o de terceiros | + +--- + +## Implementados na Fase 0 (Modo Game) + +Aplicados em bloco pelo Modo Game, revertidos no desligamento. Ver +[`SystemSilencer`](../src/GameBoost.Core/Modules/GameMode/SystemSilencer.cs). + +| Tweak | Chave | Valor | Efeito real | Risco | +|---|---|---|---|---| +| Notificações do Windows | `HKCU\Software\Microsoft\Windows\CurrentVersion\PushNotifications` → `ToastEnabled` | `0` | Impede notificação aparecendo sobre o jogo em tela cheia. Efeito de conforto, não de FPS | Baixo | +| Game DVR | `HKCU\System\GameConfigStore` → `GameDVR_Enabled` | `0` | Desliga gravação contínua em segundo plano. Ganho real em máquina fraca; em PC forte é pequeno | Baixo | +| Overlay da Game Bar | `HKCU\Software\Microsoft\GameBar` → `UseNexusForGameBarEnabled` | `0` | Evita a injeção do overlay em todo jogo aberto | Baixo | +| Painel da Game Bar | `HKCU\Software\Microsoft\GameBar` → `ShowStartupPanel` | `0` | Só evita o painel aparecer ao abrir o jogo | Baixo | + +**Reversão:** cada um grava o valor anterior e o flag `ValorAnteriorExistia`. Quando a chave +não existia, a reversão **apaga** o valor em vez de gravar zero. + +### Plano de energia + +Aplica **Desempenho Máximo** se existir, senão **Alto desempenho**; guarda o GUID do plano +anterior e restaura no desligamento. + +| Plano | GUID | +|---|---| +| Balanceado | `381b4222-f694-41f0-9685-ff5bb260df2e` | +| Alto desempenho | `8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c` | +| Desempenho Máximo | `e9a42b02-d5df-448d-aa00-03f14749eb61` | +| Economia de energia | `a1841308-3541-4fab-bc81-f71556f20b4a` | + +**Evidência:** é o item nº 1 do consenso técnico de 2026 (seção 3.4 do spec) — evita que a +CPU baixe o clock no meio do jogo. Junto com a poda da inicialização, é o ajuste de maior +valor e o mais fácil de reverter. + +### Pausa do Windows Update + +Para o serviço `wuauserv` (só para, nunca altera `StartType`). Grava `ChangeRecord` com +`estavaRodando=true`. + +**Por que importa reverter:** um travamento com o Update pausado deixaria a máquina sem +atualizações indefinidamente. Por isso a reversão acontece no desligamento, no "Reverter +tudo", na próxima abertura após travamento **e** no desinstalador. + +--- + +## Do catálogo da seção 5.8, o que ficou de fora + +Três itens do spec não entraram na Fase 5, e cada um tem um motivo diferente. + +| Tweak | Por que não entrou | +|---|---| +| Fullscreen Optimizations por executável | `GameDVR_FSEBehaviorMode` e `DISABLEDXMAXIMIZEDWINDOWEDMODE` são ajustes **por jogo**, não globais. O spec já diz "oferecer por perfil", e perfil por jogo é a Fase 6 | +| Nagle (`TcpAckFrequency`, `TCPNoDelay`) | A chave fica em `...\Tcpip\Parameters\Interfaces\`, uma por adaptador, e o ganho é marginal mesmo no melhor caso: a maioria dos jogos usa UDP, onde Nagle não existe. Entra junto com o perfil por jogo, que sabe qual jogo justifica mexer | +| Timer resolution | O P/Invoke existe em `NativeMethodsBridge`, mas a resolução de timer só vale enquanto o processo que pediu está vivo. Faz sentido dentro da sessão do Modo Game, não como um botão que liga e desliga sozinho. Fase 6 | + +### Nunca alterados + +| Item | Por quê | +|---|---| +| **VBS / Core Isolation (Memory Integrity)** | Custo medido pela comunidade de 5 a 15% em jogos CPU-bound (até 25% em casos extremos), mas é proteção contra malware de kernel. O GameBoost **informa e mede, não desliga**: mostra o estado via WMI `Win32_DeviceGuard`, o impacto estimado e o caminho nas Configurações do Windows. Trade-off de PC exclusivo de jogo vs. máquina de trabalho/banco é decisão do usuário | +| **Mitigações de Spectre/Meltdown** | Troca segurança real por poucos % | +| **Defender, UAC, SmartScreen, assinatura de drivers** | Fora de escopo, sempre | +| **Limpeza de registro** | Sem ganho mensurável. Risco puro (regra 4) | +| **Desfragmentação de SSD** | Placebo, desgasta a unidade | +| **Atualização automática de driver** | Instalar driver sozinho é risco. O GameBoost só detecta a idade e abre a página oficial | + +--- + +## Detector de reset pós-update + +Atualizações grandes do Windows (24H2, 26H2) **resetam** Game Mode, HAGS, plano de energia, +efeitos visuais e Game Bar. Nenhum concorrente avisa disso. + +Planejado para a Fase 1 como `Finding`: guardar a build do Windows junto do perfil aplicado +e, quando a build mudar e os valores não baterem mais, mostrar *"a atualização 26H2 desfez 4 +ajustes seus"* com o botão de reaplicar. diff --git a/GameBoost/installer/GameBoost.iss b/GameBoost/installer/GameBoost.iss new file mode 100644 index 0000000..f88987c --- /dev/null +++ b/GameBoost/installer/GameBoost.iss @@ -0,0 +1,100 @@ +; Instalador do GameBoost (Inno Setup 6). +; Compilar com: ISCC.exe installer\GameBoost.iss +; Antes, publicar: dotnet publish src\GameBoost.App\GameBoost.App.csproj -c Release -o publish + +#define AppName "GameBoost" +; O CI passa a versao com /DAppVersion=X.Y.Z. O valor abaixo so vale quando +; o ISCC e chamado a mao, sem o parametro. +#ifndef AppVersion + #define AppVersion "2.0.0" +#endif +#define AppAutor "Jonathan Vaz" +#define AppExe "GameBoost.exe" + +[Setup] +AppId={{7C3F9A21-4E8B-4A6D-9F2C-8B1D5E7A3C90} +AppName={#AppName} +AppVersion={#AppVersion} +AppVerName={#AppName} {#AppVersion} +AppPublisher={#AppAutor} +DefaultDirName={autopf}\{#AppName} +DefaultGroupName={#AppName} +OutputDir=Output +OutputBaseFilename={#AppName}-Setup-{#AppVersion} +Compression=lzma2/max +SolidCompression=yes +WizardStyle=modern +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +; O app encerra processos, controla servicos e purga a Standby List, entao +; precisa de admin para RODAR. Instalar, porem, pode ser so para o usuario +; atual: quem nao tem a senha de administrador da maquina ainda consegue +; instalar em %LOCALAPPDATA% e usar o app em modo somente leitura. +; +; "dialog" faz o Inno perguntar na primeira tela. A resposta muda {autopf} +; entre "Program Files" e a pasta local do usuario. +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog commandline +UninstallDisplayIcon={app}\{#AppExe} +MinVersion=10.0.17763 + +[Languages] +Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" + +[Tasks] +Name: "desktopicon"; Description: "Criar atalho na area de trabalho"; GroupDescription: "Atalhos:" +Name: "startminimized"; Description: "Iniciar o GameBoost junto com o Windows, minimizado na bandeja"; GroupDescription: "Inicializacao:"; Flags: unchecked +; Desligada por padrao de proposito: agendamento so com escolha explicita do usuario. +Name: "limpezasemanal"; Description: "Agendar limpeza semanal com o preset seguro"; GroupDescription: "Manutencao:"; Flags: unchecked + +[Files] +Source: "..\publish\{#AppExe}"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\README.md"; DestDir: "{app}"; DestName: "LEIA-ME.txt"; Flags: ignoreversion isreadme + +[Icons] +Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExe}" +Name: "{group}\Desinstalar {#AppName}"; Filename: "{uninstallexe}" +Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExe}"; Tasks: desktopicon + +[Registry] +; HKA resolve para HKCU numa instalacao so do usuario e para HKLM numa +; instalacao para todos. Fixar HKCU faria a opcao "todos os usuarios" gravar +; o autostart so para quem rodou o instalador. +Root: HKA; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; \ + ValueName: "GameBoost"; ValueData: """{app}\{#AppExe}"" --minimizado"; \ + Flags: uninsdeletevalue; Tasks: startminimized + +[Run] +Filename: "{app}\{#AppExe}"; Description: "Abrir o {#AppName}"; Flags: nowait postinstall skipifsilent + +; Tarefa semanal opcional, aos domingos as 10h. +Filename: "{sys}\schtasks.exe"; \ + Parameters: "/Create /F /TN ""GameBoost - Limpeza semanal"" /SC WEEKLY /D SUN /ST 10:00 /RL HIGHEST /TR ""\""{app}\{#AppExe}\"" --clean --preset seguro"""; \ + Flags: runhidden; Tasks: limpezasemanal + +[UninstallRun] +; Regra 1: o desinstalador desfaz as alteracoes de sistema ANTES de remover o app. +; Sem isto, desinstalar com o Modo Game ativo deixaria o Windows Update pausado +; e o plano de energia trocado para sempre. +Filename: "{app}\{#AppExe}"; Parameters: "--revert-all"; Flags: runhidden waituntilterminated; RunOnceId: "ReverterTudo" +Filename: "{sys}\schtasks.exe"; Parameters: "/Delete /F /TN ""GameBoost - Limpeza semanal"""; Flags: runhidden; RunOnceId: "RemoverTarefa" + +[UninstallDelete] +Type: filesandordirs; Name: "{app}\data" + +[Code] +// Os dados do usuario (preferencias, historico, log) ficam fora de {app} e nao sao +// apagados sem pergunta. +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +var + Dados: String; +begin + if CurUninstallStep = usPostUninstall then + begin + Dados := ExpandConstant('{localappdata}\GameBoost'); + if DirExists(Dados) then + if MsgBox('Remover tambem suas preferencias, historico e log?' + #13#10 + #13#10 + Dados, + mbConfirmation, MB_YESNO) = IDYES then + DelTree(Dados, True, True, True); + end; +end; diff --git a/GameBoost/installer/NOTAS-DA-RELEASE.md b/GameBoost/installer/NOTAS-DA-RELEASE.md new file mode 100644 index 0000000..48a6123 --- /dev/null +++ b/GameBoost/installer/NOTAS-DA-RELEASE.md @@ -0,0 +1,36 @@ +## O que baixar + +| Arquivo | Para quem | +|---|---| +| `GameBoost-Setup-*.exe` | A maioria. Instala, cria atalho e habilita a atualização automática | +| `GameBoost-*-portatil.zip` | Pen drive, máquina de terceiro, ou quem não quer instalar nada. Guarda os dados ao lado do executável e **não** se atualiza sozinho | +| `GameBoost.exe` | O executável solto, para quem sabe o que quer | +| `SHA256SUMS.txt` | Conferir que o download veio inteiro | + +Os arquivos `.nupkg` e `*-full.nupkg` são do mecanismo de atualização. Não +precisam ser baixados à mão. + +## Aviso do SmartScreen + +Esta versão pode não estar assinada. Se aparecer *"O Windows protegeu o +computador"*, o caminho é **Mais informações → Executar assim mesmo**. + +Antes disso, se quiser conferir que o arquivo é o mesmo que saiu daqui: + +```powershell +Get-FileHash .\GameBoost-Setup-2.0.0.exe -Algorithm SHA256 +``` + +e comparar com a linha correspondente do `SHA256SUMS.txt`. + +O porquê de não estar assinado, e o que está sendo feito a respeito, está em +[docs/ASSINATURA.md](../docs/ASSINATURA.md). + +## O que ele faz + +Todo o resto está no [README](../README.md). O resumo em três linhas: + +- Tudo o que ele altera, ele desfaz. O valor anterior vai para o histórico antes + da escrita, e cada ação tem botão de desfazer. +- Não tem telemetria. Nenhum dado sai da máquina. +- Quando um ajuste é marginal, está escrito que é marginal. diff --git a/GameBoost/src/GameBoost.App/App.xaml b/GameBoost/src/GameBoost.App/App.xaml new file mode 100644 index 0000000..2a3fac4 --- /dev/null +++ b/GameBoost/src/GameBoost.App/App.xaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GameBoost/src/GameBoost.App/App.xaml.cs b/GameBoost/src/GameBoost.App/App.xaml.cs new file mode 100644 index 0000000..59fd2bb --- /dev/null +++ b/GameBoost/src/GameBoost.App/App.xaml.cs @@ -0,0 +1,235 @@ +using System.Windows; +using GameBoost.App.ViewModels; +using GameBoost.App.Views; +using GameBoost.Core; +using GameBoost.Core.Logging; +using Microsoft.Extensions.DependencyInjection; + +namespace GameBoost.App; + +/// +/// Caminho grafico. Os comandos de linha nunca chegam aqui: Program os trata +/// antes de o WPF existir. +/// +public partial class App : Application +{ + private IServiceProvider? _services; + + public static IServiceProvider Services => + ((App)Current)._services ?? throw new InvalidOperationException("Container ainda nao inicializado."); + + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + + // Sem isto o app morre com APPCRASH e nao deixa rastro nenhum: o log + // para na ultima acao bem-sucedida e nao ha stack trace em lugar algum. + DispatcherUnhandledException += AoFalharNaUi; + AppDomain.CurrentDomain.UnhandledException += AoFalharForaDaUi; + TaskScheduler.UnobservedTaskException += AoFalharEmTask; + + var colecao = new ServiceCollection(); + colecao.AddGameBoostCore(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + colecao.AddSingleton(); + _services = colecao.BuildServiceProvider(); + + _services.GetRequiredService().Info("App", "Startup", null, + $"v{typeof(App).Assembly.GetName().Version} admin={CoreServices.RodandoComoAdministrador()}"); + + var janela = new ShellWindow { DataContext = _services.GetRequiredService() }; + MainWindow = janela; + janela.Show(); + + // A bandeja vem primeiro: o vigia liga o convite nela. + LigarBandeja(janela); + LigarVigiaDeJogos(); + MostrarOnboarding(janela); + } + + // ================================================================== + // Bandeja (secao 6) + // ================================================================== + + private Services.BandejaDoSistema? _bandeja; + + private void LigarBandeja(ShellWindow janela) + { + var settings = _services!.GetRequiredService().Load(); + + if (!settings.MostrarNaBandeja) + return; + + _bandeja = _services!.GetRequiredService(); + var shell = _services!.GetRequiredService(); + + _bandeja.AoAbrir += () => Dispatcher.Invoke(() => + { + janela.Show(); + janela.WindowState = WindowState.Normal; + janela.Activate(); + }); + + _bandeja.AoSair += () => Dispatcher.Invoke(() => + { + // Sair pela bandeja sai de verdade. Um programa que so finge + // fechar e continua rodando escondido e queixa legitima. + _saindoDeVez = true; + Shutdown(); + }); + + _bandeja.AoAlternarModoGame += () => Dispatcher.Invoke(shell.AlternarModoGamePelaBandeja); + _bandeja.AoLimparRam += () => Dispatcher.Invoke(shell.LimparRamPelaBandeja); + + _bandeja.Mostrar(); + janela.Bandeja = _bandeja; + } + + private bool _saindoDeVez; + + /// True enquanto fechar a janela deve so esconder. + public bool EsconderEmVezDeFechar => _bandeja is { Visivel: true } && !_saindoDeVez; + + // ================================================================== + // Deteccao automatica de jogo (secao 5.1) + // ================================================================== + + private void LigarVigiaDeJogos() + { + var settings = _services!.GetRequiredService().Load(); + + if (!settings.DetectarJogosAutomaticamente) + return; + + var vigia = _services!.GetRequiredService(); + var shell = _services!.GetRequiredService(); + + vigia.AoAbrir += evento => Dispatcher.Invoke(() => shell.JogoDetectado(evento)); + vigia.AoFechar += evento => Dispatcher.Invoke(() => shell.JogoEncerrado(evento)); + + // Sem isto o convite morre no lugar onde nasce: o shell dispara o + // evento e ninguem escuta. O jogo era detectado, o log registrava a + // deteccao, e o usuario nao via nada em lugar nenhum. + // + // Balao E faixa, nao um ou outro: com o jogo em tela cheia o Windows + // engole o balao, e a faixa fica esperando na janela. Sem tela cheia, + // o balao chega primeiro. + shell.AoPedirAviso += (titulo, texto) => Dispatcher.Invoke(() => + _bandeja?.Avisar(titulo, texto)); + + vigia.Iniciar(); + } + + // ================================================================== + // Onboarding (secao 6) + // ================================================================== + + private void MostrarOnboarding(ShellWindow janela) + { + var store = _services!.GetRequiredService(); + var settings = store.Load(); + + if (settings.OnboardingConcluido) + return; + + var vm = new OnboardingViewModel(); + var dialogo = new OnboardingWindow { DataContext = vm, Owner = janela }; + + vm.AoTerminar += abrirRelatorio => + { + settings.OnboardingConcluido = true; + store.Save(settings); + + dialogo.Close(); + + if (abrirRelatorio) + _services!.GetRequiredService().AbrirRelatorioInicial(); + }; + + dialogo.ShowDialog(); + } + + protected override void OnExit(ExitEventArgs e) + { + // Sair sem desfazer o perfil aplicado deixaria prioridade e tweaks de + // pe depois que o app sumiu. Isso e o oposto da regra 1. + // + // O GameWatcher NAO e descartado aqui: ele e singleton IDisposable, e o + // proprio container o descarta logo abaixo. Chamar nos dois lugares e o + // que fazia a excecao aparecer em toda saida. + try + { + _services?.GetService()?.Desfazer(); + } + catch (Exception ex) when (ex is InvalidOperationException or ObjectDisposedException) + { + Registrar("Exit", ex); + } + + try + { + _bandeja?.Dispose(); + (_services as ServiceProvider)?.Dispose(); + } + catch (Exception ex) when (ex is InvalidOperationException or ObjectDisposedException) + { + // Falhar ao fechar nao pode virar caixa de erro: o app ja esta + // saindo e nao ha nada que o usuario possa fazer a respeito. + Registrar("Exit", ex); + } + + base.OnExit(e); + } + + private void AoFalharNaUi(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) + { + Registrar("UI", e.Exception); + + MessageBox.Show( + "O GameBoost encontrou um erro inesperado e registrou os detalhes no log." + + Environment.NewLine + Environment.NewLine + + e.Exception.Message, + "GameBoost", MessageBoxButton.OK, MessageBoxImage.Error); + + // A janela continua de pe: um erro numa tela nao precisa derrubar o app. + e.Handled = true; + } + + private void AoFalharForaDaUi(object sender, UnhandledExceptionEventArgs e) + { + if (e.ExceptionObject is Exception ex) + Registrar("Dominio", ex); + } + + private void AoFalharEmTask(object? sender, UnobservedTaskExceptionEventArgs e) + { + Registrar("Task", e.Exception); + e.SetObserved(); + } + + private void Registrar(string origem, Exception ex) + { + try + { + _services?.GetRequiredService() + .Error("App", $"Excecao nao tratada ({origem})", ex.GetType().Name, + ex.ToString().Replace(Environment.NewLine, " | "), ex); + } + catch (Exception) + { + // Falhar ao registrar a falha nao pode virar uma segunda falha. + } + } + +} diff --git a/GameBoost/src/GameBoost.App/Controls/MiniGrafico.cs b/GameBoost/src/GameBoost.App/Controls/MiniGrafico.cs new file mode 100644 index 0000000..0d19fe3 --- /dev/null +++ b/GameBoost/src/GameBoost.App/Controls/MiniGrafico.cs @@ -0,0 +1,126 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace GameBoost.App.Controls; + +/// +/// Grafico dos ultimos 60 segundos (secao 5.5). +/// +/// Desenha em OnRender em vez de montar uma Polyline com centenas de pontos: +/// a serie e trocada inteira a cada segundo, e recriar objetos visuais nesse +/// ritmo apareceria no orcamento de CPU da propria coleta. +/// +public sealed class MiniGrafico : Control +{ + public static readonly DependencyProperty ValoresProperty = DependencyProperty.Register( + nameof(Valores), typeof(IReadOnlyList), typeof(MiniGrafico), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty MaximoProperty = DependencyProperty.Register( + nameof(Maximo), typeof(double), typeof(MiniGrafico), + new FrameworkPropertyMetadata(100.0, FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty CorDaLinhaProperty = DependencyProperty.Register( + nameof(CorDaLinha), typeof(Brush), typeof(MiniGrafico), + new FrameworkPropertyMetadata(Brushes.Teal, FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty RotuloProperty = DependencyProperty.Register( + nameof(Rotulo), typeof(string), typeof(MiniGrafico), + new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender)); + + public IReadOnlyList? Valores + { + get => (IReadOnlyList?)GetValue(ValoresProperty); + set => SetValue(ValoresProperty, value); + } + + public double Maximo + { + get => (double)GetValue(MaximoProperty); + set => SetValue(MaximoProperty, value); + } + + public Brush CorDaLinha + { + get => (Brush)GetValue(CorDaLinhaProperty); + set => SetValue(CorDaLinhaProperty, value); + } + + public string Rotulo + { + get => (string)GetValue(RotuloProperty); + set => SetValue(RotuloProperty, value); + } + + protected override void OnRender(DrawingContext dc) + { + base.OnRender(dc); + + var largura = ActualWidth; + var altura = ActualHeight; + + if (largura <= 1 || altura <= 1) + return; + + // Linhas de grade em 25, 50 e 75%. + var grade = new Pen(new SolidColorBrush(Color.FromArgb(30, 255, 255, 255)), 1); + grade.Freeze(); + + for (var i = 1; i < 4; i++) + { + var y = altura * i / 4.0; + dc.DrawLine(grade, new Point(0, y), new Point(largura, y)); + } + + var valores = Valores; + if (valores is null || valores.Count < 2) + { + DesenharRotulo(dc, "aguardando dados", largura, altura); + return; + } + + var maximo = Maximo <= 0 ? 1 : Maximo; + var passo = largura / Math.Max(valores.Count - 1, 1); + + var linha = new Pen(CorDaLinha, 1.6) { LineJoin = PenLineJoin.Round }; + linha.Freeze(); + + var geometria = new StreamGeometry(); + using (var ctx = geometria.Open()) + { + var primeiro = new Point(0, Y(valores[0], maximo, altura)); + ctx.BeginFigure(primeiro, isFilled: false, isClosed: false); + + for (var i = 1; i < valores.Count; i++) + ctx.LineTo(new Point(i * passo, Y(valores[i], maximo, altura)), isStroked: true, isSmoothJoin: false); + } + + geometria.Freeze(); + dc.DrawGeometry(null, linha, geometria); + + if (!string.IsNullOrEmpty(Rotulo)) + DesenharRotulo(dc, Rotulo, largura, altura); + } + + private static double Y(double valor, double maximo, double altura) + { + var proporcao = Math.Clamp(valor / maximo, 0, 1); + return altura - proporcao * altura; + } + + private void DesenharRotulo(DrawingContext dc, string texto, double largura, double altura) + { + var formatado = new FormattedText( + texto, + CultureInfo.CurrentUICulture, + FlowDirection.LeftToRight, + new Typeface("Segoe UI"), + 10, + new SolidColorBrush(Color.FromArgb(140, 255, 255, 255)), + VisualTreeHelper.GetDpi(this).PixelsPerDip); + + dc.DrawText(formatado, new Point(largura - formatado.Width - 2, altura - formatado.Height - 1)); + } +} diff --git a/GameBoost/src/GameBoost.App/Controls/Treemap.cs b/GameBoost/src/GameBoost.App/Controls/Treemap.cs new file mode 100644 index 0000000..b237b7c --- /dev/null +++ b/GameBoost/src/GameBoost.App/Controls/Treemap.cs @@ -0,0 +1,293 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using GameBoost.Core.Modules.DiskAnalyzer; + +namespace GameBoost.App.Controls; + +/// Um retângulo desenhado, guardado para achar o nó sob o mouse. +internal sealed record Bloco(DiskNode No, Rect Area); + +/// +/// Treemap da seção 5.4: retângulos proporcionais ao tamanho, coloridos por +/// categoria. +/// +/// Usa o algoritmo "squarified": em vez de fatiar sempre na mesma direção, ele +/// escolhe a cada passo a direção que deixa os retângulos mais próximos de +/// quadrados. Fatias compridas e finas são impossíveis de clicar e não deixam +/// comparar áreas a olho, que é justamente para o que o treemap serve. +/// +/// Desenha em OnRender: um disco cheio tem milhares de blocos por nível, e +/// criar objetos visuais para cada um travaria a navegação. +/// +public sealed class Treemap : Control +{ + private readonly List _blocos = new(); + private DiskNode? _sobMouse; + + public static readonly DependencyProperty RaizProperty = DependencyProperty.Register( + nameof(Raiz), typeof(DiskNode), typeof(Treemap), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty MaximoDeBlocosProperty = DependencyProperty.Register( + nameof(MaximoDeBlocos), typeof(int), typeof(Treemap), + new FrameworkPropertyMetadata(120, FrameworkPropertyMetadataOptions.AffectsRender)); + + public DiskNode? Raiz + { + get => (DiskNode?)GetValue(RaizProperty); + set => SetValue(RaizProperty, value); + } + + /// Acima disso os retângulos ficam menores que o clique. + public int MaximoDeBlocos + { + get => (int)GetValue(MaximoDeBlocosProperty); + set => SetValue(MaximoDeBlocosProperty, value); + } + + /// Disparado ao clicar num bloco que é pasta: o shell desce um nível. + public event Action? AoEntrarNaPasta; + + public event Action? AoPassarOMouse; + + public Treemap() + { + ClipToBounds = true; + Cursor = Cursors.Hand; + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + var no = Encontrar(e.GetPosition(this)); + if (ReferenceEquals(no, _sobMouse)) + return; + + _sobMouse = no; + AoPassarOMouse?.Invoke(no); + InvalidateVisual(); + } + + protected override void OnMouseLeave(MouseEventArgs e) + { + base.OnMouseLeave(e); + + _sobMouse = null; + AoPassarOMouse?.Invoke(null); + InvalidateVisual(); + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonUp(e); + + var no = Encontrar(e.GetPosition(this)); + + // Só pasta desce um nível: clicar num arquivo não leva a lugar nenhum. + if (no is { EhPasta: true, Filhos.Count: > 0 }) + AoEntrarNaPasta?.Invoke(no); + } + + private DiskNode? Encontrar(Point ponto) + { + for (var i = _blocos.Count - 1; i >= 0; i--) + { + if (_blocos[i].Area.Contains(ponto)) + return _blocos[i].No; + } + + return null; + } + + protected override void OnRender(DrawingContext dc) + { + base.OnRender(dc); + + _blocos.Clear(); + + var largura = ActualWidth; + var altura = ActualHeight; + + if (largura <= 4 || altura <= 4) + return; + + var fundo = new SolidColorBrush(Color.FromRgb(0x1C, 0x1F, 0x26)); + fundo.Freeze(); + dc.DrawRectangle(fundo, null, new Rect(0, 0, largura, altura)); + + var raiz = Raiz; + if (raiz is null || raiz.Tamanho <= 0) + { + Texto(dc, "Varra um disco para ver onde está o espaço", largura / 2, altura / 2, centralizado: true); + return; + } + + var filhos = raiz.Filhos + .Where(f => f.Tamanho > 0) + .OrderByDescending(f => f.Tamanho) + .Take(MaximoDeBlocos) + .ToList(); + + if (filhos.Count == 0) + { + Texto(dc, "Pasta vazia", largura / 2, altura / 2, centralizado: true); + return; + } + + Espalhar(dc, filhos, new Rect(1, 1, largura - 2, altura - 2)); + } + + /// + /// Coração do squarified: acumula itens numa faixa enquanto isso melhora a + /// proporção média dos retângulos, e fecha a faixa quando começa a piorar. + /// + private void Espalhar(DrawingContext dc, List itens, Rect area) + { + var total = itens.Sum(i => (double)i.Tamanho); + if (total <= 0) + return; + + var restantes = new List(itens); + + while (restantes.Count > 0 && area is { Width: > 1, Height: > 1 }) + { + var somaRestante = restantes.Sum(i => (double)i.Tamanho); + var horizontal = area.Width >= area.Height; + var ladoFixo = horizontal ? area.Height : area.Width; + + var faixa = new List(); + double somaFaixa = 0; + var melhorProporcao = double.MaxValue; + + foreach (var item in restantes) + { + var candidata = somaFaixa + item.Tamanho; + var proporcao = PiorProporcao(faixa, item, candidata, somaRestante, area, ladoFixo); + + if (faixa.Count > 0 && proporcao > melhorProporcao) + break; + + faixa.Add(item); + somaFaixa = candidata; + melhorProporcao = proporcao; + } + + var fracao = somaFaixa / somaRestante; + var espessura = (horizontal ? area.Width : area.Height) * fracao; + + var posicao = horizontal ? area.Top : area.Left; + + foreach (var item in faixa) + { + var parte = somaFaixa <= 0 ? 0 : item.Tamanho / somaFaixa * ladoFixo; + + var retangulo = horizontal + ? new Rect(area.Left, posicao, espessura, parte) + : new Rect(posicao, area.Top, parte, espessura); + + Desenhar(dc, item, retangulo); + posicao += parte; + } + + area = horizontal + ? new Rect(area.Left + espessura, area.Top, Math.Max(0, area.Width - espessura), area.Height) + : new Rect(area.Left, area.Top + espessura, area.Width, Math.Max(0, area.Height - espessura)); + + foreach (var item in faixa) + restantes.Remove(item); + } + } + + private static double PiorProporcao( + List faixa, DiskNode candidato, double somaComCandidato, + double somaRestante, Rect area, double ladoFixo) + { + if (somaComCandidato <= 0 || somaRestante <= 0) + return double.MaxValue; + + var comprimento = (area.Width >= area.Height ? area.Width : area.Height) + * (somaComCandidato / somaRestante); + + if (comprimento <= 0) + return double.MaxValue; + + var pior = 0.0; + + foreach (var item in faixa.Append(candidato)) + { + var lado = item.Tamanho / somaComCandidato * ladoFixo; + if (lado <= 0) + continue; + + pior = Math.Max(pior, Math.Max(comprimento / lado, lado / comprimento)); + } + + return pior == 0 ? double.MaxValue : pior; + } + + private void Desenhar(DrawingContext dc, DiskNode no, Rect area) + { + if (area.Width < 1 || area.Height < 1) + return; + + _blocos.Add(new Bloco(no, area)); + + var cor = (Color)ColorConverter.ConvertFromString(DiskCategories.Cor(no.Categoria)); + var destacado = ReferenceEquals(no, _sobMouse); + + var preenche = new SolidColorBrush(cor) { Opacity = destacado ? 1.0 : 0.78 }; + preenche.Freeze(); + + var borda = new Pen(new SolidColorBrush(Color.FromArgb(destacado ? (byte)255 : (byte)60, 20, 22, 26)), + destacado ? 2 : 1); + borda.Freeze(); + + // Deflate evita que bordas vizinhas se sobreponham e engrossem. + var desenho = Rect.Inflate(area, -0.5, -0.5); + if (desenho.Width <= 0 || desenho.Height <= 0) + desenho = area; + + dc.DrawRectangle(preenche, borda, desenho); + + // Rótulo só cabe em bloco grande; abaixo disso vira borrão. + if (area.Width > 64 && area.Height > 26) + { + Texto(dc, no.Nome, area.Left + 5, area.Top + 3, centralizado: false, maximo: area.Width - 10); + + if (area.Height > 42) + { + Texto(dc, Core.Modules.GameMode.GameModeModule.Formatar(no.Tamanho), + area.Left + 5, area.Top + 19, centralizado: false, maximo: area.Width - 10, opaco: true); + } + } + } + + private void Texto( + DrawingContext dc, string texto, double x, double y, + bool centralizado, double maximo = double.MaxValue, bool opaco = false) + { + var formatado = new FormattedText( + texto, + CultureInfo.CurrentUICulture, + FlowDirection.LeftToRight, + new Typeface("Segoe UI"), + 11.5, + new SolidColorBrush(Color.FromArgb(opaco ? (byte)190 : (byte)255, 13, 17, 23)), + VisualTreeHelper.GetDpi(this).PixelsPerDip) + { + MaxLineCount = 1, + Trimming = TextTrimming.CharacterEllipsis + }; + + if (maximo < double.MaxValue) + formatado.MaxTextWidth = Math.Max(10, maximo); + + dc.DrawText(formatado, centralizado + ? new Point(x - formatado.Width / 2, y - formatado.Height / 2) + : new Point(x, y)); + } +} diff --git a/GameBoost/src/GameBoost.App/Converters/Converters.cs b/GameBoost/src/GameBoost.App/Converters/Converters.cs new file mode 100644 index 0000000..8b541b4 --- /dev/null +++ b/GameBoost/src/GameBoost.App/Converters/Converters.cs @@ -0,0 +1,107 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Media; +using GameBoost.Core.Modules; + +namespace GameBoost.App.Converters; + +/// Badge de risco: verde, amarelo, vermelho (secao 6 do spec). +public sealed class RiscoParaCorConverter : IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var chave = (value as RiskLevel?) switch + { + RiskLevel.Alto => "RiscoAlto", + RiskLevel.Medio => "RiscoMedio", + _ => "RiscoBaixo" + }; + + return Application.Current?.TryFindResource(chave) as Brush ?? Brushes.Gray; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + => throw new NotSupportedException(); +} + +public sealed class BoolParaVisibilidadeConverter : IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var visivel = value is true; + if (parameter as string == "inverter") + visivel = !visivel; + + return visivel ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + => throw new NotSupportedException(); +} + +public sealed class InverterBoolConverter : IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + => value is not true; + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + => value is not true; +} + +/// Mostra o elemento apenas quando o texto existe e nao esta vazio. +public sealed class TextoParaVisibilidadeConverter : IValueConverter +{ + /// + /// Com ConverterParameter="inverter", aparece quando o texto esta VAZIO. + /// E o que faz o texto-fantasma de uma caixa de busca sumir assim que a + /// pessoa comeca a digitar. + /// + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var vazio = string.IsNullOrWhiteSpace(value as string); + + if (string.Equals(parameter as string, "inverter", StringComparison.OrdinalIgnoreCase)) + return vazio ? Visibility.Visible : Visibility.Collapsed; + + return vazio ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + => throw new NotSupportedException(); +} + +/// +/// True vira negrito. Serve para destacar a linha "em uso agora" na tabela de +/// DNS sem precisar de uma cor a mais na paleta. +/// +public sealed class BoolParaPesoConverter : IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + => value is true ? FontWeights.Bold : FontWeights.Normal; + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + => throw new NotSupportedException(); +} + +/// +/// Semaforo do NAT e da latencia: verde, amarelo, vermelho, cinza. +/// +/// Usa as MESMAS cores do badge de risco, de proposito. Um verde aqui e um +/// verde la precisam ser o mesmo verde, senao o usuario aprende duas escalas +/// de cor no mesmo aplicativo. +/// +public sealed class SemaforoParaCorConverter : IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + => (value as string) switch + { + "verde" => new SolidColorBrush(Color.FromRgb(0x3F, 0xB9, 0x50)), + "amarelo" => new SolidColorBrush(Color.FromRgb(0xE3, 0xB3, 0x41)), + "vermelho" => new SolidColorBrush(Color.FromRgb(0xE0, 0x5B, 0x4F)), + _ => new SolidColorBrush(Color.FromRgb(0x7A, 0x7A, 0x7A)) + }; + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + => throw new NotSupportedException(); +} diff --git a/GameBoost/src/GameBoost.App/GameBoost.App.csproj b/GameBoost/src/GameBoost.App/GameBoost.App.csproj new file mode 100644 index 0000000..8e9fd6f --- /dev/null +++ b/GameBoost/src/GameBoost.App/GameBoost.App.csproj @@ -0,0 +1,73 @@ + + + WinExe + net9.0-windows + true + + true + + $(NoWarn);WFO0003 + GameBoost.App + GameBoost + + true + true + win-x64 + true + true + pt-BR + + GameBoost.App.Program + + + + + false + app.manifest + app-dev.manifest + GameBoost-dev + + + + + + + + + + + + + + + + + + + + + + diff --git a/GameBoost/src/GameBoost.App/Program.cs b/GameBoost/src/GameBoost.App/Program.cs new file mode 100644 index 0000000..9fa68fb --- /dev/null +++ b/GameBoost/src/GameBoost.App/Program.cs @@ -0,0 +1,118 @@ +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using GameBoost.Cli; +using GameBoost.Core; +using GameBoost.Core.Logging; +using Microsoft.Extensions.DependencyInjection; + +namespace GameBoost.App; + +/// +/// Ponto de entrada do GameBoost.exe. Decide antes de qualquer coisa se a +/// execucao e de linha de comando ou grafica. +/// +/// O caminho CLI **nao** inicializa o WPF. A primeira tentativa chamava +/// Application.Shutdown() dentro de OnStartup e o processo ficava pendurado: +/// um app WPF que nunca abriu janela mantem o loop de mensagens vivo, e +/// Environment.Exit no meio da inicializacao arrisca travar. Separar o Main +/// resolve na origem: em `--scan` nem existe Application. +/// +internal static class Program +{ + [STAThread] + public static int Main(string[] args) + { + // Antes de qualquer outra coisa, inclusive de ler argumentos. + // + // O Velopack usa o proprio executavel para instalar, atualizar e + // desinstalar: ele roda o exe com argumentos proprios e espera que esta + // chamada os intercepte e encerre o processo. Se ela vier depois de + // qualquer inicializacao, a instalacao abre a janela do app no meio do + // processo de update — ou trava. + // + // Em build de desenvolvimento, sem pacote Velopack por perto, isto e + // uma chamada que nao faz nada. + Velopack.VelopackApp.Build().Run(); + + var opcoes = CommandLineOptions.Parse(args); + + if (!opcoes.EhCli) + { + App.Main(); + return 0; + } + + return ExecutarCli(opcoes); + } + + private static int ExecutarCli(CommandLineOptions opcoes) + { + // Sendo WinExe, o processo nao tem console proprio. + // + // Quando a saida ja vem redirecionada (`--scan > arquivo.txt`, um pipe, + // ou um script), o handle padrao ja e valido e basta usa-lo. So quando + // nao ha redirecionamento e preciso anexar ao console de quem chamou, + // para que o comando imprima no terminal como na v1. + // + // A primeira versao so olhava o AttachConsole e descartava a saida + // inteira quando ele falhava, entao redirecionar para arquivo produzia + // um arquivo vazio. + var redirecionado = Console.IsOutputRedirected; + var anexou = !redirecionado && AttachConsole(AttachParentProcess); + var temSaida = redirecionado || anexou; + + ServiceProvider? provider = null; + + try + { + var colecao = new ServiceCollection(); + colecao.AddGameBoostCore(); + provider = colecao.BuildServiceProvider(); + + provider.GetRequiredService() + .Info("Cli", opcoes.Comando.ToString(), null, + $"admin={CoreServices.RodandoComoAdministrador()} dryRun={opcoes.DryRun}"); + + using var saida = temSaida + ? new StreamWriter(Console.OpenStandardOutput(), new UTF8Encoding(false)) { AutoFlush = true } + : TextWriter.Null; + + return new CommandRunner(provider, saida).ExecutarAsync(opcoes).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + try + { + provider?.GetRequiredService() + .Error("Cli", opcoes.Comando.ToString(), null, "falha nao tratada", ex); + } + catch (InvalidOperationException) + { + // Container nao chegou a subir: nao ha onde registrar. + } + + if (temSaida) + Console.Error.WriteLine($"Erro: {ex.Message}"); + + return 1; + } + finally + { + provider?.Dispose(); + + if (anexou) + FreeConsole(); + } + } + + private const int AttachParentProcess = -1; + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool AttachConsole(int processId); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool FreeConsole(); +} diff --git a/GameBoost/src/GameBoost.App/Services/BandejaDoSistema.cs b/GameBoost/src/GameBoost.App/Services/BandejaDoSistema.cs new file mode 100644 index 0000000..ac6d05e --- /dev/null +++ b/GameBoost/src/GameBoost.App/Services/BandejaDoSistema.cs @@ -0,0 +1,167 @@ +using System.Drawing; +using System.Windows.Forms; +using GameBoost.Core.Logging; + +namespace GameBoost.App.Services; + +/// +/// Ícone na bandeja com o menu da seção 6: Ativar Modo Game, Limpar RAM, Abrir, +/// Sair. +/// +/// Duas decisões que valem ser ditas: +/// +/// - **Fechar a janela não fecha o app quando a bandeja está ligada**, mas o +/// menu tem "Sair" e ele sai de verdade. Programa que só finge fechar e fica +/// rodando escondido é queixa legítima; o caminho para sair existe e está +/// visível. +/// - **O ícone é desenhado em código**, não carregado de arquivo. Num publish +/// single-file, um `.ico` solto ao lado do exe não existe, e embutir recurso +/// para dezesseis pixels não vale. +/// +public sealed class BandejaDoSistema : IDisposable +{ + private readonly IGameBoostLogger _log; + private NotifyIcon? _icone; + private ToolStripMenuItem? _itemModoGame; + + public BandejaDoSistema(IGameBoostLogger log) + { + _log = log; + } + + public event Action? AoAbrir; + public event Action? AoAlternarModoGame; + public event Action? AoLimparRam; + public event Action? AoSair; + + public bool Visivel => _icone is { Visible: true }; + + public void Mostrar() + { + if (_icone is not null) + { + _icone.Visible = true; + return; + } + + try + { + var menu = new ContextMenuStrip(); + + _itemModoGame = new ToolStripMenuItem("Ativar Modo Game"); + _itemModoGame.Click += (_, _) => AoAlternarModoGame?.Invoke(); + menu.Items.Add(_itemModoGame); + + var limpar = new ToolStripMenuItem("Limpar RAM"); + limpar.Click += (_, _) => AoLimparRam?.Invoke(); + menu.Items.Add(limpar); + + menu.Items.Add(new ToolStripSeparator()); + + var abrir = new ToolStripMenuItem("Abrir GameBoost"); + abrir.Click += (_, _) => AoAbrir?.Invoke(); + menu.Items.Add(abrir); + + var sair = new ToolStripMenuItem("Sair"); + sair.Click += (_, _) => AoSair?.Invoke(); + menu.Items.Add(sair); + + _icone = new NotifyIcon + { + Icon = Desenhar(false), + Text = "GameBoost", + Visible = true, + ContextMenuStrip = menu + }; + + _icone.DoubleClick += (_, _) => AoAbrir?.Invoke(); + + _log.Info("bandeja", "Mostrar", null, "ícone na bandeja"); + } + catch (Exception ex) when (ex is InvalidOperationException or ArgumentException) + { + _log.Error("bandeja", "Mostrar", null, ex.Message, ex); + } + } + + public void Esconder() + { + if (_icone is not null) + _icone.Visible = false; + } + + /// Muda o texto do menu e a cor do ícone conforme o Modo Game. + public void AtualizarModoGame(bool ativo) + { + if (_itemModoGame is not null) + _itemModoGame.Text = ativo ? "Desligar Modo Game" : "Ativar Modo Game"; + + if (_icone is null) + return; + + var antigo = _icone.Icon; + _icone.Icon = Desenhar(ativo); + _icone.Text = ativo ? "GameBoost — Modo Game ativo" : "GameBoost"; + + // O handle do ícone antigo não sai sozinho: trocar o ícone a cada + // sessão vazaria um handle por vez. + antigo?.Dispose(); + } + + /// Balão da bandeja. Usado para o convite de Modo Game. + public void Avisar(string titulo, string texto) + { + if (_icone is null) + return; + + try + { + _icone.BalloonTipTitle = titulo; + _icone.BalloonTipText = texto; + _icone.BalloonTipIcon = ToolTipIcon.Info; + _icone.ShowBalloonTip(8000); + } + catch (Exception ex) when (ex is InvalidOperationException or ArgumentException) + { + _log.Warn("bandeja", "Avisar", titulo, ex.Message); + } + } + + /// + /// Desenha um ícone de 16×16: um quadrado arredondado com a inicial. Verde + /// quando o Modo Game está ativo, cinza quando não. + /// + private static Icon Desenhar(bool ativo) + { + using var bitmap = new Bitmap(16, 16); + using var g = Graphics.FromImage(bitmap); + + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + g.Clear(Color.Transparent); + + var cor = ativo + ? Color.FromArgb(0x2E, 0xC4, 0x9A) + : Color.FromArgb(0x8A, 0x90, 0x99); + + using var pincel = new SolidBrush(cor); + g.FillEllipse(pincel, 1, 1, 14, 14); + + using var fonte = new Font("Segoe UI", 8, FontStyle.Bold, GraphicsUnit.Pixel); + using var texto = new SolidBrush(Color.FromArgb(0x10, 0x12, 0x16)); + + g.DrawString("G", fonte, texto, new PointF(4, 3)); + + return Icon.FromHandle(bitmap.GetHicon()); + } + + public void Dispose() + { + if (_icone is null) + return; + + _icone.Visible = false; + _icone.Icon?.Dispose(); + _icone.Dispose(); + _icone = null; + } +} diff --git a/GameBoost/src/GameBoost.App/Themes/Controles.xaml b/GameBoost/src/GameBoost.App/Themes/Controles.xaml new file mode 100644 index 0000000..ad91ee0 --- /dev/null +++ b/GameBoost/src/GameBoost.App/Themes/Controles.xaml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +