|
|
|
|
@@ -123,26 +123,181 @@ void akgl_game_updateFPS()
|
|
|
|
|
game.lastIterTime = curTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* entity name -> pointer map tables
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void akgl_game_save_actorname_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp = (FILE *)userdata;
|
|
|
|
|
akgl_Actor *actor = NULL;
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
|
|
|
|
fwrite((char *)name, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp);
|
|
|
|
|
actor = SDL_GetPointerProperty(props, name, NULL);
|
|
|
|
|
fwrite(&actor, 1, sizeof(akgl_Actor *), fp);
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH_NORETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void akgl_game_save_spritename_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp = (FILE *)userdata;
|
|
|
|
|
akgl_Sprite *sprite = NULL;
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
|
|
|
|
sprite = SDL_GetPointerProperty(props, name, NULL);
|
|
|
|
|
fwrite((char *)name, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp);
|
|
|
|
|
fwrite(&sprite, 1, sizeof(akgl_Sprite *), fp);
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH_NORETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void akgl_game_save_spritesheetname_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp = (FILE *)userdata;
|
|
|
|
|
akgl_SpriteSheet *spritesheet = NULL;
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
|
|
|
|
spritesheet = SDL_GetPointerProperty(props, name, NULL);
|
|
|
|
|
fwrite((char *)name, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp);
|
|
|
|
|
fwrite(&spritesheet, 1, sizeof(akgl_SpriteSheet *), fp);
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH_NORETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp = (FILE *)userdata;
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
akgl_Character *character = NULL;
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
|
|
|
|
character = SDL_GetPointerProperty(props, name, NULL);
|
|
|
|
|
fwrite((char *)name, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp);
|
|
|
|
|
fwrite(&character, 1, sizeof(akgl_Character *), fp);
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH_NORETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
char nullval = 0x00;
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
|
|
|
|
// write the actor name pointer table
|
|
|
|
|
SDL_EnumerateProperties(
|
|
|
|
|
AKGL_REGISTRY_ACTOR,
|
|
|
|
|
&akgl_game_save_actorname_iterator,
|
|
|
|
|
(void *)fp);
|
|
|
|
|
fwrite((void *)&nullval, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp);
|
|
|
|
|
fwrite((void *)&nullval, 1, sizeof(akgl_Actor *), fp);
|
|
|
|
|
// write the sprite name pointer table
|
|
|
|
|
SDL_EnumerateProperties(
|
|
|
|
|
AKGL_REGISTRY_SPRITE,
|
|
|
|
|
&akgl_game_save_spritename_iterator,
|
|
|
|
|
(void *)fp);
|
|
|
|
|
fwrite((void *)&nullval, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp);
|
|
|
|
|
fwrite((void *)&nullval, 1, sizeof(akgl_Sprite *), fp);
|
|
|
|
|
// write the spritesheet name pointer table
|
|
|
|
|
SDL_EnumerateProperties(
|
|
|
|
|
AKGL_REGISTRY_SPRITESHEET,
|
|
|
|
|
&akgl_game_save_spritesheetname_iterator,
|
|
|
|
|
(void *)fp);
|
|
|
|
|
fwrite((void *)&nullval, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp);
|
|
|
|
|
fwrite((void *)&nullval, 1, sizeof(akgl_SpriteSheet *), fp);
|
|
|
|
|
// write the character name pointer table
|
|
|
|
|
SDL_EnumerateProperties(
|
|
|
|
|
AKGL_REGISTRY_CHARACTER,
|
|
|
|
|
&akgl_game_save_charactername_iterator,
|
|
|
|
|
(void *)fp);
|
|
|
|
|
fwrite((void *)&nullval, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp);
|
|
|
|
|
fwrite((void *)&nullval, 1, sizeof(akgl_Character *), fp);
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp = NULL;
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, fpath, AKERR_NULLPOINTER, "NULL file path");
|
|
|
|
|
fp = fopen(fpath, "wb");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, fp, errno, "%s", fpath);
|
|
|
|
|
fwrite(&game, 1, sizeof(akgl_Game), fp);
|
|
|
|
|
fclose(fp);
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, fpath, AKERR_NULLPOINTER, "NULL file path");
|
|
|
|
|
fp = fopen(fpath, "wb");
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, fp, errno, "%s", fpath);
|
|
|
|
|
fwrite(&game, 1, sizeof(akgl_Game), fp);
|
|
|
|
|
CATCH(errctx, akgl_game_save_actors(fp));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( fp != NULL )
|
|
|
|
|
fclose(fp);
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx); // SUCCEED_NORETURN if in main().
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(FILE *fp, const char *versiontype, char *curversion)
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_PropertiesID map, int namelength, int ptrlength, SDL_PropertiesID registry)
|
|
|
|
|
{
|
|
|
|
|
void *ptr = NULL;
|
|
|
|
|
char ptrstring[32];
|
|
|
|
|
char objname[namelength];
|
|
|
|
|
int retval = 0;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
while ( 1 ) {
|
|
|
|
|
retval = fread((void *)&objname, 1, namelength, fp);
|
|
|
|
|
FAIL_NONZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
(retval != namelength),
|
|
|
|
|
AKERR_IO,
|
|
|
|
|
"Corrupt save file");
|
|
|
|
|
retval = fread((void *)&ptr, 1, ptrlength, fp);
|
|
|
|
|
FAIL_NONZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
(retval != ptrlength),
|
|
|
|
|
AKERR_IO,
|
|
|
|
|
"Corrupt save file");
|
|
|
|
|
// End of the map
|
|
|
|
|
if ( ptr == 0x00 && objname[0] == 0x00 ) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// The map allows us to say "Object X has a reference to object Y at
|
|
|
|
|
// address Z. The object they had at address Z was named A. Our current
|
|
|
|
|
// instance of object named A is at address B. So we map address Z to
|
|
|
|
|
// address B, so that we can reconnect function pointers on objects loaded
|
|
|
|
|
// from the save game state."
|
|
|
|
|
|
|
|
|
|
// SDL_Properties objects can only use string keys, so we can't use the
|
|
|
|
|
// old pointer as a key without first converting it to a string.
|
|
|
|
|
memset((void *)&ptrstring, 0x00, 32);
|
|
|
|
|
snprintf((char *)&ptrstring, 32, "%p", ptr);
|
|
|
|
|
SDL_SetPointerProperty(
|
|
|
|
|
map,
|
|
|
|
|
ptrstring,
|
|
|
|
|
SDL_GetPointerProperty(registry, objname, NULL));
|
|
|
|
|
};
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion)
|
|
|
|
|
{
|
|
|
|
|
char versionstr[32];
|
|
|
|
|
semver_t current_version = {};
|
|
|
|
|
semver_t compare_version = {};
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, versiontype, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, curversion, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, newversion, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
// Check save game library version
|
|
|
|
|
@@ -155,24 +310,19 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(FILE *fp, const cha
|
|
|
|
|
(char *)curversion);
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
(fread((void *)&versionstr, 1, 32, fp) < 32),
|
|
|
|
|
AKERR_IO,
|
|
|
|
|
"Corrupt save file");
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
semver_parse((const char *)&versionstr, &compare_version),
|
|
|
|
|
semver_parse((const char *)newversion, &compare_version),
|
|
|
|
|
AKERR_VALUE,
|
|
|
|
|
"Invalid semantic %s version in save game: %s",
|
|
|
|
|
versiontype,
|
|
|
|
|
(char *)&versionstr);
|
|
|
|
|
(char *)&newversion);
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
semver_satisfies(compare_version, current_version, "="),
|
|
|
|
|
AKERR_API,
|
|
|
|
|
"Incompatible save game %s version (%s ^ %s)",
|
|
|
|
|
"Incompatible save game %s version (%s != %s)",
|
|
|
|
|
versiontype,
|
|
|
|
|
curversion,
|
|
|
|
|
(char *)&versionstr);
|
|
|
|
|
(char *)&newversion);
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
semver_free(¤t_version);
|
|
|
|
|
semver_free(&compare_version);
|
|
|
|
|
@@ -183,6 +333,11 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(FILE *fp, const cha
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath)
|
|
|
|
|
{
|
|
|
|
|
akgl_Game savegame;
|
|
|
|
|
SDL_PropertiesID actormap;
|
|
|
|
|
SDL_PropertiesID spritemap;
|
|
|
|
|
SDL_PropertiesID spritesheetmap;
|
|
|
|
|
SDL_PropertiesID charactermap;
|
|
|
|
|
FILE *fp = NULL;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
@@ -191,14 +346,38 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath)
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
fp = fopen(fpath, "rb");
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, fp, errno, "%s", fpath);
|
|
|
|
|
CATCH(errctx, akgl_game_load_versioncmp(fp, "library", (char *)AKGL_VERSION));
|
|
|
|
|
CATCH(errctx, akgl_game_load_versioncmp(fp, "game", (char *)&game.version));
|
|
|
|
|
rewind(fp);
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
(fread((void *)&game, 1, sizeof(akgl_Game), fp) < sizeof(akgl_Game)),
|
|
|
|
|
(fread((void *)&savegame, 1, sizeof(akgl_Game), fp) < sizeof(akgl_Game)),
|
|
|
|
|
AKERR_IO,
|
|
|
|
|
"Corrupt save file");
|
|
|
|
|
"Corrupt save file");
|
|
|
|
|
CATCH(errctx, akgl_game_load_versioncmp("library", (char *)&savegame.libversion, (char *)AKGL_VERSION));
|
|
|
|
|
CATCH(errctx, akgl_game_load_versioncmp("game", (char *)&savegame.version, (char *)&game.version));
|
|
|
|
|
FAIL_NONZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
strncmp((char *)&savegame.name, (char *)&game.name, 256),
|
|
|
|
|
AKERR_API,
|
|
|
|
|
"Savegame is not compatible with this game");
|
|
|
|
|
FAIL_NONZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
strncmp((char *)&savegame.uri, (char *)&game.uri, 256),
|
|
|
|
|
AKERR_API,
|
|
|
|
|
"Savegame is not compatible with this game");
|
|
|
|
|
|
|
|
|
|
memcpy((void *)&game, (void *)&savegame, sizeof(akgl_Game));
|
|
|
|
|
// Load actor name map
|
|
|
|
|
actormap = SDL_CreateProperties();
|
|
|
|
|
CATCH(errctx, akgl_game_load_objectnamemap(fp, actormap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Actor *), AKGL_REGISTRY_ACTOR));
|
|
|
|
|
// Load sprite name map
|
|
|
|
|
spritemap = SDL_CreateProperties();
|
|
|
|
|
CATCH(errctx, akgl_game_load_objectnamemap(fp, spritemap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Sprite *), AKGL_REGISTRY_SPRITE));
|
|
|
|
|
// Load spritesheet name map
|
|
|
|
|
spritesheetmap = SDL_CreateProperties();
|
|
|
|
|
CATCH(errctx, akgl_game_load_objectnamemap(fp, spritesheetmap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_SpriteSheet *), AKGL_REGISTRY_SPRITESHEET));
|
|
|
|
|
// Load character name map
|
|
|
|
|
charactermap = SDL_CreateProperties();
|
|
|
|
|
CATCH(errctx, akgl_game_load_objectnamemap(fp, charactermap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Character *), AKGL_REGISTRY_CHARACTER));
|
|
|
|
|
// Now that we have all of our pointer maps built, we can load the actual binary objects and reset their pointers
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( fp != NULL ) {
|
|
|
|
|
fclose(fp);
|
|
|
|
|
|