[GUIA] Strings en goldsrc (STRING, ALLOC_STRING, MAKE_STRING)
#1
En este Tutorial les explicare como son manipulados los Strings en goldsrc, a partir de las premisas que describiré.

Antes de empezar, se indica que para entender ciertos conceptos hay que familiarizarse primero con el proceso de compilacion de códigos en alto nivel a bajo nivel (C/C++ -> machine code) y materia de punteros en C/C++.

Todos aquellos quienes han visto codigos en SDKs han notado esta particular forma de manipular strings, pero suelen confundir porque uno no capta cuando utilizar MAKE_STRING y ALLOC_STRING por ejemplo. Todo esto sirve al momento de programar módulos también.

Estan definidos de la siguiente forma, no es necesario que lo entiendan a la primera.

Código PHP:
#define STRING(offset) reinterpret_cast<const char *>(gpGlobals->pStringBase + (uintp)offset)
#define MAKE_STRING(str) (reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(STRING(0)))
#define ALLOC_STRING (*g_engfuncs.pfnAllocString) 

Les daré una breve introduccion de cada una de estas 3 funciones.

MAKE_STRING lo que hace principalmente es obtener la direccion de memoria de un String que esta dentro del programa.

Código PHP:
    if (m_pActiveItem && !pev->viewmodel)
    {
        switch (
m_pActiveItem->m_iId)
        {
            case 
WEAPON_AWPpev->viewmodel MAKE_STRING("models/v_awp.mdl"); break;
            case 
WEAPON_G3SG1pev->viewmodel MAKE_STRING("models/v_g3sg1.mdl"); break;
            case 
WEAPON_SCOUTpev->viewmodel MAKE_STRING("models/v_scout.mdl"); break;
            case 
WEAPON_SG550pev->viewmodel MAKE_STRING("models/v_sg550.mdl"); break;
        }
    } 

Código PHP:
    pev->classname MAKE_STRING("grenade"); 

Código PHP:
CBaseEntity *CBaseEntity::Create(char *szName, const Vector &vecOrigin, const Vector &vecAnglesedict_t *pentOwner)
{
    
edict_t *pent CREATE_NAMED_ENTITY(MAKE_STRING(szName));
    
// ...


Código PHP:
    switch (m_iType)
    {
        case 
44pEntity CBaseEntity::Create("item_battery"pev->originpev->angles); break;
        case 
42pEntity CBaseEntity::Create("item_antidote"pev->originpev->angles); break;
        case 
43pEntity CBaseEntity::Create("item_security"pev->originpev->angles); break;
        case 
45pEntity CBaseEntity::Create("item_suit"pev->originpev->angles); break;
    } 

Código PHP:
    CBaseEntity *pExplosion CBaseEntity::Create("env_explosion"centeranglespOwner); 

La particularidad de estos Strings, es que estan puestos en bruto.

¿Como funciona esto? Cuando yo compilo un codigo cualquiera, con un String escrito directamente en el código mencionado, este al momento de ser compilado y transformado a un lenguaje de bajo nivel para ser ejecutado por un sistema operativo, se convierte en un trozo de memoria estatico dentro del codigo que siempre esta en la misma posición de memoria, ya que así quedo al momento de ser compilado. Usted puede abrir un HEX Editor en un programa compilado (un dll, un exe, un so) y podrá percatarse que hay textos muy bonitos puestos a lo largo del programa.

MAKE_STRING hace uso de esa característica, haciendo un casteo inteligente y transformando este String en una dirección de memoria, para que al momento de transportar este dato sea a partir de un puntero, en vez de un tamaño no-definido de bytes que harian mas tedioso el trabajo. Es por eso que, existe el pev_viewmodel2 y pev_weaponmodel2 ya que estos son datos enteros, se mueven con valores discretos, no guardan cadenas de carácteres.

¿Y como se hace para saber que tan "largo" es un string si yo siempre manejo simplemente su dirección en memoria? Siempre hay que acordarse que un String, aquí y en la quebrada del ají son "cadenas de caracteres" que siempre SIEMPRE va a terminar con un 0 al final (0, '\0' o EOS), donde se ve involucrado la funcion strlen de C/C++; su principal función es iterar caracter-por-caracter hasta encontrar un 0

Código PHP:
size_t strlen(const char *s) {
    
size_t i;
    for (
0s[i] != '\0'i++) ; // magia
    
return i;


STRING hace lo inverso a MAKE_STRING. A partir de una direccion, este ubica el String en memoria convirtiendolo a char*. Es la manera de desempaquetar el String obtenido a partir de 1 sola direccion de memoria. De esta forma, se transportan strings con facilidad entre el Engine y el Gamedll.

Tienen infinidades de ejemplos de STRING en el SDK donde estan asociados directamente a "pevs" que guardan Strings, que ahora deberían entender con lo que les expliqué sobre MAKE_STRING.

Ahora, deglosemos el código.

Código PHP:
#define STRING(offset) reinterpret_cast<const char *>(gpGlobals->pStringBase + (uintp)offset)
#define MAKE_STRING(str) (reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(STRING(0))) 

reinterpret_cast<uintp>(str) lo que hace es obtener la direccion del String puesto. El casteo que se realiza es de char* a unsigned int* siendo el tipo de dato apropiado para guardar una direccion de memoria.

Es bueno tocar este tema. Para entrar más en el asunto, un offset lo pueden relacionar con un delta, una distancia, entre 2 puntos específicos. Es por eso que cuando ustedes buscan un "offset" para (por ejemplo) editar el team (m_iTeam = 114) utilizando set_pdata_int, en lo muy profundo, obtienen la direccion de la clase (CBasePlayer) y le suman 114 (x 4), lo castean a int, y voilá, tienen el team. Si, no es tan simple como eso, podría ser otro tutorial inclusive.

Lo que ocurre con MAKE_STRING y STRING es algo parecido. En MAKE_STRING se ve que hay una diferencia entre 2 valores

reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(STRING(0))
reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(reinterpret_cast<const char *>(gpGlobals->pStringBase + (uintp)0))
reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(reinterpret_cast<const char *>(gpGlobals->pStringBase))

uintp - uintp = resta de punteros

¿Porque se calculan los Strings utilizando como punto de partida gpGlobals->pStringBase?

gpGlobals->pStringBase esta definido en el Engine. Lo que guarda esto, es mas ni menos que una tabla de strings que el engine guarda. O de una manera mas bonita, Allocated Strings Table.

ALLOC_STRING es la forma de guardar Strings que no estan en memoria estática, mas bien estan guardados en el Stack.

Si yo hago por ejemplo lo siguiente:

Código PHP:
uintp generateString(void)
{
    
char str[64];
    
sprintf(str"Hola, este es mi string de %u bytes\n", (sizeof str)/(sizeof char)); // igual utiliza menos espacio xdxd
    
return MAKE_STRING(str);


Sería invalido. ¿Porque? Bueno, la memoria de lo que guarda str desaparecerá al momento que la funcion termine su ejecucion, y la direccion de memoria que cederá MAKE_STRING será invalida, apuntará a cualquier otra cosa en medio de la ejecucion del programa y la creación de más memoria en el stack. (stack es la memoria de variables creadas en medio de la ejecucion)

Es por eso que fue creado ALLOC_STRING, lo que hace esta funcion es guardar el string en una tabla en el engine (a la cual se puede acceder mediante el pointer gpGlobals->pStringBase) donde uno puede guardar Strings que son creados en medio de la ejecucion del programa, cosa que difiere del uso de MAKE_STRING el cual es orientado a Strings que estan dentro del código del programa.

Un ejemplo:

Código PHP:
void CC4::KeyValue(KeyValueData *pkvd)
{
    if (
FStrEq(pkvd->szKeyName"detonatedelay"))
    {
        
pev->speed atof(pkvd->szValue);
        
pkvd->fHandled TRUE;
    }
    else if (
FStrEq(pkvd->szKeyName"detonatetarget"))
    {
        
pev->noise1 ALLOC_STRING(pkvd->szValue); // *
        
pkvd->fHandled TRUE;
    }
    else if (
FStrEq(pkvd->szKeyName"defusetarget"))
    {
        
pev->target ALLOC_STRING(pkvd->szValue); // *
        
pkvd->fHandled TRUE;
    }
    else
        
CBaseEntity::KeyValue(pkvd);


pkvd es parte del Stack, ya que es memoria de los parámetros de las funciones. Al terminar la funcion, esa memoria desaparece, es por eso que aquí utilizamos ALLOC_STRING para hacer una copia de este String, guardarlo en la tabla, y quedarnos con su direccion.



Se llega a la conclusion que ALLOC_STRING y MAKE_STRING sacan una distancia desde el mismo punto, cosa que STRING convierta de puntero a string ya sea a Strings estaticos guardados por MAKE_STRING o Strings guardados en la tabla mencionada mediante ALLOC_STRING.

Esta tabla da una abstraccion a lo mencionado. Las direcciones de memorias utilizadas obviamente son falsas.

Cita:0x0000 [START ENGINE BLOCK]
0x1234 [MEMDIR gpGlobals->pStringBase]
0x1250 [MEMDIR "trivago"]
0x1260 [MEMDIR "pajarussel"]
0xAAAA [END ENGINE BLOCK]

0xAAAB [START GAMEDLL BLOCK]
0xBBB0 [MEMDIR "env_explosion"]
0xBBC0 [MEMDIR "item_battery"]
0xBBB0 [MEMDIR "grenade"]
0xBBB0 [MEMDIR "models/v_sg550.mdl"]
0xFFFF [END GAMEDLL BLOCK]

Viendo esto y tomando en cuenta el ejemplo sin terminar anteriormente

Código PHP:
uintp ptr MAKE_STRING("models/v_sg550.mdl"

Código PHP:
#define STRING(offset) reinterpret_cast<const char *>(gpGlobals->pStringBase + (uintp)offset)
#define MAKE_STRING(str) (reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(STRING(0))) 

ptr vendría a ser:

(reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(reinterpret_cast<const char *>(gpGlobals->pStringBase)))
(0xBBB0 - 0x1234)
0xA97C

Este OFFSET (que es un valor numero que solo menciona una distancia) si lo paso por STRING:

reinterpret_cast<const char *>(gpGlobals->pStringBase + (uintp)offset)
reinterpret_cast<const char *>(gpGlobals->pStringBase + 0xA97C)
reinterpret_cast<const char *>(0x1234 + 0xA97C)
reinterpret_cast<const char *>(0xBBB0)
"models/v_sg550.mdl"

(Aunque realmente es 'm' ya que apunta a la primera casilla en memoria de la direccion específica)

La misma aritmetica se aplica con los Strings falsos que inventé ("trivago" y "pajarussel").

Estoy atento a sus comentarios, y a los superhumanos que me corregirán la ortografía.
Responder
#2
Entendi gran parte. Al final se va poniendo cada vez mas jodido (Para mi) y medio que me costo. Un poco de google y creo que lo voy a entender del todo.

EDIT: Esta muy bueno.
Responder
#3
Bonita guía.
No conocía la función de STRING. Si no entendí mal, se puede acceder a STRING desde AMXX con EngFunc_SzFromIndex, es correcto?
Responder
#4
(26/02/2017, 10:15 PM)Mario AR. escribió: Bonita guía.
No conocía la función de STRING. Si no entendí mal, se puede acceder a STRING desde AMXX con EngFunc_SzFromIndex, es correcto?

SzFromIndex es el equivalente de STRING efectivamente
Responder
#5
Nice, excelente guía

(26/02/2017, 08:02 PM)meTaLiCroSS escribió: La misma aritmetica se aplica con los Strings falsos que inventé ("trivago" y "pajarussel").

RoflmaoRoflmao

Responder
#6
Quizás comente algo tarde, pero no tenia acceso a internet.

Muy buena guía Metal, y si, efectivamente algunos del conjunto que lee el/los SKDs quizás no entiende esto (claramente yo), así que nuevamente te doy las gracias por tomarte la molestia de publicar esto, ahora si podre entender mejor esto.

Nothingdohere
Responder
#7
(03/03/2017, 12:52 AM)Chamo. escribió: Quizás comente algo tarde, pero no tenia acceso a internet.

Muy buena guía Metal, y si, efectivamente algunos del conjunto que lee el/los SKDs quizás no entiende esto (claramente yo), así que nuevamente te doy las gracias por tomarte la molestia de publicar esto, ahora si podre entender mejor esto.

Nothingdohere

Espero te haya servido, gracias a ti por leer
Responder
#8
Que buenos estos tipos de aportes metalicross, excelente como los demás.
Responder


Salto de foro:


Usuarios navegando en este tema: 1 invitado(s)