Replay: Add file header handling.

This commit is contained in:
Unknown W. Brackets 2019-07-04 20:36:17 -07:00
parent b0c4323714
commit 4144e71b7a
6 changed files with 113 additions and 37 deletions

View File

@ -188,3 +188,7 @@ std::string KernelTimeNowFormatted() {
std::string timestamp = StringFromFormat("%04d-%02d-%02d_%02d-%02d-%02d", years, months, days, hours, minutes, seconds);
return timestamp;
}
void KernelTimeSetBase(int64_t seconds) {
start_time = (time_t)seconds;
}

View File

@ -28,6 +28,7 @@ int sceKernelSysClock2USecWide(u32 lowClock, u32 highClock, u32 lowPtr, u32 high
u64 sceKernelUSec2SysClockWide(u32 usec);
u32 sceKernelLibcClock();
std::string KernelTimeNowFormatted();
void KernelTimeSetBase(int64_t seconds);
void __KernelTimeInit();
void __KernelTimeDoState(PointerWrap &p);

View File

@ -138,6 +138,10 @@ static time_t rtc_timegm(struct tm *tm)
#endif
static void RtcUpdateBaseTicks() {
rtcBaseTicks = 1000000ULL * rtcBaseTime.tv_sec + rtcBaseTime.tv_usec + rtcMagicOffset;
}
void __RtcInit()
{
// This is the base time, the only case we use gettimeofday() for.
@ -147,7 +151,7 @@ void __RtcInit()
rtcBaseTime.tv_sec = tv.tv_sec;
rtcBaseTime.tv_usec = 0;
// Precalculate the current time in microseconds (rtcMagicOffset is offset to 1970.)
rtcBaseTicks = 1000000ULL * rtcBaseTime.tv_sec + rtcBaseTime.tv_usec + rtcMagicOffset;
RtcUpdateBaseTicks();
}
void __RtcDoState(PointerWrap &p)
@ -158,7 +162,7 @@ void __RtcDoState(PointerWrap &p)
p.Do(rtcBaseTime);
// Update the precalc, pointless to savestate this as it's just based on the other value.
rtcBaseTicks = 1000000ULL * rtcBaseTime.tv_sec + rtcBaseTime.tv_usec + rtcMagicOffset;
RtcUpdateBaseTicks();
}
void __RtcTimeOfDay(PSPTimeval *tv)
@ -171,6 +175,19 @@ void __RtcTimeOfDay(PSPTimeval *tv)
tv->tv_usec = adjustedUs % 1000000UL;
}
int32_t RtcBaseTime(int32_t *micro) {
if (micro) {
*micro = rtcBaseTime.tv_usec;
}
return rtcBaseTime.tv_sec;
}
void RtcSetBaseTime(int32_t seconds, int32_t micro) {
rtcBaseTime.tv_sec = seconds;
rtcBaseTime.tv_usec = micro;
RtcUpdateBaseTicks();
}
static void __RtcTmToPspTime(ScePspDateTime &t, const tm *val)
{
t.year = val->tm_year + 1900;

View File

@ -27,6 +27,8 @@ struct PSPTimeval {
};
void __RtcTimeOfDay(PSPTimeval *tv);
int32_t RtcBaseTime(int32_t *micro = nullptr);
void RtcSetBaseTime(int32_t seconds, int32_t micro = 0);
void Register_sceRtc();
void __RtcInit();

View File

@ -24,6 +24,8 @@
#include "Core/Replay.h"
#include "Core/FileSystems/FileSystem.h"
#include "Core/HLE/sceCtrl.h"
#include "Core/HLE/sceKernelTime.h"
#include "Core/HLE/sceRtc.h"
enum class ReplayState {
IDLE,
@ -31,19 +33,42 @@ enum class ReplayState {
SAVE,
};
// Overall structure of file format:
//
// - ReplayFileHeader with basic data about replay (mostly timestamp for sync.)
// - An indeterminate sequence of events:
// - ReplayItemHeader (primary event details)
// - Side data of bytes listed in header, if SIDEDATA flag set on action.
//
// The header doesn't say how long the replay is, because new events are
// appended to the file as they occur. It is usually near, and always less than:
//
// (fileSize - sizeof(ReplayFileHeader)) / sizeof(ReplayItemHeader)
// File data formats below.
#pragma pack(push, 1)
static const char *REPLAY_MAGIC = "PPREPLAY";
static const int REPLAY_VERSION_MIN = 1;
static const int REPLAY_VERSION_CURRENT = 1;
struct ReplayFileHeader {
char magic[8];
u32_le version = REPLAY_VERSION_CURRENT;
u32_le reserved[3]{};
u64_le rtcBaseSeconds;
};
struct ReplayItemHeader {
ReplayAction action;
uint64_t timestamp;
u64_le timestamp;
union {
uint32_t buttons;
u32_le buttons;
uint8_t analog[2][2];
uint32_t result;
uint64_t result64;
u32_le result;
u64_le result64;
// NOTE: Certain action types have data, always sized by this/result.
uint32_t size;
u32_le size;
};
ReplayItemHeader(ReplayAction a, uint64_t t) {
@ -68,14 +93,14 @@ static const int REPLAY_MAX_FILENAME = 256;
struct ReplayFileInfo {
char filename[REPLAY_MAX_FILENAME]{};
int64_t size = 0;
uint16_t access = 0;
s64_le size = 0;
u16_le access = 0;
uint8_t exists = 0;
uint8_t isDirectory = 0;
int64_t atime = 0;
int64_t ctime = 0;
int64_t mtime = 0;
s64_le atime = 0;
s64_le ctime = 0;
s64_le mtime = 0;
};
#pragma pack(pop)
@ -101,8 +126,6 @@ static uint8_t lastAnalog[2][2]{};
static size_t replayDiskPos = 0;
static bool diskFailed = false;
// TODO: File format either needs rtc and rand seed, or must be paired with a save state.
void ReplayExecuteBlob(const std::vector<u8> &data) {
ReplayAbort();
@ -145,28 +168,52 @@ bool ReplayExecuteFile(const std::string &filename) {
return false;
}
// TODO: Header handling.
// Include initial rand state or timestamp?
// TODO: Maybe stream instead.
size_t sz = File::GetFileSize(fp);
if (sz == 0) {
ERROR_LOG(SYSTEM, "Empty replay data");
fclose(fp);
return false;
}
std::vector<u8> data;
data.resize(sz);
auto loadData = [&]() {
// TODO: Maybe stream instead.
size_t sz = File::GetFileSize(fp);
if (sz <= sizeof(ReplayFileHeader)) {
ERROR_LOG(SYSTEM, "Empty replay data");
return false;
}
if (fread(&data[0], sz, 1, fp) != 1) {
ERROR_LOG(SYSTEM, "Could not read replay data");
ReplayFileHeader fh;
if (fread(&fh, sizeof(fh), 1, fp) != 1) {
ERROR_LOG(SYSTEM, "Could not read replay file header");
return false;
}
sz -= sizeof(fh);
if (memcmp(fh.magic, REPLAY_MAGIC, sizeof(fh.magic)) != 0) {
ERROR_LOG(SYSTEM, "Replay header corrupt");
return false;
}
if (fh.version < REPLAY_VERSION_MIN) {
ERROR_LOG(SYSTEM, "Replay version %d unsupported", fh.version);
return false;
} else if (fh.version > REPLAY_VERSION_CURRENT) {
WARN_LOG(SYSTEM, "Replay version %d scary and futuristic, trying anyway", fh.version);
}
data.resize(sz);
if (fread(&data[0], sz, 1, fp) != 1) {
ERROR_LOG(SYSTEM, "Could not read replay data");
return false;
}
return true;
};
if (loadData()) {
fclose(fp);
return false;
ReplayExecuteBlob(data);
return true;
}
ReplayExecuteBlob(data);
return true;
fclose(fp);
return false;
}
bool ReplayHasMoreEvents() {
@ -219,13 +266,18 @@ bool ReplayFlushFile(const std::string &filename) {
return false;
}
// TODO: Header handling.
// Include initial rand state or timestamp?
replaySaveWroteHeader = true;
bool success = true;
if (!replaySaveWroteHeader) {
ReplayFileHeader fh;
memcpy(fh.magic, REPLAY_MAGIC, sizeof(fh.magic));
fh.rtcBaseSeconds = RtcBaseTime();
success = fwrite(&fh, sizeof(fh), 1, fp) == 1;
replaySaveWroteHeader = true;
}
size_t c = replayItems.size();
bool success = true;
if (c != 0) {
if (success && c != 0) {
// TODO: Maybe stream instead.
std::vector<u8> data;
ReplayFlushBlob(&data);

View File

@ -46,7 +46,7 @@ enum class ReplayAction : uint8_t {
struct PSPFileInfo;
// Replay from data in memory.
// Replay from data in memory. Does not manipulate base time / RNG state.
void ReplayExecuteBlob(const std::vector<u8> &data);
// Replay from data in a file. Returns false if invalid.
bool ReplayExecuteFile(const std::string &filename);