Welcome, guest. You can be a Login или register
Author
Topic title
Берсеркер
Offline
Суровый челябинский программист
2247 posts
Karma 200
Здравствуйте.
Всем известна проблема с savegame в Quake2:
ошибки "Savegame from an older version" и "ReadLevel: function pointers have moved".
Коротко о проблеме:
В savegame пишутся все entity. Некоторые поля entity являются ссылками на функции (F_FUNCTION). Сохраняются смещения функций относительно InitGame.
Если даже немного поменять код Game.dll, то адреса функций сдвинутся, или даже InitGame может поменять адрес. Предыдущие savegames аннулируются.
Предлагаю вам моё решение данной проблемы: вместо смещений адресов функций нужно сохранять индекс функции. Таким образом savegames становятся намного устойчивее к смене game.dll.
Немного кода:

// используется для аннулирования savegames при серьезных изменениях в game.dll
#define SAVEGAME_VERSION 1

int FunctionToIndex(void *func)
{
int i = 0;

i++;
if (func == (void*)misc_viper_bomb_prethink)
return i;
i++;
if (func == (void*)M_FliesOff)
return i;
...
gi.error("FunctionToIndex: unknown function\n");
return 0; // shut up compiler :(
}

void *IndexToFunction(int index)
{
int i = 0;

i++;
if (index == i)
return (void*)misc_viper_bomb_prethink;

i++;
if (index == i)
return (void*)M_FliesOff;
...
gi.error("IndexToFunction: unknown index\n");
return NULL; // shut up compiler :(
}
Машина несла меня через неведомые районы Галактики сквозь пространство математической реальности быстрее скорости света. (C) Фред Саберхаген.
Берсеркер
Offline
Суровый челябинский программист
2247 posts
Karma 200
Вы должны зарегистрировать в FunctionToIndex и IndexToFunction все функции, которые используются в вашей game.dll.
Это все функции, записываемые в поля F_FUNCTION
field_t fields[] = {
...
{"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN},
{"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN},
...
}

все функции из mmove_t:
mmove_t jorg_move_pain3 = {JORG_FRAME_pain301, JORG_FRAME_pain325, jorg_frames_pain3, jorg_run};
(в примере: jorg_run)

все функции из mframe_t:
mframe_t jorg_frames_attack2 []=
{
...
ai_charge, 0, jorgBFG,
}
(в примере: ai_charge и jorgBFG)

все функции из gitem_t:
gitem_t itemlist[] =
{
...
/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16)*/
{
"item_power_shield",
Pickup_PowerArmor,
Use_PowerArmor,
Drop_PowerArmor,
NULL,
"misc/ar3_pkup.wav",
"models/items/armor/shield/tris.md2", EF_ROTATE,
NULL,
/* icon */ "i_powershield",
/* pickup */ "Power Shield",
/* width */ 0,
60,
NULL,
IT_ARMOR,
0,
NULL,
0,
/* precache */ "misc/power2.wav misc/power1.wav"
},
...
}
(в примере: Pickup_PowerArmor, Use_PowerArmor, Drop_PowerArmor)
Машина несла меня через неведомые районы Галактики сквозь пространство математической реальности быстрее скорости света. (C) Фред Саберхаген.
Берсеркер
Offline
Суровый челябинский программист
2247 posts
Karma 200
Затем отключаем некоторые проверки и вносим прочие изменения:
[code]void WriteGame (char *filename, bool autosave)
{
FILE *f;
int i;
//// char str[16];

if (!autosave)
SaveClientData ();

f = fopen (filename, "wb");
if (!f)
gi.error ("Couldn't open %s", filename);

/// Berserker: not need more...
/*
Com_Memset (str, 0, sizeof(str));
strcpy (str, __DATE__);
fwrite (str, sizeof(str), 1, f);
*/
game.autosaved = autosave;
fwrite (&game, sizeof(game), 1, f);
game.autosaved = false;

for (i=0 ; i<game.maxclients ; i++)
WriteClient (f, &game.clients[i]);

fclose (f);
}[/code]
[code]void ReadGame (char *filename)
{
FILE *f;
int i;
//// char str[16];

gi.FreeTags (TAG_GAME);

f = fopen (filename, "rb");
if (!f)
gi.error ("Couldn't open %s", filename);

/// Berserker: not need more
/*
fread (str, sizeof(str), 1, f);
if (strcmp (str, __DATE__))
{
fclose (f);
gi.error ("Savegame from an older version.\n");
}
*/
g_edicts = (edict_s *) gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
globals.edicts = g_edicts;

fread (&game, sizeof(game), 1, f);
game.clients = (gclient_s *) gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
for (i=0 ; i<game.maxclients ; i++)
ReadClient (f, &game.clients[i]);

fclose (f);
}[/code]
[code]void WriteLevel (char *filename)
{
int i;
edict_t *ent;
FILE *f;
//// void *base;

f = fopen (filename, "wb");
if (!f)
gi.error ("Couldn't open %s", filename);

// write out edict size for checking
i = sizeof(edict_t);
fwrite (&i, sizeof(i), 1, f);

#if 0 /// Berserker: not need more
// write out a function pointer for checking
base = (void *)InitGame;
fwrite (&base, sizeof(base), 1, f);
#else
i = SAVEGAME_VERSION;
fwrite (&i, sizeof(i), 1, f);
#endif

// write out level_locals_t
WriteLevelLocals (f);

// write out all the entities
for (i=0 ; i<globals.num_edicts ; i++)
{
ent = &g_edicts[i];
if (!ent->inuse)
continue;
fwrite (&i, sizeof(i), 1, f);
WriteEdict (f, ent);
}
i = -1;
fwrite (&i, sizeof(i), 1, f);

fclose (f);
}[/code]
void ReadLevel (char *filename)
{
int entnum;
FILE *f;
int i;
//// void *base;
edict_t *ent;

f = fopen (filename, "rb");
if (!f)
gi.error ("Couldn't open %s\n", filename);

// free any dynamic memory allocated by loading the level
// base state
gi.FreeTags (TAG_LEVEL);

// wipe all the entities
Com_Memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0]));
globals.num_edicts = maxclients_cvar->value+1;

// check edict size
fread (&i, sizeof(i), 1, f);
if (i != sizeof(edict_t))
{
fclose (f);
gi.error ("ReadLevel: mismatched edict size\n");
}

#if 0 /// Berserker: not need more
// check function pointer base address
fread (&base, sizeof(base), 1, f);
#ifdef _WIN32
if (base != (void *)InitGame)
{
fclose (f);
gi.error ("ReadLevel: function pointers have moved");
}
#else
gi.dprintf("Function offsets %d\n", ((byte *)base) - ((byte *)InitGame));
#endif
#else
fread (&i, sizeof(i), 1, f);
if (i != SAVEGAME_VERSION) /// Berserker: savegame version check
{
fclose (f);
gi.error ("ReadLevel: Savegame from a different version (%i, must be %i)\n", i, SAVEGAME_VERSION);
}
#endif

// load the level locals
ReadLevelLocals (f);
...
}

void WriteField1 (FILE *f, field_t *field, byte *base)
{
...
//relative to code segment
case F_FUNCTION:
#if 0
if (*(byte **)p == NULL)
index = 0;
else
index = *(byte **)p - ((byte *)InitGame);
*(int *)p = index;
#else
if (*(byte **)p == NULL)
*(int *)p = 0;
else
*(int *)p = FunctionToIndex(*(byte **)p); /// Berserker: translate function to index
#endif
break;
...
}

void ReadField (FILE *f, field_t *field, byte *base)
{
...
//relative to code segment
case F_FUNCTION:
index = *(int *)p;
#if 0
if ( index == 0 )
*(byte **)p = NULL;
else
*(byte **)p = ((byte *)InitGame) + index;
#else
if ( index == 0 )
*(byte **)p = NULL;
else
*(byte **)p = (byte*)IndexToFunction(index); /// Berserker: translate index to function
#endif
break;
...
}

Самое муторное тут: заполнение FunctionToIndex и IndexToFunction, я потратил на это свыше часа, собрав около 500 функций, но дело это стоящее.
И последнее: неплохо было бы работать со своей папкой SAVE, например у меня: SAVE.q2b :)

Надеюсь, идея ясна?
Проверил так: переместил код одного из монстров в другое место, скомпилил game.dll, предыдущая savegame загрузилась замечательно ;)

С Рождеством!!! :)
Машина несла меня через неведомые районы Галактики сквозь пространство математической реальности быстрее скорости света. (C) Фред Саберхаген.
Берсеркер
Offline
Суровый челябинский программист
2247 posts
Karma 200
это была первая реализация. Сделаю покрасивее, сведу функции в таблицу.
Машина несла меня через неведомые районы Галактики сквозь пространство математической реальности быстрее скорости света. (C) Фред Саберхаген.
Берсеркер
Offline
Суровый челябинский программист
2247 posts
Karma 200
Вот код покрасивее:
[code]void *savefuncs[] = {
actor_attack,
actor_dead,
actor_die,
...
Weapon_Shotgun,
Weapon_SuperShotgun
};

int FunctionToIndex(void *func)
{
int i;
int size = sizeof(savefuncs)/sizeof(void*);
for (i=0; i<size; i++)
if (savefuncs[i] == func)
return i;
gi.error("FunctionToIndex: unknown function\n");
return 0; // shut up compiler :(
}


void *IndexToFunction(int index)
{
int size = sizeof(savefuncs)/sizeof(void*);
if (index < size)
return savefuncs[index];
gi.error("IndexToFunction: unknown index\n");
return NULL; // shut up compiler :(
}[/code]
Машина несла меня через неведомые районы Галактики сквозь пространство математической реальности быстрее скорости света. (C) Фред Саберхаген.
Берсеркер
Offline
Суровый челябинский программист
2247 posts
Karma 200
Не учёл, что подобное шаманство требуется для F_MMOVE (адреса mmove_t)

void WriteField1 (FILE *f, field_t *field, byte *base)
...
//relative to data segment
case F_MMOVE:
#if 0
if (*(byte **)p == NULL)
index = 0;
else
index = *(byte **)p - (byte *)&mmove_reloc;
*(int *)p = index;
#else
if (*(byte **)p == NULL)
*(int *)p = 0;
else
*(int *)p = MmoveToIndex(*(byte **)p); /// Berserker: translate mmove to index
#endif
break;
...



void ReadField (FILE *f, field_t *field, byte *base)
...
//relative to data segment
case F_MMOVE:
index = *(int *)p;
#if 0
if (index == 0)
*(byte **)p = NULL;
else
*(byte **)p = (byte *)&mmove_reloc + index;
#else
if ( index == 0 )
*(byte **)p = NULL;
else
*(byte **)p = (byte*)IndexToMmove(index); /// Berserker: translate index to mmove
#endif
break;
...


[code]void *savemmoves[] = {
&actor_move_attack,
...
&tank_move_walk
};

int MmoveToIndex(void *func)
{
int i;
int size = sizeof(savemmoves)/sizeof(void*);
for (i=0; i<size; i++)
if (savemmoves[i] == func)
return i;
gi.error("MmoveToIndex: unknown mmove\n");
return 0; // shut up compiler :(
}


void *IndexToMmove(int index)
{
int size = sizeof(savemmoves)/sizeof(void*);
if (index < size)
return savemmoves[index];
gi.error("IndexToMmove: unknown index\n");
return NULL; // shut up compiler :(
}[/code]
Машина несла меня через неведомые районы Галактики сквозь пространство математической реальности быстрее скорости света. (C) Фред Саберхаген.