• Bem vindo à nossa comunidade - Quer se juntar ao resto dos nossos membros? Registre-se*Registros aprovados pela adminitração

Adicionando Comandos e Arquivos de configurações (.ini) e Fixes

cruskado

Banido
Olá a todos, hoje vamos dar continuidade ao tutorial. Antes de mais nada, vou explicar o porque não forma postados novos módulos nas semanas anteriores.

Bom como muitos puderam ver, nós da GXSoft, saimos da parceria com a RagezoneBrasil, ou melhor, fomos excluídos. Portanto tivemos um tempo de pensar e repensar o que seria feito ou não, e optamos por desistir da ragezone. Dessa forma caso você queria as atualizações dos módulos o único jeito é vindo até nosso blog e conferindo todo Sábado.

Qualquer dúvida poste no fórum a sua dúvida. (www.globalextreme.com.br)

Então sem mais delongas vamos ao 8° módulo da primeira temporada.

Adicionando Comandos e Arquivos de Configurações.Vamos então abrir nosso projeto Season4.sln, e se você seguiu o tutorial à risca e parou junto com a gente, você deve ter feito como ultima etapa, adicionar a conexão com SQL.

Pois bem, nesta parte vou ensinar como vamos criar alguns comandos básicos, bem como criar os arquivos de configurações destes comandos por exemplo. Queria ressaltar que apartir deste 8° módulo, irei postar o nosso código fonte para vocês.

Vamos neste momento criar uma nova pasta na nossa solution, com o nome de Comandos, e dentro dela adicionaremos um arquivo .cpp com o nome Commands, portanto, Commands.cpp.

Dentro dele, como de praxe, vamos adicionar o include das nossas bibliotecas e também a StdAfx, que fica nossas bibliotecas padrões.

Bom, vamos à uma breve explicação. Os comandos, são enviados via pacotes de dados do client para o servidor, dessa forma, nossos comandos tem que ser colocados no packet certo. Infelizmente não vou poder entrar em detalhes de como descobrir estes pacotes, pois eu não sei direito, e não vou passar informações que eu não tenho 100% de certeza para vocês. Mas você pode pegar esses pacotes nas sources do GameServer 18 que tem por ai na net. De qualquer forma vou postar alguns packets reconhecidos por mim, para que possamos trabalhar bem.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

// 0x00 = DataSend,

// 0x01 = OK,

// 0x02 = Tela de Account invalid,

// 0x03 = Tela de Account already connected,

// 0x04 = Tela quando Server esta cheio,

// 0x05 = Conta Bloqueada,

// 0x06 = Nova Versao Requerida,

// 0x07 = Erro de conexao,

// 0x08 = Conexao falhou após 3 tentativas,

// 0x09 = Sem info de chars,

// 0x0A = Seu tempo de vip terminou,

// 0x0B = Seu tempo de vip terminou,

// 0x0C = Seu tempo de conexao por IP terminou,

// 0x0D = Seu tempo de conexao por IP terminou,

// 0x0E = Erro de Conexao,

// 0x0F = Erro de Conexao,

// 0x10 = Erro de Conexao,

// 0x11 = Tela de "Somente player acima de 15 anos podem jogar,

Vamos então começar a criar nossa estrutura, para que os comandos possam ser funcionais. Existem muitas maneiras de se fazer comandos, vou mostrar uma bem leve e funcional, utilizando struct. Vamos criar um novo arquivo chamado Struct.h, podemos coloca-lo junto ao StdAfx.h, dentro dele vamos adicionar as seguintes instruções:

1

2

3

4

5

6

7

#include "StdAfx.h"

 

struct Plugin

{

 

};

extern Plugin plugin;

Essa será nossa struct principal, vamos jogar todas nossas subStruct dentro dela, para ficar mais organizado. Então vamos criar um simples comando para enviar uma mensagem dentro do jogo. Criamos uma nova struct, (sempre a cima da struct principal) com o nome CMD_MENSAGEM, com algumas variaveis simples que vamos utilizar, que são:
ativo, level, zen, ficando assim:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

#include "StdAfx.h"

 

struct CMD_MENSAGEM

{

    int ativo;

    short level;

    unsigned int zen;

};

 

struct Plugin

{

    CMD_MENSAGEM cmdMensagem;

};

extern Plugin plugin;

Pudemos notar que não colocamos o Extern da nova struct, isso porque a mesma foi chamada dentro da nossa struct principal, ou struct pai, dessa forma não precisando fazer um novo extern, visto que ja está com um pseudo extern da nossa struct pai.

Mas porque colocar a struct dentro de uma struct pai?
Bom como eu disse, é mais para organização e um nível consideravel de ganho de processamento.

Se você não entendeu a parte de colocar a sub struct dentro da struct pai, vou explicar em linhas curtas. O que fizemos foi apenas criar uma variavel com o nome cmdMensagem, do TIPO CMD_MENSAGEM, nada mais é que a função do extern abaixo da struct pai, que é uma variavel plugin do TIPO Plugin. Se esta explicação não foi suficiente para você entender sobre tipagem de variavel, visite o nosso primeiro módulo que expliquei muito bem sobre isso.

Bom, muitos podem estar se perguntando sobre o porque das variaveis terem os tipos colocados, no caso, int, short, unsigned int. Bom como mensionei anteriormente, podemos fazer de varios jeitos, como a informação de ativo, somente poderá ter dois valores consideraveis (ligado ou desligado), poderiamos utilizar uma variavel do tipo BOOL também. Não faz muita diferença, você ganhará um nível de processamento quase imperseptível visto que estas informações são carregadas apenas UMA vez pelo GS. As variaveis unsigned int, servem apenas para deixar entrar valores POSITIVOS. A minha recomendação é sempre usar a mesma tipagem da OBJECTSTRUCT original do GameServer, para isso bara o arquivo objectstruct.h e procure a linha que você gostaria de ver o tipo de variavel, como por exemplo, a variavel Level da objectstruct tem a seguinte instrução: /*<thisrel this+0xaa>*/ /*|0x2|*/ short Level;

Dessa forma criamos nossa struct a variavel level como short também, pois short supre a necessidade de level, visto que não passa do range maximo do tipo short que é:  signed: -32768 até 32767 e unsigned: 0 até 65535 (por padrão as variaveis sempre são signed).

Precisamos chamar nossa struct pai para ser usadas nos arquivos .cpp, dessa forma vamos abrir o arquivo cFunc.cpp e logo após a instrução #include “includes.h” adicionamos a linha: Plugin plugin;

Vamos voltar agora ao nosso arquivo commands.cpp e vamos começar a criar nosso comando:

Primeiramente vamos criar uma função para ler nosso arquivo de configuração para que possa ser manipulado as variaveis que colocamos na nossa substruct CMD_MENSAGEM. Criamos então o seguinte código:

1

2

3

4

void InitConfigs()

{

 

}

dentro dessa função vamos adicionar nossas variaveis, da seguinte maneira:

1

2

3

plugin.cmdMensagem.ativo = GetPrivateProfileInt("Comandos","ComandoAtivo",1,IniCommands);

plugin.cmdMensagem.level = GetPrivateProfileInt("Comandos","LevelParaUsar",6,IniCommands);

plugin.cmdMensagem.zen = GetPrivateProfileInt("Commandos","ZenParaUsar",10000,IniCommands);

A Função GetPrivateProfileInt, possuí os seguintes atributos: (nomedakey, nomedasubkey, valordefault, nomedaini). Antes de criar o arquivo de configuração vocês puderam atentar que a linha IniCommands está com um erro, sublinhada em vermelho, isso significa que não existe a variavel IniCommands, então vamos abrir nosso arquivo Includes.h e inserir logo abaixo da linha #define IniConnect “.\\Connect.ini” a seguinte instrução:

1

#define IniCommands ".\\Comandos.ini"

a linha acima define a variavel IniCommands sendo o valor string precedido.

Pronto a linha já está resolvida, compile a nossa dll e se tudo correu bem ela compilará sem problemas. Claro supondo que vocês não esqueceram de adicionar a nova biblioteca “struct.h” q criamos no arquivo Includes.h.

Para que nossas configurações sejam lidas e feitas, precisamos de duas coisas ainda: a primeira chamar a função que criamos InitConfigs() na nossa aplicação, entao abrimos o arquivo Season4.cpp e logo após a linha MudarProtocolo(); adicionamos a seguinte linha: InitConfigs(); e Claro não esqueça de colocar a chamada da nossa função InitConfigs(); tambem no arquivo Includes.h
pronto, o próximo passo é criar o arquivo de configuração. Lembrem-se do #define que criamos para nossa variavel IniCommands, pois é no local estipulado no nosso define que vamos ter que criar nosso arquivo de configuração. Pois bem, como podemos ver nosso arquivo vai se encontrar na pasta raiz da dll, ou seja, na mesma pasta onde a dll se encontra.

Criamos um novo arquivo .ini e dentro dele a estrutura sempre vai ser a seguinte:

1

2

[nomedakey]

subkey = valor

Portanto nossos 2 arquivos de configurações ficarão da seguinte maneira:

1

2

3

4

5

6

[Comandos]

ComandoAtivo        =   1

LevelParaUsar   =    6

ZenParaUsar     =    1000000

 

arquivo Comandos.ini

1

2

3

4

5

6

7

[Connection]

SQLIP               = (local)

SQLLogin            = sa

SQLPassword     = suasenhadosql

SQLDB               = MuOnline

 

arquivo Connet.ini

Vamos agora iniciar a programação de nosso comando propriamente dito. Abra o arquivo Commands.cpp e após a função InitConfigs vamos criar a função de nosso comando, mas antes disso vou postar algumas funções que serão úteis para nós, mas que já expliquei como procura elas em uma video-aula nos modulos anteriores. Você pode copiar sem problemas, mas caso você faça isso sem ao menos tentar procurar e estudar como busca offsets, infelizmente você será só mais um que copia. Portanto eu recomendo altamente que a cada função que você utilizar, pegue o offset e tente acha-lo, pois você vai se ambientar muito mais com o desenvolvimento fazendo isso.

Funções e offsets para serem inseridos no arquivo Offsets.h

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

#define MIN_PLAYERID 8000

#define MAX_PLAYERID 9000

#define CGAddExperience         0x7A81A8

#define OBJECT_STARTUSERINDEX (MAX_PLAYERID - 1000)

#define gObjSetMonsters ((void(*)(int,int))0x0040616D)

#define gObjAddMonsters ((int(*)(unsigned char))0x004044F3)

#define SendBarMsg ((void(*)(DWORD,LPSTR,DWORD)) 0x004B3370)

#define ChatTargetSendNogs ((void(*)(OBJECTSTRUCT*,char* Text,DWORD PlayerID)) 0x00438F00)

#define CloseClient ((void(*)(DWORD))0x0040788D)

#define EventMonsterItemDrop            (0x00420330)

#define ExternalgEventMonsterItemDrop_Hook  (0x00402AD1)

#define  ExternalSocketItemAdd_Hook      (0x00402E9B)

#define GCServerMsgStringSend ((void(*) (char * Texto,DWORD PlayerID,int Type)) 0x004066B3)

#define DataRecv ((void(*) (BYTE,PBYTE,DWORD,DWORD,...)) 0x004368E0)

#define GCMoneySend ((void(*) (DWORD,int)) 0x004075EA)

#define DataSend ((void(*) (DWORD PlayerID,PBYTE Packet,DWORD Size)) 0x004B3370)

#define DataSend_jmp ((void(*) (DWORD PlayerID,PBYTE Packet,DWORD Size)) 0x004055BF)

#define DataSendAll ((void(*)(PBYTE lpMsg, int iMsgSize)) 0x0040729D)

#define ItemSerialCreateSend ((int(*)(DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD)) 0x00407004)

#define gObjInventoryDeleteItem ((void(*)(int,int)) 0x00402464)

#define gObjTeleport ((void(*) (int,unsigned char,unsigned char,unsigned char)) 0x00403382)

#define CGLevelUpPointAdd ((int(*)(BYTE * Arg1,DWORD PlayerID)) 0x004073DD)

#define gObjMonsterTopHitDamageUser ((DWORD(*)(OBJECTSTRUCT*))0x004074FA)

#define gEventMonsterItemDrop ((int(*) (BYTE*,BYTE*)) 0x00420330)

#define ItemGetNumMake (0x00558A80)

#define GCPkLevelSend ((void(*)(int,unsigned char)) 0x0043C2F0)

#define CGDurationMagicRecv ((void(*)(PBYTE aRecv, int aIndex)) 0x00459A90)

#define GCLevelUpMsgSend ((void(*)(int, unsigned char)) 0x0043BDC0)

#define gObjViewportListProtocolCreate ((void(*) (OBJECTSTRUCT*)) 0x0052F870)

#define gObjCloseSet ((void(*)(DWORD aIndex, DWORD flag)) 0x00401B5E)

#define gObjOffset (*(char**)0x7A5502C)

#define gObjGetIndex ((int(*)(const char*))0x00401EF6)

#define GameMonsterAllCloseAndReload ((void(*)()) 0x0040759F)

#define pShopDataLoad ((void(*)()) 0x00406B6D)

#define gObjCalCharacter ((void(*)(int)) 0x004E8AC0)

#define gObjSealUserSetExp ((void(*)(OBJECTSTRUCT* lpObj, __int64 & Experience, int Flag))0x0060B360) //Last Function from BuffEffect (Outside CBuffManager class)

#define gObjMoveGate            ((int(*)(int aIndex, int GateNumber)) 0x00532530)

#define AddBuff ((void(*)(LPOBJ lpObj,BYTE state, BYTE ViewSkillState,int TargetIndex, int IsSkill, int a6, int Time  )) 0x004051E6)

#define GCInventoryItemDeleteSend2 ((void(*)(int,int,unsigned char)) 0x00403AE9)

#define GCInventoryItemOneSend ((void(*) (int, int)) 0x0043C210)

#define gObjSize 0x2228

#define gObjMaxUsr 0x1000

#define gObjPlayer 0x1F40

#define gObjMapID 0x131

#define gObjPosX 0x12C

#define gObjPosY 0x12E

#define gObjZen 0xD8

#define gObjLupp 0xAC

#define gObjStr 0xDC

#define gObjDex 0xDE

#define gObjVit 0xE0

#define gObjEne 0xE2

#define gObjLogin 0x6C

#define gObjLvl 0xAA

#define gObjNick 0x77

#define gObjClass 0xA6

#define gObjCtl 0x1F4

#define gObjPk 0x125

#define gObjLead 0x100

#define gObjDir 0x130

#define gObjExp 0xB4

#define gObjNextExp 0xB8

#define gObjLife 0xE4

#define gObjMaxLife 0xE8

#define gObjAddLife 0x134

#define gObjShield 0x13C

#define gObjMaxShield 0x140

#define gObjAddShield 0x144

#define gObjInventory 0xE7C

#define gObjNpc 0xA4

#define gObjIP 0x18

#define gObj_isonline 0x4

#define gObjINV 0xCAC

#define ITEMGET(x,y) ((x)*512 + (y))

Voltando ao commands.cpp e ao nosso comando vamos criar o seguinte código:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

void ComandoMensagem(int aIndex)

{

    OBJECTSTRUCT *gObj = (OBJECTSTRUCT*)OBJECT_POINTER(aIndex);

 

    if(plugin.cmdMensagem.ativo != 1)

    {

        GCServerMsgStringSend("Comando Desabilitado",aIndex,1);

        return;

    }

    if(plugin.cmdMensagem.level > gObj->Level)

    {

        GCServerMsgStringSend("Você não possui level para usar",aIndex,1);

        return;

    }

    if(plugin.cmdMensagem.zen > gObj->Money)

    {

        GCServerMsgStringSend("Você não tem ZEN para usar",aIndex,1);

        return;

    }

    GCServerMsgStringSend("Olá Você conseguiu Usar O Comando com sucesso",aIndex,1);

    return;

}

Na função acima a única novidade em relação à tudo já explicado é a linha do ponteiro que chama os objetos da objectstruct, não tem muito segredo, é um simples ponteiro dentro de uma variavel chamada gObj. e para chamar os atributos basta usar gObj precedido de “->” sem aspas.

Vamos agora criar a nossa chamada para nosso comando, que será hookado no protocolo como eu disse anteriormente, vamos criar a seguinte função:

1

2

3

4

5

6

7

8

9

10

/*#---------------Inicio do DataSend (Hook)---------------#*/

 

void ChatDataSend(DWORD aIndex, LPBYTE Protocol)

{

    char StringComandoMensagem[] = "/mensagem";

    if(!memicmp(&Protocol[13],StringComandoMensagem,strlen(StringComandoMensagem)))

    {

        ComandoMensagem(aIndex);

    }

}

Explicação simples: Criamos a função que vamos jogar no protocolo, com os atributos aIndex (codigo do player que executou) e Protocol (pacote responsavel pelo comando)
Dentro da função criamos uma variavel pra string do comando que no caso é “/mensagem” sem aspas e fazemos um if para comparar se o que foi digitado (/mensagem) confere mesmo com o comando criado, e caso positivo ele entra no if e vai para a função do comando que criamos (ComandoMensagem(aIndex);). Lembrando de colocar a chamada da nossa função de hook no arquivo includes.h.

Vamos agora abrir nosso arquivo Protocolo.cpp e localizar o Packet 0x00, dentro dele vamos incluir a linha do nosso hook ficando assim:

1

2

3

4

case 0x00:

        {

            ChatDataSend(aIndex, aRecv);

        }break;

Após isso você criou um novo comando para seu projeto. Vamos agora testar nosso comando? Para isso precisamos colocar alguns fixes padrões para o gameserver funcionar, vamos então para a parte final de nosso módulo de hoje.

Fixes para o GameServerVamos criar uma nova pasta no nosso projeto chamada Fixes e dentro dele o arquivo Fixes.cpp e dentro do arquivo Fixes.cpp vamos criar a função Fixs();

Não esqueça de coloca-la no arquivo Includes.h e não esqueça de chamar nossas bibliotecas no arquivo Fixes.cpp.
Vou postar para vocês alguns fixes deste GameServer, mas antes temos que criar uma nova função no arquivo cFunc.cpp, ao final deste arquivo adicione o seguinte:

1

2

3

4

5

6

7

8

9

10

11

12

/*Seta RETN + 4 NOP no offset desejado.*/

void SetRRetn(DWORD dwOffset)

{

    *(BYTE*)(dwOffset)=0xC3;

    *(BYTE*)(dwOffset+1)=0x90;

    *(BYTE*)(dwOffset+2)=0x90;

    *(BYTE*)(dwOffset+3)=0x90;

    *(BYTE*)(dwOffset+4)=0x90;

}

/*------------------------------------------------------------------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------------------------------------------------------------------*/

Não se esquecendo de colocar a chamada no arquivo Includes, podemos voltar para o arquivo Fixes.cpp e inserimos o seguinte código:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

#include "StdAfx.h"

#include "Includes.h"

 

void Fixes()

{

    //CheckSum

        SetByte(0x00438CD1,0xEB);

        SetByte(0x00438D3A,0xEB);

    //Personal ID

        SetNop(0x0043B9D6,5);

    //Guild ID

        SetNop(0x004504E9,2);

        SetNop(0x00450376,6);

    // Potion Fix

    SetNop(0x0042F650,8);

    SetNop(0x00502243,8);

    SetByte(0x005023D9,0xEB);

    // Destroy Giocp

    SetRRetn(0x00403233);

    // Serial 0x00000000 fix

    SetRRetn(0x004069AB);

    // HACK TOOL DC Fix

    SetRRetn(0x00404584);

        // Socket Serial

    SetByte(0x004369B7,0xEB);

    // Hack reporting FIX

    SetByte(0x0043880E,0x90);

    SetByte(0x0043880F,0x90);

    SetByte(0x00438830,0xE9);

    SetByte(0x00438831,0x74);

    SetByte(0x00438832,0x01);

    SetByte(0x00438833,0x00);

    SetByte(0x00438835,0x90);

    //Option Reload

    SetNop(0x00639618, 16);

    // Protocol Error FIX

    SetNop(0x00458612,27);

    // error-L2 : Index(%d) %x %x %x FIX

    SetNop(0x004B33DE,6);

    // Invalid Socket FIX

    SetByte(0x004B409F,0x90);

    SetByte(0x004B40A0,0x90);

    SetByte(0x004B41CF,0xEB);

    // ggauth.dll Unload

    SetNop(0x0056D6F3,5);

    SetByte(0x0056D708,0xEB);

    // AiElement Error

    SetByte(0x005B031C,0xFF);

    // Illusion Temple enter

    SetByte(0x005D9771,0xEB);

    // Raklion Fix

    SetByte(0x0061018E,0x81);

    SetByte(0x0061018F,0x51);

    SetByte(0x00610190,0x01);

    // Fix Crash Dump File

    SetByte(0x004C7041,0xE9);

    SetByte(0x004C7042,0x2E);

    SetByte(0x004C7043,0x01);

    SetByte(0x004C7044,0x00);

    SetByte(0x004C7046,0x90);

    // GM Attack Fix

    SetByte(0x0050514B,0xEB);

    //-- Packets Per Second

    SetByte(0x004B10FB,0xE9);

    SetByte(0x004B10FC,0x42);

    SetByte(0x004B10FD,0x01);

    SetByte(0x004B10FE,0x00);

    SetByte(0x004B10FF,0x00);

    SetByte(0x004B1100,0x90);

    //ds masterfix

    SetByte(0x00460396,0xEB);

    // Marlon Quest

    SetByte(0x005044B8,0xEB);

    SetByte(0x00504519,0xEB);

    // BC Master Enter FIX

    SetByte(0x0057FDB3,0x33);

    SetByte(0x0057FDB4,0xC0);

    SetByte(0x0057FDB5,0x90);

    SetByte(0x0057FE6D,0x33);

    SetByte(0x0057FE6E,0xC0);

    SetByte(0x0057FE6F,0x90);

}

Denovo insisto, podem copiar sem problemas, mas dessa forma você não estará aprendendo, apenas se enganando e sendo mais um dentre muitos.

Explicação, cada linha dessa é uma alteração no gameserver, e para entender o que cada valor desse faz precisa estudar um pouco de hexadecimal e olly. Por exemplo quando temos SetByte(0xXXXXXX, 0xEB) estamos setando o offset 0xXXXXXX como JMP em asm. Não vem ao caso ensinar isso pois foge do assunto pelomenos deste módulo, quem sabe mais pra frente.

Vamos agora no arquivo Season4.cpp e adicionamos após InitConfigs(); a seguinte linha:
Fixes();

Pronto já podemos abrir nosso servidor e testar nosso novo comando. Compile a dll para isso, e se você seguiu passo-a-passo do módulo não terá problemas.

Supondo que você já tenha feito o HOOK da dll no GameServer (módulo 7), você precisará deste patch de correção para os SHOPS (
Por favor, Entrar ou Registrar para ver o conteúdo das URLs!
) e extraia dentro da pasta DATA do MuServer. Pronto agora vamos ligar os arquivos:

Por favor, Entrar ou Registrar para ver o conteúdo das URLs!


Bom, como podem ver todos os links ligaram PERFEITAMENTE. Vamos agora ate o jogo para testar nosso comando novo:

Por favor, Entrar ou Registrar para ver o conteúdo das URLs!
Por favor, Entrar ou Registrar para ver o conteúdo das URLs!
Por favor, Entrar ou Registrar para ver o conteúdo das URLs!
Por favor, Entrar ou Registrar para ver o conteúdo das URLs!


Bom, por hoje é só. Você pode conferir como criar um comando simples e testar. Nos proximos módulos vou colocando novos comandos para vocês e também novas customizações.

Espero que tenham gostado deste módulo e até a próxima!

OBS: QUAISQUER DÚVIDAS PODEM POSTAR NO FÓRUM,  QUE VOU TENTAR RESPONDER TODAS PARA VOCÊS.

Abraços a todos e bons estudos!

Credito=Cruskado por trazer para xpz um tuto que ajudar muita jente
 
Topo Bottom