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

Open
Maicon wants to merge 56 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>
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.