Brazil
CodeFactory ®
Desenvolvendo DLLs para o PMS - Módulo 1
Vou falar aqui o básico sobre programação de DLLs para serem usadas com o Plugin Management System, para os que ainda não conhecem o sistema, vocês poderão ter mais informações neste tópico: [Somente usuários registrados podem vem os links. ].
Antes de mais nada, vocês têm que saber que a base disponível para ser usada no PMS é o C++, e, para efeito de estudo, vou usar o Visual C++ Express 2008 neste tutorial, que pode ser encontrado, gratuitamente, aqui: [Somente usuários registrados podem vem os links. ].
Devo lembrar a vocês que, DLLs feitas pelo Visual Studio 2008 precisam do .net FrameWork 3.5 instalado para serem rodadas. Para os mais familiarizados com a linguagem C++, é recomendado usar o VC6, que não precisa de .net FrameWork para funcionamento.
Só para familiarizá-los um pouco com o Visual Studio:
O arquivo do projeto no Visual Studio é de formato .dsp, então quando eu falar para abrir o projeto, trata-se do arquivo Custom.dsp (Usando a base que será disponibilizada aqui).
Quando eu falar de compilar o arquivo e testá-lo, você pode compilar usando a tecla F7 do seu teclado, ou, no menu de opções superior do programa, clique em Build e logo em seguida em Build Solution.
Para alterar o nome do arquivo de saída, vá em Project -> "Nome do Projeto" Properties (é a última opção, no nosso caso será Custom Properties), em seguida, abra o sub-menu proveniente da linha COnfiguration Properties, procure o sub-menu Linker e clique nele, a primeira linha de configuração (Output File) tem um parâmetro assim: $(OutDir)\$(ProjectName).dll, altere esta linha para que a compilação tenha o nome desejado e vá parar no diretório desejado, se quiser, por exemplo, que a DLL se chame fulano.dll, e o diretório de saída seja direto no C:, altere a linha para C:\fulano.dll .
Nas configurações normais, a DLL tratada neste tópico terá nome de Custom.dll e será encontrada no diretório Debug, dentro da pasta do projeto!
Agora que você talvez esteja mais familiarizado com nosso compilador (VC++ 2008), baixe este arquivo:
[Somente usuários registrados podem vem os links. ] ( Re-Upload By Mr.TwoHam )
Nele estão contidas as funções que serão chamadas pela DLL principal para incluir no GameServer nosso código, e, principalmente, as funções que IMPORTAMOS da DLL principal, que são nossa base para usarmos as funções do GameServer.
Quero que carreguem o projeto (Custom.dsp) no Visual Studio, e na barra lateral, onde você tem uma lista de arquivos, vocês procurem o arquivo Main.cpp, nele estão as 3 funções que são exportadas da DLL base para serem usadas pela DLL Principal, a DLL principal pegará essas funções e injetará o código contido nelas dentro dos processos do GameServer, com isso, sem nenhuma necessidade de hookar nada, você tem uma DLL funcionando no GameServer.
Uma consideração muito importante, é o fato de que a base da DLL que tem aqui disponível não sabe como importar funções "do nada", existe um arquivo que possuí as informações de como devem ser importadas as funções, o arquivo se chama BrCFPMS.lib e se encontra dentro da pasta Debug, ele NÃO deve ser apagado, ou a compilação não será mais executada pelo programa. E já com isso, vou puchar um gancho aqui e falar, ao atualizar a DLL principal, o arquivo BrCFPMS.lib que você tem pode deixar de ser funcional com a DLL principal em uso, já que esse arquivo é um fruto dessa DLL principal, então, para atualizar sua source para DLLs principais mais novas, basta substituir o arquivo BrCFPMS.lib antigo da pasta Debug por um novo e pronto!
Hoje, para começar com um pouco de código em C++ para vocês testarem o quão fácil e eficiente é programar em PMS, vamos voltar ao arquivo Main.cpp e adicionar uns códigos na função ProtocolCoreEx, o que a função ProtocolCoreEx faz está definido no arquivo Main.cpp, espero que dêm uma lida! ;D
Código:
extern "C" _declspec(dllexport) void ProtocolCoreEx(BYTE protoNum, LPBYTE aRecv, DWORD aLen, C_OBJECT * gObj)
{
}
Rápida explicação, esta função serve para receber dados do client, são os dados que vêm do client para o GameServer que passam por aí, ela possuí 4 argumentos:
(BYTE protoNum, LPBYTE aRecv, DWORD aLen, C_OBJECT * gObj)
protoNum: ID do packet recebido, a ID serve como um guia para sabermos que tipo de informação o Packet traz, no próximo módulo de aprendizado de programação em PMS eu vou abordar os tipos mais comuns de Index (ID) de Packet.
aRecv: O packet em si, essa variável possuí todos os BYTES recebidos pelo GameServer do client.
aLen: tamanho, em bytes, do packet recebido.
gObj: Informações do player, usando essa variável você poderá acessar os dados dos players.
os typedefs (BYTE, LPBYTE, DWORD e C_OBJECT *) eu vou abordar mais pra frente, é conhecimento um pouco mais avançado.
Hoje não vamos tratar de organização, é só o básico de sabedoria sobre o PMS, para que vocês possam ter base de como adicionar funções no sistema, então, vou colocar uma função simples direto no arquivo Main.cpp e dentro da função ProtocolCore, mais a frente iremos apagar essa função e colocá-la num lugar mais adequado!
O ID de packet que trabalharemos hoje é o 0x00, para quem não é acostumado, 0x antes de um número indica que ele é em hexadecimal, para calcular o seu valor em decimal, usem a calculadora científica do Windows (ou a calculadora de programador que tem no Windows 7). O ID de packet 0x00 para MuOnline, representa os packets de Chat, ou seja, toda mensagem que você envia através do client, passa pelas funções do packet 0x00.
Precisamos interceptar esse packet, para isso, vamos adicionar dentro do nosso ProtocolCoreEx o seguinte:
Código:
extern "C" _declspec(dllexport) void ProtocolCoreEx(BYTE protoNum, LPBYTE aRecv, DWORD aLen, C_OBJECT * gObj)
{
if(protoNum == 0x00)
{
}
}
Para quem não sabe, o if é um operador de comparação, ele é usado para comparar valores, o == nesse caso é o tipo de comparação, para compararmos se um valor é igual a outro, usa-se ==, e não apenas =, o = é um operador de assimilação, mas isso não importa no momento. Os comparadores são:
Código:
== -> igual a.
> -> maior que.
< -> menor que.
>= -> maior ou igual a.
<= -> menor ou igual a.
Quando acomparação dentro do if é verdadeira, o sistema executa o bloco de código abaixo (blocos de código são separados uns dos outros usando {}, lembre-se que, sempre que você abre uma {, você deve fecha-la}.
Nossa comparação, (protoNum (ID do Packet) é igual a 0x00?) Se sim, continue no bloco de código abaixo, e agora vamos incluir nosso bloco de código.
Código:
extern "C" _declspec(dllexport) void ProtocolCoreEx(BYTE protoNum, LPBYTE aRecv, DWORD aLen, C_OBJECT * gObj)
{
if(protoNum == 0x00)
{
if(memcmp(&aRecv[13],"/info",5) == 0)
{
GameServer.MsgSendAnnounce(gObj,"Seu Nome é: %s",gObj->GetName());
GameServer.MsgSendAnnounce(gObj,"Você está no Level: %d", gObj->GetLevel());
}
}
}
Agora vamos entender esse novo bloco de código, vamos começar pelo segundo if:
if(memcmp(&aRecv[13],"/info",strlen("/info")) == 0)
O if, como vocês já sabem, compara dois números, agora, memcmp, isso não me parece um número, bom, do ponto de vista humano realmente não é, mas analisando o código, temos que memcmp é uma função de retorno, ou seja, ela "retorna" um número quando é executada, e é esse número que é comparado.
A função memcmp é uma função que compara grandes quantidades de memória, lembre-se que o if pode comparar apenas UM número, e não uma sequencia númerica por exemplo, aí entra a função memcmp, ela toma consigo três argumentos, o primeiro deles é o ponto inicial de memória que ela comparará, o segundo argumento é com o que deve ser comparado e o terceiro argumento é o tamanho da comparação em bytes.
O retorno da função memcmp, a efeito de estudo, é 0 se os dados forem iguais, e diferente de 0 se os dados forem diferentes.
Com relação aos argumentos usados, o primeiro argumento, &aRecv[13], se trata de um endereço de memória, como já falei, o aRecv possuí todos os dados do packet recebido, quando colocamos aRecv[x], o x é o byte que desejamos acessar, e & significa "o endereço de", portanto, &aRecv[13], é equivalente a "o endereço do décimo terceiro byte de aRecv", isso é um pouco complexo para iniciantes por enquanto, mas não se apeguem a isso ainda, tudo a seu tempo, esse tipo de variável se chama ponteiro, vou abordar elas mais pra frente! ;D
Vocês devem estar se perguntando de onde surgiu então este "13", o 13 é o BYTE do packet onde se inicia a mensagem que o client enviou, do 0 ao 12, tem header, size e um monte de porcaria xD, a partir do 13 vem os dados da mensagem, por isso nossa comparação tem que começar no 13!
O segundo argumento dessa função é o "a que comparar" digamos assim, você quer comparar o &aRecv[13] com /info (Sem as aspas mesmo, aspas em C++ é indicação de string, ou seja, texto!).
E o terceiro argumento é o tamanho da comparação, / - 1, i - 2, n - 3, f - 4, o - 5, ou seja, 5 BYTES, lembrando que cada letra em C++ no modelo ASCII ocupa 1 BYTE de memória (talvez eu aborde isso mais pra frente).
Então temos que, se esse memcmp for igual a 0, executará o próximo bloco de código, que é o primeiro bloco onde vocês verão o efeito do uso do PMS, como a vida fica mais fácil! xDDD
GameServer é uma classe, classes em C++ guardam funções, as funções dessa classe não precisam ser feitas, elas já existem, estão dentro da DLL principal, você só está as acessando usando a DLL Custom que você está programando, quando você digita GameServer. deverá aparecer uma lista de funções dessa classe, se não aparecer, sem problemas, basta você ir no arquivo GSClass.h que você terá todas as funções existentes nessa classe, para fazermos nosso comando /info, precisamos apenas de uma função que envia mensagens para client, vou usar como exemplo a função MsgSendAnnounce, que envia mensagens como as mensagens de GM, mas apenas para 1 jogador, essa função tem 2 argumentos, sendo que o primeiro é o alvo (C_OBJECT * Target), como eu já disse para vocês, as informações dos players no PMS são trazidas no ProtocolCore através do argumento gObj, que também é do tipo C_OBJECT*, portanto, o primeiro argumento, é gObj.
O Segundo argumento é o texto a ser mandado para o client, podemos digitar qualquer coisa que possua até 140 linhas, vocês devem ter visto que eu coloquei no final de um dos textos "%d" e no outro "%s". Basta você saber que quando tem um %"algumacoisa" em algum texto, ele não aparecerá, no seu lugar, aparecerá algum tipo de dado especial que você definiu, os tipos de "%" mais comuns são:
Código:
%d -> d vem de data, imprime dados numéricos.
%c -> c vem de char, que significa letra, imprime uma letra no lguar do %c.
%x -> x vem do 0"x", lá dos numeros hexadecimais, imprime os numeros em hexa.
%s -> s vem de string, imprime um texto.
E por fim, quando a função possuí na sua lista de argumentos um "...", significa que essa função é capaz de juntar argumentos colocados posteriormente aos das funções ào texto, substituindo os "%" por dados, nesse caso temos na primeira linha um %s e gObj->GetName() (falarei das funções da gObj jájá), essa função retorna o endereço de uma string, ou seja, um pequeno texto, que no caso é o nome do personagem, e ele será impresso no lugar desse %s.
Já na segunda linha temos um %d e gObj->GetLevel(), essa função retorna um valor numérico, que neste caso, é o próprio level do personagem, que será impresso no lugar do %d.
E com isso acaba nosso código, basta compilar (como já disse mais acima, apertando F7).
Para usar sua DLL agora, é muito simples, nada de hooks nem coisas dificeis, basta você ter a base do GameServer com a dll principal (BrCFPMS.dll) que podem ser baixados atualizados neste tópico: [Somente usuários registrados podem vem os links. ]
E usar essa pasta GameServer junto com algum MuServer 0.97D que podem ser encontrados na net, sua DLL será automaticamente carregada e você pode testar o comando no jogo:
Screenshot:
[Somente usuários registrados podem vem os links. ]
(O texto está com esses "?" porque meu client não pega acentos! xDDD)
[Somente usuários registrados podem vem os links. ]
PS. Comentem, lancem seus plugins, fiquem a vontade, só procurem não tirar proveito financeiro disso.
Mr.MariN.