513d613387
I would've preferred to just go with 4-spaces for indent and no tabs, but lao is a bit conservative about it. :^) Still, this is a ton better than mixing different styles all over the place, especially within the same file.
787 lines
19 KiB
C
787 lines
19 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT-License
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2018, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
|
|
*/
|
|
|
|
#include "taisei.h"
|
|
|
|
#include "replay.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
|
|
#include "global.h"
|
|
|
|
static uint8_t replay_magic_header[] = REPLAY_MAGIC_HEADER;
|
|
|
|
void replay_init(Replay *rpy) {
|
|
memset(rpy, 0, sizeof(Replay));
|
|
log_debug("Replay at %p initialized for writing", (void*)rpy);
|
|
}
|
|
|
|
ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t seed, Difficulty diff, Player *plr) {
|
|
ReplayStage *s;
|
|
|
|
rpy->stages = (ReplayStage*)realloc(rpy->stages, sizeof(ReplayStage) * (++rpy->numstages));
|
|
s = rpy->stages + rpy->numstages - 1;
|
|
memset(s, 0, sizeof(ReplayStage));
|
|
|
|
s->capacity = REPLAY_ALLOC_INITIAL;
|
|
s->events = (ReplayEvent*)malloc(sizeof(ReplayEvent) * s->capacity);
|
|
|
|
s->stage = stage->id;
|
|
s->seed = seed;
|
|
s->diff = diff;
|
|
|
|
s->plr_pos_x = floor(creal(plr->pos));
|
|
s->plr_pos_y = floor(cimag(plr->pos));
|
|
|
|
s->plr_points = plr->points;
|
|
s->plr_continues_used = plr->continues_used;
|
|
s->plr_focus = plr->focus;
|
|
s->plr_char = plr->mode->character->id;
|
|
s->plr_shot = plr->mode->shot_mode;
|
|
s->plr_lives = plr->lives;
|
|
s->plr_life_fragments = plr->life_fragments;
|
|
s->plr_bombs = plr->bombs;
|
|
s->plr_bomb_fragments = plr->bomb_fragments;
|
|
s->plr_power = plr->power;
|
|
s->plr_graze = plr->graze;
|
|
s->plr_inputflags = plr->inputflags;
|
|
|
|
log_debug("Created a new stage %p in replay %p", (void*)s, (void*)rpy);
|
|
return s;
|
|
}
|
|
|
|
void replay_stage_sync_player_state(ReplayStage *stg, Player *plr) {
|
|
plr->points = stg->plr_points;
|
|
plr->continues_used = stg->plr_continues_used;
|
|
plr->mode = plrmode_find(stg->plr_char, stg->plr_shot);
|
|
plr->pos = stg->plr_pos_x + I * stg->plr_pos_y;
|
|
plr->focus = stg->plr_focus;
|
|
plr->lives = stg->plr_lives;
|
|
plr->life_fragments = stg->plr_life_fragments;
|
|
plr->bombs = stg->plr_bombs;
|
|
plr->bomb_fragments = stg->plr_bomb_fragments;
|
|
plr->power = stg->plr_power;
|
|
plr->graze = stg->plr_graze;
|
|
plr->inputflags = stg->plr_inputflags;
|
|
}
|
|
|
|
static void replay_destroy_stage(ReplayStage *stage) {
|
|
free(stage->events);
|
|
memset(stage, 0, sizeof(ReplayStage));
|
|
}
|
|
|
|
void replay_destroy_events(Replay *rpy) {
|
|
if(!rpy) {
|
|
return;
|
|
}
|
|
|
|
if(rpy->stages) {
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
|
ReplayStage *stg = rpy->stages + i;
|
|
free(stg->events);
|
|
stg->events = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void replay_destroy(Replay *rpy) {
|
|
if(!rpy) {
|
|
return;
|
|
}
|
|
|
|
if(rpy->stages) {
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
|
replay_destroy_stage(rpy->stages + i);
|
|
}
|
|
|
|
free(rpy->stages);
|
|
}
|
|
|
|
free(rpy->playername);
|
|
|
|
memset(rpy, 0, sizeof(Replay));
|
|
}
|
|
|
|
void replay_stage_event(ReplayStage *stg, uint32_t frame, uint8_t type, uint16_t value) {
|
|
assert(stg != NULL);
|
|
|
|
ReplayStage *s = stg;
|
|
ReplayEvent *e = s->events + s->numevents++;
|
|
e->frame = frame;
|
|
e->type = type;
|
|
e->value = value;
|
|
|
|
if(s->numevents >= s->capacity) {
|
|
log_debug("Replay stage reached its capacity of %d, reallocating", s->capacity);
|
|
s->capacity *= 2;
|
|
s->events = (ReplayEvent*)realloc(s->events, sizeof(ReplayEvent) * s->capacity);
|
|
log_debug("The new capacity is %d", s->capacity);
|
|
}
|
|
|
|
if(type == EV_OVER) {
|
|
log_debug("The replay is OVER");
|
|
}
|
|
}
|
|
|
|
static void replay_write_string(SDL_RWops *file, char *str, uint16_t version) {
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
SDL_WriteU8(file, strlen(str));
|
|
} else {
|
|
SDL_WriteLE16(file, strlen(str));
|
|
}
|
|
|
|
SDL_RWwrite(file, str, 1, strlen(str));
|
|
}
|
|
|
|
static bool replay_write_stage_event(ReplayEvent *evt, SDL_RWops *file) {
|
|
SDL_WriteLE32(file, evt->frame);
|
|
SDL_WriteU8(file, evt->type);
|
|
SDL_WriteLE16(file, evt->value);
|
|
|
|
return true;
|
|
}
|
|
|
|
static uint32_t replay_calc_stageinfo_checksum(ReplayStage *stg, uint16_t version) {
|
|
uint32_t cs = 0;
|
|
|
|
cs += stg->stage;
|
|
cs += stg->seed;
|
|
cs += stg->diff;
|
|
cs += stg->plr_points;
|
|
cs += stg->plr_char;
|
|
cs += stg->plr_shot;
|
|
cs += stg->plr_pos_x;
|
|
cs += stg->plr_pos_y;
|
|
cs += stg->plr_focus;
|
|
cs += stg->plr_power;
|
|
cs += stg->plr_lives;
|
|
cs += stg->plr_life_fragments;
|
|
cs += stg->plr_bombs;
|
|
cs += stg->plr_bomb_fragments;
|
|
cs += stg->plr_inputflags;
|
|
cs += stg->numevents;
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
cs += stg->plr_continues_used;
|
|
cs += stg->flags;
|
|
}
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV2) {
|
|
cs += stg->plr_graze;
|
|
}
|
|
|
|
log_debug("%08x", cs);
|
|
return cs;
|
|
}
|
|
|
|
static bool replay_write_stage(ReplayStage *stg, SDL_RWops *file, uint16_t version) {
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
SDL_WriteLE32(file, stg->flags);
|
|
}
|
|
|
|
SDL_WriteLE16(file, stg->stage);
|
|
SDL_WriteLE32(file, stg->seed);
|
|
SDL_WriteU8(file, stg->diff);
|
|
SDL_WriteLE32(file, stg->plr_points);
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
SDL_WriteU8(file, stg->plr_continues_used);
|
|
}
|
|
|
|
SDL_WriteU8(file, stg->plr_char);
|
|
SDL_WriteU8(file, stg->plr_shot);
|
|
SDL_WriteLE16(file, stg->plr_pos_x);
|
|
SDL_WriteLE16(file, stg->plr_pos_y);
|
|
SDL_WriteU8(file, stg->plr_focus);
|
|
SDL_WriteLE16(file, stg->plr_power);
|
|
SDL_WriteU8(file, stg->plr_lives);
|
|
SDL_WriteU8(file, stg->plr_life_fragments);
|
|
SDL_WriteU8(file, stg->plr_bombs);
|
|
SDL_WriteU8(file, stg->plr_bomb_fragments);
|
|
SDL_WriteU8(file, stg->plr_inputflags);
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV2) {
|
|
SDL_WriteLE16(file, stg->plr_graze);
|
|
}
|
|
|
|
SDL_WriteLE16(file, stg->numevents);
|
|
SDL_WriteLE32(file, 1 + ~replay_calc_stageinfo_checksum(stg, version));
|
|
|
|
return true;
|
|
}
|
|
|
|
static void fix_flags(Replay *rpy) {
|
|
rpy->flags |= REPLAY_GFLAG_CLEAR;
|
|
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
|
if(!(rpy->stages[i].flags & REPLAY_SFLAG_CLEAR)) {
|
|
rpy->flags &= ~REPLAY_SFLAG_CLEAR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool replay_write(Replay *rpy, SDL_RWops *file, uint16_t version) {
|
|
uint16_t base_version = (version & ~REPLAY_VERSION_COMPRESSION_BIT);
|
|
bool compression = (version & REPLAY_VERSION_COMPRESSION_BIT);
|
|
int i, j;
|
|
|
|
SDL_RWwrite(file, replay_magic_header, sizeof(replay_magic_header), 1);
|
|
SDL_WriteLE16(file, version);
|
|
|
|
if(base_version >= REPLAY_STRUCT_VERSION_TS102000_REV0) {
|
|
TaiseiVersion v;
|
|
TAISEI_VERSION_GET_CURRENT(&v);
|
|
|
|
if(taisei_version_write(file, &v) != TAISEI_VERSION_SIZE) {
|
|
log_warn("Failed to write game version: %s", SDL_GetError());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void *buf;
|
|
SDL_RWops *abuf = NULL;
|
|
SDL_RWops *vfile = file;
|
|
|
|
if(compression) {
|
|
abuf = SDL_RWAutoBuffer(&buf, 64);
|
|
vfile = SDL_RWWrapZWriter(abuf, REPLAY_COMPRESSION_CHUNK_SIZE, false);
|
|
}
|
|
|
|
replay_write_string(vfile, config_get_str(CONFIG_PLAYERNAME), base_version);
|
|
fix_flags(rpy);
|
|
|
|
if(base_version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
SDL_WriteLE32(vfile, rpy->flags);
|
|
}
|
|
|
|
SDL_WriteLE16(vfile, rpy->numstages);
|
|
|
|
for(i = 0; i < rpy->numstages; ++i) {
|
|
if(!replay_write_stage(rpy->stages + i, vfile, base_version)) {
|
|
if(compression) {
|
|
SDL_RWclose(vfile);
|
|
SDL_RWclose(abuf);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(compression) {
|
|
SDL_RWclose(vfile);
|
|
SDL_WriteLE32(file, SDL_RWtell(file) + SDL_RWtell(abuf) + 4);
|
|
SDL_RWwrite(file, buf, SDL_RWtell(abuf), 1);
|
|
SDL_RWclose(abuf);
|
|
vfile = SDL_RWWrapZWriter(file, REPLAY_COMPRESSION_CHUNK_SIZE, false);
|
|
}
|
|
|
|
for(i = 0; i < rpy->numstages; ++i) {
|
|
ReplayStage *stg = rpy->stages + i;
|
|
for(j = 0; j < stg->numevents; ++j) {
|
|
if(!replay_write_stage_event(stg->events + j, vfile)) {
|
|
if(compression) {
|
|
SDL_RWclose(vfile);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(compression) {
|
|
SDL_RWclose(vfile);
|
|
}
|
|
|
|
// useless byte to simplify the premature EOF check, can be anything
|
|
SDL_WriteU8(file, REPLAY_USELESS_BYTE);
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef REPLAY_LOAD_DEBUG
|
|
#define PRINTPROP(prop,fmt) log_debug(#prop " = %" # fmt " [%"PRIi64" / %"PRIi64"]", prop, SDL_RWtell(file), filesize)
|
|
#else
|
|
#define PRINTPROP(prop,fmt) (void)(prop)
|
|
#endif
|
|
|
|
#define CHECKPROP(prop,fmt) PRINTPROP(prop,fmt); if(filesize > 0 && SDL_RWtell(file) == filesize) { log_warn("%s: Premature EOF", source); return false; }
|
|
|
|
static void replay_read_string(SDL_RWops *file, char **ptr, uint16_t version) {
|
|
size_t len;
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
len = SDL_ReadU8(file);
|
|
} else {
|
|
len = SDL_ReadLE16(file);
|
|
}
|
|
|
|
*ptr = malloc(len + 1);
|
|
memset(*ptr, 0, len + 1);
|
|
|
|
SDL_RWread(file, *ptr, 1, len);
|
|
}
|
|
|
|
static bool replay_read_header(Replay *rpy, SDL_RWops *file, int64_t filesize, size_t *ofs, const char *source) {
|
|
uint8_t header[sizeof(replay_magic_header)];
|
|
(*ofs) += sizeof(header);
|
|
|
|
SDL_RWread(file, header, sizeof(header), 1);
|
|
|
|
if(memcmp(header, replay_magic_header, sizeof(header))) {
|
|
log_warn("%s: Incorrect header", source);
|
|
return false;
|
|
}
|
|
|
|
CHECKPROP(rpy->version = SDL_ReadLE16(file), u);
|
|
(*ofs) += 2;
|
|
|
|
uint16_t base_version = (rpy->version & ~REPLAY_VERSION_COMPRESSION_BIT);
|
|
bool compression = (rpy->version & REPLAY_VERSION_COMPRESSION_BIT);
|
|
bool gamev_assumed = false;
|
|
|
|
switch(base_version) {
|
|
case REPLAY_STRUCT_VERSION_TS101000: {
|
|
// legacy format with no versioning, assume v1.1
|
|
TAISEI_VERSION_SET(&rpy->game_version, 1, 1, 0, 0);
|
|
gamev_assumed = true;
|
|
break;
|
|
}
|
|
|
|
case REPLAY_STRUCT_VERSION_TS102000_REV0:
|
|
case REPLAY_STRUCT_VERSION_TS102000_REV1:
|
|
case REPLAY_STRUCT_VERSION_TS102000_REV2:
|
|
{
|
|
if(taisei_version_read(file, &rpy->game_version) != TAISEI_VERSION_SIZE) {
|
|
log_warn("%s: Failed to read game version", source);
|
|
return false;
|
|
}
|
|
|
|
(*ofs) += TAISEI_VERSION_SIZE;
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
log_warn("%s: Unknown struct version %u", source, base_version);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
char *gamev = taisei_version_tostring(&rpy->game_version);
|
|
log_info("Struct version %u (%scompressed), game version %s%s",
|
|
base_version, compression ? "" : "un", gamev, gamev_assumed ? " (assumed)" : "");
|
|
free(gamev);
|
|
|
|
if(compression) {
|
|
CHECKPROP(rpy->fileoffset = SDL_ReadLE32(file), u);
|
|
(*ofs) += 4;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool replay_read_meta(Replay *rpy, SDL_RWops *file, int64_t filesize, const char *source) {
|
|
uint16_t version = rpy->version & ~REPLAY_VERSION_COMPRESSION_BIT;
|
|
|
|
replay_read_string(file, &rpy->playername, version);
|
|
PRINTPROP(rpy->playername, s);
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
CHECKPROP(rpy->flags = SDL_ReadLE32(file), u);
|
|
}
|
|
|
|
CHECKPROP(rpy->numstages = SDL_ReadLE16(file), u);
|
|
|
|
if(!rpy->numstages) {
|
|
log_warn("%s: No stages in replay", source);
|
|
return false;
|
|
}
|
|
|
|
rpy->stages = malloc(sizeof(ReplayStage) * rpy->numstages);
|
|
memset(rpy->stages, 0, sizeof(ReplayStage) * rpy->numstages);
|
|
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
|
ReplayStage *stg = rpy->stages + i;
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
CHECKPROP(stg->flags = SDL_ReadLE32(file), u);
|
|
}
|
|
|
|
CHECKPROP(stg->stage = SDL_ReadLE16(file), u);
|
|
CHECKPROP(stg->seed = SDL_ReadLE32(file), u);
|
|
CHECKPROP(stg->diff = SDL_ReadU8(file), u);
|
|
CHECKPROP(stg->plr_points = SDL_ReadLE32(file), u);
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
CHECKPROP(stg->plr_continues_used = SDL_ReadU8(file), u);
|
|
}
|
|
|
|
CHECKPROP(stg->plr_char = SDL_ReadU8(file), u);
|
|
CHECKPROP(stg->plr_shot = SDL_ReadU8(file), u);
|
|
CHECKPROP(stg->plr_pos_x = SDL_ReadLE16(file), u);
|
|
CHECKPROP(stg->plr_pos_y = SDL_ReadLE16(file), u);
|
|
CHECKPROP(stg->plr_focus = SDL_ReadU8(file), u);
|
|
CHECKPROP(stg->plr_power = SDL_ReadLE16(file), u);
|
|
CHECKPROP(stg->plr_lives = SDL_ReadU8(file), u);
|
|
CHECKPROP(stg->plr_life_fragments = SDL_ReadU8(file), u);
|
|
CHECKPROP(stg->plr_bombs = SDL_ReadU8(file), u);
|
|
CHECKPROP(stg->plr_bomb_fragments = SDL_ReadU8(file), u);
|
|
CHECKPROP(stg->plr_inputflags = SDL_ReadU8(file), u);
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV2) {
|
|
CHECKPROP(stg->plr_graze = SDL_ReadLE16(file), u);
|
|
}
|
|
|
|
CHECKPROP(stg->numevents = SDL_ReadLE16(file), u);
|
|
|
|
if(replay_calc_stageinfo_checksum(stg, version) + SDL_ReadLE32(file)) {
|
|
log_warn("%s: Stageinfo is corrupt", source);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool replay_read_events(Replay *rpy, SDL_RWops *file, int64_t filesize, const char *source) {
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
|
ReplayStage *stg = rpy->stages + i;
|
|
|
|
if(!stg->numevents) {
|
|
log_warn("%s: No events in stage", source);
|
|
return false;
|
|
}
|
|
|
|
stg->events = malloc(sizeof(ReplayEvent) * stg->numevents);
|
|
memset(stg->events, 0, sizeof(ReplayEvent) * stg->numevents);
|
|
|
|
for(int j = 0; j < stg->numevents; ++j) {
|
|
ReplayEvent *evt = stg->events + j;
|
|
|
|
CHECKPROP(evt->frame = SDL_ReadLE32(file), u);
|
|
CHECKPROP(evt->type = SDL_ReadU8(file), u);
|
|
CHECKPROP(evt->value = SDL_ReadLE16(file), u);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool replay_read(Replay *rpy, SDL_RWops *file, ReplayReadMode mode, const char *source) {
|
|
int64_t filesize; // must be signed
|
|
SDL_RWops *vfile = file;
|
|
|
|
if(!source) {
|
|
source = "<unknown>";
|
|
}
|
|
|
|
if(!(mode & REPLAY_READ_ALL) ) {
|
|
log_fatal("%s: Called with invalid read mode %x", source, mode);
|
|
}
|
|
|
|
mode &= REPLAY_READ_ALL;
|
|
filesize = SDL_RWsize(file);
|
|
|
|
if(filesize < 0) {
|
|
log_warn("%s: SDL_RWsize() failed: %s", source, SDL_GetError());
|
|
}
|
|
|
|
if(mode & REPLAY_READ_META) {
|
|
memset(rpy, 0, sizeof(Replay));
|
|
|
|
if(filesize > 0 && filesize <= sizeof(replay_magic_header) + 2) {
|
|
log_warn("%s: Replay file is too short (%"PRIi64")", source, filesize);
|
|
return false;
|
|
}
|
|
|
|
size_t ofs = 0;
|
|
|
|
if(!replay_read_header(rpy, file, filesize, &ofs, source)) {
|
|
return false;
|
|
}
|
|
|
|
bool compression = false;
|
|
|
|
if(rpy->version & REPLAY_VERSION_COMPRESSION_BIT) {
|
|
if(rpy->fileoffset < SDL_RWtell(file)) {
|
|
log_warn("%s: Invalid offset %"PRIi32"", source, rpy->fileoffset);
|
|
return false;
|
|
}
|
|
|
|
vfile = SDL_RWWrapZReader(SDL_RWWrapSegment(file, ofs, rpy->fileoffset, false),
|
|
REPLAY_COMPRESSION_CHUNK_SIZE, true);
|
|
filesize = -1;
|
|
compression = true;
|
|
}
|
|
|
|
if(!replay_read_meta(rpy, vfile, filesize, source)) {
|
|
if(compression) {
|
|
SDL_RWclose(vfile);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if(compression) {
|
|
SDL_RWclose(vfile);
|
|
vfile = file;
|
|
} else {
|
|
rpy->fileoffset = SDL_RWtell(file);
|
|
}
|
|
}
|
|
|
|
if(mode & REPLAY_READ_EVENTS) {
|
|
if(!(mode & REPLAY_READ_META)) {
|
|
if(!rpy->fileoffset) {
|
|
log_fatal("%s: Tried to read events before reading metadata", source);
|
|
}
|
|
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
|
if(rpy->stages[i].events) {
|
|
log_warn("%s: BUG: Reading events into a replay that already had events, call replay_destroy_events() if this is intended", source);
|
|
replay_destroy_events(rpy);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(SDL_RWseek(file, rpy->fileoffset, RW_SEEK_SET) < 0) {
|
|
log_warn("%s: SDL_RWseek() failed: %s", source, SDL_GetError());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool compression = false;
|
|
|
|
if(rpy->version & REPLAY_VERSION_COMPRESSION_BIT) {
|
|
vfile = SDL_RWWrapZReader(file, REPLAY_COMPRESSION_CHUNK_SIZE, false);
|
|
filesize = -1;
|
|
compression = true;
|
|
}
|
|
|
|
if(!replay_read_events(rpy, vfile, filesize, source)) {
|
|
if(compression) {
|
|
SDL_RWclose(vfile);
|
|
}
|
|
|
|
replay_destroy_events(rpy);
|
|
return false;
|
|
}
|
|
|
|
if(compression) {
|
|
SDL_RWclose(vfile);
|
|
}
|
|
|
|
// useless byte to simplify the premature EOF check, can be anything
|
|
SDL_ReadU8(file);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#undef CHECKPROP
|
|
#undef PRINTPROP
|
|
|
|
static char* replay_getpath(const char *name, bool ext) {
|
|
return ext ? strfmt("storage/replays/%s.%s", name, REPLAY_EXTENSION) :
|
|
strfmt("storage/replays/%s", name);
|
|
}
|
|
|
|
bool replay_save(Replay *rpy, const char *name) {
|
|
char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
|
|
char *sp = vfs_repr(p, true);
|
|
log_info("Saving %s", sp);
|
|
free(sp);
|
|
|
|
SDL_RWops *file = vfs_open(p, VFS_MODE_WRITE);
|
|
free(p);
|
|
|
|
if(!file) {
|
|
log_warn("VFS error: %s", vfs_get_error());
|
|
return false;
|
|
}
|
|
|
|
bool result = replay_write(rpy, file, REPLAY_STRUCT_VERSION_WRITE);
|
|
SDL_RWclose(file);
|
|
return result;
|
|
}
|
|
|
|
static const char* replay_mode_string(ReplayReadMode mode) {
|
|
if((mode & REPLAY_READ_ALL) == REPLAY_READ_ALL) {
|
|
return "full";
|
|
}
|
|
|
|
if(mode & REPLAY_READ_META) {
|
|
return "meta";
|
|
}
|
|
|
|
if(mode & REPLAY_READ_EVENTS) {
|
|
return "events";
|
|
}
|
|
|
|
log_fatal("Bad mode %i", mode);
|
|
}
|
|
|
|
bool replay_load(Replay *rpy, const char *name, ReplayReadMode mode) {
|
|
char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
|
|
char *sp = vfs_repr(p, true);
|
|
log_info("Loading %s (%s)", sp, replay_mode_string(mode));
|
|
|
|
SDL_RWops *file = vfs_open(p, VFS_MODE_READ);
|
|
free(p);
|
|
|
|
if(!file) {
|
|
log_warn("VFS error: %s", vfs_get_error());
|
|
free(sp);
|
|
return false;
|
|
}
|
|
|
|
bool result = replay_read(rpy, file, mode, sp);
|
|
|
|
if(!result) {
|
|
replay_destroy(rpy);
|
|
}
|
|
|
|
free(sp);
|
|
SDL_RWclose(file);
|
|
return result;
|
|
}
|
|
|
|
bool replay_load_syspath(Replay *rpy, const char *path, ReplayReadMode mode) {
|
|
log_info("Loading %s (%s)", path, replay_mode_string(mode));
|
|
SDL_RWops *file;
|
|
|
|
#ifndef __WINDOWS__
|
|
if(!strcmp(path, "-"))
|
|
file = SDL_RWFromFP(stdin,false);
|
|
else
|
|
file = SDL_RWFromFile(path, "rb");
|
|
#else
|
|
file = SDL_RWFromFile(path, "rb");
|
|
#endif
|
|
|
|
if(!file) {
|
|
log_warn("SDL_RWFromFile() failed: %s", SDL_GetError());
|
|
return false;
|
|
}
|
|
|
|
bool result = replay_read(rpy, file, mode, path);
|
|
|
|
if(!result) {
|
|
replay_destroy(rpy);
|
|
}
|
|
|
|
SDL_RWclose(file);
|
|
return result;
|
|
}
|
|
|
|
void replay_copy(Replay *dst, Replay *src, bool steal_events) {
|
|
int i;
|
|
|
|
replay_destroy(dst);
|
|
memcpy(dst, src, sizeof(Replay));
|
|
|
|
dst->playername = (char*)malloc(strlen(src->playername)+1);
|
|
strcpy(dst->playername, src->playername);
|
|
|
|
dst->stages = (ReplayStage*)malloc(sizeof(ReplayStage) * src->numstages);
|
|
memcpy(dst->stages, src->stages, sizeof(ReplayStage) * src->numstages);
|
|
|
|
for(i = 0; i < src->numstages; ++i) {
|
|
ReplayStage *s, *d;
|
|
s = src->stages + i;
|
|
d = dst->stages + i;
|
|
|
|
if(steal_events) {
|
|
s->events = NULL;
|
|
} else {
|
|
d->capacity = s->numevents;
|
|
d->events = (ReplayEvent*)malloc(sizeof(ReplayEvent) * d->capacity);
|
|
memcpy(d->events, s->events, sizeof(ReplayEvent) * d->capacity);
|
|
}
|
|
}
|
|
}
|
|
|
|
void replay_stage_check_desync(ReplayStage *stg, int time, uint16_t check, ReplayMode mode) {
|
|
if(!stg || time % (FPS * 5)) {
|
|
return;
|
|
}
|
|
|
|
if(mode == REPLAY_PLAY) {
|
|
if(stg->desync_check && stg->desync_check != check) {
|
|
log_warn("Replay desync detected! %u != %u", stg->desync_check, check);
|
|
stg->desynced = true;
|
|
} else {
|
|
log_debug("%u OK", check);
|
|
}
|
|
}
|
|
#ifdef REPLAY_WRITE_DESYNC_CHECKS
|
|
else {
|
|
// log_debug("%u", check);
|
|
replay_stage_event(stg, time, EV_CHECK_DESYNC, (int16_t)check);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int replay_find_stage_idx(Replay *rpy, uint8_t stageid) {
|
|
assert(rpy != NULL);
|
|
assert(rpy->stages != NULL);
|
|
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
|
if(rpy->stages[i].stage == stageid) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
log_warn("Stage %X was not found in the replay", stageid);
|
|
return -1;
|
|
}
|
|
|
|
void replay_play(Replay *rpy, int firstidx) {
|
|
if(rpy != &global.replay) {
|
|
replay_copy(&global.replay, rpy, true);
|
|
}
|
|
|
|
if(firstidx >= global.replay.numstages || firstidx < 0) {
|
|
log_warn("No stage #%i in the replay", firstidx);
|
|
return;
|
|
}
|
|
|
|
global.replaymode = REPLAY_PLAY;
|
|
|
|
for(int i = firstidx; i < global.replay.numstages; ++i) {
|
|
ReplayStage *rstg = global.replay_stage = global.replay.stages+i;
|
|
StageInfo *gstg = stage_get(rstg->stage);
|
|
|
|
if(!gstg) {
|
|
log_warn("Invalid stage %X in replay at %i skipped.", rstg->stage, i);
|
|
continue;
|
|
}
|
|
|
|
global.plr.mode = plrmode_find(rstg->plr_char, rstg->plr_shot);
|
|
stage_loop(gstg);
|
|
|
|
if(global.game_over == GAMEOVER_ABORT) {
|
|
break;
|
|
}
|
|
|
|
if(global.game_over == GAMEOVER_RESTART) {
|
|
--i;
|
|
}
|
|
|
|
global.game_over = 0;
|
|
}
|
|
|
|
global.game_over = 0;
|
|
global.replaymode = REPLAY_RECORD;
|
|
replay_destroy(&global.replay);
|
|
global.replay_stage = NULL;
|
|
free_resources(false);
|
|
}
|