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
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
200 {"results":[{"ident":"body","result":{ … }}]}
Dados de sessão fictícios. O hash acima é MD5 de verdade, calculado no seu navegador.
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 |
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.
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.
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.
-
Denylist absoluta
Qualquer nome que case com
buy,purchase,shop,refresh,revive,instantousummoné recusado, mesmo que alguém o coloque na allowlist depois. -
Allowlist
O que não está listado explicitamente não é enviado.
-
Varredura dos argumentos
Recusa qualquer
starmoneyoudiamond,free: falseepaid: true, em qualquer profundidade do objeto. -
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.
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 |
|---|---|---|
quests | missões diárias e dos Special Events | não |
mail | coleta o correio em lotes de 50, preservando energia | não |
guildGifts | fichas de amizade para a guilda inteira, uma vez por dia | não |
dailyBonus | bônus diário | não |
expedition | Airship: recolhe as concluídas e envia novas | não |
outland | raid dos 3 chefes e o baú gratuito de cada | não |
soulAtrium | recarga gratuita, a cada 12h | não |
raidMissions | raid da campanha, com teto porque gasta energia | não |
arena | PvP de um time, pode custar ranking | não |
grandArena | recolhe as moedas acumuladas e joga o PvP de 3 times | não |
tower | sobe a torre: pula, luta e abre baús | sim |
dungeon | masmorra da guilda, experimental | sim |
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.
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:
userGetInfodevolveu 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.
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.