2012-07-14 16:37:52 +02:00
|
|
|
/*
|
|
|
|
* This software is licensed under the terms of the MIT-License
|
2017-02-11 04:52:08 +01:00
|
|
|
* See COPYING for further information.
|
2012-07-14 16:37:52 +02:00
|
|
|
* ---
|
|
|
|
* Copyright (C) 2011, Lukas Weber <laochailan@web.de>
|
|
|
|
* Copyright (C) 2012, Alexeyew Andrew <http://akari.thebadasschoobs.org/>
|
|
|
|
*/
|
|
|
|
|
2012-07-14 19:46:03 +02:00
|
|
|
#include "replay.h"
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
2017-02-09 01:13:06 +01:00
|
|
|
#include <time.h>
|
2012-07-14 19:46:03 +02:00
|
|
|
|
|
|
|
#include "global.h"
|
2012-07-15 12:16:27 +02:00
|
|
|
#include "paths/native.h"
|
2017-02-05 02:25:17 +01:00
|
|
|
#include "taisei_err.h"
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
static uint8_t replay_magic_header[] = REPLAY_MAGIC_HEADER;
|
2012-07-14 19:46:03 +02:00
|
|
|
|
2012-08-07 05:28:41 +02:00
|
|
|
void replay_init(Replay *rpy) {
|
2012-07-14 19:46:03 +02:00
|
|
|
memset(rpy, 0, sizeof(Replay));
|
2017-02-17 17:03:49 +01:00
|
|
|
stralloc(&rpy->playername, config_get_str(CONFIG_PLAYERNAME));
|
2012-08-07 05:28:41 +02:00
|
|
|
printf("replay_init(): replay initialized for writting\n");
|
|
|
|
}
|
|
|
|
|
2017-02-10 00:24:19 +01:00
|
|
|
ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t seed, Difficulty diff, uint32_t points, Player *plr) {
|
2012-08-07 05:28:41 +02:00
|
|
|
ReplayStage *s;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
rpy->stages = (ReplayStage*)realloc(rpy->stages, sizeof(ReplayStage) * (++rpy->numstages));
|
2017-02-23 11:06:32 +01:00
|
|
|
s = rpy->stages + rpy->numstages - 1;
|
2012-08-07 05:28:41 +02:00
|
|
|
memset(s, 0, sizeof(ReplayStage));
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-05 02:25:17 +01:00
|
|
|
s->capacity = REPLAY_ALLOC_INITIAL;
|
|
|
|
s->events = (ReplayEvent*)malloc(sizeof(ReplayEvent) * s->capacity);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-05 02:25:17 +01:00
|
|
|
s->stage = stage->id;
|
|
|
|
s->seed = seed;
|
2017-02-10 00:24:19 +01:00
|
|
|
s->diff = diff;
|
|
|
|
s->points = points;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-08 20:59:43 +01:00
|
|
|
s->plr_pos_x = floor(creal(plr->pos));
|
|
|
|
s->plr_pos_y = floor(cimag(plr->pos));
|
2017-02-05 02:25:17 +01:00
|
|
|
|
|
|
|
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;
|
2017-02-08 20:59:43 +01:00
|
|
|
s->plr_power = plr->power;
|
2017-02-05 02:25:17 +01:00
|
|
|
s->plr_moveflags = plr->moveflags;
|
|
|
|
|
2012-08-07 05:28:41 +02:00
|
|
|
printf("replay_init_stage(): created a new stage for writting\n");
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2017-02-15 18:34:47 +01:00
|
|
|
void replay_stage_sync_player_state(ReplayStage *stg, Player *plr) {
|
|
|
|
plr->points = stg->points;
|
|
|
|
plr->shot = stg->plr_shot;
|
|
|
|
plr->cha = stg->plr_char;
|
|
|
|
plr->pos = stg->plr_pos_x + I * stg->plr_pos_y;
|
|
|
|
plr->focus = stg->plr_focus;
|
|
|
|
plr->fire = stg->plr_fire;
|
|
|
|
plr->lifes = stg->plr_lifes;
|
|
|
|
plr->bombs = stg->plr_bombs;
|
|
|
|
plr->power = stg->plr_power;
|
|
|
|
plr->moveflags = stg->plr_moveflags;
|
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
static void replay_destroy_stage(ReplayStage *stage) {
|
2017-02-23 15:05:55 +01:00
|
|
|
free(stage->events);
|
2012-08-07 05:28:41 +02:00
|
|
|
memset(stage, 0, sizeof(ReplayStage));
|
2012-07-14 19:46:03 +02:00
|
|
|
}
|
|
|
|
|
2017-02-09 07:01:38 +01:00
|
|
|
void replay_destroy_events(Replay *rpy) {
|
2017-02-23 15:05:55 +01:00
|
|
|
if(!rpy) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-09 07:01:38 +01:00
|
|
|
if(rpy->stages) {
|
|
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
2017-02-23 11:06:32 +01:00
|
|
|
ReplayStage *stg = rpy->stages + i;
|
2017-02-23 15:05:55 +01:00
|
|
|
free(stg->events);
|
|
|
|
stg->events = NULL;
|
2017-02-09 07:01:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-14 19:46:03 +02:00
|
|
|
void replay_destroy(Replay *rpy) {
|
2017-02-23 15:05:55 +01:00
|
|
|
if(!rpy) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-08-07 05:28:41 +02:00
|
|
|
if(rpy->stages) {
|
2017-02-09 05:06:46 +01:00
|
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
2017-02-23 11:06:32 +01:00
|
|
|
replay_destroy_stage(rpy->stages + i);
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
2012-08-07 05:28:41 +02:00
|
|
|
free(rpy->stages);
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-23 15:05:55 +01:00
|
|
|
free(rpy->playername);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2012-07-14 19:46:03 +02:00
|
|
|
memset(rpy, 0, sizeof(Replay));
|
|
|
|
printf("Replay destroyed.\n");
|
|
|
|
}
|
|
|
|
|
2017-02-10 00:24:19 +01:00
|
|
|
void replay_stage_event(ReplayStage *stg, uint32_t frame, uint8_t type, int16_t value) {
|
|
|
|
if(!stg) {
|
2012-07-14 19:46:03 +02:00
|
|
|
return;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
2017-02-10 00:24:19 +01:00
|
|
|
|
|
|
|
ReplayStage *s = stg;
|
2017-02-23 11:06:32 +01:00
|
|
|
ReplayEvent *e = s->events + s->numevents;
|
2017-02-10 00:24:19 +01:00
|
|
|
e->frame = frame;
|
2017-02-05 02:25:17 +01:00
|
|
|
e->type = type;
|
2017-02-05 03:04:31 +01:00
|
|
|
e->value = (uint16_t)value;
|
2017-02-09 05:06:46 +01:00
|
|
|
s->numevents++;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
if(s->numevents >= s->capacity) {
|
2017-02-09 01:13:06 +01:00
|
|
|
printf("Replay stage reached its capacity of %d, reallocating\n", s->capacity);
|
|
|
|
s->capacity *= 2;
|
2012-08-07 05:28:41 +02:00
|
|
|
s->events = (ReplayEvent*)realloc(s->events, sizeof(ReplayEvent) * s->capacity);
|
|
|
|
printf("The new capacity is %d\n", s->capacity);
|
2012-07-14 19:46:03 +02:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
if(type == EV_OVER) {
|
2012-07-15 12:16:27 +02:00
|
|
|
printf("The replay is OVER\n");
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
2012-07-15 12:16:27 +02:00
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
static void replay_write_string(SDL_RWops *file, char *str) {
|
2017-02-05 02:25:17 +01:00
|
|
|
SDL_WriteLE16(file, strlen(str));
|
|
|
|
SDL_RWwrite(file, str, 1, strlen(str));
|
2012-07-15 12:16:27 +02:00
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
static int replay_write_stage_event(ReplayEvent *evt, SDL_RWops *file) {
|
2017-02-05 02:25:17 +01:00
|
|
|
SDL_WriteLE32(file, evt->frame);
|
|
|
|
SDL_WriteU8(file, evt->type);
|
2017-02-05 03:04:31 +01:00
|
|
|
SDL_WriteLE16(file, evt->value);
|
2012-07-15 20:19:31 +02:00
|
|
|
|
2017-02-11 04:52:08 +01:00
|
|
|
return true;
|
2012-07-15 20:19:31 +02:00
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
static uint32_t replay_calc_stageinfo_checksum(ReplayStage *stg) {
|
2017-02-09 01:55:01 +01:00
|
|
|
uint32_t cs = 0;
|
|
|
|
cs += stg->stage;
|
|
|
|
cs += stg->seed;
|
|
|
|
cs += stg->diff;
|
|
|
|
cs += stg->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_fire;
|
|
|
|
cs += stg->plr_power;
|
|
|
|
cs += stg->plr_lifes;
|
|
|
|
cs += stg->plr_bombs;
|
|
|
|
cs += stg->plr_moveflags;
|
2017-02-09 05:06:46 +01:00
|
|
|
cs += stg->numevents;
|
2017-02-09 01:55:01 +01:00
|
|
|
return cs;
|
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
static int replay_write_stage(ReplayStage *stg, SDL_RWops *file) {
|
2017-02-09 01:13:06 +01:00
|
|
|
SDL_WriteLE16(file, stg->stage);
|
2017-02-05 02:25:17 +01:00
|
|
|
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);
|
2017-02-08 20:59:43 +01:00
|
|
|
SDL_WriteLE16(file, stg->plr_pos_x);
|
|
|
|
SDL_WriteLE16(file, stg->plr_pos_y);
|
2017-02-05 02:25:17 +01:00
|
|
|
SDL_WriteU8(file, stg->plr_focus);
|
|
|
|
SDL_WriteU8(file, stg->plr_fire);
|
2017-02-08 20:59:43 +01:00
|
|
|
SDL_WriteLE16(file, stg->plr_power);
|
2017-02-05 02:25:17 +01:00
|
|
|
SDL_WriteU8(file, stg->plr_lifes);
|
|
|
|
SDL_WriteU8(file, stg->plr_bombs);
|
|
|
|
SDL_WriteU8(file, stg->plr_moveflags);
|
2017-02-09 05:06:46 +01:00
|
|
|
SDL_WriteLE16(file, stg->numevents);
|
2017-02-09 01:55:01 +01:00
|
|
|
SDL_WriteLE32(file, 1 + ~replay_calc_stageinfo_checksum(stg));
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-02-11 04:52:08 +01:00
|
|
|
return true;
|
2012-07-15 20:19:31 +02:00
|
|
|
}
|
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
int replay_write(Replay *rpy, SDL_RWops *file, bool compression) {
|
2017-02-05 02:25:17 +01:00
|
|
|
uint8_t *u8_p;
|
2017-02-09 05:06:46 +01:00
|
|
|
int i, j;
|
2017-02-05 02:25:17 +01:00
|
|
|
|
|
|
|
for(u8_p = replay_magic_header; *u8_p; ++u8_p) {
|
|
|
|
SDL_WriteU8(file, *u8_p);
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
uint16_t version = REPLAY_STRUCT_VERSION;
|
|
|
|
|
|
|
|
if(compression) {
|
|
|
|
version |= REPLAY_VERSION_COMPRESSION_BIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_WriteLE16(file, version);
|
|
|
|
|
|
|
|
void *buf;
|
2017-02-23 04:13:20 +01:00
|
|
|
SDL_RWops *abuf = NULL;
|
2017-02-22 21:52:25 +01:00
|
|
|
SDL_RWops *vfile = file;
|
|
|
|
|
|
|
|
if(compression) {
|
2017-02-23 04:13:20 +01:00
|
|
|
abuf = SDL_RWAutoBuffer(&buf, 64);
|
2017-02-23 05:04:42 +01:00
|
|
|
vfile = SDL_RWWrapZWriter(abuf, REPLAY_COMPRESSION_CHUNK_SIZE, false);
|
2017-02-22 21:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
replay_write_string(vfile, rpy->playername);
|
|
|
|
SDL_WriteLE16(vfile, rpy->numstages);
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
for(i = 0; i < rpy->numstages; ++i) {
|
2017-02-23 11:06:32 +01:00
|
|
|
if(!replay_write_stage(rpy->stages + i, vfile)) {
|
2017-02-23 10:53:57 +01:00
|
|
|
if(compression) {
|
|
|
|
SDL_RWclose(vfile);
|
|
|
|
SDL_RWclose(abuf);
|
|
|
|
}
|
|
|
|
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2012-08-07 05:28:41 +02:00
|
|
|
}
|
2012-07-15 20:19:31 +02:00
|
|
|
}
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
if(compression) {
|
|
|
|
SDL_RWclose(vfile);
|
|
|
|
SDL_WriteLE32(file, SDL_RWtell(file) + SDL_RWtell(abuf) + 4);
|
2017-02-23 04:13:20 +01:00
|
|
|
SDL_RWwrite(file, buf, SDL_RWtell(abuf), 1);
|
|
|
|
SDL_RWclose(abuf);
|
2017-02-23 05:04:42 +01:00
|
|
|
vfile = SDL_RWWrapZWriter(file, REPLAY_COMPRESSION_CHUNK_SIZE, false);
|
2017-02-22 21:52:25 +01:00
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
for(i = 0; i < rpy->numstages; ++i) {
|
2017-02-23 11:06:32 +01:00
|
|
|
ReplayStage *stg = rpy->stages + i;
|
2017-02-09 05:06:46 +01:00
|
|
|
for(j = 0; j < stg->numevents; ++j) {
|
2017-02-23 11:06:32 +01:00
|
|
|
if(!replay_write_stage_event(stg->events + j, vfile)) {
|
2017-02-23 10:53:57 +01:00
|
|
|
if(compression) {
|
|
|
|
SDL_RWclose(vfile);
|
|
|
|
}
|
|
|
|
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
if(compression) {
|
|
|
|
SDL_RWclose(vfile);
|
|
|
|
}
|
|
|
|
|
2017-02-09 01:13:06 +01:00
|
|
|
// useless byte to simplify the premature EOF check, can be anything
|
|
|
|
SDL_WriteU8(file, REPLAY_USELESS_BYTE);
|
|
|
|
|
2017-02-11 04:52:08 +01:00
|
|
|
return true;
|
2012-07-14 16:37:52 +02:00
|
|
|
}
|
2012-07-15 12:16:27 +02:00
|
|
|
|
2017-02-09 01:13:06 +01:00
|
|
|
#ifdef REPLAY_LOAD_GARBAGE_TEST
|
2017-02-09 05:06:46 +01:00
|
|
|
#define PRINTPROP(prop,fmt) printf("replay_read(): " #prop " = %" # fmt " [%li / %li]\n", prop, SDL_RWtell(file), filesize)
|
2017-02-09 01:13:06 +01:00
|
|
|
#else
|
2017-02-21 21:31:46 +01:00
|
|
|
#define PRINTPROP(prop,fmt) (void)(prop)
|
2017-02-09 01:13:06 +01:00
|
|
|
#endif
|
|
|
|
|
2017-02-21 21:31:46 +01:00
|
|
|
#define CHECKPROP(prop,fmt) PRINTPROP(prop,fmt); if(filesize > 0 && SDL_RWtell(file) == filesize) { warnx("replay_read(): premature EOF"); return false; }
|
2017-02-09 01:13:06 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
static void replay_read_string(SDL_RWops *file, char **ptr) {
|
|
|
|
size_t len = SDL_ReadLE16(file);
|
2017-02-09 01:13:06 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
*ptr = malloc(len + 1);
|
|
|
|
memset(*ptr, 0, len + 1);
|
2017-02-09 01:13:06 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
SDL_RWread(file, *ptr, 1, len);
|
|
|
|
}
|
2017-02-09 01:13:06 +01:00
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
static int replay_read_header(Replay *rpy, SDL_RWops *file, int64_t filesize) {
|
2017-02-09 05:06:46 +01:00
|
|
|
for(uint8_t *u8_p = replay_magic_header; *u8_p; ++u8_p) {
|
2017-02-05 02:25:17 +01:00
|
|
|
if(SDL_ReadU8(file) != *u8_p) {
|
|
|
|
warnx("replay_read(): incorrect header");
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2012-07-15 20:19:31 +02:00
|
|
|
}
|
|
|
|
}
|
2012-07-15 12:16:27 +02:00
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
CHECKPROP(rpy->version = SDL_ReadLE16(file), u);
|
|
|
|
|
|
|
|
if((rpy->version & ~REPLAY_VERSION_COMPRESSION_BIT) != REPLAY_STRUCT_VERSION) {
|
2017-02-05 02:25:17 +01:00
|
|
|
warnx("replay_read(): incorrect version");
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-05 02:25:17 +01:00
|
|
|
}
|
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
if(rpy->version & REPLAY_VERSION_COMPRESSION_BIT) {
|
|
|
|
CHECKPROP(rpy->fileoffset = SDL_ReadLE32(file), u);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int replay_read_meta(Replay *rpy, SDL_RWops *file, int64_t filesize) {
|
2017-02-05 02:25:17 +01:00
|
|
|
replay_read_string(file, &rpy->playername);
|
2017-02-21 21:31:46 +01:00
|
|
|
PRINTPROP(rpy->playername, s);
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
CHECKPROP(rpy->numstages = SDL_ReadLE16(file), u);
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
if(!rpy->numstages) {
|
2017-02-05 02:25:17 +01:00
|
|
|
warnx("replay_read(): no stages in replay");
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-05 02:25:17 +01:00
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
rpy->stages = malloc(sizeof(ReplayStage) * rpy->numstages);
|
|
|
|
memset(rpy->stages, 0, sizeof(ReplayStage) * rpy->numstages);
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
2017-02-23 11:06:32 +01:00
|
|
|
ReplayStage *stg = rpy->stages + i;
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-02-09 01:13:06 +01:00
|
|
|
CHECKPROP(stg->stage = SDL_ReadLE16(file), u);
|
|
|
|
CHECKPROP(stg->seed = SDL_ReadLE32(file), u);
|
|
|
|
CHECKPROP(stg->diff = SDL_ReadU8(file), u);
|
|
|
|
CHECKPROP(stg->points = SDL_ReadLE32(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_fire = SDL_ReadU8(file), u);
|
|
|
|
CHECKPROP(stg->plr_power = SDL_ReadLE16(file), u);
|
|
|
|
CHECKPROP(stg->plr_lifes = SDL_ReadU8(file), u);
|
|
|
|
CHECKPROP(stg->plr_bombs = SDL_ReadU8(file), u);
|
|
|
|
CHECKPROP(stg->plr_moveflags = SDL_ReadU8(file), u);
|
2017-02-09 05:06:46 +01:00
|
|
|
CHECKPROP(stg->numevents = SDL_ReadLE16(file), u);
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-02-09 01:55:01 +01:00
|
|
|
if(replay_calc_stageinfo_checksum(stg) + SDL_ReadLE32(file)) {
|
|
|
|
warnx("replay_read(): stageinfo is corrupt");
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-09 01:55:01 +01:00
|
|
|
}
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
2017-02-11 04:52:08 +01:00
|
|
|
return true;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
2017-02-09 01:55:01 +01:00
|
|
|
|
2017-02-21 21:31:46 +01:00
|
|
|
static int replay_read_events(Replay *rpy, SDL_RWops *file, int64_t filesize) {
|
2017-02-09 05:06:46 +01:00
|
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
2017-02-23 11:06:32 +01:00
|
|
|
ReplayStage *stg = rpy->stages + i;
|
2017-02-09 05:06:46 +01:00
|
|
|
|
|
|
|
if(!stg->numevents) {
|
2017-02-05 02:25:17 +01:00
|
|
|
warnx("replay_read(): no events in stage");
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-05 02:25:17 +01:00
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
stg->events = malloc(sizeof(ReplayEvent) * stg->numevents);
|
|
|
|
memset(stg->events, 0, sizeof(ReplayEvent) * stg->numevents);
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
for(int j = 0; j < stg->numevents; ++j) {
|
2017-02-23 11:06:32 +01:00
|
|
|
ReplayEvent *evt = stg->events + j;
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-02-09 01:13:06 +01:00
|
|
|
CHECKPROP(evt->frame = SDL_ReadLE32(file), u);
|
|
|
|
CHECKPROP(evt->type = SDL_ReadU8(file), u);
|
|
|
|
CHECKPROP(evt->value = SDL_ReadLE16(file), u);
|
2017-02-05 02:25:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-11 04:52:08 +01:00
|
|
|
return true;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int replay_read(Replay *rpy, SDL_RWops *file, ReplayReadMode mode) {
|
2017-02-11 10:52:37 +01:00
|
|
|
int64_t filesize; // must be signed
|
2017-02-22 21:52:25 +01:00
|
|
|
SDL_RWops *vfile = file;
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
mode &= REPLAY_READ_ALL;
|
|
|
|
|
|
|
|
if(!mode) {
|
|
|
|
errx(-1, "replay_read(): called with invalid read mode");
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
filesize = SDL_RWsize(file);
|
|
|
|
|
|
|
|
if(filesize < 0) {
|
|
|
|
warnx("replay_read(): SDL_RWsize() failed: %s", SDL_GetError());
|
2017-02-21 21:31:46 +01:00
|
|
|
} else {
|
|
|
|
printf("replay_read(): %li bytes\n", (long int)filesize);
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if(mode & REPLAY_READ_META) {
|
|
|
|
memset(rpy, 0, sizeof(Replay));
|
|
|
|
|
2017-02-21 21:31:46 +01:00
|
|
|
if(filesize > 0 && filesize <= sizeof(replay_magic_header) + 2) {
|
2017-02-09 05:06:46 +01:00
|
|
|
warnx("replay_read(): replay file is too short (%i)", filesize);
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
if(!replay_read_header(rpy, file, filesize)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool compression = false;
|
|
|
|
|
|
|
|
if(rpy->version & REPLAY_VERSION_COMPRESSION_BIT) {
|
|
|
|
if(rpy->fileoffset < SDL_RWtell(file)) {
|
|
|
|
warnx("replay_read(): invalid offset %li", (long int)rpy->fileoffset);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-02-23 05:04:42 +01:00
|
|
|
vfile = SDL_RWWrapZReader(SDL_RWWrapSegment(file, 0, rpy->fileoffset, false),
|
|
|
|
REPLAY_COMPRESSION_CHUNK_SIZE, true);
|
2017-02-22 21:52:25 +01:00
|
|
|
filesize = -1;
|
|
|
|
compression = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!replay_read_meta(rpy, vfile, filesize)) {
|
|
|
|
if(compression) {
|
|
|
|
SDL_RWclose(vfile);
|
|
|
|
}
|
|
|
|
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
2017-02-22 21:52:25 +01:00
|
|
|
|
|
|
|
if(compression) {
|
|
|
|
SDL_RWclose(vfile);
|
|
|
|
vfile = file;
|
|
|
|
} else {
|
|
|
|
rpy->fileoffset = SDL_RWtell(file);
|
|
|
|
}
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if(mode & REPLAY_READ_EVENTS) {
|
|
|
|
if(!(mode & REPLAY_READ_META)) {
|
|
|
|
if(!rpy->fileoffset) {
|
|
|
|
errx(-1, "replay_read(): tried to read events before reading metadata");
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
2017-02-09 07:01:38 +01:00
|
|
|
for(int i = 0; i < rpy->numstages; ++i) {
|
|
|
|
if(rpy->stages[i].events) {
|
|
|
|
warnx("replay_read(): reading events into a replay that already had events, call replay_destroy_events() if this is intended");
|
|
|
|
replay_destroy_events(rpy);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
if(SDL_RWseek(file, rpy->fileoffset, RW_SEEK_SET) < 0) {
|
|
|
|
warnx("replay_read(): SDL_RWseek() failed: %s", SDL_GetError());
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
bool compression = false;
|
|
|
|
|
|
|
|
if(rpy->version & REPLAY_VERSION_COMPRESSION_BIT) {
|
2017-02-23 05:04:42 +01:00
|
|
|
vfile = SDL_RWWrapZReader(file, REPLAY_COMPRESSION_CHUNK_SIZE, false);
|
2017-02-22 21:52:25 +01:00
|
|
|
filesize = -1;
|
|
|
|
compression = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!replay_read_events(rpy, vfile, filesize)) {
|
|
|
|
if(compression) {
|
|
|
|
SDL_RWclose(vfile);
|
|
|
|
}
|
|
|
|
|
2017-02-09 07:01:38 +01:00
|
|
|
replay_destroy_events(rpy);
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
if(compression) {
|
|
|
|
SDL_RWclose(vfile);
|
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
// useless byte to simplify the premature EOF check, can be anything
|
|
|
|
SDL_ReadU8(file);
|
|
|
|
}
|
2017-02-09 01:13:06 +01:00
|
|
|
|
2017-02-11 04:52:08 +01:00
|
|
|
return true;
|
2017-02-05 02:25:17 +01:00
|
|
|
}
|
2012-07-16 17:47:06 +02:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
#undef CHECKPROP
|
|
|
|
#undef PRINTPROP
|
|
|
|
|
2017-02-21 21:31:46 +01:00
|
|
|
char* replay_getpath(const char *name, bool ext) {
|
2012-07-16 17:47:06 +02:00
|
|
|
char *p = (char*)malloc(strlen(get_replays_path()) + strlen(name) + strlen(REPLAY_EXTENSION) + 3);
|
2017-02-09 05:06:46 +01:00
|
|
|
|
|
|
|
if(ext) {
|
2012-07-29 22:39:52 +02:00
|
|
|
sprintf(p, "%s/%s.%s", get_replays_path(), name, REPLAY_EXTENSION);
|
2017-02-09 05:06:46 +01:00
|
|
|
} else {
|
2012-07-29 22:39:52 +02:00
|
|
|
sprintf(p, "%s/%s", get_replays_path(), name);
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
2012-07-16 17:47:06 +02:00
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
2017-02-21 21:31:46 +01:00
|
|
|
int replay_save(Replay *rpy, const char *name) {
|
2012-07-29 22:39:52 +02:00
|
|
|
char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
|
2012-07-16 17:47:06 +02:00
|
|
|
printf("replay_save(): saving %s\n", p);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-05 02:25:17 +01:00
|
|
|
SDL_RWops *file = SDL_RWFromFile(p, "wb");
|
2012-08-05 00:44:08 +02:00
|
|
|
free(p);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-05 02:25:17 +01:00
|
|
|
if(!file) {
|
|
|
|
warnx("replay_save(): SDL_RWFromFile() failed: %s\n", SDL_GetError());
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2012-07-16 17:47:06 +02:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
int result = replay_write(rpy, file, REPLAY_WRITE_COMPRESSED);
|
2017-02-05 02:25:17 +01:00
|
|
|
SDL_RWclose(file);
|
2012-07-16 17:47:06 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-02-21 21:31:46 +01:00
|
|
|
int replay_load(Replay *rpy, const char *name, ReplayReadMode mode) {
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
if(mode & REPLAY_READ_RAWPATH) {
|
|
|
|
p = (char*)name;
|
|
|
|
} else {
|
|
|
|
p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
|
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
printf("replay_load(): loading %s (mode %i)\n", p, mode);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-05 02:25:17 +01:00
|
|
|
SDL_RWops *file = SDL_RWFromFile(p, "rb");
|
2017-02-21 21:31:46 +01:00
|
|
|
|
|
|
|
if(!(mode & REPLAY_READ_RAWPATH)) {
|
|
|
|
free(p);
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-05 02:25:17 +01:00
|
|
|
if(!file) {
|
|
|
|
warnx("replay_save(): SDL_RWFromFile() failed: %s\n", SDL_GetError());
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2012-07-16 17:47:06 +02:00
|
|
|
}
|
2017-02-09 05:06:46 +01:00
|
|
|
|
|
|
|
int result = replay_read(rpy, file, mode);
|
2017-02-05 02:25:17 +01:00
|
|
|
|
|
|
|
if(!result) {
|
|
|
|
replay_destroy(rpy);
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_RWclose(file);
|
2012-07-16 17:47:06 +02:00
|
|
|
return result;
|
|
|
|
}
|
2012-07-29 22:39:52 +02:00
|
|
|
|
2017-02-11 04:52:08 +01:00
|
|
|
void replay_copy(Replay *dst, Replay *src, bool steal_events) {
|
2012-08-07 05:28:41 +02:00
|
|
|
int i;
|
2017-02-21 21:31:46 +01:00
|
|
|
|
2012-08-03 03:06:34 +02:00
|
|
|
replay_destroy(dst);
|
2012-07-29 22:39:52 +02:00
|
|
|
memcpy(dst, src, sizeof(Replay));
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2012-08-05 00:09:37 +02:00
|
|
|
dst->playername = (char*)malloc(strlen(src->playername)+1);
|
2012-08-03 03:06:34 +02:00
|
|
|
strcpy(dst->playername, src->playername);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
dst->stages = (ReplayStage*)malloc(sizeof(ReplayStage) * src->numstages);
|
|
|
|
memcpy(dst->stages, src->stages, sizeof(ReplayStage) * src->numstages);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
for(i = 0; i < src->numstages; ++i) {
|
2012-08-07 05:28:41 +02:00
|
|
|
ReplayStage *s, *d;
|
2017-02-23 11:06:32 +01:00
|
|
|
s = src->stages + i;
|
|
|
|
d = dst->stages + i;
|
2017-02-09 07:01:38 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2012-08-07 05:28:41 +02:00
|
|
|
}
|
2012-07-29 22:39:52 +02:00
|
|
|
}
|
2017-02-05 03:58:27 +01:00
|
|
|
|
2017-02-10 00:24:19 +01:00
|
|
|
void replay_stage_check_desync(ReplayStage *stg, int time, uint16_t check, ReplayMode mode) {
|
|
|
|
if(!stg || time % (FPS * 5)) {
|
2017-02-05 03:58:27 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-10 00:24:19 +01:00
|
|
|
if(mode == REPLAY_PLAY) {
|
|
|
|
if(stg->desync_check && stg->desync_check != check) {
|
|
|
|
warnx("replay_check_desync(): Replay desync detected! %u != %u\n", stg->desync_check, check);
|
2017-02-05 03:58:27 +01:00
|
|
|
} else {
|
|
|
|
printf("replay_check_desync(): %u OK\n", check);
|
|
|
|
}
|
|
|
|
}
|
2017-02-10 00:24:19 +01:00
|
|
|
#ifdef REPLAY_WRITE_DESYNC_CHECKS
|
|
|
|
else {
|
|
|
|
printf("replay_stage_check_desync(): %u\n", check);
|
|
|
|
replay_stage_event(stg, time, EV_CHECK_DESYNC, (int16_t)check);
|
|
|
|
}
|
|
|
|
#endif
|
2017-02-05 03:58:27 +01:00
|
|
|
}
|
2017-02-09 01:13:06 +01:00
|
|
|
|
|
|
|
int replay_test(void) {
|
|
|
|
#ifdef REPLAY_LOAD_GARBAGE_TEST
|
|
|
|
int sz = getenvint("TAISEI_REPLAY_LOAD_GARBAGE_TEST");
|
|
|
|
int headsz = sizeof(replay_magic_header) + 8; // 8 = version (uint16) + strlen (uint16) + plrname "test" (char[4])
|
|
|
|
|
|
|
|
if(sz <= 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t *u8_p, *buf = malloc(sz + headsz);
|
|
|
|
SDL_RWops *handle = SDL_RWFromMem(buf, sz + headsz);
|
|
|
|
|
|
|
|
// SDL_RWwrite(handle, replay_magic_header, 1, sizeof(replay_magic_header));
|
|
|
|
|
|
|
|
for(u8_p = replay_magic_header; *u8_p; ++u8_p) {
|
|
|
|
SDL_WriteU8(handle, *u8_p);
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_WriteLE16(handle, REPLAY_STRUCT_VERSION);
|
|
|
|
SDL_WriteLE16(handle, 4);
|
|
|
|
SDL_WriteU8(handle, 't');
|
|
|
|
SDL_WriteU8(handle, 'e');
|
|
|
|
SDL_WriteU8(handle, 's');
|
|
|
|
SDL_WriteU8(handle, 't');
|
|
|
|
|
|
|
|
printf("replay_test(): wrote a valid replay header\n");
|
|
|
|
|
|
|
|
RandomState rnd;
|
|
|
|
tsrand_init(&rnd, time(0));
|
|
|
|
|
|
|
|
for(int i = 0; i < sz; ++i) {
|
|
|
|
SDL_WriteU8(handle, tsrand_p(&rnd) & 0xFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("replay_test(): wrote %i bytes of garbage\n", sz);
|
|
|
|
|
|
|
|
SDL_RWseek(handle, 0, RW_SEEK_SET);
|
|
|
|
|
|
|
|
for(int i = 0; i < headsz; ++i) {
|
|
|
|
printf("%x ", buf[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
Replay rpy;
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
if(replay_read(&rpy, handle, REPLAY_READ_ALL)) {
|
2017-02-09 01:13:06 +01:00
|
|
|
errx(-1, "Succeeded loading garbage data as a replay... that shouldn't happen");
|
|
|
|
}
|
|
|
|
|
2017-02-09 01:55:01 +01:00
|
|
|
replay_destroy(&rpy);
|
2017-02-09 01:13:06 +01:00
|
|
|
free(buf);
|
|
|
|
return 1;
|
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
}
|
2017-02-21 21:31:46 +01:00
|
|
|
|
|
|
|
void replay_play(Replay *rpy, int firststage) {
|
|
|
|
if(rpy != &global.replay) {
|
|
|
|
replay_copy(&global.replay, rpy, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
global.replaymode = REPLAY_PLAY;
|
|
|
|
|
|
|
|
if(global.replay.numstages == 1) {
|
|
|
|
firststage = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int i = firststage; i < global.replay.numstages; ++i) {
|
|
|
|
ReplayStage *rstg = global.replay_stage = global.replay.stages+i;
|
|
|
|
StageInfo *gstg = stage_get(rstg->stage);
|
|
|
|
|
|
|
|
if(!gstg) {
|
|
|
|
printf("replay_play(): Invalid stage %d in replay at %i skipped.\n", rstg->stage, i);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
global.stage = gstg;
|
|
|
|
gstg->loop();
|
|
|
|
|
|
|
|
if(global.game_over == GAMEOVER_ABORT) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
global.game_over = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
global.game_over = 0;
|
|
|
|
global.replaymode = REPLAY_RECORD;
|
|
|
|
replay_destroy(&global.replay);
|
|
|
|
global.replay_stage = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void replay_play_path(const char *path, int firststage) {
|
|
|
|
replay_destroy(&global.replay);
|
|
|
|
|
|
|
|
if(!replay_load(&global.replay, path, REPLAY_READ_ALL | REPLAY_READ_RAWPATH)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
replay_play(&global.replay, firststage);
|
|
|
|
}
|