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
|
|
|
* ---
|
2017-09-12 03:28:15 +02:00
|
|
|
* Copyright (c) 2011-2017, Lukas Weber <laochailan@web.de>.
|
|
|
|
* Copyright (c) 2012-2017, Andrei Alexeyev <akari@alienslab.net>.
|
2012-07-14 16:37:52 +02:00
|
|
|
*/
|
|
|
|
|
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"
|
2017-02-05 02:25:17 +01:00
|
|
|
|
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));
|
Implemented a simple and consistent logging subsystem
The goal of this change is mainly to clean up Taisei's codebase and
improve its console output. I've been frustrated by files littered with
inconsistent printf/fprintf/warnx/errx calls for a long time, and now I
actually did something about it.
All the above functions are now considered deprecated and result in a
compile-time warning when used. Instead, the following macros should be
used:
log_debug(format, ...)
log_info(format, ...)
log_warn(format, ...)
log_err(format, ...)
As you can see, all of them have the same printf-like interface. But
they have different functionality and purpose:
log_debug is intended for very verbose and specific information. It
does nothing in release builds, much like assert(), so don't use
expressions with side-effects in its arguments.
log_info is for various status updates that are expected during
normal operation of the program.
log_warn is for non-critical failures or other things that may be
worth investigating, but don't inherently render the program
non-functional.
log_err is for when the only choice is to give up. Like errx, it
also terminates the program. Unlike errx, it actually calls abort(),
which means the cleanup functions are not ran -- but on the other
hand, you get a debuggable backtrace. However, if you're trying to
catch programming errors, consider using assert() instead.
All of them produce output that contains a timestamp, the log level
identifier, the calling function's name, and the formatted message.
The newline at the end of the format string is not required -- no, it is
actually *prohibited*. The logging system will take care of the line
breaks by itself, don't litter the code with that shit.
Internally, the logging system is based on the SDL_RWops abstraction,
and may have multiple, configurable destinations. This makes it easily
extensible. Currently, log_debug and log_info are set to write to
stdout, log_warn and log_err to stderr, and all of them also to the file
log.txt in the Taisei config directory.
Consequently, the nasty freopen hacks we used to make Taisei write to
log files on Windows are no longer needed -- which is a very good thing,
considering they probably would break if the configdir path contains
UTF-8 characters. SDL_RWFromFile does not suffer this limitation.
As an added bonus, it's also thread-safe.
Note about printf and fprintf: in very few cases, the logging system is
not a good substitute for these functions. That is, when you care about
writing exactly to stdout/stderr and about exactly how the output looks.
However, I insist on keeping the deprecation warnings on them to not
tempt anyone to use them for logging/debugging out of habit and/or
laziness.
For this reason, I've added a tsfprintf function to util.c. It is
functionally identical to fprintf, except it returns void. Yes, the name
is deliberately ugly. Avoid using it if possible, but if you must, only
use it to write to stdout or stderr. Do not write to actual files with
it, use SDL_RWops.
2017-03-13 03:51:58 +01:00
|
|
|
log_debug("Replay at %p initialized for writing", (void*)rpy);
|
2012-08-07 05:28:41 +02:00
|
|
|
}
|
|
|
|
|
2017-10-29 23:45:24 +01:00
|
|
|
ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t seed, Difficulty diff, 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;
|
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
|
|
|
|
2017-10-29 23:45:24 +01:00
|
|
|
s->plr_points = plr->points;
|
|
|
|
s->plr_continues_used = plr->continues_used;
|
2017-02-05 02:25:17 +01:00
|
|
|
s->plr_focus = plr->focus;
|
2017-10-08 13:30:51 +02:00
|
|
|
s->plr_char = plr->mode->character->id;
|
|
|
|
s->plr_shot = plr->mode->shot_mode;
|
2017-03-21 11:09:32 +01:00
|
|
|
s->plr_lives = plr->lives;
|
|
|
|
s->plr_life_fragments = plr->life_fragments;
|
2017-02-05 02:25:17 +01:00
|
|
|
s->plr_bombs = plr->bombs;
|
2017-03-21 11:09:32 +01:00
|
|
|
s->plr_bomb_fragments = plr->bomb_fragments;
|
2017-02-08 20:59:43 +01:00
|
|
|
s->plr_power = plr->power;
|
2017-03-06 15:24:57 +01:00
|
|
|
s->plr_inputflags = plr->inputflags;
|
2017-02-05 02:25:17 +01:00
|
|
|
|
Implemented a simple and consistent logging subsystem
The goal of this change is mainly to clean up Taisei's codebase and
improve its console output. I've been frustrated by files littered with
inconsistent printf/fprintf/warnx/errx calls for a long time, and now I
actually did something about it.
All the above functions are now considered deprecated and result in a
compile-time warning when used. Instead, the following macros should be
used:
log_debug(format, ...)
log_info(format, ...)
log_warn(format, ...)
log_err(format, ...)
As you can see, all of them have the same printf-like interface. But
they have different functionality and purpose:
log_debug is intended for very verbose and specific information. It
does nothing in release builds, much like assert(), so don't use
expressions with side-effects in its arguments.
log_info is for various status updates that are expected during
normal operation of the program.
log_warn is for non-critical failures or other things that may be
worth investigating, but don't inherently render the program
non-functional.
log_err is for when the only choice is to give up. Like errx, it
also terminates the program. Unlike errx, it actually calls abort(),
which means the cleanup functions are not ran -- but on the other
hand, you get a debuggable backtrace. However, if you're trying to
catch programming errors, consider using assert() instead.
All of them produce output that contains a timestamp, the log level
identifier, the calling function's name, and the formatted message.
The newline at the end of the format string is not required -- no, it is
actually *prohibited*. The logging system will take care of the line
breaks by itself, don't litter the code with that shit.
Internally, the logging system is based on the SDL_RWops abstraction,
and may have multiple, configurable destinations. This makes it easily
extensible. Currently, log_debug and log_info are set to write to
stdout, log_warn and log_err to stderr, and all of them also to the file
log.txt in the Taisei config directory.
Consequently, the nasty freopen hacks we used to make Taisei write to
log files on Windows are no longer needed -- which is a very good thing,
considering they probably would break if the configdir path contains
UTF-8 characters. SDL_RWFromFile does not suffer this limitation.
As an added bonus, it's also thread-safe.
Note about printf and fprintf: in very few cases, the logging system is
not a good substitute for these functions. That is, when you care about
writing exactly to stdout/stderr and about exactly how the output looks.
However, I insist on keeping the deprecation warnings on them to not
tempt anyone to use them for logging/debugging out of habit and/or
laziness.
For this reason, I've added a tsfprintf function to util.c. It is
functionally identical to fprintf, except it returns void. Yes, the name
is deliberately ugly. Avoid using it if possible, but if you must, only
use it to write to stdout or stderr. Do not write to actual files with
it, use SDL_RWops.
2017-03-13 03:51:58 +01:00
|
|
|
log_debug("Created a new stage %p in replay %p", (void*)s, (void*)rpy);
|
2012-08-07 05:28:41 +02:00
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2017-02-15 18:34:47 +01:00
|
|
|
void replay_stage_sync_player_state(ReplayStage *stg, Player *plr) {
|
2017-10-29 23:45:24 +01:00
|
|
|
plr->points = stg->plr_points;
|
|
|
|
plr->continues_used = stg->plr_continues_used;
|
2017-10-08 13:30:51 +02:00
|
|
|
plr->mode = plrmode_find(stg->plr_char, stg->plr_shot);
|
2017-02-15 18:34:47 +01:00
|
|
|
plr->pos = stg->plr_pos_x + I * stg->plr_pos_y;
|
|
|
|
plr->focus = stg->plr_focus;
|
2017-03-21 11:09:32 +01:00
|
|
|
plr->lives = stg->plr_lives;
|
|
|
|
plr->life_fragments = stg->plr_life_fragments;
|
2017-02-15 18:34:47 +01:00
|
|
|
plr->bombs = stg->plr_bombs;
|
2017-03-21 11:09:32 +01:00
|
|
|
plr->bomb_fragments = stg->plr_bomb_fragments;
|
2017-02-15 18:34:47 +01:00
|
|
|
plr->power = stg->plr_power;
|
2017-03-06 15:24:57 +01:00
|
|
|
plr->inputflags = stg->plr_inputflags;
|
2017-02-15 18:34:47 +01:00
|
|
|
}
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2017-09-19 20:46:28 +02:00
|
|
|
void replay_stage_event(ReplayStage *stg, uint32_t frame, uint8_t type, uint16_t value) {
|
2017-02-10 00:24:19 +01:00
|
|
|
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-09-19 20:46:28 +02: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-09-19 20:46:28 +02:00
|
|
|
e->value = value;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
if(s->numevents >= s->capacity) {
|
Implemented a simple and consistent logging subsystem
The goal of this change is mainly to clean up Taisei's codebase and
improve its console output. I've been frustrated by files littered with
inconsistent printf/fprintf/warnx/errx calls for a long time, and now I
actually did something about it.
All the above functions are now considered deprecated and result in a
compile-time warning when used. Instead, the following macros should be
used:
log_debug(format, ...)
log_info(format, ...)
log_warn(format, ...)
log_err(format, ...)
As you can see, all of them have the same printf-like interface. But
they have different functionality and purpose:
log_debug is intended for very verbose and specific information. It
does nothing in release builds, much like assert(), so don't use
expressions with side-effects in its arguments.
log_info is for various status updates that are expected during
normal operation of the program.
log_warn is for non-critical failures or other things that may be
worth investigating, but don't inherently render the program
non-functional.
log_err is for when the only choice is to give up. Like errx, it
also terminates the program. Unlike errx, it actually calls abort(),
which means the cleanup functions are not ran -- but on the other
hand, you get a debuggable backtrace. However, if you're trying to
catch programming errors, consider using assert() instead.
All of them produce output that contains a timestamp, the log level
identifier, the calling function's name, and the formatted message.
The newline at the end of the format string is not required -- no, it is
actually *prohibited*. The logging system will take care of the line
breaks by itself, don't litter the code with that shit.
Internally, the logging system is based on the SDL_RWops abstraction,
and may have multiple, configurable destinations. This makes it easily
extensible. Currently, log_debug and log_info are set to write to
stdout, log_warn and log_err to stderr, and all of them also to the file
log.txt in the Taisei config directory.
Consequently, the nasty freopen hacks we used to make Taisei write to
log files on Windows are no longer needed -- which is a very good thing,
considering they probably would break if the configdir path contains
UTF-8 characters. SDL_RWFromFile does not suffer this limitation.
As an added bonus, it's also thread-safe.
Note about printf and fprintf: in very few cases, the logging system is
not a good substitute for these functions. That is, when you care about
writing exactly to stdout/stderr and about exactly how the output looks.
However, I insist on keeping the deprecation warnings on them to not
tempt anyone to use them for logging/debugging out of habit and/or
laziness.
For this reason, I've added a tsfprintf function to util.c. It is
functionally identical to fprintf, except it returns void. Yes, the name
is deliberately ugly. Avoid using it if possible, but if you must, only
use it to write to stdout or stderr. Do not write to actual files with
it, use SDL_RWops.
2017-03-13 03:51:58 +01:00
|
|
|
log_debug("Replay stage reached its capacity of %d, reallocating", s->capacity);
|
2017-02-09 01:13:06 +01:00
|
|
|
s->capacity *= 2;
|
2012-08-07 05:28:41 +02:00
|
|
|
s->events = (ReplayEvent*)realloc(s->events, sizeof(ReplayEvent) * s->capacity);
|
Implemented a simple and consistent logging subsystem
The goal of this change is mainly to clean up Taisei's codebase and
improve its console output. I've been frustrated by files littered with
inconsistent printf/fprintf/warnx/errx calls for a long time, and now I
actually did something about it.
All the above functions are now considered deprecated and result in a
compile-time warning when used. Instead, the following macros should be
used:
log_debug(format, ...)
log_info(format, ...)
log_warn(format, ...)
log_err(format, ...)
As you can see, all of them have the same printf-like interface. But
they have different functionality and purpose:
log_debug is intended for very verbose and specific information. It
does nothing in release builds, much like assert(), so don't use
expressions with side-effects in its arguments.
log_info is for various status updates that are expected during
normal operation of the program.
log_warn is for non-critical failures or other things that may be
worth investigating, but don't inherently render the program
non-functional.
log_err is for when the only choice is to give up. Like errx, it
also terminates the program. Unlike errx, it actually calls abort(),
which means the cleanup functions are not ran -- but on the other
hand, you get a debuggable backtrace. However, if you're trying to
catch programming errors, consider using assert() instead.
All of them produce output that contains a timestamp, the log level
identifier, the calling function's name, and the formatted message.
The newline at the end of the format string is not required -- no, it is
actually *prohibited*. The logging system will take care of the line
breaks by itself, don't litter the code with that shit.
Internally, the logging system is based on the SDL_RWops abstraction,
and may have multiple, configurable destinations. This makes it easily
extensible. Currently, log_debug and log_info are set to write to
stdout, log_warn and log_err to stderr, and all of them also to the file
log.txt in the Taisei config directory.
Consequently, the nasty freopen hacks we used to make Taisei write to
log files on Windows are no longer needed -- which is a very good thing,
considering they probably would break if the configdir path contains
UTF-8 characters. SDL_RWFromFile does not suffer this limitation.
As an added bonus, it's also thread-safe.
Note about printf and fprintf: in very few cases, the logging system is
not a good substitute for these functions. That is, when you care about
writing exactly to stdout/stderr and about exactly how the output looks.
However, I insist on keeping the deprecation warnings on them to not
tempt anyone to use them for logging/debugging out of habit and/or
laziness.
For this reason, I've added a tsfprintf function to util.c. It is
functionally identical to fprintf, except it returns void. Yes, the name
is deliberately ugly. Avoid using it if possible, but if you must, only
use it to write to stdout or stderr. Do not write to actual files with
it, use SDL_RWops.
2017-03-13 03:51:58 +01:00
|
|
|
log_debug("The new capacity is %d", 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) {
|
Implemented a simple and consistent logging subsystem
The goal of this change is mainly to clean up Taisei's codebase and
improve its console output. I've been frustrated by files littered with
inconsistent printf/fprintf/warnx/errx calls for a long time, and now I
actually did something about it.
All the above functions are now considered deprecated and result in a
compile-time warning when used. Instead, the following macros should be
used:
log_debug(format, ...)
log_info(format, ...)
log_warn(format, ...)
log_err(format, ...)
As you can see, all of them have the same printf-like interface. But
they have different functionality and purpose:
log_debug is intended for very verbose and specific information. It
does nothing in release builds, much like assert(), so don't use
expressions with side-effects in its arguments.
log_info is for various status updates that are expected during
normal operation of the program.
log_warn is for non-critical failures or other things that may be
worth investigating, but don't inherently render the program
non-functional.
log_err is for when the only choice is to give up. Like errx, it
also terminates the program. Unlike errx, it actually calls abort(),
which means the cleanup functions are not ran -- but on the other
hand, you get a debuggable backtrace. However, if you're trying to
catch programming errors, consider using assert() instead.
All of them produce output that contains a timestamp, the log level
identifier, the calling function's name, and the formatted message.
The newline at the end of the format string is not required -- no, it is
actually *prohibited*. The logging system will take care of the line
breaks by itself, don't litter the code with that shit.
Internally, the logging system is based on the SDL_RWops abstraction,
and may have multiple, configurable destinations. This makes it easily
extensible. Currently, log_debug and log_info are set to write to
stdout, log_warn and log_err to stderr, and all of them also to the file
log.txt in the Taisei config directory.
Consequently, the nasty freopen hacks we used to make Taisei write to
log files on Windows are no longer needed -- which is a very good thing,
considering they probably would break if the configdir path contains
UTF-8 characters. SDL_RWFromFile does not suffer this limitation.
As an added bonus, it's also thread-safe.
Note about printf and fprintf: in very few cases, the logging system is
not a good substitute for these functions. That is, when you care about
writing exactly to stdout/stderr and about exactly how the output looks.
However, I insist on keeping the deprecation warnings on them to not
tempt anyone to use them for logging/debugging out of habit and/or
laziness.
For this reason, I've added a tsfprintf function to util.c. It is
functionally identical to fprintf, except it returns void. Yes, the name
is deliberately ugly. Avoid using it if possible, but if you must, only
use it to write to stdout or stderr. Do not write to actual files with
it, use SDL_RWops.
2017-03-13 03:51:58 +01:00
|
|
|
log_debug("The replay is OVER");
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
2012-07-15 12:16:27 +02:00
|
|
|
}
|
|
|
|
|
2017-10-29 23:45:24 +01:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2017-02-05 02:25:17 +01:00
|
|
|
SDL_RWwrite(file, str, 1, strlen(str));
|
2012-07-15 12:16:27 +02:00
|
|
|
}
|
|
|
|
|
2017-04-18 21:48:18 +02:00
|
|
|
static bool 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-10-29 23:45:24 +01:00
|
|
|
static uint32_t replay_calc_stageinfo_checksum(ReplayStage *stg, uint16_t version) {
|
2017-02-09 01:55:01 +01:00
|
|
|
uint32_t cs = 0;
|
2017-10-29 23:45:24 +01:00
|
|
|
|
2017-02-09 01:55:01 +01:00
|
|
|
cs += stg->stage;
|
|
|
|
cs += stg->seed;
|
|
|
|
cs += stg->diff;
|
2017-10-29 23:45:24 +01:00
|
|
|
cs += stg->plr_points;
|
2017-02-09 01:55:01 +01:00
|
|
|
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;
|
2017-03-21 11:09:32 +01:00
|
|
|
cs += stg->plr_lives;
|
|
|
|
cs += stg->plr_life_fragments;
|
2017-02-09 01:55:01 +01:00
|
|
|
cs += stg->plr_bombs;
|
2017-03-21 11:09:32 +01:00
|
|
|
cs += stg->plr_bomb_fragments;
|
2017-03-06 15:24:57 +01:00
|
|
|
cs += stg->plr_inputflags;
|
2017-02-09 05:06:46 +01:00
|
|
|
cs += stg->numevents;
|
2017-10-29 23:45:24 +01:00
|
|
|
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
|
|
cs += stg->plr_continues_used;
|
|
|
|
cs += stg->flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
log_debug("%08x", cs);
|
2017-02-09 01:55:01 +01:00
|
|
|
return cs;
|
|
|
|
}
|
|
|
|
|
2017-10-29 23:45:24 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
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);
|
2017-10-29 23:45:24 +01:00
|
|
|
SDL_WriteLE32(file, stg->plr_points);
|
|
|
|
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
|
|
SDL_WriteU8(file, stg->plr_continues_used);
|
|
|
|
}
|
|
|
|
|
2017-02-05 02:25:17 +01:00
|
|
|
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);
|
2017-02-08 20:59:43 +01:00
|
|
|
SDL_WriteLE16(file, stg->plr_power);
|
2017-03-21 11:09:32 +01:00
|
|
|
SDL_WriteU8(file, stg->plr_lives);
|
|
|
|
SDL_WriteU8(file, stg->plr_life_fragments);
|
2017-02-05 02:25:17 +01:00
|
|
|
SDL_WriteU8(file, stg->plr_bombs);
|
2017-03-21 11:09:32 +01:00
|
|
|
SDL_WriteU8(file, stg->plr_bomb_fragments);
|
2017-03-06 15:24:57 +01:00
|
|
|
SDL_WriteU8(file, stg->plr_inputflags);
|
2017-02-09 05:06:46 +01:00
|
|
|
SDL_WriteLE16(file, stg->numevents);
|
2017-10-29 23:45:24 +01:00
|
|
|
SDL_WriteLE32(file, 1 + ~replay_calc_stageinfo_checksum(stg, version));
|
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-10-29 23:45:24 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 21:50:22 +02:00
|
|
|
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);
|
2017-02-09 05:06:46 +01:00
|
|
|
int i, j;
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-03-28 23:45:06 +02:00
|
|
|
SDL_RWwrite(file, replay_magic_header, sizeof(replay_magic_header), 1);
|
2017-09-26 21:50:22 +02:00
|
|
|
SDL_WriteLE16(file, version);
|
2017-02-22 21:52:25 +01:00
|
|
|
|
2017-09-26 21:50:22 +02:00
|
|
|
if(base_version >= REPLAY_STRUCT_VERSION_TS102000_REV0) {
|
|
|
|
TaiseiVersion v;
|
|
|
|
TAISEI_VERSION_GET_CURRENT(&v);
|
2017-02-22 21:52:25 +01:00
|
|
|
|
2017-09-26 21:50:22 +02:00
|
|
|
if(taisei_version_write(file, &v) != TAISEI_VERSION_SIZE) {
|
|
|
|
log_warn("Failed to write game version: %s", SDL_GetError());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2017-02-22 21:52:25 +01:00
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2017-10-29 23:45:24 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-02-22 21:52:25 +01:00
|
|
|
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-10-29 23:45:24 +01:00
|
|
|
if(!replay_write_stage(rpy->stages + i, vfile, base_version)) {
|
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-10-29 23:45:24 +01:00
|
|
|
#ifdef REPLAY_LOAD_DEBUG
|
2017-04-21 01:11:53 +02:00
|
|
|
#define PRINTPROP(prop,fmt) log_debug(#prop " = %" # fmt " [%"PRIi64" / %"PRIi64"]", 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-09-26 01:59:33 +02:00
|
|
|
#define CHECKPROP(prop,fmt) PRINTPROP(prop,fmt); if(filesize > 0 && SDL_RWtell(file) == filesize) { log_warn("%s: Premature EOF", source); return false; }
|
2017-02-09 01:13:06 +01:00
|
|
|
|
2017-10-29 23:45:24 +01:00
|
|
|
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);
|
|
|
|
}
|
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-09-26 01:59:33 +02:00
|
|
|
static bool replay_read_header(Replay *rpy, SDL_RWops *file, int64_t filesize, size_t *ofs, const char *source) {
|
2017-03-28 23:45:06 +02:00
|
|
|
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))) {
|
2017-09-26 01:59:33 +02:00
|
|
|
log_warn("%s: Incorrect header", source);
|
2017-03-28 23:45:06 +02: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);
|
2017-02-27 20:14:20 +01:00
|
|
|
(*ofs) += 2;
|
2017-02-22 21:52:25 +01:00
|
|
|
|
2017-09-26 21:50:22 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-10-29 23:45:24 +01:00
|
|
|
case REPLAY_STRUCT_VERSION_TS102000_REV0:
|
|
|
|
case REPLAY_STRUCT_VERSION_TS102000_REV1:
|
|
|
|
{
|
2017-09-26 21:50:22 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-02-05 02:25:17 +01:00
|
|
|
}
|
|
|
|
|
2017-09-26 21:50:22 +02:00
|
|
|
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) {
|
2017-02-22 21:52:25 +01:00
|
|
|
CHECKPROP(rpy->fileoffset = SDL_ReadLE32(file), u);
|
2017-09-26 21:50:22 +02:00
|
|
|
(*ofs) += 4;
|
2017-02-22 21:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
static bool replay_read_meta(Replay *rpy, SDL_RWops *file, int64_t filesize, const char *source) {
|
2017-10-29 23:45:24 +01:00
|
|
|
uint16_t version = rpy->version & ~REPLAY_VERSION_COMPRESSION_BIT;
|
|
|
|
|
|
|
|
replay_read_string(file, &rpy->playername, version);
|
2017-02-21 21:31:46 +01:00
|
|
|
PRINTPROP(rpy->playername, s);
|
2017-02-05 02:25:17 +01:00
|
|
|
|
2017-10-29 23:45:24 +01:00
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
|
|
CHECKPROP(rpy->flags = SDL_ReadLE32(file), u);
|
|
|
|
}
|
|
|
|
|
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-09-26 01:59:33 +02:00
|
|
|
log_warn("%s: No stages in replay", source);
|
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-10-29 23:45:24 +01:00
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
|
|
CHECKPROP(stg->flags = SDL_ReadLE32(file), u);
|
|
|
|
}
|
|
|
|
|
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);
|
2017-10-29 23:45:24 +01:00
|
|
|
CHECKPROP(stg->plr_points = SDL_ReadLE32(file), u);
|
|
|
|
|
|
|
|
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
|
|
|
|
CHECKPROP(stg->plr_continues_used = SDL_ReadU8(file), u);
|
|
|
|
}
|
|
|
|
|
2017-02-09 01:13:06 +01:00
|
|
|
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);
|
2017-03-21 11:09:32 +01:00
|
|
|
CHECKPROP(stg->plr_lives = SDL_ReadU8(file), u);
|
|
|
|
CHECKPROP(stg->plr_life_fragments = SDL_ReadU8(file), u);
|
2017-02-09 01:13:06 +01:00
|
|
|
CHECKPROP(stg->plr_bombs = SDL_ReadU8(file), u);
|
2017-03-21 11:09:32 +01:00
|
|
|
CHECKPROP(stg->plr_bomb_fragments = SDL_ReadU8(file), u);
|
2017-03-06 15:24:57 +01:00
|
|
|
CHECKPROP(stg->plr_inputflags = 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-10-29 23:45:24 +01:00
|
|
|
if(replay_calc_stageinfo_checksum(stg, version) + SDL_ReadLE32(file)) {
|
2017-09-26 01:59:33 +02:00
|
|
|
log_warn("%s: Stageinfo is corrupt", source);
|
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-09-26 01:59:33 +02:00
|
|
|
static bool replay_read_events(Replay *rpy, SDL_RWops *file, int64_t filesize, const char *source) {
|
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-09-26 01:59:33 +02:00
|
|
|
log_warn("%s: No events in stage", source);
|
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
|
|
|
}
|
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
bool replay_read(Replay *rpy, SDL_RWops *file, ReplayReadMode mode, const char *source) {
|
2017-04-21 01:28:15 +02:00
|
|
|
int64_t filesize; // must be signed
|
2017-02-22 21:52:25 +01:00
|
|
|
SDL_RWops *vfile = file;
|
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
if(!source) {
|
|
|
|
source = "<unknown>";
|
|
|
|
}
|
2017-02-09 05:06:46 +01:00
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
if(!(mode & REPLAY_READ_ALL) ) {
|
|
|
|
log_fatal("%s: Called with invalid read mode %x", source, mode);
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
mode &= REPLAY_READ_ALL;
|
2017-02-09 05:06:46 +01:00
|
|
|
filesize = SDL_RWsize(file);
|
|
|
|
|
|
|
|
if(filesize < 0) {
|
2017-09-26 01:59:33 +02:00
|
|
|
log_warn("%s: SDL_RWsize() failed: %s", source, SDL_GetError());
|
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-09-26 01:59:33 +02:00
|
|
|
log_warn("%s: Replay file is too short (%"PRIi64")", source, filesize);
|
2017-02-11 04:52:08 +01:00
|
|
|
return false;
|
2017-02-09 05:06:46 +01:00
|
|
|
}
|
|
|
|
|
2017-02-27 20:14:20 +01:00
|
|
|
size_t ofs = 0;
|
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
if(!replay_read_header(rpy, file, filesize, &ofs, source)) {
|
2017-02-22 21:52:25 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool compression = false;
|
|
|
|
|
|
|
|
if(rpy->version & REPLAY_VERSION_COMPRESSION_BIT) {
|
|
|
|
if(rpy->fileoffset < SDL_RWtell(file)) {
|
2017-09-26 01:59:33 +02:00
|
|
|
log_warn("%s: Invalid offset %"PRIi32"", source, rpy->fileoffset);
|
2017-02-22 21:52:25 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-02-27 20:14:20 +01:00
|
|
|
vfile = SDL_RWWrapZReader(SDL_RWWrapSegment(file, ofs, rpy->fileoffset, false),
|
2017-02-23 05:04:42 +01:00
|
|
|
REPLAY_COMPRESSION_CHUNK_SIZE, true);
|
2017-02-22 21:52:25 +01:00
|
|
|
filesize = -1;
|
|
|
|
compression = true;
|
|
|
|
}
|
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
if(!replay_read_meta(rpy, vfile, filesize, source)) {
|
2017-02-22 21:52:25 +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);
|
|
|
|
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) {
|
2017-09-26 01:59:33 +02:00
|
|
|
log_fatal("%s: Tried to read events before reading metadata", source);
|
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) {
|
2017-09-26 01:59:33 +02:00
|
|
|
log_warn("%s: BUG: Reading events into a replay that already had events, call replay_destroy_events() if this is intended", source);
|
2017-02-09 07:01:38 +01:00
|
|
|
replay_destroy_events(rpy);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-09 05:06:46 +01:00
|
|
|
if(SDL_RWseek(file, rpy->fileoffset, RW_SEEK_SET) < 0) {
|
2017-09-26 01:59:33 +02:00
|
|
|
log_warn("%s: SDL_RWseek() failed: %s", source, 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;
|
|
|
|
}
|
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
if(!replay_read_events(rpy, vfile, filesize, source)) {
|
2017-02-22 21:52:25 +01:00
|
|
|
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-04-18 21:48:18 +02:00
|
|
|
static char* replay_getpath(const char *name, bool ext) {
|
|
|
|
return ext ? strfmt("storage/replays/%s.%s", name, REPLAY_EXTENSION) :
|
|
|
|
strfmt("storage/replays/%s", name);
|
2012-07-16 17:47:06 +02:00
|
|
|
}
|
|
|
|
|
2017-04-18 21:48:18 +02:00
|
|
|
bool replay_save(Replay *rpy, const char *name) {
|
2012-07-29 22:39:52 +02:00
|
|
|
char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
|
2017-04-27 11:10:49 +02:00
|
|
|
char *sp = vfs_repr(p, true);
|
2017-04-18 21:48:18 +02:00
|
|
|
log_info("Saving %s", sp);
|
|
|
|
free(sp);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-04-18 21:48:18 +02:00
|
|
|
SDL_RWops *file = vfs_open(p, VFS_MODE_WRITE);
|
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) {
|
2017-04-18 21:48:18 +02:00
|
|
|
log_warn("VFS error: %s", vfs_get_error());
|
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-09-26 21:50:22 +02:00
|
|
|
bool result = replay_write(rpy, file, REPLAY_STRUCT_VERSION_WRITE);
|
2017-02-05 02:25:17 +01:00
|
|
|
SDL_RWclose(file);
|
2012-07-16 17:47:06 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
static const char* replay_mode_string(ReplayReadMode mode) {
|
2017-09-26 21:50:22 +02:00
|
|
|
if((mode & REPLAY_READ_ALL) == REPLAY_READ_ALL) {
|
2017-09-26 01:59:33 +02:00
|
|
|
return "full";
|
|
|
|
}
|
|
|
|
|
|
|
|
if(mode & REPLAY_READ_META) {
|
|
|
|
return "meta";
|
|
|
|
}
|
|
|
|
|
|
|
|
if(mode & REPLAY_READ_EVENTS) {
|
|
|
|
return "events";
|
|
|
|
}
|
|
|
|
|
|
|
|
log_fatal("Bad mode %i", mode);
|
|
|
|
}
|
|
|
|
|
2017-04-18 21:48:18 +02:00
|
|
|
bool replay_load(Replay *rpy, const char *name, ReplayReadMode mode) {
|
|
|
|
char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
|
2017-04-27 11:10:49 +02:00
|
|
|
char *sp = vfs_repr(p, true);
|
2017-09-26 01:59:33 +02:00
|
|
|
log_info("Loading %s (%s)", sp, replay_mode_string(mode));
|
2017-02-21 21:31:46 +01:00
|
|
|
|
2017-04-18 21:48:18 +02:00
|
|
|
SDL_RWops *file = vfs_open(p, VFS_MODE_READ);
|
|
|
|
free(p);
|
|
|
|
|
|
|
|
if(!file) {
|
|
|
|
log_warn("VFS error: %s", vfs_get_error());
|
2017-09-26 01:59:33 +02:00
|
|
|
free(sp);
|
2017-04-18 21:48:18 +02:00
|
|
|
return false;
|
2017-02-21 21:31:46 +01:00
|
|
|
}
|
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
bool result = replay_read(rpy, file, mode, sp);
|
2017-04-18 21:48:18 +02:00
|
|
|
|
|
|
|
if(!result) {
|
|
|
|
replay_destroy(rpy);
|
|
|
|
}
|
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
free(sp);
|
2017-04-18 21:48:18 +02:00
|
|
|
SDL_RWclose(file);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool replay_load_syspath(Replay *rpy, const char *path, ReplayReadMode mode) {
|
2017-09-26 01:59:33 +02:00
|
|
|
log_info("Loading %s (%s)", path, replay_mode_string(mode));
|
2017-04-02 16:53:27 +02:00
|
|
|
SDL_RWops *file;
|
2017-04-18 21:48:18 +02:00
|
|
|
|
2017-04-02 16:53:27 +02:00
|
|
|
#ifndef __WINDOWS__
|
2017-04-18 21:48:18 +02:00
|
|
|
if(!strcmp(path, "-"))
|
2017-04-02 16:53:27 +02:00
|
|
|
file = SDL_RWFromFP(stdin,false);
|
|
|
|
else
|
2017-04-18 21:48:18 +02:00
|
|
|
file = SDL_RWFromFile(path, "rb");
|
2017-04-02 16:53:27 +02:00
|
|
|
#else
|
2017-04-18 21:48:18 +02:00
|
|
|
file = SDL_RWFromFile(path, "rb");
|
2017-04-02 16:53:27 +02:00
|
|
|
#endif
|
2017-02-21 21:31:46 +01:00
|
|
|
|
2017-02-05 02:25:17 +01:00
|
|
|
if(!file) {
|
Implemented a simple and consistent logging subsystem
The goal of this change is mainly to clean up Taisei's codebase and
improve its console output. I've been frustrated by files littered with
inconsistent printf/fprintf/warnx/errx calls for a long time, and now I
actually did something about it.
All the above functions are now considered deprecated and result in a
compile-time warning when used. Instead, the following macros should be
used:
log_debug(format, ...)
log_info(format, ...)
log_warn(format, ...)
log_err(format, ...)
As you can see, all of them have the same printf-like interface. But
they have different functionality and purpose:
log_debug is intended for very verbose and specific information. It
does nothing in release builds, much like assert(), so don't use
expressions with side-effects in its arguments.
log_info is for various status updates that are expected during
normal operation of the program.
log_warn is for non-critical failures or other things that may be
worth investigating, but don't inherently render the program
non-functional.
log_err is for when the only choice is to give up. Like errx, it
also terminates the program. Unlike errx, it actually calls abort(),
which means the cleanup functions are not ran -- but on the other
hand, you get a debuggable backtrace. However, if you're trying to
catch programming errors, consider using assert() instead.
All of them produce output that contains a timestamp, the log level
identifier, the calling function's name, and the formatted message.
The newline at the end of the format string is not required -- no, it is
actually *prohibited*. The logging system will take care of the line
breaks by itself, don't litter the code with that shit.
Internally, the logging system is based on the SDL_RWops abstraction,
and may have multiple, configurable destinations. This makes it easily
extensible. Currently, log_debug and log_info are set to write to
stdout, log_warn and log_err to stderr, and all of them also to the file
log.txt in the Taisei config directory.
Consequently, the nasty freopen hacks we used to make Taisei write to
log files on Windows are no longer needed -- which is a very good thing,
considering they probably would break if the configdir path contains
UTF-8 characters. SDL_RWFromFile does not suffer this limitation.
As an added bonus, it's also thread-safe.
Note about printf and fprintf: in very few cases, the logging system is
not a good substitute for these functions. That is, when you care about
writing exactly to stdout/stderr and about exactly how the output looks.
However, I insist on keeping the deprecation warnings on them to not
tempt anyone to use them for logging/debugging out of habit and/or
laziness.
For this reason, I've added a tsfprintf function to util.c. It is
functionally identical to fprintf, except it returns void. Yes, the name
is deliberately ugly. Avoid using it if possible, but if you must, only
use it to write to stdout or stderr. Do not write to actual files with
it, use SDL_RWops.
2017-03-13 03:51:58 +01:00
|
|
|
log_warn("SDL_RWFromFile() failed: %s", 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
|
|
|
|
2017-09-26 01:59:33 +02:00
|
|
|
bool result = replay_read(rpy, file, mode, path);
|
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) {
|
2017-03-13 22:50:34 +01:00
|
|
|
log_warn("Replay desync detected! %u != %u", stg->desync_check, check);
|
2017-09-19 20:46:28 +02:00
|
|
|
stg->desynced = true;
|
2017-02-05 03:58:27 +01:00
|
|
|
} else {
|
Implemented a simple and consistent logging subsystem
The goal of this change is mainly to clean up Taisei's codebase and
improve its console output. I've been frustrated by files littered with
inconsistent printf/fprintf/warnx/errx calls for a long time, and now I
actually did something about it.
All the above functions are now considered deprecated and result in a
compile-time warning when used. Instead, the following macros should be
used:
log_debug(format, ...)
log_info(format, ...)
log_warn(format, ...)
log_err(format, ...)
As you can see, all of them have the same printf-like interface. But
they have different functionality and purpose:
log_debug is intended for very verbose and specific information. It
does nothing in release builds, much like assert(), so don't use
expressions with side-effects in its arguments.
log_info is for various status updates that are expected during
normal operation of the program.
log_warn is for non-critical failures or other things that may be
worth investigating, but don't inherently render the program
non-functional.
log_err is for when the only choice is to give up. Like errx, it
also terminates the program. Unlike errx, it actually calls abort(),
which means the cleanup functions are not ran -- but on the other
hand, you get a debuggable backtrace. However, if you're trying to
catch programming errors, consider using assert() instead.
All of them produce output that contains a timestamp, the log level
identifier, the calling function's name, and the formatted message.
The newline at the end of the format string is not required -- no, it is
actually *prohibited*. The logging system will take care of the line
breaks by itself, don't litter the code with that shit.
Internally, the logging system is based on the SDL_RWops abstraction,
and may have multiple, configurable destinations. This makes it easily
extensible. Currently, log_debug and log_info are set to write to
stdout, log_warn and log_err to stderr, and all of them also to the file
log.txt in the Taisei config directory.
Consequently, the nasty freopen hacks we used to make Taisei write to
log files on Windows are no longer needed -- which is a very good thing,
considering they probably would break if the configdir path contains
UTF-8 characters. SDL_RWFromFile does not suffer this limitation.
As an added bonus, it's also thread-safe.
Note about printf and fprintf: in very few cases, the logging system is
not a good substitute for these functions. That is, when you care about
writing exactly to stdout/stderr and about exactly how the output looks.
However, I insist on keeping the deprecation warnings on them to not
tempt anyone to use them for logging/debugging out of habit and/or
laziness.
For this reason, I've added a tsfprintf function to util.c. It is
functionally identical to fprintf, except it returns void. Yes, the name
is deliberately ugly. Avoid using it if possible, but if you must, only
use it to write to stdout or stderr. Do not write to actual files with
it, use SDL_RWops.
2017-03-13 03:51:58 +01:00
|
|
|
log_debug("%u OK", check);
|
2017-02-05 03:58:27 +01:00
|
|
|
}
|
|
|
|
}
|
2017-02-10 00:24:19 +01:00
|
|
|
#ifdef REPLAY_WRITE_DESYNC_CHECKS
|
|
|
|
else {
|
2017-09-26 01:59:33 +02:00
|
|
|
// log_debug("%u", check);
|
2017-02-10 00:24:19 +01:00
|
|
|
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
|
|
|
|
2017-04-04 11:10:54 +02:00
|
|
|
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) {
|
2017-02-21 21:31:46 +01:00
|
|
|
if(rpy != &global.replay) {
|
|
|
|
replay_copy(&global.replay, rpy, true);
|
|
|
|
}
|
|
|
|
|
2017-04-04 11:10:54 +02:00
|
|
|
if(firstidx >= global.replay.numstages || firstidx < 0) {
|
|
|
|
log_warn("No stage #%i in the replay", firstidx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-21 21:31:46 +01:00
|
|
|
global.replaymode = REPLAY_PLAY;
|
|
|
|
|
2017-04-04 11:10:54 +02:00
|
|
|
for(int i = firstidx; i < global.replay.numstages; ++i) {
|
2017-02-21 21:31:46 +01:00
|
|
|
ReplayStage *rstg = global.replay_stage = global.replay.stages+i;
|
|
|
|
StageInfo *gstg = stage_get(rstg->stage);
|
|
|
|
|
|
|
|
if(!gstg) {
|
2017-04-02 10:16:52 +02:00
|
|
|
log_warn("Invalid stage %x in replay at %i skipped.", rstg->stage, i);
|
2017-02-21 21:31:46 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-10-08 13:30:51 +02:00
|
|
|
global.plr.mode = plrmode_find(rstg->plr_char, rstg->plr_shot);
|
2017-02-26 13:17:48 +01:00
|
|
|
stage_loop(gstg);
|
2017-02-21 21:31:46 +01:00
|
|
|
|
|
|
|
if(global.game_over == GAMEOVER_ABORT) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-04-20 23:50:33 +02:00
|
|
|
if(global.game_over == GAMEOVER_RESTART) {
|
|
|
|
--i;
|
|
|
|
}
|
|
|
|
|
2017-02-21 21:31:46 +01:00
|
|
|
global.game_over = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
global.game_over = 0;
|
|
|
|
global.replaymode = REPLAY_RECORD;
|
|
|
|
replay_destroy(&global.replay);
|
|
|
|
global.replay_stage = NULL;
|
2017-03-11 03:51:56 +01:00
|
|
|
free_resources(false);
|
2017-02-21 21:31:46 +01:00
|
|
|
}
|