[Resolvido]Jogo MMORPG EMU

Olá,

estou estudando sockets e já tenho um código aqui. Consigo enviar e receber string’s mas aí começa a dúvida… quero fazer um emulador para algum jogo RPG. O jogo que escolhi não envia pacotes criptografados já tirei essa dúvida com um developer de outra linguagem. Utilizo o wireshark para capturar os pacotes:

0000   00 1f 33 76 45 c8 0c 60 76 06 2f 8e 08 00 45 00  ..3vE..`v./...E.
0010   00 83 3d 06 00 00 80 11 6c 07 c0 a8 00 02 4e 8a  ..=.....l.....N.
0020   82 28 db 4d af 37 00 6f 03 cb 64 31 3a 61 64 32  .(.M.7.o..d1:ad2
0030   3a 69 64 32 30 3a c0 e2 f8 1c 21 14 fd c4 a4 df  :id20:....!.....
0040   90 9a be 5f f2 31 72 93 ec 7b 36 3a 74 61 72 67  ..._.1r..{6:targ
0050   65 74 32 30 3a c0 ef a6 ca 4f 58 ea f7 11 3e 57  et20:....OX...>W
0060   de b6 95 97 01 da ff f1 7c 65 31 3a 71 39 3a 66  ........|e1:q9:f
0070   69 6e 64 5f 6e 6f 64 65 31 3a 74 34 3a 2d 1b 00  ind_node1:t4:-..
0080   00 31 3a 76 34 3a 55 54 40 76 31 3a 79 31 3a 71  .1:v4:UT@v1:y1:q
0090   65                                               e

Eu quero entender o que tenho que pegar desse pacote capturado para enviar os bytes :
os.write(0x12);
O código aqui manda esse byte 0x12 mas eu quero entender como pego esses códigos da captura acima? Porque olhei alguns emuladores feitos em java e eles usam o write do pacote de socket’s. Pedi ajuda e eles não quiseram explicar… o que preciso saber é apenas isso… pegar a resposta de um cliente de jogo e fazer o tratamento… para isso uso o Wireshark que está todo configurado… mas o que quero entender é isso… já mandei também MP’s para um cara aqui do fórum e ele ainda não me respondeu e também já procurei em vários lugares no Google… Espero que alguém realmente me ajude:)

Isso é um levantamento que estou fazendo nada a ver com faculdade, pois já terminei :slight_smile:

Obrigado

Você sabe como o protocolo do jogo funciona? Você deve usar um DataInputStream para ler do Socket.

Ele não usa protocolo algum diferenciado. Apenas usa o TCP mesmo… Eu já consigo isso, ainda agora consegui capturar o que o cliente me enviou… No caso ele enviou uns bytes eu capturei, e confirmei que era mesmo a conta que ue havia inserido no jogo. Pedi para imprimir usando ou println… agora quero entender como usar aqueles códigos bytes 0x12 essas coisas que eu quero entender e como enviá-los em java… Então são duas dúvidas, como identificar esses bytes usando o wireshark ou outra ferramenta que alguém saiba utilizar. E como enviá-los corretamente… Tentei agora com uma forma mas não deu jeito… caso queiram, posso postar o código… mas não sei se vai ajudar em muita coisa…

Obrigado pela resposta e espero que me ajude a solucionar isso… estou sem dormir ainda hUEAHUAE :slight_smile:

Eu estava perguntando justamente do protocolo de aplicação. Se roda na internet obviamente usa TCP ou UDP.
Toda aplicação usa um protocolo, que roda sobre o TCP, nem que seja um proprietário.
Se você não consegue qual é, será realmente difícil entender esses bytes.

Que jogo é?

No java, para imprimir aquela sequencia, basta-la ler o que vier do Socket com um InputStream e imprimir byte a byte, em hexa.

Cara, estou testando com o jogo Ragnarok… mas quero fazer para Grand Chase… bom, o que o wireshark aponta que é o protocolo TCP mesmo. Não sei se tem como mascarar o protocolo… Como faço para imprimir de byte a byte o que recebo? Abaixo a parte do código que captura o que vêm do cliente.

public void run() {
        try {
            // cria um DataInput para receber os Streams do cliente.
            DataInputStream din = new DataInputStream(socket.getInputStream());
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            BufferedReader entrada = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            
            // ficar recebendo sempre
            while (true) {

                System.out.println("Entrada: " + entrada.readLine());
                byte mensagem = din.readByte();
                log.info("din mensagem: " + mensagem);
                log.info(bis.read());

                // ... ler a proxima mensagem
                String message = din.readUTF();
                log.info("Recebendo a mensagem " + message + ". De: " + socket.getInetAddress());

Essa é a parte da classe que extend uma Thread. Com ela imprime isso:

04/07/2010 20:01:05 com.gcjproject.loginserver.LoginServer <init>
INFO: Iniciando o servidor...
04/07/2010 20:01:05 com.gcjproject.loginserver.LoginServer listen
INFO: Aguardando troca de informações na porta: 6900
04/07/2010 20:01:18 com.gcjproject.loginserver.LoginServer listen
INFO: Conexao aceita de: Socket[addr=/127.0.0.1,port=58442,localport=6900]
Entrada: d     loginGUJ                senhaGUJ

Ou seja, eu consigo capturar o que o cliente envia e com isso eu consigo imprimir isso… Só que não consigo responder ao cliente e nem sei o que responder ao cliente, entende? Não sei qual packet enviar para ele e nem como enviá-lo… Eu enviei uns mas não sei se o cliente realmente recebeu ou como ele interpretou… Aguardo maiores informações… espero realmente me ajude a terminar isso.

Obrigado

EDIT:
Bom, peguei isso no meu emulador quando enviei o login e a senha… usando um método que retorna o hex dos bytes recebidos.

0c6076062f8e001f337645c808004500003400004000370619ffbd01ac19c0a800021af4e6f8af1e9c7f96c90c19801216d03e070000020405ac0101040201030306

Bom, eu usei o programa do wireshark também e retornou isso… E eu já vi também o que o servidor envia uma com uns dados parecidos com esses para o Cliente do jogo e este cliente diz que a senha está errada. Ou seja, o pacote padrão de definição que a senha está errada. O meu teste é como enviar essa “stringzona” para o cliente e ele mostrar na tela o erro de senha… só para testes mesmo… Estou testando esses packet’s num servidor private aí. :smiley:

Agradeço novamente.

Rode este programa.

class TesteImprimirBytes {
    public static void main (String[] args) {
        byte[] bytes = new byte[256];
        for (int i = 0; i < bytes.length; ++i) {
            bytes[i] = (byte) i;
        }
        sun.misc.HexDumpEncoder hde = new sun.misc.HexDumpEncoder();
        String str = hde.encode (bytes);
        System.out.println (str);
    }
}

Ele imprime:

0000: 00 01 02 03 04 05 06 07   08 09 0A 0B 0C 0D 0E 0F  ................
0010: 10 11 12 13 14 15 16 17   18 19 1A 1B 1C 1D 1E 1F  ................
0020: 20 21 22 23 24 25 26 27   28 29 2A 2B 2C 2D 2E 2F   !"#$%&'()*+,-./
0030: 30 31 32 33 34 35 36 37   38 39 3A 3B 3C 3D 3E 3F  0123456789:;<=>?
0040: 40 41 42 43 44 45 46 47   48 49 4A 4B 4C 4D 4E 4F  @ABCDEFGHIJKLMNO
0050: 50 51 52 53 54 55 56 57   58 59 5A 5B 5C 5D 5E 5F  PQRSTUVWXYZ[\]^_
0060: 60 61 62 63 64 65 66 67   68 69 6A 6B 6C 6D 6E 6F  `abcdefghijklmno
0070: 70 71 72 73 74 75 76 77   78 79 7A 7B 7C 7D 7E 7F  pqrstuvwxyz.....
0080: 80 81 82 83 84 85 86 87   88 89 8A 8B 8C 8D 8E 8F  ................
0090: 90 91 92 93 94 95 96 97   98 99 9A 9B 9C 9D 9E 9F  ................
00A0: A0 A1 A2 A3 A4 A5 A6 A7   A8 A9 AA AB AC AD AE AF  ................
00B0: B0 B1 B2 B3 B4 B5 B6 B7   B8 B9 BA BB BC BD BE BF  ................
00C0: C0 C1 C2 C3 C4 C5 C6 C7   C8 C9 CA CB CC CD CE CF  ................
00D0: D0 D1 D2 D3 D4 D5 D6 D7   D8 D9 DA DB DC DD DE DF  ................
00E0: E0 E1 E2 E3 E4 E5 E6 E7   E8 E9 EA EB EC ED EE EF  ................
00F0: F0 F1 F2 F3 F4 F5 F6 F7   F8 F9 FA FB FC FD FE FF  ................

Puts amigo, obrigado… já aprendi como fazer o DUMP estava doido atrás disso também. O problema agora mano é como enviar para o cliente entende? Eu quero saber como fazer pra enviar isso em bytes pro cliente. É essa a duvida… :wink:

Obrigado

PS.: se possível dá uma lida no edit que eu fiz no post anterior.

Você não está entendendo.

Toda aplicação em rede cria um protocolo. Leia aqui o que é um protocolo exatamente, se você ainda não sabe:
http://www.guj.com.br/posts/list/136538.java#735860

O protocolo é justamente a especificação que diz o que enviar para o servidor, em que formato e como os bytes estão organizados.

Na internet, utilizamos pilhas de protocolos. Na camada mais baixa, existe o protocolo da placa de rede. Se for uma placa ethernet comum, esse protocolo chama-se CSMA/CD.
Sobre ele, roda o protocolo IP, que liga duas redes entre si. Sobre o protocolo IP, roda o protocolo TCP, que dá garantias de entrega e ordenação dos pacotes de rede. Praticamente todas as aplicações de internet usam esses protocolos, mas existirá ainda outro, sobre o TCP, que é o que o whireshark mostra para você no campo de dados dos pacotes TCP.

Trata-se do protocolo do Grand Chase ou do Rag, que é o que você está tentando descobrir.

Entretanto, esses dois protocolos não são públicos, por isso, o whireshark não os decodifica. Foram criados pelas empresas donas desses jogos e, como você mesmo já constatou, tratam-se de protocolos não orientados a textos. As empresas não irão disponibilizar documentação desses protocolos justamente para você não criar clientes falsos ou tentar criar hacks. Ninguém, em fórum nenhum, poderá te dizer como esses bytes são organizados, a menos que já tenha feito ele mesmo o trabalho de decodificar.

Descobrir quais bytes enviar ou receber para os clientes será uma tarefa árdua, que envolverá dias. Trata-se de fazer a engenharia reversa do protocolo do jogo.

Você deve gravar tráfego de várias sessões de jogo e analisar o que cada byte ali está fazendo. Haverá bytes para indicar o tamanho das mensagens, para indicar quantidades de itens, etc. Por exemplo, um item como uma espada +3 pode ser codificada simplesmente por um código de item, que pode ter 1, 2 ou 4 bytes. Pegar um item pode envolver um tipo de mensagem, atacar outro, etc… Isso varia de jogo para jogo, de protocolo para protocolo. E, como todos os bytes estão misturados, será muitíssimo difícil descobrir o que é o que, sem muita análise. Você deve iniciar um trabalho sistemático, usando o cliente oficial do jogo. Fazer coisas como: pegar um item do chão, gravar os dados com wireshark. Soltar o mesmo item, gravar novamente os dados. Pegar novamente, ver se alguma coisa mudou. Trocar de item, ver o que mudou. E assim por diante, até que você comece a entender que bytes são aqueles, e como foram organizados.

Sem achar a descrição exata do protocolo do jogo na internet, você terá uma tarefa realmente gigante à frente. Antes de tentar fazer isso, eu procuraria na internet se ninguém já fez esse tipo de trabalho, e disponibilizou alguma documentação, programa open source ou plugin do whireshark, com o protocolo já descrito. Caso você ainda assim pretenda seguir em frente, boa sorte. Mas saiba que é uma tarefa que levará meses.

Poxa, o Vini deve ter desanimado o cara agora, hehe.

Mas assim, se não me engano existe um BOT opensource para ragnarok, acho que se chama Open Kore. Talvez o pessoal lá já tenha feito esse trabalho de descobrir qual o padrão do protocolo.

Desanimou nada ^^

Eu já sabia que teria que verificar o hash dos pacotes, entender o header de envio e verificar cada ação juntamente com o pacote que foi tratado. Em momento algum pensei que fosse ser uma tarefa fácil pois mexo com emulador em java de outro jogo mas que não criei… Por isso, que consegui fazer uma parte do meu… Sobre o trabalho que você descreveu eu só não sabia que recebia o nome de protocolo apenas isso… :wink:

É tem o OpenKore mesmo que eles liberaram os pacotes… o problema é… eu quero entender como enviar os pacotes que recebo só isso. Já tentei com DataOutPut enviar os mesmos dados que recebi criptografados… ou seja, leia o post anterior… Como envio aquele código? De restante, é claro que os primeiros bytes irão mudar. Mas de restante a dúvida é essa…

Agradeço de novo a atenção :slight_smile:

Todo socket tem um OutputStream. Nele existe o método write. Basta usar esse método para enviar os bytes que quiser.

Por exemplo, para mandar a sequencia que você descreveu:
00 1f 33 76 45

Você pode simplesmente fazer:

OutputStream os = socket.getOutputStream(); os.write(0x0); os.write(0x1f); os.write(0x33); os.write(0x76); os.write(0x45);

E that’s it. Se você tiver os valores num array, fica ainda mais fácil.

byte array[] = {0x0, (byte)0x1f, (byte)0x33, (byte)0x76, (byte)0x45}; os.write(array);

Como se trata de um jogo em rede, eu recomendo fortemente que você dê uma lida sobre java.nio, e use para isso a classe ByteBuffer. Fica ainda mais simples tratar protocolos proprietários com ela.

Agora, pode ser que você perceba que bytes em sequencia representam os tipos short ou int. Por exemplo, vamos supor que ali o primeiro byte fosse o identificador da mensagem, que 00 significasse “entregar gold”, e que os quatro bytes seguintes representassem o valor dessa transferência, nesse caso, a bagatela de 523.466.309. Um código com o DataOuputStream para isso ficaria:

[code]OuputStream os = socket.getOuputStream();
DataOuputStream dos = new DataOutputStream(os);

dos.write(0x00); //Mensagem: Transferir grana
dos.writeInt(523466309); //Valor, em hexa 1F337645[/code]

Provavelmente é assim que seu programa irá codificar pacotes quando você souber as estruturas exatas que estiver lidando.

Car*******

Nossa!! você leu meus pensamentos estou lendo neste exato momento sobre o NIO. Encontrei no emulador java de outro jogo. Cara essa sua explicação foi super 10 mesmo, você é demais… :slight_smile:

Vou colocar como resolvido… Ou seja, todas as dúvidas que eu tinha foram eliminadas com essa sua última explicação. Era isso que eu queria saber desde o início… Muito Obrigado mesmo!!!

Bom, dá uma lida no post sobre protocolos que linkei ali em cima.
Ele também explicada um pouco sobre isso.

Mas ele é mais genérico, explicando como protocolos são normalmente organizados e como vc divide sua aplicação em camadas.

Se seu trabalho evoluir e tiver interesse em escrever um artigo sobre protocolos de jogos usando como exemplo o Grand Chase, entre em contato. Seria uma ótima contribuição para o Ponto V! :wink:

Ah. Outra coisa. O Grand Chase, Perfect World, Ragnarok e demais jogos da LUG provavelmente foram implementados em C ou C++.

O problema é que o C++ tem os tipos “unsigned” e o java não. Isso pode dar dores de cabeça fortes. Vamos supor que você precise passar o valor 230 para um byte. O byte no java vai de -128 até 127. No C++, um unsigned byte vai de 0 até 255 e, portanto, suporta o 230.

Para guardar esse valor, em java, você teria usado um short:

short valor = 230;

E teria então que fazer um cast para byte.

os.write((byte) valor);

O problema maior está na hora de fazer o contrário, ou seja, ler esse dado. O primeiro bit do byte que você ler será interpretado como sinal, e você acabará lendo 230 como um número negativo, que não fará o menor sentido. Usar o DataInputStream também não ajuda. O método readShort dele lê dois bytes, não 1. Como fazer para o java ler um byte só, e parar de interpretar aquele bit como sinal, e passar a interpreta-lo como bit de dados?

A resposta é fazer isso aqui:

short valor = (short) (0xFF && os.read());

Isso fará a leitura de 1 byte, transformará o bit de sinal em um bit de dados, e gravará o resultado num short (já que o byte no java não suporta mesmo o número 230).

Meio pentelho, não? Como você vai usar nio, e vai lidar com a classe ByteBuffer, já postei aqui no fórum uma classe que faz exatamente esse tipo de trabalho. Chama-se ByteBufferWorker. Dá uma olhada nesse tópico aqui:
http://www.guj.com.br/posts/list/48335.java#253015

Outra vantagem é que ela já possui um método para leitura de strings terminadas com \0, como também é padrão do C++.

Eu sempre achei uma grande falha da Sun não ter esse tipo de suporte nas classes para a leitura de dados da rede. Afinal, o mundo fora da VM trabalha com unsigned e, no mínimo, métodos que façam esse tipo de conversão, como os da classe ByteBufferWorker já deveriam existir.

Outro detalhe que pode te incomodar, é que o servidor do Grand Chase ou do Rag pode ter um padrão de endianess diferente, ou seja, pode ser big endian.

Ou seja, um inteiro com os bytes:
1f 22 76 45

Terá o valor dado pela sequencia little endian:
45 76 22 1f

O ByteBuffer trata isso automaticamente, desde que você informe a ele que está lendo de um local BigEndian. Já os OutputStreams não. Entretanto, é bom saber dessa possibilidade na hora de estudar o protocolo.

No padrão Big Endian, os bytes mais significativos estão no final, ou seja, o número é escrito de trás para a frente byte-a-byte (mas não bit-a-bit).

Se você começar a notar sequencias de 4 bytes terminadas por um valor:
00 00 00 1A

Isso pode indicar um inteiro little endian. O contrário, pode indicar um inteiro big endian:
1A 00 00 00

Na Siemens, havia até mesmo situações que tinhamos os dois tipos de endianess num só pacote. Isso pq nosso protocolo poderia carregar no seu interior mensagens da central. Nosso protocolo era little endian e o da central big endian. Por sorte o bytebuffer permite que você mude sua configuração a cada leitura.

Hummm Interessante… bom Vini eu encontrei isso aí quando eu resolvi abrir um jar chamado MMOCore. E tinha bastante coisa sobre packet’s e observei essa possibilidade do endian. Mas é bom que você pode compartilhar com bastante conhecimento. Eu também consegui um início de um emulador MMO feito em java para o jogo Flyff. Bem simples mesmo e o developer parou mas já tem umas coisas para se basear. Para tomar por base, estou estudando 2 emuladores que foram feitos em java: L2java e JavaFlyff(sendo que este último desandou mas eu tenho os códigos). O caso do Flyff segundo developer os pacotes são puros, ou seja, sem criptografia. Talvez eu até pegue esse jogo para fazer e monte do meu jeito… Qualquer coisa, se quiser os códigos do Flyff para dar uma olhada é só falar, lembrando o jogo foi desenvolvido pela empresa Aeonsoft.

Só para lembrar tenho muito a aprender… mas estou me esforçando e não quero que esse projeto caia no esquecimento… tenho vontade de aprender e por pra frente mesmo. Mas é difícil sem ter algum material para orientar… e assim que estivermos Ok aceitarei seu convite com certeza!! Eu mesmo leio algumas coisas de lá :slight_smile: Como o uso do ScriptEngine porque vou precisar também dele.

Obrigado pelas dicas são de GRANDE ajuda :slight_smile: