Skip to content
Permalink
Browse files

Replay: Add file header handling.

  • Loading branch information...
unknownbrackets committed Jul 5, 2019
1 parent b0c4323 commit 4144e71b7ad17eda76858483518c6ab357333ee4
Showing with 112 additions and 36 deletions.
  1. +4 −0 Core/HLE/sceKernelTime.cpp
  2. +1 −0 Core/HLE/sceKernelTime.h
  3. +19 −2 Core/HLE/sceRtc.cpp
  4. +2 −0 Core/HLE/sceRtc.h
  5. +85 −33 Core/Replay.cpp
  6. +1 −1 Core/Replay.h
@@ -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;
}
@@ -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);
@@ -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;
@@ -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();
@@ -24,26 +24,51 @@
#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,
EXECUTE,
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?
std::vector<u8> data;
auto loadData = [&]() {
// TODO: Maybe stream instead.
size_t sz = File::GetFileSize(fp);
if (sz <= sizeof(ReplayFileHeader)) {
ERROR_LOG(SYSTEM, "Empty replay data");
return false;
}

// TODO: Maybe stream instead.
size_t sz = File::GetFileSize(fp);
if (sz == 0) {
ERROR_LOG(SYSTEM, "Empty replay data");
fclose(fp);
return false;
}
ReplayFileHeader fh;
if (fread(&fh, sizeof(fh), 1, fp) != 1) {
ERROR_LOG(SYSTEM, "Could not read replay file header");
return false;
}
sz -= sizeof(fh);

std::vector<u8> data;
data.resize(sz);
if (memcmp(fh.magic, REPLAY_MAGIC, sizeof(fh.magic)) != 0) {
ERROR_LOG(SYSTEM, "Replay header corrupt");
return false;
}

if (fread(&data[0], sz, 1, fp) != 1) {
ERROR_LOG(SYSTEM, "Could not read replay data");
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);
@@ -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);

0 comments on commit 4144e71

Please sign in to comment.
You can’t perform that action at this time.