mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-23 05:19:56 +00:00
Replay: Add file header handling.
This commit is contained in:
parent
b0c4323714
commit
4144e71b7a
@ -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();
|
||||
|
120
Core/Replay.cpp
120
Core/Replay.cpp
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user