PHP: parse_str_packet

OLD , PHP

May 27, 2004

https://github.com/soywiz/phprobot

parse_str_packet is a function that I have createed specifically for the ragnarok's bot I'm doing. It depends on other simple stream string functions that I created to unpack numbers, strings and simple types.

parse_str_packet receive two parameters: &$dy $fmt. The first parameter $d in addition to the rest of functions to unpack is a reference to a string variable that contains the packet. In PHP a reference is equivalent to an alias and it is needed to access variables whose name is unknown inside functions. It is pretty useful (and faster) to access for example to variables found in… $ejemplo['valor1'][0]['valor2'], it requires less process; you make a reference and that's it.

The other parameter is a string that contains the format of the packets and here it is the interesting part.

I have a file containing “the definition” of the packets that I'm already parsing with functions. And I will make, that instead of calling the functions, it will use that format that calling to the function it generates an array with the associated values already parsed.

For example, the first packet of all the received ones is the 0069 packet.

In the file I have a line like this:

0069 a[login_id;account_id;login_id2;last_login;account_sex;servers]llll-z[24]w-bx[rest][a[host;port;name;users;main;newn]rlnf[ip]wz[20]www]

It is pretty hard to understand, but this allows makes the code much more clear, without needing 20 lines of code to parse the code.

It works like this:

It iterates the string looking for characters that do things. For example: * a[list separated by ;] a allows to define the name of the keys of the parameters that are going to be defined (in order). * l extracts a DWORD (32 bits) from the string in local order (unless you define it otherwise) * w is like l but extracts a WORD (16 bits) * b is like w and l but extracts a BYTE (8 bits) * - removes immediately the previously parsed parameter. For example, a parameter that it is not required, can be extracted. Or a parameter kept due to compatibility reasons but not used anymore. * z[X] extracts a stringZ in the following X caracters. You can use the “rest” parameter to extract the remaining of the string. * s[X] does the same as z[X] but it keeps the whole string including the NULL (0) characters * r inverts the order of extraction of the packet to LOCAL, using the INTERNET order (big endian). (used by IPs) * n uses the LOCAL packet order * f[function] applies a filter to the last specified parameter (calls to a php function using as parameter the last used packet). IP is defined as synonymous of long2ip.

Now, the powerful part :) :

  • x[repeat][expresion] extracts REPEAT times a expression “EXPRESION” and introduces the result of that parameter as an internal array. REPEAT allows to use a number, “rest” to execute it until the end of the string or to use a previously defined parameter. For example, if we want to repeat the number defined two packets before, it is used as REPEAT: p:2

Aaaaand that's all. For now I was able to define all the packets, but if it doesn't allows me to define some packets I guess I will have to touch it a bit. And I have included it in the PHPWiz system.

The objective of all this is that the reception functions do the stuff instead of also doing the parsing. It is slower, but has a lot of advantages (previously quoted ;) ) and some more like, changing a single file, making small changes on the protocol without changing the code.

In any case, and since it is done already it is super cool comparing to other bots that have a huge SWITCH where they define the size of the packets and extracts everything there. Thanks to the PHP power, I simply define a function like:

recv_serv_0073

and at runtime PHP detects eh function and associates it to the packet (by only defining it [no need to write it in any other place]). In addition I use an array with all the sizes of the packets and that extracts the packet using a gneric class. It obtains the ID of the packet, it checks the list… obtains teh size, and if the size is variable it extracts the following two btyes, and extracts a string with the packet and the ID, and then the function is called.

I have also noticed that that way of creating packets is a bit confusing, and makes that if you don't know exactly the packet size, you can lose information and you cannot recover the stream (different from the WoW's protocol) that indicates always t he size of the packet, so even if you don't know it, you can ignore it and continue with the next packet. I'm complaining? No. Gravity ensures that way that it is a bit harder to make a client ;)

And that's all more or less. I have noticed that message that I placed before was wrong. I initially though that it was the encoding but I have now noticed that it was not the case.

UPDATE: Yes, it was the encoding… but from the form where I wrote the post :P after that I have updated the encoding, I have checked which one was required:

I have checked this: http://www.intellidimension.com/default.rsp?topic=/pages/rdfgateway/reference/script/response_charset.rsp

I have changed the encoding to iso-8859-1, I have noticed that windows-1252 would work too but the windows word… well… :P ISO sounds better ;) Furthermore ISO, we have standards for something :D


parse_str_packet es una función que he hecho específicamente para el bot del ragnarok que estoy haciendo. Depende de otras funciones de stream de cadenas sencillitas que creé para desempaquetar números, cadenas y tipos sencillos.

parse_str_packet recibe dos parámetros: &$d y $fmt. El primer parámetro $d al igual que en el resto de funciones para desempaquetar es una referencia a una variable de tipo cadena que contiene el paquete. En php una referencia es equivalente a un alias y se necesita para acceder a variables cuyo nombre desconoces en las funciones. Y es muy útil (y mas rápido) para acceder por ejemplo a variables que se encuentran en… $ejemplo['valor1'][0]['valor2'] , requiere menos proceso haces una referencia y ya está.

El otro parámetro es otra cadena que contiene el formato de los paquetes y aquí está lo interesante.

Tengo un fichero que contiene “la definición” de los paquetes que ya estoy parseando con funciones. Y haré que en vez de llamar a las funciones se utilice ese formato que llamando a la función genera un array con valores asociativos y ya parseados.

Por ejemplo, el primer paquete de todos que se recibe es el 0069

Yo en el fichero tengo una línea así.

0069 a[login_id;account_id;login_id2;last_login;account_sex;servers]llll-z[24]w-bx[rest][a[host;port;name;users;main;newn]rlnf[ip]wz[20]www]

Es bastante dificil de comprender, pero ésto permite que el código quede mucho mas claro, sin necesidad de 20 líneas para parsear el código ni nada por el estilo.

El funcionamiento es el siguiente:

se va recorriendo la cadena en busca de caracteres que “hacen cosas” por ejemplo: * a[lista separada por ;]a sirve para definir el nombre de las claves de los parámetros que se van a definir (por orden). * l extrae un paquete DWORD (32 bytes) de la cadena en el orden local (al menos que se defina lo contrario) * w es como l pero un WORD (16 bytes) * b es como w y l pero un BYTE (8 bits) * - elimina el parámetro parseado anteriormente. Por ejemplo un parámetro que no se necesita se puede extraer. O simplemente que se mantiene por compatibilidad pero no se requiere. * z[X] extrae un stringZ en los prox X caracteres. Se puede utilizar el parámetro “rest” para que extraiga el resto de la cadena. * s[X] lo mismo que z[X] pero se queda con toda la cadena en vez de solo con los caracteres antes de un NULL (0) * r invierte el orden de extracción de paquetes al de LOCAL, utiliza el de INTERNET. (utilizado para IPs) * n utiliza el orden de paquetes LOCAL * f[funcion] aplica un filtro al último parámetro indicado (llama a una función de php utilizando como parámetro el último paquete). Se define ‘ip' como sinónimo de long2ip.

Ahora viene lo mas potente :)

  • x[repeat][expresion] extrae REPEAT veces una expresión “EXPRESION” y introduce el resultado en ese parámetro como un array interno. REPEAT permite utilizar un número, “rest” para hacerlo hasta que se acabe la cadena o utilizando un parámetro anteriormente definido. Por ejemplo si quieremos que se repita el número de veces que se haya definido 2 paquetes antes se utiliza como REPEAT: p:2

Bueeeeeno y esto es todo. Por ahora he podido definir todos los paquetes, si alguno no puedo supongo que retocaría un poco esta función (que la he incluido en el sistema PHPWiz).

El objetivo de esto es que las funciones de recepción simplemente hagan cosas y que no se dediquen a parsear. Es mas lento, pero tiene muchas ventajas (anteriormente citadas ;) ) y otras como poder, cambiando solamente un fichero, hacer cambios pequeños en el protocolo sin cambiar el código.

En cualquier caso tal y como está hecho ya mola muxo a diferencia de otros bots que tienen un gran SWITCH donde definen el tamaño de los paquetes y ahí extraen todo. Gracias a la potencia de php, simplemente defino una función, por ejemplo:

recv_serv_0073

y en runtime PHP me detecta la función y la asocia al paquete (sin mas que definirla [no hay que ponerla en ningún otro sitio]). Utilizo además un array con todos los tamaños de los paquetes y eso extrae el paquete utilizando una clase genérica. Obtiene el ID del paquete se va a la lista… obtiene el tamaño, si es un tamaño variable se sacan los 2 siguientes bytes, y se saca un string con el paquete y el ID. Con eso se llama a la función.

También he visto que esa forma de crear paquetes es un poco criptográfica y hace que si no se conoce exactamente esos tamaños se pierda información y no se pueda recuperar (a diferencia del protocolo del wow) que siempre dice el tamaño del paquete de forma que aunke lo desconozcas puedas pasar de el y seguir con otro. ¿Quejas? NO. Gravity así se asegura de que es un pokito mas dificil hacer un cliente ;)

Bueno eso es todo mas o menos. He visto que el mensaje de antes se ha puesto mal, inicialmente he pensado que era el encoding pero ya he visto ke no.

ACTUALIZACIÓN: Sí, era el encoding… pero del formulario donde he puesto el post :P luego he actualuzado el Encoding, he estado mirando cual era el que necesitaba:

Y he visto un sitio: http://www.intellidimension.com/default.rsp?topic=/pages/rdfgateway/reference/script/response_charset.rsp

He cambiado el encoding a iso-8859-1 he visto que también servía el windows-1252 pero la palabra windows… como ke… :P ISO suena mejor ;) ad+ ISO para algo están los estándares :D