From ff3986c6cb9598a6282876f346d1b78194fcf485 Mon Sep 17 00:00:00 2001 From: "Andrei \"Akari\" Alexeyev" Date: Sun, 5 Feb 2017 03:25:17 +0200 Subject: [PATCH] New binary replay format --- src/player.h | 4 +- src/replay.c | 409 ++++++++++++++++++++++----------------------------- src/replay.h | 65 ++++---- src/stage.c | 12 +- 4 files changed, 219 insertions(+), 271 deletions(-) diff --git a/src/player.h b/src/player.h index 9b96e385..10916807 100644 --- a/src/player.h +++ b/src/player.h @@ -24,12 +24,12 @@ enum { }; typedef enum { - Youmu, + Youmu = 0, Marisa } Character; typedef enum { - YoumuOpposite, + YoumuOpposite = 0, YoumuHoming, MarisaLaser = YoumuOpposite, diff --git a/src/replay.c b/src/replay.c index 3bd759ae..6f79403e 100644 --- a/src/replay.c +++ b/src/replay.c @@ -14,39 +14,49 @@ #include "global.h" #include "paths/native.h" +#include "taisei_err.h" + +static uint8_t replay_magic_header[] = { + 0x68, 0x6f, 0x6e, 0x6f, 0xe2, 0x9d, 0xa4, 0x75, 0x6d, 0x69 +}; void replay_init(Replay *rpy) { memset(rpy, 0, sizeof(Replay)); rpy->active = True; + rpy->playername = malloc(strlen(tconfig.strval[PLAYERNAME]) + 1); + strcpy(rpy->playername, tconfig.strval[PLAYERNAME]); + printf("replay_init(): replay initialized for writting\n"); } -ReplayStage* replay_init_stage(Replay *rpy, StageInfo *stage, int seed, Player *plr) { +ReplayStage* replay_init_stage(Replay *rpy, StageInfo *stage, uint64_t seed, Player *plr) { ReplayStage *s; rpy->stages = (ReplayStage*)realloc(rpy->stages, sizeof(ReplayStage) * (++rpy->stgcount)); s = &(rpy->stages[rpy->stgcount-1]); memset(s, 0, sizeof(ReplayStage)); - s->capacity = REPLAY_ALLOC_INITIAL; - s->events = (ReplayEvent*)malloc(sizeof(ReplayEvent) * s->capacity); + s->capacity = REPLAY_ALLOC_INITIAL; + s->events = (ReplayEvent*)malloc(sizeof(ReplayEvent) * s->capacity); - s->stage = stage->id; - s->seed = seed; - s->diff = global.diff; - s->points = global.points; - - s->plr_pos = plr->pos; - s->plr_focus = plr->focus; - s->plr_fire = plr->fire; - s->plr_char = plr->cha; - s->plr_shot = plr->shot; - s->plr_lifes = plr->lifes; - s->plr_bombs = plr->bombs; - s->plr_power = plr->power; - s->plr_mflags = plr->moveflags; + s->stage = stage->id; + s->seed = seed; + s->diff = global.diff; + s->points = global.points; + s->plr_pos_x._double = creal(plr->pos); + s->plr_pos_y._double = cimag(plr->pos); + + s->plr_focus = plr->focus; + s->plr_fire = plr->fire; + s->plr_char = plr->cha; + s->plr_shot = plr->shot; + s->plr_lifes = plr->lifes; + s->plr_bombs = plr->bombs; + s->plr_power._double = plr->power; + s->plr_moveflags = plr->moveflags; + printf("replay_init_stage(): created a new stage for writting\n"); replay_select(rpy, rpy->stgcount-1); return s; @@ -81,19 +91,19 @@ ReplayStage* replay_select(Replay *rpy, int stage) { return rpy->current; } -void replay_event(Replay *rpy, int type, int key) { +void replay_event(Replay *rpy, uint8_t type, uint16_t key) { if(!rpy->active) return; ReplayStage *s = rpy->current; ReplayEvent *e = &(s->events[s->ecount]); e->frame = global.frames; - e->type = (char)type; - e->key = (short)key; + e->type = type; + e->key = key; s->ecount++; if(s->ecount >= s->capacity) { - printf("Replay reached it's capacity of %d, reallocating\n", s->capacity); + printf("Replay reached its capacity of %d, reallocating\n", s->capacity); s->capacity += REPLAY_ALLOC_ADDITIONAL; s->events = (ReplayEvent*)realloc(s->events, sizeof(ReplayEvent) * s->capacity); printf("The new capacity is %d\n", s->capacity); @@ -103,219 +113,146 @@ void replay_event(Replay *rpy, int type, int key) { printf("The replay is OVER\n"); } -void replay_write_separator(FILE *file) { - fputs(":", file); +void replay_write_string(SDL_RWops *file, char *str) { + SDL_WriteLE16(file, strlen(str)); + SDL_RWwrite(file, str, 1, strlen(str)); } -void replay_write_string(FILE *file, char *str) { - fputs(str, file); - replay_write_separator(file); -} +int replay_write_stage_event(ReplayEvent *evt, SDL_RWops *file) { + SDL_WriteLE32(file, evt->frame); + SDL_WriteU8(file, evt->type); + SDL_WriteLE16(file, evt->key); -void replay_write_int(FILE *file, int val) { - fprintf(file, "%d", val); - replay_write_separator(file); -} - -void replay_write_double(FILE *file, double val) { - fprintf(file, "%f", val); - replay_write_separator(file); -} - -void replay_write_complex(FILE *file, complex val) { - replay_write_double(file, creal(val)); - replay_write_double(file, cimag(val)); -} - -int replay_write(Replay *rpy, FILE *file) { - int i, j; - - // header - replay_write_int(file, REPLAY_MAGICNUMBER); - replay_write_string(file, tconfig.strval[PLAYERNAME]); - replay_write_string(file, "Get out of here, you nasty cheater!"); - replay_write_int(file, rpy->stgcount); - - for(j = 0; j < rpy->stgcount; ++j) { - ReplayStage *stg = &(rpy->stages[j]); - - // initial game settings - replay_write_int(file, stg->stage); - replay_write_int(file, stg->seed); - replay_write_int(file, stg->diff); - replay_write_int(file, stg->points); - - // initial player settings - replay_write_int(file, stg->plr_char); - replay_write_int(file, stg->plr_shot); - replay_write_complex(file, stg->plr_pos); - replay_write_int(file, stg->plr_focus); - replay_write_int(file, stg->plr_fire); - replay_write_double(file, stg->plr_power); - replay_write_int(file, stg->plr_lifes); - replay_write_int(file, stg->plr_bombs); - replay_write_int(file, stg->plr_mflags); - - // events - replay_write_int(file, stg->ecount); - - for(i = 0; i < stg->ecount; ++i) { - ReplayEvent *e = &(stg->events[i]); - replay_write_int(file, e->frame); - replay_write_int(file, e->type); - replay_write_int(file, e->key); - } - } - return True; } -// read order -enum { - // header - RPY_H_MAGIC = 0, - RPY_H_META1, - RPY_H_META2, - RPY_H_STAGECOUNT, - - // initial game settings - RPY_G_STAGE, - RPY_G_SEED, - RPY_G_DIFF, - RPY_G_PTS, - - // initial player settings - RPY_P_CHAR, - RPY_P_SHOT, - RPY_P_POSREAL, - RPY_P_POSIMAG, - RPY_P_FOCUS, - RPY_P_FIRE, - RPY_P_POWER, - RPY_P_LIFES, - RPY_P_BOMBS, - RPY_P_MFLAGS, - - // events - RPY_E_COUNT, - RPY_E_FRAME, - RPY_E_TYPE, - RPY_E_KEY -}; +int replay_write_stage(ReplayStage *stg, SDL_RWops *file) { + int i; -#define INTOF(s) ((int)strtol(s, NULL, 10)) -#define FLOATOF(s) ((float)strtod(s, NULL)) + SDL_WriteLE32(file, stg->stage); + SDL_WriteLE32(file, stg->seed); + SDL_WriteU8(file, stg->diff); + SDL_WriteLE32(file, stg->points); + SDL_WriteU8(file, stg->plr_char); + SDL_WriteU8(file, stg->plr_shot); + SDL_WriteLE64(file, stg->plr_pos_x._int); + SDL_WriteLE64(file, stg->plr_pos_y._int); + SDL_WriteU8(file, stg->plr_focus); + SDL_WriteU8(file, stg->plr_fire); + SDL_WriteLE64(file, stg->plr_power._int); + SDL_WriteU8(file, stg->plr_lifes); + SDL_WriteU8(file, stg->plr_bombs); + SDL_WriteU8(file, stg->plr_moveflags); + SDL_WriteLE32(file, stg->ecount); -int replay_read(Replay *rpy, FILE *file) { - int readstate = RPY_H_MAGIC; - int bufidx = 0, eidx = 0; - char buf[REPLAY_READ_MAXSTRLEN], c; - ReplayStage *s; - int stgnum = 0; - memset(rpy, 0, sizeof(Replay)); - - while((c = fgetc(file)) != EOF) { - if(c == ':') { - buf[bufidx] = 0; - bufidx = 0; - - switch(readstate) { - case RPY_H_MAGIC: { - int magic = INTOF(buf); - if(magic != REPLAY_MAGICNUMBER) { - printf("replay_read(): invalid magic number: %d\n", magic); - replay_destroy(rpy); - return False; - } - - break; - } - - case RPY_H_META1: - stralloc(&rpy->playername, buf); - printf("replay_read(): replay META1 is: %s\n", buf); - break; - - case RPY_H_META2: // skip - break; - - case RPY_H_STAGECOUNT: - rpy->stgcount = INTOF(buf); - if(rpy->stgcount <= 0) { - printf("replay_read(): insane stage count: %i\n", rpy->stgcount); - replay_destroy(rpy); - return False; - } - rpy->stages = (ReplayStage*)malloc(sizeof(ReplayStage) * rpy->stgcount); - s = &(rpy->stages[0]); - memset(s, 0, sizeof(ReplayStage)); - break; - - case RPY_G_STAGE: s->stage = INTOF(buf); break; - case RPY_G_SEED: s->seed = INTOF(buf); break; - case RPY_G_DIFF: s->diff = INTOF(buf); break; - case RPY_G_PTS: s->points = INTOF(buf); break; - - case RPY_P_CHAR: s->plr_char = INTOF(buf); break; - case RPY_P_SHOT: s->plr_shot = INTOF(buf); break; - case RPY_P_POSREAL: s->plr_pos = FLOATOF(buf); break; - case RPY_P_POSIMAG: s->plr_pos += FLOATOF(buf) * I;break; - case RPY_P_FOCUS: s->plr_focus = INTOF(buf); break; - case RPY_P_FIRE: s->plr_fire = INTOF(buf); break; - case RPY_P_POWER: s->plr_power = FLOATOF(buf); break; - case RPY_P_LIFES: s->plr_lifes = INTOF(buf); break; - case RPY_P_BOMBS: s->plr_bombs = INTOF(buf); break; - case RPY_P_MFLAGS: s->plr_mflags = INTOF(buf); break; - - case RPY_E_COUNT: - s->capacity = s->ecount = INTOF(buf); - if(s->capacity <= 0) { - printf("replay_read(): insane capacity in stage %i: %i\n", stgnum, s->capacity); - replay_destroy(rpy); - return False; - } - s->events = (ReplayEvent*)malloc(sizeof(ReplayEvent) * s->capacity); - break; - - case RPY_E_FRAME: s->events[eidx].frame = INTOF(buf); break; - case RPY_E_TYPE: s->events[eidx].type = INTOF(buf); break; - case RPY_E_KEY: - s->events[eidx].key = INTOF(buf); - eidx++; - - if(eidx == s->capacity) { - if((++stgnum) >= rpy->stgcount) - return True; - s = &(rpy->stages[stgnum]); - readstate = RPY_G_STAGE; - eidx = 0; - continue; - } - - break; - } - - if(readstate == RPY_E_KEY) - readstate = RPY_E_FRAME; - else - ++readstate; - } else { - buf[bufidx++] = c; - if(bufidx >= REPLAY_READ_MAXSTRLEN) { - printf("replay_read(): item is too long\n"); - replay_destroy(rpy); - return False; - } + for(i = 0; i < stg->ecount; ++i) { + if(!replay_write_stage_event(&stg->events[i], file)) { + return False; } } - - printf("replay_read(): replay isn't properly terminated\n"); - replay_destroy(rpy); - return False; + + return True; } -#undef FLOATOF -#undef INTOF +int replay_write(Replay *rpy, SDL_RWops *file) { + uint8_t *u8_p; + int i; + + for(u8_p = replay_magic_header; *u8_p; ++u8_p) { + SDL_WriteU8(file, *u8_p); + } + + SDL_WriteLE16(file, REPLAY_STRUCT_VERSION); + replay_write_string(file, rpy->playername); + SDL_WriteLE32(file, rpy->stgcount); + + for(i = 0; i < rpy->stgcount; ++i) { + if(!replay_write_stage(&rpy->stages[i], file)) { + return False; + } + } + + return True; +} + +void replay_read_string(SDL_RWops *file, char **ptr) { + size_t len = SDL_ReadLE16(file); + + *ptr = malloc(len + 1); + memset(*ptr, 0, len + 1); + + SDL_RWread(file, *ptr, 1, len); +} + +int replay_read(Replay *rpy, SDL_RWops *file) { + uint8_t *u8_p; + int i, j; + + memset(rpy, 0, sizeof(Replay)); + + for(u8_p = replay_magic_header; *u8_p; ++u8_p) { + SDL_WriteU8(file, *u8_p); + if(SDL_ReadU8(file) != *u8_p) { + warnx("replay_read(): incorrect header"); + return False; + } + } + + if(SDL_ReadLE16(file) != REPLAY_STRUCT_VERSION) { + warnx("replay_read(): incorrect version"); + return False; + } + + replay_read_string(file, &rpy->playername); + + rpy->stgcount = SDL_ReadLE32(file); + + if(!rpy->stgcount) { + warnx("replay_read(): no stages in replay"); + return False; + } + + rpy->stages = malloc(sizeof(ReplayStage) * rpy->stgcount); + memset(rpy->stages, 0, sizeof(ReplayStage) * rpy->stgcount); + + for(i = 0; i < rpy->stgcount; ++i) { + ReplayStage *stg = &rpy->stages[i]; + + stg->stage = SDL_ReadLE32(file); + stg->seed = SDL_ReadLE32(file); + stg->diff = SDL_ReadU8(file); + stg->points = SDL_ReadLE32(file); + stg->plr_char = SDL_ReadU8(file); + stg->plr_shot = SDL_ReadU8(file); + stg->plr_pos_x._int = SDL_ReadLE64(file); + stg->plr_pos_y._int = SDL_ReadLE64(file); + stg->plr_focus = SDL_ReadU8(file); + stg->plr_fire = SDL_ReadU8(file); + stg->plr_power._int = SDL_ReadLE64(file); + stg->plr_lifes = SDL_ReadU8(file); + stg->plr_bombs = SDL_ReadU8(file); + stg->plr_moveflags = SDL_ReadU8(file); + stg->ecount = SDL_ReadLE32(file); + + if(!stg->ecount) { + warnx("replay_read(): no events in stage"); + return False; + } + + stg->events = malloc(sizeof(ReplayEvent) * stg->ecount); + memset(stg->events, 0, sizeof(ReplayEvent) * stg->ecount); + + for(j = 0; j < stg->ecount; ++j) { + ReplayEvent *evt = &stg->events[j]; + + evt->frame = SDL_ReadLE32(file); + evt->type = SDL_ReadU8(file); + evt->key = SDL_ReadLE16(file); + } + } + + return True; +} char* replay_getpath(char *name, int ext) { char *p = (char*)malloc(strlen(get_replays_path()) + strlen(name) + strlen(REPLAY_EXTENSION) + 3); @@ -330,18 +267,16 @@ int replay_save(Replay *rpy, char *name) { char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION)); printf("replay_save(): saving %s\n", p); - FILE *fp = fopen(p, "w"); - + SDL_RWops *file = SDL_RWFromFile(p, "wb"); free(p); - if(!fp) { - printf("replay_save(): fopen() failed\n"); + if(!file) { + warnx("replay_save(): SDL_RWFromFile() failed: %s\n", SDL_GetError()); return False; } - int result = replay_write(rpy, fp); - fflush(fp); - fclose(fp); + int result = replay_write(rpy, file); + SDL_RWclose(file); return result; } @@ -349,17 +284,21 @@ int replay_load(Replay *rpy, char *name) { char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION)); printf("replay_load(): loading %s\n", p); - FILE *fp = fopen(p, "r"); - + SDL_RWops *file = SDL_RWFromFile(p, "rb"); free(p); - if(!fp) { - printf("replay_load(): fopen() failed\n"); + if(!file) { + warnx("replay_save(): SDL_RWFromFile() failed: %s\n", SDL_GetError()); return False; } - int result = replay_read(rpy, fp); - fclose(fp); + int result = replay_read(rpy, file); + + if(!result) { + replay_destroy(rpy); + } + + SDL_RWclose(file); return result; } diff --git a/src/replay.h b/src/replay.h index 214c23dc..9531014d 100644 --- a/src/replay.h +++ b/src/replay.h @@ -12,33 +12,47 @@ #include "stage.h" #include "player.h" +#define REPLAY_ALLOC_INITIAL 1000 +#define REPLAY_ALLOC_ADDITIONAL 500 +#define REPLAY_EXTENSION "tsr" +#define REPLAY_STRUCT_VERSION 0 +#define REPLAY_MAX_NAME_LENGTH 128 + +typedef union float64_u { + // ASSUMPTION: the "double" type is an IEEE 754 binary64 + + double _double; + uint64_t _int; +} float64_u; + typedef struct ReplayEvent { - int frame; - char type; - short key; + uint32_t frame; + uint8_t type; + uint16_t key; } ReplayEvent; typedef struct ReplayStage { // initial game settings - int stage; - int seed; // this also happens to be the game initiation time - and we use this property, don't break it please - int diff; - int points; + uint32_t stage; + uint32_t seed; // this also happens to be the game initiation time - and we use this property, don't break it please + uint8_t diff; + uint32_t points; // initial player settings - Character plr_char; - ShotMode plr_shot; - complex plr_pos; - short plr_focus; - short plr_fire; - float plr_power; - int plr_lifes; - int plr_bombs; - int plr_mflags; + uint8_t plr_char; + uint8_t plr_shot; + float64_u plr_pos_x; + float64_u plr_pos_y; + uint8_t plr_focus; + uint8_t plr_fire; + float64_u plr_power; + uint8_t plr_lifes; + uint8_t plr_bombs; + uint8_t plr_moveflags; // events ReplayEvent *events; - int ecount; + uint32_t ecount; // The fields below should not be stored int capacity; @@ -47,11 +61,12 @@ typedef struct ReplayStage { typedef struct Replay { // metadata + // uint16_t version; char *playername; // stages (NOTE FOR FUTURE: stages do not represent stage runs in particular, they can and should be used to store stuff like spell practice runs, too) ReplayStage *stages; - int stgcount; + uint32_t stgcount; // The fields below should not be stored int active; @@ -65,24 +80,18 @@ enum { }; void replay_init(Replay *rpy); -ReplayStage* replay_init_stage(Replay *rpy, StageInfo *stage, int seed, Player *plr); +ReplayStage* replay_init_stage(Replay *rpy, StageInfo *stage, uint64_t seed, Player *plr); void replay_destroy(Replay *rpy); void replay_destroy_stage(ReplayStage *stage); ReplayStage* replay_select(Replay *rpy, int stage); -void replay_event(Replay *rpy, int type, int key); +void replay_event(Replay *rpy, uint8_t type, uint16_t key); -int replay_write(Replay *rpy, FILE *file); -int replay_read(Replay *rpy, FILE *file); +int replay_write(Replay *rpy, SDL_RWops *file); +int replay_read(Replay *rpy, SDL_RWops *file); char* replay_getpath(char *name, int ext); // must be freed int replay_save(Replay *rpy, char *name); int replay_load(Replay *rpy, char *name); void replay_copy(Replay *dst, Replay *src); -#define REPLAY_ALLOC_INITIAL 1000 -#define REPLAY_ALLOC_ADDITIONAL 500 -#define REPLAY_MAGICNUMBER 1337 -#define REPLAY_EXTENSION "tsr" -#define REPLAY_READ_MAXSTRLEN 128 - #endif diff --git a/src/stage.c b/src/stage.c index 7a14f01d..14a76ad4 100644 --- a/src/stage.c +++ b/src/stage.c @@ -459,7 +459,7 @@ void stage_loop(StageInfo* info, StageRule start, StageRule end, StageRule draw, return; } - int seed = time(0); + uint32_t seed = (uint32_t)time(0); tsrand_switch(&global.rand_game); tsrand_seed_p(&global.rand_game, seed); stage_start(); @@ -467,26 +467,26 @@ void stage_loop(StageInfo* info, StageRule start, StageRule end, StageRule draw, if(global.replaymode == REPLAY_RECORD) { if(global.replay.active) replay_init_stage(&global.replay, info, seed, &global.plr); - printf("Random seed: %d\n", seed); + printf("Random seed: %u\n", seed); } else { ReplayStage *stg = global.replay.current; printf("REPLAY_PLAY mode: %d events, stage: \"%s\"\n", stg->ecount, stage_get(stg->stage)->title); tsrand_seed_p(&global.rand_game, stg->seed); - printf("Random seed: %d\n", stg->seed); + printf("Random seed: %u\n", stg->seed); global.diff = stg->diff; global.points = stg->points; global.plr.shot = stg->plr_shot; global.plr.cha = stg->plr_char; - global.plr.pos = stg->plr_pos; + global.plr.pos = stg->plr_pos_x._double + I * stg->plr_pos_y._double; global.plr.focus = stg->plr_focus; global.plr.fire = stg->plr_fire; global.plr.lifes = stg->plr_lifes; global.plr.bombs = stg->plr_bombs; - global.plr.power = stg->plr_power; - global.plr.moveflags = stg->plr_mflags; + global.plr.power = stg->plr_power._double; + global.plr.moveflags = stg->plr_moveflags; stg->playpos = 0; }