feat: detecta jogo possuido nao instalado (is_game_owned) p/ modo DLC automatico #1

Open
Maicon wants to merge 66 commits from maicon into main
Owner

Detecta automaticamente, sem toggle manual, se o jogo ja e do usuario mesmo sem estar instalado, combinando 4 sinais locais do Steam por OR (appmanifest.acf, registry.vdf, localconfig.vdf, sharedconfig.vdf).

  • Possuido -> modo DLC seguro: nao reivindica o base, nao mexe em depotcache/config.vdf.
  • Nao possuido -> ativacao completa (unlock do base).

Erra para o lado de possuido (OR entre as fontes) porque tratar um jogo possuido como nao-possuido trava a execucao do jogo base.

Generated with Claude Code

Detecta automaticamente, sem toggle manual, se o jogo ja e do usuario mesmo sem estar instalado, combinando 4 sinais locais do Steam por OR (appmanifest.acf, registry.vdf, localconfig.vdf, sharedconfig.vdf). - Possuido -> modo DLC seguro: nao reivindica o base, nao mexe em depotcache/config.vdf. - Nao possuido -> ativacao completa (unlock do base). Erra para o lado de possuido (OR entre as fontes) porque tratar um jogo possuido como nao-possuido trava a execucao do jogo base. Generated with Claude Code
Substitui fallback com senha em texto plano por exit 1, alinhando
o comportamento com upload_release.sh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove stage 'Commit version bump' que causava push -> SCM polling -> build loop

auto_bump.sh agora usa BUILD_NUMBER do Jenkins como 4o componente da versao

version.txt so mantem MAJOR.MINOR.PATCH (sem 4o digito)

Fix URLs internas 175.15.15.55 -> git.olymp.com.br

Remove URL interna 175.15.15.55:3000 do updater.py (sempre timeout externo)
- Nova config LDAP (LDAP_ENABLED, LDAP_SERVER, LDAP_PORT, LDAP_DOMAIN, LDAP_BASE_DN)
- Login tenta LDAP primeiro, fallback para DB local
- Usuarios AD criados automaticamente no admin_users ao primeiro login
- Adicionado ldap3==2.9.1 as dependencias
- Adicionado coluna 'role' ao model AdminUser
- Nova rota /api/users com CRUD (list, create, update, delete)
- /api/users/me retorna dados do usuario logado
- Middleware require_role() para controle de acesso por funcao
- Generete chave e settings restritos a admin
- Tela 'Usuarios' no admin SPA (sidebar) com CRUD completo
- Usuarios LDAP criados com role=operator por padrao
- Admin seed no startup com role=admin
- Migracao automatica da coluna role em banco existente
- check_status_cached agora tenta contato com o servidor primeiro
- Só usa cache offline se o servidor estiver inacessivel
- Banimento agora detectado na proxima verificacao (5min) ao inves de 24h
- Offline grace period mantido como fallback
- Adicionado https://steam-activador.maikiller.com.br/forgejo como
  primeira opcao em FORGEJO_URLS
- Rota /forgejo configurada no Traefik, proxy reverso para Forgejo
  interno (172.15.14.35:3000) com SSL Let's Encrypt
- _normalize_download_url agora trata ambas URLs (interna e publica)
- Update checks funcionam de qualquer lugar com internet
- Remove rota /forgejo do Traefik (Forgejo nao fica mais publico)
- Cria /api/updates/latest e /api/updates/download/:filename
  no license server, proxy do Forgejo interno, autenticado
  via license_key (mesmo padrao do /api/manifests)
- updater.py agora usa license server API ao inves de
  chamar Forgejo diretamente (funciona de qualquer lugar)
- Remove codigo morto (auth forgejo, URLs internas,
  parse de release, parallel fetch)
- /api/updates/ost/latest e /api/updates/ost/download/:filename
  proxy do GitHub (OpenSteam001/OpenSteamTool) pelo license server
- /api/updates/changelog retorna historico de releases do Forgejo
- ost_updater.py agora usa license server API (sem chamar GitHub direto)
- update_log_tab.py usa license server ao inves de Forgejo direto
- Tudo autenticado via license_key, nada exposto na internet
- Botao editar usuario (modal com role + reset senha)
- Botao excluir usuarios (bloqueia auto-exclusao)
- POST /api/users/sync-ad importa usuarios do Active Directory
- Filtro: exclui contas de computador ($), Guest, krbtgt, Administrator
- LDAP_ADMIN_USER e LDAP_ADMIN_PASS no config
- Usuarios AD importados com role operator por padrao
Cliente:
- is_activated() nao invalida por expiracao (so falta de ativacao/hardware)
- check_status_cached() offline sempre mantem acesso
- _on_license_verified() nao bloqueia quando invalido apenas por expiracao;
  banimento/revogacao/hardware diferente ainda bloqueiam

Servidor:
- /api/manifests/keys e _require_license aceitam status {active, expired}
  para chaves expiradas continuarem recebendo as manifest keys

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- expires_at do servidor e naive/UTC; parse agora trata como UTC (antes
  era interpretado no fuso local, deslocando a data)
- _parse_server_ts() centraliza a conversao; check_status/_cached passam
  a atualizar o expires_at gravado a partir da resposta do servidor
- barra de licenca so mostra "expirou" quando remaining<=0 (antes usava
  days<=0, marcando como vencida qualquer licenca com <24h restantes)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quando falta menos de 24h para expirar, exibe "expira em Xh" (ou Ymin
na ultima hora) com data/hora, em vez de "(0d)".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O download salvava os .manifest em data_root()/.manifests (pasta unica
compartilhada), mas a ativacao os procura em games/{app_id}/.manifests.
Resultado: 0 manifests copiados para o depotcache -> Steam acusa
"Configuracao de conteudo invalida" ao instalar. Agora o download usa
output_dir=games/{app_id}, casando com a ativacao (mesmo padrao do
lua_import_tab).

Tambem: parse_manifests_for_gids passa a reconhecer o nome real
"{depot}_{gid}.manifest" (2 partes), alem de "{app}_{depot}_{gid}".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
validate_manifest so aceitava \x00\x00\x00\x01/\x02 e rejeitava TODOS os
manifests reais do Steam, cujo header e PROTOBUF_PAYLOAD_MAGIC 0x71F617D0
(bytes D0 17 F6 71). Com isso 0 manifests eram copiados ao depotcache e o
Steam acusava "Configuracao de conteudo invalida". Agora o magic correto
e aceito (formatos legados mantidos).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Causa: repo Fairyvmos/BlankTMing publica DecryptionKey criptografada
(96 bytes / 192 hex). Depot key valida do Steam = 32 bytes / 64 hex.
O app injetava a key invalida no config.vdf e .lua -> Steam baixava mas
nao descriptografava ("conteudo ainda criptografado").

- is_valid_depot_key(): valida 64 hex; parse_key_vdf filtra invalidas
- fetch_from_github_repo: se o repo nao tem depot key valida, retorna None
  para o fluxo "auto" seguir para outra fonte (luagen/ryuu/hubcap)
- activate_opensteamtool: ignora keys invalidas ja registradas e avisa

Tambem reverte a mudanca de output_dir do download_tab: fetch_and_prepare
ja aninha games/{app_id} internamente; o correto e output_dir=data_root()
(a versao anterior causava aninhamento duplo). Os "0 copiados" originais
eram apenas os bugs de regex + magic byte, ja corrigidos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Manifests no depotcache sao nomeados por depot id (447041_*.manifest),
nao por app id (447040). A remocao so procurava prefixo {app_id}_, entao
NUNCA removia os manifests do jogo -> ficavam orfaos no depotcache.

Para jogos que o usuario JA POSSUI, esses artefatos orfaos (manifests
antigos + depot keys no config.vdf) conflitam com o download legitimo do
Steam: botao de pausar/retomar desabilitado e status preso em "instalando".

- remove_from_steam_library agora recebe depot_ids e remove os manifests
  de cada depot, alem de limpar as DecryptionKey do config.vdf
- vdf_manager.remove_depot_keys_from_config(): remove keys orfas
- main.py passa os depot_ids do game_manager na remocao

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ativar um jogo que o usuario ja possui/instalou gera conflito com o
download legitimo (botao travado / preso em "instalando"). Agora o app
detecta o appmanifest_{app_id}.acf nas bibliotecas e avisa antes de ativar.

- steam_emulator.is_game_installed(app_id): checa ACF em todas as libs
- activate_opensteamtool: loga aviso e mostra toast sticky (fora de batch)
  recomendando instalar direto pela Steam

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- find_install_dir_from_manifest filtrava por {app_id}_ mas manifests sao
  nomeados por depot id -> nunca casava (retornava Game_{app_id}). Agora
  varre qualquer .manifest (mesmo bug de depot-id-vs-app-id ja corrigido
  na remocao/deploy). Afeta todos os jogos, nao so o WD2.
- GITHUB_REPOS reordenado: Auiowu e tymolu233 (keys validas 64 hex) antes
  do Fairyvmos (keys criptografadas, so fallback de manifests). Melhora a
  cobertura de jogos futuros sem depender do depotkeys.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quando o jogo base ja esta instalado/possuido, ativar injetava tudo e
conflitava com o download legitimo. Agora, com base_installed=True:
- escreve o .lua normalmente (addappid do base + de cada DLC + keys) ->
  as DLCs sao desbloqueadas
- NAO copia manifests ao depotcache e NAO altera o config.vdf -> nao
  interfere no download legitimo do jogo base (sem botao travado / preso
  em "instalando")

is_game_installed decide o modo; aviso atualizado para "modo DLC".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
No modo DLC (base ja instalado/possuido) o .lua ainda reivindicava o
jogo base no SteamTools: addappid(base) + chaves de depot do base, sem
copiar os manifests ao depotcache. Isso fazia o SteamTools assumir o
base e tentar decriptar conteudo ja instalado legitimamente -> o jogo
nao iniciava mais.

Agora generate_lua_for_opensteamtool tem caminho base_installed=True que
gera um .lua DLC-only: apenas addappid() das DLCs, sem o base e sem
chaves de depot do base. Modo normal permanece identico. Aviso quando
modo DLC nao tem nenhuma DLC (lua sem addappid extra, sem efeito).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adiciona status Bloqueado/Ativo aos usuarios do license server:
- models: coluna is_blocked em admin_users (+ migracao ALTER TABLE no
  startup, para bancos existentes)
- auth/middleware: login (local e AD) e require_admin rejeitam usuario
  bloqueado com 403 -> token existente tambem deixa de funcionar
- users: PATCH aceita is_blocked; list/create/update/me expoem o campo;
  proibe bloquear a si mesmo e o admin padrao (evita auto-tranca/lockout)
- frontend: coluna Status, botoes Bloquear/Desbloquear (so admin),
  badge do usuario logado; acoes escondidas para self e admin embutido;
  campos de login nao vem mais preenchidos com admin/admin

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
is_game_owned combina 4 sinais locais do Steam por OR (appmanifest.acf,
registry.vdf, localconfig.vdf, sharedconfig.vdf) para decidir sozinho, sem
toggle manual, se o jogo ja e do usuario mesmo sem estar instalado. Possuido
-> modo DLC seguro (nao reivindica o base, nao mexe em depotcache/config.vdf);
nao possuido -> ativacao completa. Erra para 'possuido' (OR) porque tratar
possuido como nao-possuido trava a execucao do jogo base.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
O #225 classificava como possuido qualquer app_id presente em
registry.vdf/localconfig.vdf/sharedconfig.vdf. Essas secoes 'apps' NAO
indicam posse -- listam jogos apenas vistos/interagidos (favoritos,
ocultos, demos, family share, tracos de ativacoes antigas). Isso dava
falso-positivo no alvo de unlock (Watch_Dogs 2), mandando-o pro modo DLC
sem manifests/keys/config.vdf -> base nunca desbloqueava e "Jogar" nao
fazia nada. Agora owned == appmanifest_{app_id}.acf no disco (unico sinal
confiavel); nao-instalado -> ativacao completa (unlock do base), que e o
uso principal e que ja funcionava. Remove _owned_via_license_files e
helpers VDF (mortos). bump 1.0.13.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ao clicar "Instalar" na Steam, o alvo de unlock (WD2) ganha um
appmanifest_{app_id}.acf em estado de download. is_game_installed
contava a mera presenca do .acf -> caia no modo DLC -> pulava
depotcache/config.vdf -> base nunca provisionado -> nao baixa/nao lanca
(usuario reportou "depotcache nao copiado"). Agora so conta como
instalado se StateFlags tem bit 4 (FullyInstalled) e nao bit 2
(UpdateRequired). Jogo baixando -> ativacao completa (unlock). bump 1.0.14.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
O comportamento/codigo volta 100% ao build #214 (commit 9f80759). So o
numero de versao sobe para 1.0.15 porque o auto-updater nao faz downgrade
(1.0.10 < 1.0.14 instalado) -- com 1.0.15 o updater entrega o rollback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- validate_manifest: aceita o magic real dos manifests do Steam
  (PROTOBUF_PAYLOAD_MAGIC 0x71F617D0). Antes exigia 0x00000002 e
  rejeitava todos os manifests -> depotcache ficava vazio ("0 copiados").
- parse_manifests_for_gids: aceita nomes de 2 partes (depot_gid.manifest)
  gerados por DDM/ManifestHub/CDN, alem do formato antigo de 3 partes.
- fetch_and_prepare: resultado parcial de steamcdn/depotdl (menos manifests
  que chaves) nao encerra mais a busca; tenta as fontes seguintes e usa o
  HubcapDB (manifests completos + DLCs), com fallback para o melhor parcial.
- ordem automatica sem ryuu; usa apenas a chave do hubcap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Jenkins estava fora quando bc8948c foi enviado; commit vazio para
disparar o build novamente.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adiciona downloader.hubcap_status() (consulta /user/stats) e uma barra
na tela principal mostrando se a chave HubcapManifest esta operando,
uso diario (n/limite) e quando expira — com destaque (amarelo/vermelho)
quando falta pouco para renovar. Verificacao roda em background para
nao travar a UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GitHub (Fairyvmos) publica depot keys cifradas (192 hex) e retorna como
sucesso, entao o loop parava nele e nunca tentava o HubcapDB -> Steam dava
'conteudo ainda criptografado'. Reordena o modo auto para hubcap primeiro;
sem chave de API ele e pulado e cai nas fontes antigas.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'agent any' as vezes escalonava no no Windows lilith-windows, onde 'sh'
nao existe -> build #234 falhou com 'Cannot run program sh'. Todo o
pipeline usa sh/bash/sshpass, entao fixa no controller Linux (built-in),
tornando o agendamento deterministico e permanente (sobrevive a reboot).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Os CTs do forgejo e do mariadb sairam do pve para o friday e mudaram de
segmento: 172.15.14.35 -> 172.20.0.35 e 172.15.14.37 -> 172.20.0.37.
Sem isso o Jenkinsfile, os scripts de release e o license_server ficam
apontando para IPs mortos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmw1Nc3geU9FqFZr4ewEM4
Sintoma: jogo ativado com o HubcapDB selecionado terminava com "conteudo
ainda criptografado" no Steam. Investigado no App 2183900 (Space Marine 2):
o .lua e o config.vdf do PC tinham chaves de 192 hex, enquanto o HubcapDB
entrega as chaves corretas de 64 hex para os 5 depots.

Causa raiz: fetch_and_prepare._is_incomplete contava a chave do PROPRIO
app_id ao comparar chaves x manifests. O HubcapDB sempre inclui a linha
"MAIN APPLICATION" (addappid(<app>, 1, "<key>")), que marca posse e nunca
tem manifest. Resultado real do HubcapDB (5 chaves / 4 manifests) era
classificado como PARCIAL, o fallback seguia para o GitHub e o Fairyvmos
vencia com chaves CIFRADAS de 192 hex. Reordenar as fontes (c47625e) nao
resolvia porque o descarte acontecia depois da ordem.

- lua_parser: DEPOT_KEY_HEX_LEN/is_valid_depot_key. Depot key do Steam e
  AES-256 (32 bytes = 64 hex); qualquer outro comprimento vai para
  rejected_keys em vez de depot_keys_ready.
- downloader: _is_incomplete ignora a chave do app_id; fonte que so trouxe
  chaves invalidas e descartada em vez de vencer a disputa.
- ost_installer: generate_lua_for_opensteamtool passa a EMITIR setManifestid
  (o parametro manifest_gids era recebido e ignorado, entao o depot nunca
  ficava preso ao manifest do depotcache) e omite chave invalida.
- vdf_manager: busca Software/Valve/Steam case-insensitive. O Steam grava
  ora "Valve"/"Steam", ora minusculo; a busca fixa criava um ramo DUPLICADO
  e as chaves nunca chegavam ao Steam. Tambem recusa chave fora de 64 hex.
- gitignore para frontend_python/steam-backups/ (contem config.vdf real).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2JmFTpher2KiUuvNr4Yqx
Complemento de 2860c84. A validacao de 64 hex tinha ficado incompleta: os
caminhos github/steamcdn/depotdl montam depot_keys_ready a partir do
Key.vdf (parse_key_vdf) ou da saida do DepotDownloaderMod e NAO passam pelo
lua_parser, entao continuavam aceitando chave de qualquer comprimento. Foi
exatamente por ai que as 192 hex do Fairyvmos entraram.

- _sanitize_depot_keys() aplicado em fetch_from_github_repo,
  fetch_from_steamcdn e fetch_from_depotdl.
- GITHUB_REPOS: removido Fairyvmos/BlankTMing. Medido em 730, 570, 292030,
  1174180 e 271590: 100% das chaves com 192 hex (cifradas). Auiowu e
  tymolu233 devolvem 64 hex nos mesmos apps e ficam.
- SOURCES/FETCH_MAP/order: removidas luagen (API morta, HTTP 404 com pagina
  HTML em todos os apps testados, custava 3 retries com backoff por
  ativacao) e ryuu (exige chave que nao temos, ja fora da ordem auto desde
  2026-07-03). fetch_from_luagen/fetch_from_ryuu e as URLs deletadas.

Ordem automatica agora: hubcap -> github -> steamcdn -> depotdl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2JmFTpher2KiUuvNr4Yqx
Implementa as 5 melhorias levantadas na revisao pos-incidente das chaves
cifradas.

1. Deploy automatico do license server (era a causa do updater quebrado):
   - license_server/deploy.sh: build + redeploy idempotente no build server,
     roda health check, so recria o container se a imagem mudou. Tira
     FORGEJO_URL do env-file para o default do codigo (IP novo) ser a fonte
     unica de verdade -> nao repete o drift pve->friday.
   - Jenkinsfile: stage 'Deploy License Server' (so na branch maicon).

2. Testes no CI:
   - Jenkinsfile: stage 'Test' roda pytest em container antes do build.
   - Corrige 2 testes de test_downloader desatualizados (mock de _urlopen
     mudou de .read() para bytes diretos no refactor).
   - Adiciona TestDepotKeyValidation travando a regressao das chaves de
     192 hex (Fairyvmos). 42/42 passando.

3. Seguranca do license server:
   - JWT_SECRET rotacionado (era 'change-me-in-production' -> permitia FORJAR
     token de qualquer admin). Senha do admin local trocada (era admin/admin;
     login real e via AD, conta 'maicon' intacta).
   - CORS: allow_origins=['*']+credentials (invalido e aberto) -> origem
     publica do servico via CORS_ORIGINS.

4. Integridade do update:
   - upload_release.sh sobe <asset>.sha256; /api/updates/latest anexa o campo
     sha256; o updater ja valida (expected_sha256). Degrada para null em
     releases sem o hash.

5. Limpeza do ryuu: KNOWN_KEYS, keyring_helper.get_all_keys e strings mortas
   de traducao (ryuu/luagen).

Dockerfile: pip --timeout/--retries (wheel do cryptography estourava rede).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2JmFTpher2KiUuvNr4Yqx
O Forgejo autentica contra o AD e ocasionalmente devolve 401 (latência de
auth) — o mesmo sintoma do git push que falha e passa no retry. O curl do
stage Prepare assets nao tinha retry, entao um unico 401 derrubava o build
(#239 e #240 falharam assim, com o stage Test ja verde). Adiciona
--retry 5 --retry-delay 4 --retry-all-errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2JmFTpher2KiUuvNr4Yqx
Jogos de terceiros (Ubisoft/Uplay) publicam no listofdlc appids que nao sao
conteudo: sao entitlements cuja unica funcao e entregar uma CD key externa
(appinfo com legacykeylinkedexternally / legacykeyproofofpurchase).

Injetar posse desses appids no .lua quebra o jogo: antes de abrir, o Steam
busca a CD key no servidor, o servidor nega (nao ha licenca real) e o launch
morre com

    GameAction [AppID 460930, ActionID 2] : LaunchApp failed with
    GettingLegacyKey with "15"       (15 = k_EResultAccessDenied)

O jogo nem chega a iniciar e desmarcar a DLC na UI do Steam NAO resolve --
quem dispara a busca da chave e a POSSE injetada, nao o checkbox.

Caso real: Ghost Recon Wildlands (460930). Dos 42 appids do listofdlc, 24 sao
so chave (594560 "WW Uplay Activation", 601290-601303 pre-order, e os gemeos
"Ubisoft Connect" dos pacotes cosmeticos: 4784390/4784410/4784430/4784450).
Cada pacote tem DOIS appids -- 4784380 Cold Eye Pack (conteudo) e 4784390
Cold Eye Pack Ubisoft Connect (so chave) -- e o gerador injetava os dois.

- app_has_legacy_cd_key(): consulta api.steamcmd.net e retorna None quando
  nao da para determinar.
- filter_legacy_key_app_ids(): aplicado em downloader._finalize (pega todas
  as fontes) e no import manual de .lua. Appid indeterminado e MANTIDO -- so
  sai o que foi confirmado -- e os dois casos aparecem no log.
- addtoken do appid descartado tambem sai (usa o cache, sem rede).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126Y7XBCmByPnatoDJiiBat
O gerador emitia setManifestid para TODOS os depots, inclusive os do jogo
base de quem comprou o jogo. O pin congela o depot no manifest que a fonte
tinha no dia da ativacao: no PC do Maicon o Wildlands ficou com
460932/460934/460935 presos em manifests de 29/07 enquanto a build publica
era de 19/08 12:50 (a Ubisoft atualizou justamente esses 3 depots).

Para conteudo NAO possuido o pin continua obrigatorio -- sem ele o Steam
resolve o manifest mais novo pelo PICS, nao consegue baixa-lo e a instalacao
quebra. So muda para o que a conta licencia de verdade.

- steam_licenses.py (novo): le appcache/packageinfo.vdf (magic 0x06565528) e
  devolve os appids que a conta licencia. E a unica fonte OFFLINE confiavel de
  "isto foi comprado". Cache por mtime; erro de parse/arquivo ausente devolve
  set() vazio = "nao sei".
- downloader.get_depot_owner_map(): {depot: appid dono} pelo appinfo (depot com
  dlcappid pertence a DLC, o resto ao jogo).
- generate_lua_for_opensteamtool(unpinned_depots=...): omite o setManifestid
  desses depots e deixa um comentario no .lua explicando.
- steam_emulator._depots_do_usuario() cruza os dois e loga [POSSE] no resultado
  da ativacao. Qualquer falha => set() vazio => fixa tudo (comportamento antigo).

Conferido com o packageinfo.vdf real do PC do Maicon: dos 30 depots do 460930,
28 sao do jogo comprado (sem pin) e 2 sao das DLCs que ele nao possui --
573831 Narco Road e 573832 Fallen Ghosts -- que seguem fixadas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126Y7XBCmByPnatoDJiiBat
O POST /releases mandava so tag_name. Sem target_commitish o Forgejo cria a
tag no branch DEFAULT (main), parado em 9d5b8ed desde 11/06 -- entao toda
release de v1.0.16.237 a v1.0.18.244 ficou apontando para codigo de junho
enquanto o binario vinha da maicon. Checkout da tag nao reproduzia o build.

O changelog sofria do mesmo: PREV_TAG caia nessas tags de main, o range virava
"tudo desde 11/06" e cada release relistava os mesmos ~60 commits (dava para
ver o corpo crescendo 62 -> 63 -> 65 -> 66 -> 67 linhas). Ainda havia um
off-by-one: 'head -2 | tail -1' pegava a PENULTIMA tag, porque a tag atual
ainda nao existe no workspace quando o script roda.

- target_commitish = GIT_COMMIT (fallback git rev-parse HEAD) no payload.
- PREV_TAG passa a ser a tag de maior versao que seja ANCESTRAL do build,
  descartando tag pendurada em outro branch.
- Teto de 30 linhas no changelog + "... e mais N commits", porque enquanto as
  tags velhas nao sairem do repo a ancestral de maior versao ainda e a
  v1.0.10.15. A partir da segunda build isso se resolve sozinho.
- Sem tag ancestral nenhuma, cai em "Ultimos commits" (git log -10) em vez de
  omitir a secao inteira.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y3VEfACnoSsqPRSL6VVoBb
Jogo destravado por depot key nao tem entitlement na conta, e o Steam Cloud
e liberado do lado da Valve: o cliente tenta sincronizar, o servidor recusa e
o jogador leva "nao foi possivel sincronizar" toda partida. Nao ha ajuste no
cliente nem hook na DLL que mude isso -- a decisao e server-side.

Este commit poe um cofre nosso no lugar, generico para qualquer app_id.

Servidor (license_server):
- routes/saves.py: upload multipart em chunks (hash do stream, nunca carrega o
  zip em memoria), download por versao, listagem, delete e uso por licenca
- models: save_slots + save_versions; retencao das N ultimas versoes com poda
  do arquivo no disco, nao so do registro
- upload identico ao ultimo nao cria versao nova (dedup por sha256)
- deploy.sh: volume /srv/steam-saves; recria o container tambem quando a
  imagem nao mudou mas o volume esta faltando, senao o save cairia no fs efemero
- config: DB_URL e STATIC_DIR sobrepoem, para o servico rodar fora do container

Cliente (frontend_python):
- appinfo_reader.py: parser do appcache/appinfo.vdf, incluindo a tabela de
  strings da v29 (a lib vdf nao entende o envelope nem os indices u32)
- save_paths.py: descoberta em 3 niveis -- secao ufs do appinfo (exata),
  heuristica por nome de pasta (palpite, marcado como tal na UI) e override
  manual; resolve raizes UFS, rootoverrides e filtro de plataforma
- save_sync.py: zip DETERMINISTICO (data fixa + ordem estavel) para o hash
  depender so do conteudo; snapshot local obrigatorio antes de restaurar;
  protecao contra path traversal no restore
- save_watcher.py: backup automatico a cada 5 min por MTIME dos saves, nao por
  processo do jogo -- funciona para jogo fora da Steam e emulador, sem psutil
- steam_cloud.py: desliga cloudenabled do app no localconfig.vdf (mata a caixa
  de erro); recusa gravar com o Steam aberto, que reescreveria o arquivo
- ui/saves_tab.py: aba Saves com status, envio, restauracao e historico

Testes: 27 novos (parser v29/v28, resolucao de raizes, determinismo do zip,
isolamento entre licencas, cota, retencao). CI passa a rodar license_server/tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015iMCPYa2dwZ4aDyPQEW4Sv
A build #246 quebrou em ModuleNotFoundError: ldap3 -- a lista de pacotes do
stage Test era escrita a mao e ficou incompleta (app/routes/auth.py importa
ldap_auth, que importa ldap3, mesmo com LDAP_ENABLED=false).

Listar pacote a pacote deriva a cada dependencia nova do servidor. Instalar
com -r license_server/requirements.txt fecha o buraco na origem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015iMCPYa2dwZ4aDyPQEW4Sv
This pull request has changes conflicting with the target branch.
  • frontend_python/main.py
  • frontend_python/updater.py
  • version.txt
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin maicon:maicon
git switch maicon

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff maicon
git switch maicon
git rebase main
git switch main
git merge --ff-only maicon
git switch maicon
git rebase main
git switch main
git merge --no-ff maicon
git switch main
git merge --squash maicon
git switch main
git merge --ff-only maicon
git switch main
git merge maicon
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Maicon/steam-activator!1
No description provided.