185 lines
4.9 KiB
C
185 lines
4.9 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#include "log.h"
|
|
#include "replay.h"
|
|
#include "rw_common.h"
|
|
|
|
#include "rwops/rwops_autobuf.h"
|
|
|
|
attr_nonnull_all
|
|
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 void fix_flags(Replay *rpy) {
|
|
dynarray_foreach_elem(&rpy->stages, ReplayStage *stg, {
|
|
if(!(stg->flags & REPLAY_SFLAG_CLEAR)) {
|
|
rpy->flags &= ~REPLAY_GFLAG_CLEAR;
|
|
return;
|
|
}
|
|
});
|
|
|
|
rpy->flags |= REPLAY_GFLAG_CLEAR;
|
|
}
|
|
|
|
static bool replay_write_stage(ReplayStage *stg, SDL_RWops *file, uint16_t version) {
|
|
assert(version >= REPLAY_STRUCT_VERSION_TS103000_REV2);
|
|
|
|
SDL_WriteLE32(file, stg->flags);
|
|
SDL_WriteLE16(file, stg->stage);
|
|
SDL_WriteLE64(file, stg->start_time);
|
|
SDL_WriteLE64(file, stg->rng_seed);
|
|
SDL_WriteU8(file, stg->diff);
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
|
|
SDL_WriteLE16(file, stg->skip_frames);
|
|
}
|
|
|
|
SDL_WriteLE64(file, stg->plr_points);
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
|
|
SDL_WriteU8(file, stg->plr_total_lives_used);
|
|
SDL_WriteU8(file, stg->plr_total_bombs_used);
|
|
}
|
|
|
|
SDL_WriteU8(file, stg->plr_total_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);
|
|
|
|
if(version <= REPLAY_STRUCT_VERSION_TS104000_REV0) {
|
|
// NOTE: old plr_focus field
|
|
SDL_WriteU8(file, 0);
|
|
}
|
|
|
|
SDL_WriteLE16(file, stg->plr_power);
|
|
SDL_WriteU8(file, stg->plr_lives);
|
|
SDL_WriteLE16(file, stg->plr_life_fragments);
|
|
SDL_WriteU8(file, stg->plr_bombs);
|
|
SDL_WriteLE16(file, stg->plr_bomb_fragments);
|
|
SDL_WriteU8(file, stg->plr_inputflags);
|
|
SDL_WriteLE32(file, stg->plr_graze);
|
|
SDL_WriteLE32(file, stg->plr_point_item_value);
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS103000_REV3) {
|
|
SDL_WriteLE64(file, stg->plr_points_final);
|
|
}
|
|
|
|
if(version == REPLAY_STRUCT_VERSION_TS104000_REV0) {
|
|
// NOTE: These fields were always bugged; older taisei only wrote zeroes here.
|
|
uint8_t buf[5] = { 0 };
|
|
SDL_RWwrite(file, buf, sizeof(buf), 1);
|
|
}
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
|
|
SDL_WriteU8(file, stg->plr_stage_lives_used_final);
|
|
SDL_WriteU8(file, stg->plr_stage_bombs_used_final);
|
|
SDL_WriteU8(file, stg->plr_stage_continues_used_final);
|
|
}
|
|
|
|
if(stg->events.num_elements > UINT16_MAX) {
|
|
log_error("Too many events in replay, cannot write this");
|
|
return false;
|
|
}
|
|
|
|
SDL_WriteLE16(file, stg->events.num_elements);
|
|
SDL_WriteLE32(file, 1 + ~replay_struct_stage_metadata_checksum(stg, version));
|
|
|
|
return true;
|
|
}
|
|
|
|
static void replay_write_stage_events(ReplayStage *stg, SDL_RWops *file) {
|
|
dynarray_foreach_elem(&stg->events, ReplayEvent *evt, {
|
|
SDL_WriteLE32(file, evt->frame);
|
|
SDL_WriteU8(file, evt->type);
|
|
SDL_WriteLE16(file, evt->value);
|
|
});
|
|
}
|
|
|
|
static bool replay_write_events(Replay *rpy, SDL_RWops *file) {
|
|
dynarray_foreach_elem(&rpy->stages, ReplayStage *stg, {
|
|
replay_write_stage_events(stg, file);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool replay_write(Replay *rpy, SDL_RWops *file, uint16_t version) {
|
|
assert(version >= REPLAY_STRUCT_VERSION_TS103000_REV2);
|
|
|
|
uint16_t base_version = (version & ~REPLAY_VERSION_COMPRESSION_BIT);
|
|
bool compression = (version & REPLAY_VERSION_COMPRESSION_BIT);
|
|
|
|
SDL_RWwrite(file, replay_magic_header, sizeof(replay_magic_header), 1);
|
|
SDL_WriteLE16(file, version);
|
|
|
|
TaiseiVersion v;
|
|
TAISEI_VERSION_GET_CURRENT(&v);
|
|
|
|
if(taisei_version_write(file, &v) != TAISEI_VERSION_SIZE) {
|
|
log_error("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 = replay_wrap_stream_compress(version, abuf, false);
|
|
}
|
|
|
|
replay_write_string(vfile, rpy->playername, base_version);
|
|
fix_flags(rpy);
|
|
|
|
SDL_WriteLE32(vfile, rpy->flags);
|
|
SDL_WriteLE16(vfile, rpy->stages.num_elements);
|
|
|
|
dynarray_foreach_elem(&rpy->stages, ReplayStage *stg, {
|
|
if(!replay_write_stage(stg, 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 = replay_wrap_stream_compress(version, file, false);
|
|
}
|
|
|
|
bool events_ok = replay_write_events(rpy, vfile);
|
|
|
|
if(compression) {
|
|
SDL_RWclose(vfile);
|
|
}
|
|
|
|
if(!events_ok) {
|
|
return false;
|
|
}
|
|
|
|
// useless byte to simplify the premature EOF check, can be anything
|
|
SDL_WriteU8(file, REPLAY_USELESS_BYTE);
|
|
|
|
return true;
|
|
}
|