HWB hero wars bot

hero-wars-bot · v0.3.1

Nenhum clique. Só protocolo.

O Hero Wars conversa com o servidor por JSON-RPC assinado. Este bot fala esse protocolo direto do navegador, então cada ação do jogo vira uma chamada de função em vez de um botão procurado na tela.

validado na conta real 01/08/2026 · servidor wb · torre do andar 1 ao 50 · 26 batalhas · 2min19s

POST heroes-wb.nextersglobal.com/api/
X-Request-Id 42
X-Auth-Token 1f2e3d4c5b6a7988
X-Auth-Session-Id 9c7b41ee0d2a
corpo {"calls":[{"name":"towerStartBattle",…}]}
X-Env-Unique-Session-Id b3f0a1

md5 da concatenação

42:1f2e3d4c5b6a7988:9c7b41ee0d2a:{"calls":[{"name":"towerStartBattle","args":{"heroes":[26,14,18,38,1],"pet":6006},"ident":"body"}]}:LIBRARY-VERSION=1UNIQUE-SESSION-ID=b3f0a1

X-Auth-Signature 9ee1a833e1d10759555053e8bbe63df5

200 {"results":[{"ident":"body","result":{ … }}]}

Dados de sessão fictícios. O hash acima é MD5 de verdade, calculado no seu navegador.

src/core/net.js

Por que não é mais um pixel‑bot

O bot anterior tirava print da tela, procurava botões com template matching, lia texto com OCR e movia o mouse. Isso amarra tudo à resolução, ao idioma, à velocidade da animação e à sua tela ficar livre. Um atraso de carregamento faz o clique cair no lugar errado.

pixel-bot, aposentado este bot
achar o botão na tela net.call('towerStartBattle', args)
esperar a animação da luta simulação local, instantânea
quebra se mudar a resolução independe de tela
precisa do navegador visível roda com a aba em segundo plano
clique errado gasta esmeralda a camada de segurança bloqueia gasto
src/core/signature.js

A assinatura

Todo request leva um X-Auth-Signature. Descobrir o formato exato foi o centro do projeto: os três últimos campos vão colados, sem separador nenhum entre eles.

X-Auth-Signature = md5(
    X-Request-Id      + ':' +
    X-Auth-Token      + ':' +
    X-Auth-Session-Id + ':' +
    <corpo do POST>  + ':' +
    'LIBRARY-VERSION=1'
    'UNIQUE-SESSION-ID='   + X-Env-Unique-Session-Id
  [ 'UNIQUE-SESSION-UUID=' + X-Env-Unique-Session-Uuid ]
)

MD5 escrito à mão

A API assina com MD5 e o Web Crypto do navegador não oferece MD5. A implementação é própria e vem testada contra o node:crypto em 200 tamanhos de entrada e em UTF‑8.

Hooks passivos

Os ganchos em XMLHttpRequest não tocam no tráfego: as requisições do jogo passam intactas e o bot só lê os cabeçalhos para aprender a sessão. Reescrever seria mais elegante, mas um bug nosso na assinatura quebraria o jogo inteiro.

src/core/game.js

O motor de batalha do próprio jogo

O bundle do jogo é Haxe compilado e registra suas classes como propriedades de nome pontuado, tipo game.battle.controller.instant.BattleInstantPlay. Instalando um setter em Object.prototype para esses nomes antes do bundle carregar, o bot captura as referências e roda o mesmo motor determinístico do cliente. A luta acontece localmente e o resultado já vai pronto no *EndBattle.

Os métodos vêm ofuscados. A resolução é por nome real (o mapa __properties__) e por formato, por exemplo "a estática de DataStorage que é instância de BattleConfigStorage", nunca por índice posicional, que quebraria a cada patch.

Isto só funciona se o script rodar em document-start. Injetar depois captura a sessão e a rede continua funcionando, mas o motor não.

src/core/safety.js

Quatro portas, nesta ordem

O bot roda sozinho de madrugada, sem ninguém olhando. Então a camada de segurança fecha por padrão e a ordem das checagens é parte do desenho.

  1. Denylist absoluta

    Qualquer nome que case com buy, purchase, shop, refresh, revive, instant ou summon é recusado, mesmo que alguém o coloque na allowlist depois.

  2. Allowlist

    O que não está listado explicitamente não é enviado.

  3. Varredura dos argumentos

    Recusa qualquer starmoney ou diamond, free: false e paid: true, em qualquer profundidade do objeto.

  4. Dry-run, ligado por padrão

    Monta a chamada, assina e registra no log, mas não envia nada que altere a conta enquanto você não desligar.

A denylist roda antes da allowlist de propósito, e o preço disso é errar para o lado seguro: ela já barrou coisa legítima duas vezes, o {starmoney: 0} que significa justamente "não gaste" e o gacha_refill, que casa com gacha mas é a recarga gratuita.

src/tasks/

O que ele faz sozinho

Cada tarefa é um módulo que se registra no runner, nasce desligada e declara as chamadas que precisa. Só a torre e a masmorra dependem do motor de batalha.

tarefa o que faz motor
questsmissões diárias e dos Special Eventsnão
mailcoleta o correio em lotes de 50, preservando energianão
guildGiftsfichas de amizade para a guilda inteira, uma vez por dianão
dailyBonusbônus diárionão
expeditionAirship: recolhe as concluídas e envia novasnão
outlandraid dos 3 chefes e o baú gratuito de cadanão
soulAtriumrecarga gratuita, a cada 12hnão
raidMissionsraid da campanha, com teto porque gasta energianão
arenaPvP de um time, pode custar rankingnão
grandArenarecolhe as moedas acumuladas e joga o PvP de 3 timesnão
towersobe a torre: pula, luta e abre baússim
dungeonmasmorra da guilda, experimentalsim

A segunda passada

Ao fim de cada ciclo o runner refaz missões e correio. Torre, arena, Outland e expedições completam missões e geram cartas, e sem essa segunda passada as recompensas ficariam paradas até o ciclo seguinte.

Por que as arenas nascem desligadas

Diferente da torre, ali não há simulação possível: arenaAttack resolve a luta inteira no servidor e devolve o resultado, sem um StartBattle para inspecionar antes. Toda tentativa é aposta, e perder derruba o ranking, que na Grande Arena é justamente o que define a renda por hora.

01/08/2026

O que já rodou de verdade

Nada aqui é teste sintético. Isto é o log de uma conta real, servidor wb, nível 130.

  • assinatura aceita pelo servidor: userGetInfo devolveu os dados da conta
  • ciclo de coleta completo: 7 de 7 missões diárias, 339 de 340 cartas (uma preservada por conter energia), bônus diário e 3 de 3 expedições
  • torre inteira do andar 1 ao 50: 26 batalhas vencidas e 15 baús abertos em 2min19s, com a simulação local decidindo cada luta
  • falta a masmorra de ponta a ponta, ainda experimental e desligada por padrão

Não use a formação salva da torre

A primeira versão preferia teamGetAll.tower achando que respeitava a escolha do jogador. Na conta real essa formação estava abandonada, 19k de poder contra 228k dos cinco melhores, e a simulação dava derrota já no andar 1. Hoje o critério é poder.

O andar 50 é o fim

Pedir towerNextFloor lá responde NotFound, Floor #51. Descobrir isso custou uma execução inteira travada no topo.

README

Aviso

Automatizar o Hero Wars viola os termos de uso da Nexters e pode resultar em banimento da conta. É um projeto pessoal, de engenharia de protocolo, rodado contra a conta do próprio autor.

O repositório é privado e nada é distribuído aqui: esta página descreve o projeto, não entrega o bot. Nenhum código de terceiros faz parte dele.