rodecomp/Source/Client/Replay.cpp
ragnarok-clanker 6cc9472ff2 Decompile 10 functions in Source/Client/Replay.cpp, Source/Client/Session.cpp, ...
Batch #14976 by KWDev: 10 byte-identical, 0 confirmed by peer review.

- fn_22397 CBlurPC::OnProcess: byte-identical
- fn_22401 CHalluPC2::OnProcess: byte-identical
- fn_07568 NAsyncWorkRequestQueue::Complete: byte-identical
- fn_09371 UICashEmotionListChildWnd_Purchase::UpdateExpiry: byte-identical
- fn_04291 CReputeMgr::GetGroupReputes: byte-identical
- fn_04292 CReputeMgr::GetAllReputes: byte-identical
- fn_23574 CSession::HasUnknown809C: byte-identical
- fn_06988 CEZ2STREffectMgr::FindById: byte-identical
- fn_19313 ReplayFileList::FileName: byte-identical
- fn_05438 RenewQuestOngoingInfo::GetDateString: byte-identical

Merged automatically: every function is byte-identical to the original.
2026-11-09 02:52:38 +00:00

1404 lines
57 KiB
C++

#include "Client/Replay.h"
#include "UI/Window/WindowId.h"
#include "Network/Packets/PacketId.h"
#include "Contents/Configuration/ServiceConfiguration.h"
#include "Contents/Skills/SkillInfo.h"
#include "Client/Session.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <list>
#include <string>
#include <windows.h>
#include <timeapi.h>
#include "Contents/EntryQueueMgr.h"
#include "UI/EntryQueue/UIEntryQueueWnd.h"
#include "UI/Window/UIWindowMgr.h"
#include "Contents/RenewQuestMgr.h"
#include "Contents/ClientState.h"
#include "Contents/Configuration/ServiceOptions.h"
#include "Contents/Messenger/MessengerMember.h"
// GLOBAL: gl_24549
int g_replayAnimationScaling = 0;
// GLOBAL: gl_24630
static int g_replayFlushCounter = 0;
// STRING: st_16831
static const char g_replayHeaderText[] = "<< Ragnarok Replay File Version 0.%02d >>\r\n \xA1\xD8 Do not modify file! ";
// STRING: st_16832
static const char g_replayHeaderPrefix[] = "\r\n";
#pragma pack(push, 1)
struct ReplayTaggedRecordHeader {
/* 0000 */ std::uint16_t id;
/* 0002 */ std::uint32_t bytes;
};
struct ReplayRecordHeader {
/* 0000 */ std::uint32_t frame;
/* 0004 */ std::uint32_t tick;
/* 0008 */ std::uint16_t bytes;
// INLINED
ReplayRecordHeader() : frame(0), tick(0), bytes(0) {}
~ReplayRecordHeader();
};
struct ReplayMetadataTag {
/* 0000 */ std::int16_t id;
/* 0002 */ std::uint32_t bytes;
// INLINED
ReplayMetadataTag() : id(0), bytes(0) {}
// INLINED
~ReplayMetadataTag() {
this->id = 0;
this->bytes = 0;
}
// INLINED
void Read(const unsigned char* source) {
this->bytes = 0;
this->id = 0;
std::memcpy(this, source, sizeof(*this));
}
};
#pragma pack(pop)
static_assert(sizeof(ReplayTaggedRecordHeader) == 6);
static_assert(sizeof(ReplayMetadataTag) == 6);
static_assert(sizeof(ReplayRecordHeader) == 10);
// OFFSET: 0x006DA340
// FUNCTION: fn_06623
std::vector<unsigned char> BuildReplayTaggedRecord(std::uint16_t id, const void* data, std::uint32_t bytes) {
std::vector<unsigned char> record(bytes + 6u, 0);
const ReplayTaggedRecordHeader header{id, bytes};
std::memcpy(record.data(), &header, sizeof(header));
if (bytes != 0) {
std::memcpy(record.data() + sizeof(header), data, bytes);
}
return record;
}
// INLINED
static std::vector<unsigned char> MakeReplayRecord(std::uint16_t id, const void* data, std::uint32_t bytes) {
std::vector<unsigned char> record(bytes + sizeof(ReplayTaggedRecordHeader), 0);
ReplayTaggedRecordHeader* header = reinterpret_cast<ReplayTaggedRecordHeader*>(record.data());
header->id = id;
header->bytes = bytes;
if (bytes != 0) {
std::memcpy(record.data() + sizeof(ReplayTaggedRecordHeader), data, bytes);
}
return record;
}
// INLINED
template <class Record> static void AppendRecord(std::vector<unsigned char>& records, const Record& record) {
records.insert(records.end(), record.begin(), record.end());
}
// OFFSET: 0x00E3DBA0
// FUNCTION: fn_24195
std::vector<unsigned char> BuildEntryQueueReplayRecords() {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(6500, nullptr, 0));
if (g_entryQueueMgr->current != nullptr) {
const int id = g_entryQueueMgr->current->id;
AppendRecord(records, MakeReplayRecord(6511, &id, sizeof(id)));
}
const unsigned char ready = g_entryQueueMgr->ready;
AppendRecord(records, MakeReplayRecord(6512, &ready, sizeof(ready)));
std::string text = g_entryQueueMgr->queueName.text;
if (text.size() != 0) {
AppendRecord(records, BuildReplayTaggedRecord(6513, text.c_str(), text.size()));
}
text = g_entryQueueMgr->characterName.text;
if (text.size() != 0) {
AppendRecord(records, BuildReplayTaggedRecord(6514, text.c_str(), text.size()));
}
if (UIEntryQueueWnd* window = dynamic_cast<UIEntryQueueWnd*>(g_windowMgr.LookupWindow(WID_JOINTO_BATTLEFIELDWND))) {
const int selected = window->SelectedIndex();
AppendRecord(records, MakeReplayRecord(6515, &selected, sizeof(selected)));
}
if (UIEntryQueueStandByWnd* window = dynamic_cast<UIEntryQueueStandByWnd*>(g_windowMgr.LookupWindow(WID_ENTRYQUEUESTANDBYWND))) {
AppendRecord(records, MakeReplayRecord(6517, &window->m_waiting, sizeof(window->m_waiting)));
AppendRecord(records, MakeReplayRecord(6518, &window->m_ranking, sizeof(window->m_ranking)));
AppendRecord(records, BuildReplayTaggedRecord(6516, window->m_queueName.c_str(), window->m_queueName.size()));
}
AppendRecord(records, MakeReplayRecord(6501, nullptr, 0));
return records;
}
// OFFSET: 0x00B83920
// FUNCTION: fn_19301
ReplayCachedPacket::ReplayCachedPacket(const ReplayCachedPacket& other)
: enabled(other.enabled), bytes(other.bytes) {
}
// OFFSET: 0x00B839D0
// FUNCTION: fn_19302
ReplaySection::ReplaySection() : id(0), bytes(0), fileOffset(0) {
}
// OFFSET: 0x00B83D00
// FUNCTION: fn_19305
inline ReplayRecordHeader::~ReplayRecordHeader() {
this->frame = 0;
this->tick = 0;
this->bytes = 0;
}
// OFFSET: 0x00B83D20
// FUNCTION: fn_19306
ReplaySection::~ReplaySection() {
this->id = 0;
this->bytes = 0;
this->fileOffset = 0;
}
// OFFSET: 0x00B83540
// FUNCTION: fn_19300
Replay::Replay() : cipherIndex(0), cipherBase(0), cipherKey(0), recordingState(0), playbackState(0) {
this->state = 1;
this->baseFrame = 0;
this->started = 0;
this->frame = 0;
this->elapsed = 0;
this->speed = 1.0f;
std::memset(&this->header, 0, sizeof(this->header));
std::memset(&this->header.options, 1, sizeof(this->header.options));
this->ResetFile();
this->previousTick = timeGetTime();
this->playbackState = 0;
this->recordingState = 0;
this->cachedPackets.resize(3143, ReplayCachedPacket{});
this->cachedPackets[2459].enabled = true;
this->cachedPackets[470].enabled = true;
}
// OFFSET: 0x00B855B0
// FUNCTION: fn_19326
bool Replay::WriteSection(int id, std::vector<unsigned char>& bytes, int event) {
if (bytes.empty()) {
return false;
}
const std::uint32_t size = static_cast<std::uint32_t>(bytes.size());
unsigned char* data = bytes.data();
if (id <= 0 || id >= 26 || size == 0 || data == nullptr) {
return false;
}
if (event == 14) {
this->sections[id].id = static_cast<std::uint16_t>(id);
this->sections[id].bytes = size;
this->sections[id].fileOffset = this->fileOffset;
this->fileOffset += size;
}
const ReplaySection& section = this->sections[id];
if (section.bytes != 0) {
auto* words = reinterpret_cast<std::uint32_t*>(data);
this->cipherBase = this->header.key0 >> 5;
const int count = section.bytes / 4;
this->cipherIndex = 0;
this->cipherKey = this->header.key1 >> 3;
for (int index = 0; index < count; ++index) {
++this->cipherIndex;
words[index] ^= (this->cipherBase + this->cipherIndex) * this->cipherKey;
}
}
std::fseek(this->file, static_cast<long>(section.fileOffset), SEEK_SET);
return std::fwrite(data, section.bytes, 1, this->file) != 0;
}
// OFFSET: 0x00B857E0
// FUNCTION: fn_19327
int Replay::WriteHeader(int event) {
this->header.records = this->records;
this->header.fileOffset = this->fileOffset;
this->header.closingSignature = 0x52414705;
this->header.signature = 0x52414705;
switch (event) {
case 11: {
this->sections[25].id = 111;
bool written;
{
std::vector<unsigned char> metadata = this->BuildMetadata();
written = this->WriteSection(2, metadata, 14);
}
if (!written) {
return 0;
}
char text[100] = {};
std::sprintf(text, g_replayHeaderText, 5);
std::sprintf(text + 95, g_replayHeaderPrefix);
std::fseek(this->file, 0, SEEK_SET);
std::fwrite(text, sizeof(text), 1, this->file);
std::fwrite(&this->header, 12, 1, this->file);
return 1;
}
case 12: {
if (++g_replayFlushCounter != 1200) {
return 1;
}
this->header.closingSignature = 0x51404605;
this->header.signature = 0x51404605;
bool written;
{
std::vector<unsigned char> metadata = this->BuildMetadata();
written = this->WriteSection(2, metadata, 15);
}
if (!written) {
return 0;
}
std::fseek(this->file, 112, SEEK_SET);
std::fwrite(this->sections, sizeof(this->sections), 1, this->file);
g_replayFlushCounter = 0;
return 1;
}
case 13: {
bool written;
{
std::vector<unsigned char> metadata = this->BuildMetadata();
written = this->WriteSection(2, metadata, 15);
}
if (!written) {
return 0;
}
std::fseek(this->file, 112, SEEK_SET);
std::fwrite(this->sections, sizeof(this->sections), 1, this->file);
return 1;
}
default:
return 0;
}
}
// OFFSET: 0x00B85A20
// FUNCTION: fn_19328
std::vector<unsigned char> Replay::BuildMetadata() {
std::vector<unsigned char> metadata;
AppendRecord(metadata, MakeReplayRecord(950, nullptr, 0));
AppendRecord(metadata, MakeReplayRecord(961, &this->header.service, sizeof(this->header.service)));
AppendRecord(metadata, MakeReplayRecord(962, &this->header.serviceType, sizeof(this->header.serviceType)));
AppendRecord(metadata, MakeReplayRecord(963, &this->header.sessionSyncValue, sizeof(this->header.sessionSyncValue)));
AppendRecord(metadata, MakeReplayRecord(964, this->header.characterName, sizeof(this->header.characterName)));
AppendRecord(metadata, MakeReplayRecord(965, this->header.mapName, sizeof(this->header.mapName)));
AppendRecord(metadata, MakeReplayRecord(966, &this->header.accountId, sizeof(this->header.accountId)));
AppendRecord(metadata, MakeReplayRecord(967, &this->header.cellX, sizeof(this->header.cellX)));
AppendRecord(metadata, MakeReplayRecord(968, &this->header.cellY, sizeof(this->header.cellY)));
AppendRecord(metadata, MakeReplayRecord(969, &this->header.playerDirection, sizeof(this->header.playerDirection)));
AppendRecord(metadata, MakeReplayRecord(970, &this->header.duration, sizeof(this->header.duration)));
AppendRecord(metadata, MakeReplayRecord(971, &this->header.records, sizeof(this->header.records)));
AppendRecord(metadata, MakeReplayRecord(972, &this->header.fileOffset, sizeof(this->header.fileOffset)));
AppendRecord(metadata, MakeReplayRecord(973, &this->header.options, sizeof(this->header.options)));
AppendRecord(metadata, MakeReplayRecord(960, &this->header.closingSignature, sizeof(this->header.closingSignature)));
AppendRecord(metadata, MakeReplayRecord(951, nullptr, 0));
return metadata;
}
// OFFSET: 0x00B875E0
// FUNCTION: fn_19354
Replay& Replay::Instance() {
// GLOBAL: gl_24631
static Replay g_replay;
return g_replay;
}
// OFFSET: 0x00B83D50
// FUNCTION: fn_19307
bool Replay::MatchesService(int mode) const {
const int type = this->header.serviceType;
if (mode == 2) {
if (type == 0 || type == 13 || type == 1 || type == 2) {
return true;
}
} else if (g_serviceMode == type) {
return true;
}
return false;
}
// OFFSET: 0x00B83D90
// FUNCTION: fn_19308
int Replay::AdvanceFrame() {
if (!g_replayAnimationScaling) {
++this->frame;
return 1;
}
const std::uint32_t now = timeGetTime();
if (this->state != 6) {
++this->frame;
this->elapsed += static_cast<std::uint32_t>(static_cast<float>(now - this->previousTick) * this->speed);
if (static_cast<int>(this->elapsed) >= static_cast<int>(this->header.duration)) {
this->previousTick = now;
this->elapsed = this->header.duration;
this->state = 6;
return 0;
}
}
this->previousTick = now;
return 1;
}
// OFFSET: 0x00B83E10
// FUNCTION: fn_19309
void Replay::ResetPlayback() {
if (this->state != 3 && this->state != 8) {
this->records = 0;
this->frame = 0;
this->elapsed = 0;
}
}
// OFFSET: 0x00B83E40
// FUNCTION: fn_19310
bool Replay::Cipher(std::uint32_t bytes, void* data) {
if (!data || !bytes) {
return false;
}
auto* words = static_cast<std::uint32_t*>(data);
this->cipherBase = this->header.key0 >> 5;
const int count = bytes / 4;
this->cipherIndex = 0;
this->cipherKey = this->header.key1 >> 3;
for (int index = 0; index < count; ++index) {
++this->cipherIndex;
words[index] ^= (this->cipherBase + this->cipherIndex) * this->cipherKey;
}
return true;
}
// OFFSET: 0x00B83F20
// FUNCTION: fn_19312
void Replay::CloseFile() {
if (this->file) {
if (this->fileMode == 31) {
this->WriteHeader(13);
}
std::fclose(this->file);
this->file = nullptr;
}
}
// OFFSET: 0x00B84340
// FUNCTION: fn_19314
ReplayTime Replay::Duration() const {
const unsigned seconds = static_cast<int>(this->header.duration) / 1000;
const unsigned minutes = seconds / 60;
const unsigned hours = minutes / 60;
ReplayTime time;
time.hours = static_cast<std::uint16_t>(hours);
time.minutes = static_cast<char>(minutes % 60);
time.seconds = static_cast<char>(seconds % 60);
return time;
}
// OFFSET: 0x00B843A0
// FUNCTION: fn_19315
int Replay::FindSection(int id) const {
if (id < 1 || id > 25) {
return -1;
}
if (id == this->sections[id].id) {
return id;
}
for (int index = 0; index < 26; ++index) {
if (this->sections[index].id == id) {
return index;
}
}
return -1;
}
// OFFSET: 0x00B84620
// FUNCTION: fn_19318
int Replay::SpeedLevel() const {
if (0.25f == this->speed) {
return 1;
}
if (0.5f == this->speed) {
return 2;
}
if (1.0f == this->speed) {
return 3;
}
if (2.0f == this->speed) {
return 4;
}
if (4.0f == this->speed) {
return 5;
}
if (8.0f == this->speed) {
return 6;
}
if (16.0f == this->speed) {
return 7;
}
return 0;
}
// OFFSET: 0x00B846D0
// FUNCTION: fn_19319
void Replay::ResetFile() {
this->header.Reset();
this->records = 0;
this->fileMode = -1;
this->file = nullptr;
this->fileOffset = 0;
}
// OFFSET: 0x00B84930
// FUNCTION: fn_19320
std::vector<unsigned char> Replay::ReadSection(int id) {
std::vector<unsigned char> data;
if (id < 1 || id > 25) {
return data;
}
const int index = this->FindSection(id);
if (index != -1 && this->sections[index].fileOffset != 0 && this->sections[index].bytes != 0) {
const ReplaySection& section = this->sections[index];
data.resize(section.bytes);
std::fseek(this->file, static_cast<long>(section.fileOffset), SEEK_SET);
std::fread(data.data(), section.bytes, 1, this->file);
this->Cipher(section.bytes, data.data());
}
return data;
}
// OFFSET: 0x00B84E40
// FUNCTION: fn_19322
std::uint32_t Replay::ParseMetadata(const std::vector<unsigned char>& data) {
if (data.empty()) {
return 0xFFFFFFFF;
}
ReplayMetadataTag tag;
std::uint32_t offset = 6;
tag.Read(&data[0]);
if (tag.id != 0x3B6) {
offset = 0xFFFFFFFF;
} else {
while (offset < data.size()) {
tag.Read(&data[offset]);
offset += 6;
switch (tag.id) {
case 0x3B7:
return offset;
case 0x3C1:
std::memcpy(&this->header.service, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3C2:
std::memcpy(&this->header.serviceType, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3C3:
std::memcpy(&this->header.sessionSyncValue, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3C6:
std::memcpy(&this->header.accountId, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3C4:
std::memcpy(this->header.characterName, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3C5:
std::memcpy(this->header.mapName, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3C7:
std::memcpy(&this->header.cellX, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3C8:
std::memcpy(&this->header.cellY, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3C9:
std::memcpy(&this->header.playerDirection, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3CA:
std::memcpy(&this->header.duration, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3CB:
std::memcpy(&this->header.records, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3CC:
std::memcpy(&this->header.fileOffset, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3CD:
std::memcpy(&this->header.options, &data[offset], tag.bytes);
offset += tag.bytes;
break;
case 0x3C0:
std::memcpy(&this->header.closingSignature, &data[offset], tag.bytes);
offset += tag.bytes;
if (this->header.signature != this->header.closingSignature) {
std::uint32_t fileOffset = this->fileOffset;
ReplayRecordHeader record;
int count = static_cast<int>(this->header.records) - 1;
if (count > 0) {
do {
std::fseek(this->file, static_cast<long>(fileOffset), SEEK_SET);
std::fread(&record, sizeof(record), 1, this->file);
fileOffset += record.bytes + sizeof(record);
--count;
} while (count != 0);
}
this->header.duration = record.tick;
}
break;
default:
offset += tag.bytes;
break;
}
}
}
return offset;
}
// OFFSET: 0x00B85350
// FUNCTION: fn_19323
bool Replay::ReadRecord(unsigned char* buffer, std::uint16_t& id, bool force) {
if (!force && this->state != 7) {
return false;
}
ReplayRecordHeader record;
bool result = false;
std::fseek(this->file, static_cast<long>(this->fileOffset), SEEK_SET);
std::fread(&record, sizeof(record), 1, this->file);
if (this->header.records > this->records && this->elapsed >= record.tick) {
std::fread(buffer, record.bytes, 1, this->file);
this->Cipher(record.bytes, buffer);
std::memcpy(&id, buffer, sizeof(id));
this->fileOffset += record.bytes + sizeof(record);
result = true;
++this->records;
}
return result;
}
// OFFSET: 0x00B854C0
// FUNCTION: fn_19324
bool Replay::AllowsEvent(int event) const {
if (!g_replayAnimationScaling) {
return false;
}
if (event == 271 || event == 510 || event == 201 || event == 286 || event == 580) {
return false;
}
if (event == 195) {
return this->header.options.Get(ReplayRecordOption::Equip) == 0;
}
if (event == 194) {
return this->header.options.Get(ReplayRecordOption::Item) == 0;
}
if (event == 196) {
return this->header.options.Get(ReplayRecordOption::Skill) == 0;
}
if (event == 199) {
return this->header.options.Get(ReplayRecordOption::Party) == 0;
}
if (event == 192) {
return this->header.options.Get(ReplayRecordOption::Status) == 0;
}
return true;
}
// OFFSET: 0x00B85580
// FUNCTION: fn_19325
void Replay::Rewind() {
this->records = 0;
this->fileOffset = this->sections[this->FindSection(1)].fileOffset;
}
// OFFSET: 0x00B83EC0
// FUNCTION: fn_19311
__declspec(noinline) void ReplayFileList::Clear() {
this->selected = -1;
this->files.erase(this->files.begin(), this->files.end());
}
// OFFSET: 0x00B84B60
// FUNCTION: fn_19321
void ReplayFileList::Refresh() {
this->Clear();
char directory[260] = {};
GetCurrentDirectoryA(sizeof(directory), directory);
std::string pattern(directory, std::strlen(directory));
pattern.append("\\Replay\\*.rrf", 13);
WIN32_FIND_DATAA found;
HANDLE search = FindFirstFileA(pattern.c_str(), &found);
if (search != INVALID_HANDLE_VALUE) {
BOOL more = TRUE;
while (more) {
if ((found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
const std::size_t length = std::strlen(found.cFileName);
const char* extension = found.cFileName + length - 4;
if (extension[0] == '.' && extension[1] == 'r' && extension[2] == 'r' && extension[3] == 'f') {
ReplayFileEntry entry = {};
std::memcpy(entry.name, found.cFileName, length);
entry.name[length] = '\0';
entry.index = 0;
if (length == 23) {
char number[6] = {};
std::memcpy(number, entry.name + 15, 4);
entry.index = std::atoi(number);
}
this->files.push_back(entry);
}
}
more = FindNextFileA(search, &found);
}
}
FindClose(search);
}
// OFFSET: 0x00B84300
// FUNCTION: fn_19313
const char* ReplayFileList::FileName(int index) const {
if (this->files.size() <= static_cast<unsigned>(index)) {
return nullptr;
}
return this->files[index].name;
}
// OFFSET: 0x00B86AF0
// FUNCTION: fn_19336
char* ReplayFileList::SetPath(const char* value) {
if (!value || !this->pathEnabled) {
return nullptr;
}
std::memset(this->path, 0, sizeof(this->path));
std::memcpy(this->path, value, std::strlen(value));
return this->path;
}
// OFFSET: 0x00B870D0
// FUNCTION: fn_19338
bool ReplayFileList::Select(int index) {
if (index < 0) {
return false;
}
const int count = static_cast<int>(this->files.size());
if (count <= index) {
index = count - 1;
}
this->selected = index;
return true;
}
// STRING: st_10400
static const char g_replayExtensionFormat[] = "%s.rrf";
// STRING: st_10401
static const char g_replayPathFormat[] = "Replay\\%s.rrf";
// STRING: st_10402
static const char g_replayNewFileFormat[] = "Replay\\RagnarokReplay_%04d.rrf";
// OFFSET: 0x00B87360
// FUNCTION: fn_19344
bool Replay::SetOptions(const void* data, int bytes) {
std::memset(&this->header.options, 0, bytes);
std::memcpy(&this->header.options, data, bytes);
return true;
}
// OFFSET: 0x00B86B50
// FUNCTION: fn_19337
int ReplayFileList::Open(int mode, int argument1, int argument2) {
if (!this->replay) {
return 0;
}
if (mode == 0x20) {
if (this->selected < 0 || this->files.empty()) {
return 0;
}
std::string name("Replay\\");
const ReplayFileEntry& entry = this->files[this->selected];
name.append(entry.name, std::strlen(entry.name));
std::memset(this->path, 0, sizeof(this->path));
std::memcpy(this->path, name.data(), name.size());
this->replay->fileMode = 0x20;
this->replay->file = std::fopen(this->path, "rb");
if (this->replay->file) {
std::fseek(this->replay->file, 100, SEEK_SET);
std::memset(&this->replay->header, 0, 0xC);
std::fread(&this->replay->header, 0xC, 1, this->replay->file);
if (this->replay->header.signature == 0x52414705u || this->replay->header.signature == 0x51404605u) {
for (int index = 0; index <= 0x1A; ++index) {
ReplaySection section = {};
std::fread(&section, sizeof(section), 1, this->replay->file);
if (section.id > 0x1A) {
break;
}
this->replay->sections[section.id] = section;
}
if (this->replay->ParseMetadata(this->replay->ReadSection(2)) != 0xFFFFFFFFu) {
return 1;
}
std::memset(&this->replay->header, 0, sizeof(this->replay->header));
}
if (this->replay->file) {
std::fclose(this->replay->file);
this->replay->file = nullptr;
}
std::memset(&this->replay->header, 0, sizeof(this->replay->header));
std::memset(this->replay->sections, 0, sizeof(this->replay->sections));
}
return 0;
}
this->Refresh();
if (!this->pathEnabled) {
int index = 1;
for (std::size_t i = 0; i < this->files.size(); ++i) {
if (this->files[i].index == index) {
++index;
}
}
std::memset(this->path, 0, sizeof(this->path));
std::sprintf(this->path, g_replayNewFileFormat, index);
} else {
if (this->rejectExistingName) {
char name[0x104];
std::memset(name, 0, sizeof(name));
std::sprintf(name, g_replayExtensionFormat, this->path);
for (std::size_t i = 0; i < this->files.size(); ++i) {
if (std::strcmp(name, this->files[i].name) == 0) {
std::memset(this->path, 0, sizeof(this->path));
return -2;
}
}
}
char name[0x104];
std::memset(name, 0, sizeof(name));
std::sprintf(name, g_replayPathFormat, this->path);
std::memcpy(this->path, name, sizeof(this->path));
}
this->replay->fileMode = 0x1F;
this->replay->file = std::fopen(this->path, "wb");
if (!this->replay->file) {
return 0;
}
this->replay->fileOffset = 0x174;
return 1;
}
// OFFSET: 0x00B87680
// FUNCTION: fn_19355
ReplayFileList& ReplayFileList::Instance() {
// GLOBAL: gl_24867
static ReplayFileList g_replayFileList;
return g_replayFileList;
}
// OFFSET: 0x00B87110
// FUNCTION: fn_19339
bool Replay::SetHeaderInfo(
std::uint32_t key0, std::uint32_t key1, const char* characterName, const char* mapName, int accountId, int cellX, int cellY, int unknown0078, int service,
int serviceType, int unknown0014
) {
if (!characterName || !mapName) {
return false;
}
std::memcpy(this->header.mapName, mapName, sizeof(this->header.mapName));
std::memcpy(this->header.characterName, characterName, sizeof(this->header.characterName));
this->header.service = service;
this->header.serviceType = serviceType;
this->header.sessionSyncValue = unknown0014;
this->header.key0 = key0;
this->header.key1 = key1;
this->header.accountId = accountId;
this->header.cellX = cellX;
this->header.cellY = cellY;
this->header.playerDirection = unknown0078;
this->header.signature = 0x52414705;
this->header.closingSignature = 0x52414705;
return true;
}
// OFFSET: 0x00B871C0
// FUNCTION: fn_19340
void Replay::IncreaseSpeed() {
if (0.25f == this->speed) {
this->speed = 0.5f;
} else if (0.5f == this->speed) {
this->speed = 1.0f;
} else if (1.0f == this->speed) {
this->speed = 2.0f;
} else if (2.0f == this->speed || 4.0f == this->speed) {
this->speed = 4.0f;
}
}
// OFFSET: 0x00B87240
// FUNCTION: fn_19341
void Replay::DecreaseSpeed() {
if (0.25f == this->speed || 0.5f == this->speed) {
this->speed = 0.25f;
} else if (1.0f == this->speed) {
this->speed = 0.5f;
} else if (2.0f == this->speed) {
this->speed = 1.0f;
} else if (4.0f == this->speed) {
this->speed = 2.0f;
}
}
// OFFSET: 0x00B872C0
// FUNCTION: fn_19342
void Replay::RestartClock() {
if (this->state != 3 && this->state != 8) {
this->baseFrame = this->frame;
this->started = this->previousTick = timeGetTime();
}
}
// OFFSET: 0x00E3E460
// FUNCTION: fn_24196
std::vector<unsigned char> CSession::BuildStatusReplayPacket() {
std::vector<unsigned char> packet(29, 0);
unsigned char* data = packet.data();
*reinterpret_cast<std::uint16_t*>(data) = 0x983;
*reinterpret_cast<std::uint16_t*>(data + 2) = 0x2A1;
*reinterpret_cast<std::uint32_t*>(data + 9) = 9999;
*reinterpret_cast<std::uint32_t*>(data + 4) = this->m_aid;
data[8] = 1;
*reinterpret_cast<std::uint32_t*>(data + 13) = 9999;
*reinterpret_cast<std::uint32_t*>(data + 17) = this->replayPacketMode;
return packet;
}
// OFFSET: 0x00E31240
// FUNCTION: fn_24172
std::vector<unsigned char> BuildSkillInfoListPacket(std::uint16_t id, const std::list<CSkillInfo>& skills) {
const int bytes = static_cast<int>(skills.size()) * 37 + 4;
std::vector<unsigned char> packet(bytes, 0);
unsigned char* data = packet.data();
*reinterpret_cast<std::uint16_t*>(data) = id;
*reinterpret_cast<std::uint16_t*>(data + 2) = static_cast<std::uint16_t>(bytes);
unsigned char* record = data + 4;
for (const CSkillInfo& skill : skills) {
*reinterpret_cast<std::uint16_t*>(record) = static_cast<std::uint16_t>(skill.id);
*reinterpret_cast<std::uint32_t*>(record + 2) = skill.type;
*reinterpret_cast<std::uint16_t*>(record + 6) = static_cast<std::uint16_t>(skill.level);
*reinterpret_cast<std::uint16_t*>(record + 8) = static_cast<std::uint16_t>(skill.spCost);
record[36] = skill.upgradable != 0;
*reinterpret_cast<std::uint16_t*>(record + 10) = static_cast<std::uint16_t>(skill.range);
const char* name = skill.name;
std::memcpy(record + 12, name, (std::min)(std::strlen(name), std::size_t{24}));
record[35] = 0;
record += 37;
}
return packet;
}
// OFFSET: 0x00E44640
// FUNCTION: fn_24205
std::vector<unsigned char> BuildRenewQuestReplayRecords() {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(3500, nullptr, 0));
{
const int version = 51;
AppendRecord(records, MakeReplayRecord(3502, &version, sizeof(version)));
}
const int size = g_renewQuestMgr->GetOngoingQuestsSize();
char* buffer = new char[size];
g_renewQuestMgr->WriteOngoingQuests(buffer);
AppendRecord(records, BuildReplayTaggedRecord(3510, buffer, size));
if (buffer != nullptr) {
delete buffer;
}
AppendRecord(records, MakeReplayRecord(3501, nullptr, 0));
return records;
}
// OFFSET: 0x00E449C0
// FUNCTION: fn_24206
std::vector<unsigned char> CSession::BuildShortcutReplayRecords() {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(7000, nullptr, 0));
AppendRecord(records, MakeReplayRecord(7100, this->m_shortcutSlots, sizeof(this->m_shortcutSlots)));
AppendRecord(records, MakeReplayRecord(7101, &this->quickSlot, sizeof(this->quickSlot) + sizeof(this->alternateQuickSlot)));
AppendRecord(records, MakeReplayRecord(7001, nullptr, 0));
return records;
}
// OFFSET: 0x00E44DF0
// FUNCTION: fn_24207
std::vector<unsigned char> CSession::BuildSkillListReplayPacket(short id) {
switch (id) {
case ID_ZC_SKILLINFO_LIST:
return BuildSkillInfoListPacket(0x10F, this->m_skillBundle.skills);
case ID_ZC_HOSKILLINFO_LIST:
return BuildSkillInfoListPacket(0x235, this->m_homunSkills);
case ID_ZC_MER_SKILLINFO_LIST:
return BuildSkillInfoListPacket(0x29D, this->m_mercSkills);
default:
return {};
}
}
// OFFSET: 0x00B86440
// FUNCTION: fn_19329
void Replay::CachePacket(std::uint16_t id, std::uint16_t size, const unsigned char* data) {
if (size == 0 || id > 3142 || data == nullptr) {
return;
}
if (!this->cachedPackets[static_cast<short>(id)].enabled) {
return;
}
this->cachedPackets[static_cast<short>(id)].bytes.resize(size, 0);
this->cachedPackets[static_cast<short>(id)].bytes.assign(data, data + size);
}
// OFFSET: 0x00E42EB0
// FUNCTION: fn_24203
std::vector<unsigned char> CSession::BuildReservedReplayRecords() {
std::vector<unsigned char> records;
return records;
}
// OFFSET: 0x00E41290
// FUNCTION: fn_24200
std::vector<unsigned char> CSession::BuildItemReplayRecords() {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(4500, nullptr, 0));
AppendRecord(records, MakeReplayRecord(4701, &this->exchangeMoney, sizeof(this->exchangeMoney)));
AppendRecord(records, MakeReplayRecord(4702, &this->otherExchangeMoney, sizeof(this->otherExchangeMoney)));
AppendRecord(records, MakeFramedMessage(4510, SerializeItems(this->m_inventoryItems)));
AppendRecord(records, MakeFramedMessage(4511, SerializeItems(this->shopItems)));
AppendRecord(records, MakeFramedMessage(4512, SerializeItems(this->purchaseItems)));
AppendRecord(records, MakeFramedMessage(4513, SerializeItems(this->sellItems)));
AppendRecord(records, MakeFramedMessage(4514, SerializeItems(this->storageItems)));
AppendRecord(records, MakeFramedMessage(4515, SerializeItems(this->cartItems)));
AppendRecord(records, MakeFramedMessage(4516, SerializeItems(this->merchantShopItems)));
AppendRecord(records, MakeFramedMessage(4517, SerializeItems(this->merchantMirrorItems)));
AppendRecord(records, MakeFramedMessage(4518, SerializeItems(this->merchantMyShopItems)));
AppendRecord(records, MakeFramedMessage(4519, SerializeItems(this->merchantPurchaseItems)));
AppendRecord(records, MakeFramedMessage(4520, SerializeItems(this->merchantHopeToSellItems)));
AppendRecord(records, MakeFramedMessage(4521, SerializeItems(this->merchantPurchaseSavedItems)));
AppendRecord(records, MakeFramedMessage(4522, SerializeItems(this->filteredInventoryItems)));
AppendRecord(records, MakeFramedMessage(4601, SerializeItems(std::list<ITEM_INFO>(std::begin(this->equippedItems), std::end(this->equippedItems)))));
AppendRecord(records, MakeFramedMessage(4602, SerializeItems(std::list<ITEM_INFO>(std::begin(this->alternateEquippedItems), std::end(this->alternateEquippedItems)))));
AppendRecord(records, MakeFramedMessage(4603, SerializeItems(std::list<ITEM_INFO>(std::begin(this->costumeItems), std::end(this->costumeItems)))));
AppendRecord(records, MakeFramedMessage(4604, SerializeItems(std::list<ITEM_INFO>(std::begin(this->alternateCostumeItems), std::end(this->alternateCostumeItems)))));
AppendRecord(records, MakeFramedMessage(4605, SerializeItems(std::list<ITEM_INFO>(std::begin(this->m_exchangeItems), std::end(this->m_exchangeItems)))));
AppendRecord(records, MakeFramedMessage(4606, SerializeItems(std::list<ITEM_INFO>(std::begin(this->m_itemSlots), std::end(this->m_itemSlots)))));
AppendRecord(records, MakeReplayRecord(4501, nullptr, 0));
return records;
}
// OFFSET: 0x00E42F20
// FUNCTION: fn_24204
std::vector<unsigned char> CSession::BuildCompanionReplayRecords() {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(5000, nullptr, 0));
AppendRecord(records, MakeReplayRecord(5101, &this->spAndHomunculusStats[0x08], 4));
AppendRecord(records, MakeReplayRecord(5102, &this->spAndHomunculusStats[0x0C], 32));
AppendRecord(records, MakeReplayRecord(5103, &this->spAndHomunculusStats[0x2C], 4));
AppendRecord(records, MakeReplayRecord(5104, &this->spAndHomunculusStats[0x30], 4));
AppendRecord(records, MakeReplayRecord(5105, &this->spAndHomunculusStats[0x34], 4));
AppendRecord(records, MakeReplayRecord(5106, &this->spAndHomunculusStats[0x38], 4));
AppendRecord(records, MakeReplayRecord(5107, &this->spAndHomunculusStats[0x3C], 4));
AppendRecord(records, MakeReplayRecord(5108, &this->spAndHomunculusStats[0x40], 4));
AppendRecord(records, MakeReplayRecord(5109, &this->spAndHomunculusStats[0x44], 4));
AppendRecord(records, MakeReplayRecord(5110, &this->spAndHomunculusStats[0x48], 4));
AppendRecord(records, MakeReplayRecord(5111, &this->m_homunJob, sizeof(this->m_homunJob)));
AppendRecord(records, MakeReplayRecord(5112, this->homunLevel, 4));
AppendRecord(records, MakeReplayRecord(5113, &this->m_homunHp, sizeof(this->m_homunHp)));
AppendRecord(records, MakeReplayRecord(5114, this->homunMaxHp, 4));
AppendRecord(records, MakeReplayRecord(5115, &this->m_homunSp, sizeof(this->m_homunSp)));
AppendRecord(records, MakeReplayRecord(5116, &this->homunMaxSp, sizeof(this->homunMaxSp)));
AppendRecord(records, MakeReplayRecord(5117, &this->homunFriendly, sizeof(this->homunFriendly)));
AppendRecord(records, MakeReplayRecord(5118, &this->homunAccessory, sizeof(this->homunAccessory)));
AppendRecord(records, MakeReplayRecord(5119, &this->m_homunExp, sizeof(this->m_homunExp)));
AppendRecord(records, MakeReplayRecord(5120, &this->m_homunMaxExp, sizeof(this->m_homunMaxExp)));
AppendRecord(records, MakeReplayRecord(5121, &this->homunHunger, sizeof(this->homunHunger)));
AppendRecord(records, MakeReplayRecord(5122, &this->homunMaxHunger, sizeof(this->homunMaxHunger)));
AppendRecord(records, MakeReplayRecord(5123, &this->homunNameModified, sizeof(this->homunNameModified)));
AppendRecord(records, MakeReplayRecord(5124, &this->homunEnemyGid, sizeof(this->homunEnemyGid)));
AppendRecord(records, MakeReplayRecord(5125, &this->homunEnemyX, sizeof(this->homunEnemyX)));
AppendRecord(records, MakeReplayRecord(5126, &this->homunEnemyY, sizeof(this->homunEnemyY)));
AppendRecord(records, MakeReplayRecord(5127, &this->moveHomunEnemy, sizeof(this->moveHomunEnemy)));
AppendRecord(records, MakeReplayRecord(5128, &this->selectHomunEnemy, sizeof(this->selectHomunEnemy)));
AppendRecord(records, MakeReplayRecord(5129, &this->attackHomunEnemy, sizeof(this->attackHomunEnemy)));
AppendRecord(records, MakeReplayRecord(5130, &this->homunAlive, sizeof(this->homunAlive)));
AppendRecord(records, MakeReplayRecord(5131, &this->m_traceAi, sizeof(this->m_traceAi)));
AppendRecord(records, MakeReplayRecord(5132, &this->m_homunActionState, sizeof(this->m_homunActionState)));
AppendRecord(records, MakeReplayRecord(5133, &this->companionTargetState[0x0], 4));
AppendRecord(records, MakeReplayRecord(5134, &this->companionTargetState[0x4], 4));
AppendRecord(records, MakeReplayRecord(5201, &this->companionTargetState[0x8], 4));
AppendRecord(records, MakeReplayRecord(5202, this->m_mercenaryName, sizeof(this->m_mercenaryName)));
AppendRecord(records, MakeReplayRecord(5203, &this->m_mercenaryStats[0], sizeof(this->m_mercenaryStats[0])));
AppendRecord(records, MakeReplayRecord(5204, &this->m_mercenaryStats[1], sizeof(this->m_mercenaryStats[1])));
AppendRecord(records, MakeReplayRecord(5205, &this->m_mercenaryStats[2], sizeof(this->m_mercenaryStats[2])));
AppendRecord(records, MakeReplayRecord(5206, &this->m_mercenaryStats[3], sizeof(this->m_mercenaryStats[3])));
AppendRecord(records, MakeReplayRecord(5207, &this->m_mercenaryStats[4], sizeof(this->m_mercenaryStats[4])));
AppendRecord(records, MakeReplayRecord(5208, &this->m_mercenaryStats[5], sizeof(this->m_mercenaryStats[5])));
AppendRecord(records, MakeReplayRecord(5209, &this->m_mercenaryStats[6], sizeof(this->m_mercenaryStats[6])));
AppendRecord(records, MakeReplayRecord(5210, &this->m_mercenaryStats[7], sizeof(this->m_mercenaryStats[7])));
AppendRecord(records, MakeReplayRecord(5211, &this->m_mercenaryHp, sizeof(this->m_mercenaryHp)));
AppendRecord(records, MakeReplayRecord(5212, &this->m_mercenaryMaxHp, sizeof(this->m_mercenaryMaxHp)));
AppendRecord(records, MakeReplayRecord(5213, &this->m_mercenaryClass, sizeof(this->m_mercenaryClass)));
AppendRecord(records, MakeReplayRecord(5214, &this->m_mercenaryLevel, sizeof(this->m_mercenaryLevel)));
AppendRecord(records, MakeReplayRecord(5215, &this->m_mercenarySp, sizeof(this->m_mercenarySp)));
AppendRecord(records, MakeReplayRecord(5216, &this->m_mercenaryMaxSp, sizeof(this->m_mercenaryMaxSp)));
AppendRecord(records, MakeReplayRecord(5217, &this->m_mercenaryLoyalty, sizeof(this->m_mercenaryLoyalty)));
AppendRecord(records, MakeReplayRecord(5218, &this->m_mercenarySummons, sizeof(this->m_mercenarySummons)));
AppendRecord(records, MakeReplayRecord(5219, &this->m_mercenaryKills, sizeof(this->m_mercenaryKills)));
AppendRecord(records, MakeReplayRecord(5220, this->mercenaryAttackRange, 4));
AppendRecord(records, MakeReplayRecord(5221, &this->m_mercenaryExpiration, sizeof(this->m_mercenaryExpiration)));
AppendRecord(records, MakeReplayRecord(5222, &this->mercenaryEnemyGid, sizeof(this->mercenaryEnemyGid)));
AppendRecord(records, MakeReplayRecord(5223, &this->mercenaryEnemyX, sizeof(this->mercenaryEnemyX)));
AppendRecord(records, MakeReplayRecord(5224, &this->mercenaryEnemyY, sizeof(this->mercenaryEnemyY)));
AppendRecord(records, MakeReplayRecord(5225, &this->moveMercenaryEnemy, sizeof(this->moveMercenaryEnemy)));
AppendRecord(records, MakeReplayRecord(5226, &this->selectMercenaryEnemy, sizeof(this->selectMercenaryEnemy)));
AppendRecord(records, MakeReplayRecord(5227, &this->attackMercenaryEnemy, sizeof(this->attackMercenaryEnemy)));
AppendRecord(records, MakeReplayRecord(5228, &this->mercenaryAlive, sizeof(this->mercenaryAlive)));
AppendRecord(records, MakeReplayRecord(5229, &this->m_mercenaryActionState, sizeof(this->m_mercenaryActionState)));
AppendRecord(records, MakeReplayRecord(5301, &this->petGid, sizeof(this->petGid)));
AppendRecord(records, MakeReplayRecord(5302, this->petNameModified, 4));
AppendRecord(records, MakeReplayRecord(5303, this->petName, 32));
AppendRecord(records, MakeReplayRecord(5304, &this->petAccessory, sizeof(this->petAccessory)));
AppendRecord(records, MakeReplayRecord(5305, &this->petJob, sizeof(this->petJob)));
AppendRecord(records, MakeReplayRecord(5306, &this->petLevel, sizeof(this->petLevel)));
AppendRecord(records, MakeReplayRecord(5307, &this->petHunger, sizeof(this->petHunger)));
AppendRecord(records, MakeReplayRecord(5308, &this->m_petFriendly, sizeof(this->m_petFriendly)));
AppendRecord(records, MakeReplayRecord(5309, &this->petEggIndex, sizeof(this->petEggIndex)));
AppendRecord(records, MakeReplayRecord(5310, &this->petPreviousHunger, sizeof(this->petPreviousHunger)));
AppendRecord(records, MakeReplayRecord(5401, &this->elementalGid, sizeof(this->elementalGid)));
AppendRecord(records, MakeReplayRecord(5402, &this->elementalHp, sizeof(this->elementalHp)));
AppendRecord(records, MakeReplayRecord(5403, &this->elementalSp, sizeof(this->elementalSp)));
AppendRecord(records, MakeReplayRecord(5404, &this->elementalMaxHp, sizeof(this->elementalMaxHp)));
AppendRecord(records, MakeReplayRecord(5405, &this->elementalMaxSp, sizeof(this->elementalMaxSp)));
AppendRecord(records, MakeReplayRecord(5001, nullptr, 0));
return records;
}
namespace {
#pragma pack(push, 1)
struct ReplayShopNameRecord {
/* 0000 */ std::int16_t unknown;
/* 0002 */ char name[16];
/* 0012 */ int shopId;
};
#pragma pack(pop)
struct ReplayShopItemRecord {
/* 0000 */ int shopId;
/* 0004 */ unsigned int index;
/* 0008 */ int itemId;
/* 000C */ int first;
/* 0010 */ int second;
};
struct ReplayShopFlagRecord {
/* 0000 */ int shopId;
/* 0004 */ char flag;
};
}
// OFFSET: 0x00E30A90
// FUNCTION: fn_24171
std::vector<unsigned char> MessengerMember::Serialize() const {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(251, nullptr, 0));
AppendRecord(records, MakeReplayRecord(253, &this->valid, sizeof(this->valid)));
AppendRecord(records, MakeReplayRecord(254, &this->accountId, sizeof(this->accountId)));
AppendRecord(records, MakeReplayRecord(255, &this->characterId, sizeof(this->characterId)));
AppendRecord(records, MakeReplayRecord(258, &this->leader, sizeof(this->leader)));
AppendRecord(records, MakeReplayRecord(259, &this->offline, sizeof(this->offline)));
AppendRecord(records, MakeReplayRecord(260, &this->color, sizeof(this->color)));
AppendRecord(records, BuildReplayTaggedRecord(256, this->name.c_str(), this->name.size()));
AppendRecord(records, BuildReplayTaggedRecord(257, this->map.c_str(), this->map.size()));
AppendRecord(records, MakeReplayRecord(252, nullptr, 0));
return records;
}
// OFFSET: 0x00E3E550
// FUNCTION: fn_24197
std::vector<unsigned char> CSession::BuildCharacterReplayRecords() {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(1000, nullptr, 0));
AppendRecord(records, MakeReplayRecord(1010, &this->m_aid, sizeof(this->m_aid)));
AppendRecord(records, MakeReplayRecord(1011, &this->m_gid, sizeof(this->m_gid)));
AppendRecord(records, MakeReplayRecord(1012, &this->m_navigationPlayerMap[0x14], 4));
AppendRecord(records, MakeReplayRecord(1013, &this->m_navigationPlayerMap[0x18], 4));
AppendRecord(records, MakeReplayRecord(1014, &this->m_job, sizeof(this->m_job)));
AppendRecord(records, MakeReplayRecord(1015, &this->playerExperienceState[0x4], 8));
AppendRecord(records, MakeReplayRecord(1016, &this->m_level, sizeof(this->m_level)));
AppendRecord(records, MakeReplayRecord(1017, this->statusPoints, 4));
AppendRecord(records, MakeReplayRecord(1018, &this->playerExperienceState[0xC], 8));
AppendRecord(records, MakeReplayRecord(1019, &this->m_jobLevel, sizeof(this->m_jobLevel)));
AppendRecord(records, MakeReplayRecord(1020, &this->m_skillPoint, 4));
AppendRecord(records, MakeReplayRecord(1021, &this->m_guildSkillPoint, sizeof(this->m_guildSkillPoint)));
AppendRecord(records, MakeReplayRecord(1022, this->companionSkillPoints, 4));
AppendRecord(records, MakeReplayRecord(1023, &this->companionSkillPoints[0x4], 4));
AppendRecord(records, MakeReplayRecord(1024, &this->m_standardStats[0], sizeof(this->m_standardStats[0])));
AppendRecord(records, MakeReplayRecord(1025, &this->m_standardStats[1], sizeof(this->m_standardStats[1])));
AppendRecord(records, MakeReplayRecord(1026, &this->m_standardStats[2], sizeof(this->m_standardStats[2])));
AppendRecord(records, MakeReplayRecord(1027, &this->m_standardStats[3], sizeof(this->m_standardStats[3])));
AppendRecord(records, MakeReplayRecord(1028, &this->m_standardStats[4], sizeof(this->m_standardStats[4])));
AppendRecord(records, MakeReplayRecord(1029, &this->m_standardStats[5], sizeof(this->m_standardStats[5])));
AppendRecord(records, MakeReplayRecord(1030, this->combatStats, 4));
AppendRecord(records, MakeReplayRecord(1031, &this->combatStats[0x4], 4));
AppendRecord(records, MakeReplayRecord(1032, &this->combatStats[0x8], 4));
AppendRecord(records, MakeReplayRecord(1033, &this->combatStats[0xC], 4));
AppendRecord(records, MakeReplayRecord(1034, &this->combatStats[0x10], 4));
AppendRecord(records, MakeReplayRecord(1035, &this->combatStats[0x14], 4));
AppendRecord(records, MakeReplayRecord(1036, &this->combatStats[0x18], 4));
AppendRecord(records, MakeReplayRecord(1037, &this->combatStats[0x1C], 4));
AppendRecord(records, MakeReplayRecord(1038, &this->combatStats[0x20], 4));
AppendRecord(records, MakeReplayRecord(1039, &this->combatStats[0x24], 4));
AppendRecord(records, MakeReplayRecord(1040, &this->combatStats[0x28], 4));
AppendRecord(records, MakeReplayRecord(1041, &this->combatStats[0x2C], 4));
AppendRecord(records, MakeReplayRecord(1042, &this->combatStats[0x30], 4));
AppendRecord(records, MakeReplayRecord(1043, &this->combatStats[0x34], 4));
AppendRecord(records, MakeReplayRecord(1044, &this->combatStats[0x38], 4));
AppendRecord(records, MakeReplayRecord(1045, &this->combatStats[0x3C], 4));
AppendRecord(records, MakeReplayRecord(1046, &this->combatStats[0x40], 4));
AppendRecord(records, MakeReplayRecord(1047, &this->combatStats[0x44], 4));
AppendRecord(records, MakeReplayRecord(1048, &this->combatStats[0x48], 4));
AppendRecord(records, MakeReplayRecord(1049, &this->combatStats[0x4C], 4));
AppendRecord(records, MakeReplayRecord(1050, &this->equippedArrowIndex, sizeof(this->equippedArrowIndex)));
AppendRecord(records, MakeReplayRecord(1051, &this->m_zeny, sizeof(this->m_zeny)));
AppendRecord(records, MakeReplayRecord(1052, this->healthPoints, 4));
AppendRecord(records, MakeReplayRecord(1053, &this->healthPoints[0x4], 4));
AppendRecord(records, MakeReplayRecord(1054, &this->playerExperienceState[0x14], 4));
AppendRecord(records, MakeReplayRecord(1055, &this->playerExperienceState[0x1C], 4));
AppendRecord(records, MakeReplayRecord(1056, this->optionFlags, 4));
AppendRecord(records, MakeReplayRecord(1057, &this->monsterInfoEnabled, sizeof(this->monsterInfoEnabled)));
AppendRecord(records, MakeReplayRecord(1058, this->clientTimeOffset, 4));
AppendRecord(records, MakeReplayRecord(1059, &this->characterLoginData[0x30], 4));
AppendRecord(records, MakeReplayRecord(1060, &this->m_head, sizeof(this->m_head)));
AppendRecord(records, MakeReplayRecord(1061, &this->weaponAppearance[0x4], 8));
AppendRecord(records, MakeReplayRecord(1062, &this->weaponAppearance[0xC], 4));
AppendRecord(records, MakeReplayRecord(1063, &this->m_bodyStyle, sizeof(this->m_bodyStyle)));
AppendRecord(records, MakeReplayRecord(1064, &this->m_headPalette, sizeof(this->m_headPalette)));
AppendRecord(records, MakeReplayRecord(1065, this->accessoryAppearance, 4));
AppendRecord(records, MakeReplayRecord(1066, &this->accessoryAppearance[0x4], 4));
AppendRecord(records, MakeReplayRecord(1067, &this->accessoryAppearance[0x8], 4));
AppendRecord(records, MakeReplayRecord(1068, this->statusLevelEffects, 4));
AppendRecord(records, MakeReplayRecord(1069, &this->statusLevelEffects[0x4], 4));
AppendRecord(records, MakeReplayRecord(1070, &this->m_jobFlags, sizeof(this->m_jobFlags)));
AppendRecord(records, MakeReplayRecord(1071, &this->m_robe, sizeof(this->m_robe)));
AppendRecord(records, MakeReplayRecord(1072, &this->equipmentViewState[0x4], 4));
AppendRecord(records, MakeReplayRecord(1073, &this->equipmentViewState[0x8], 4));
AppendRecord(records, MakeReplayRecord(1074, &this->equipmentViewState[0xC], 4));
AppendRecord(records, MakeReplayRecord(1075, &this->equipmentViewState[0x10], 4));
AppendRecord(records, MakeReplayRecord(1076, &this->equipmentViewState[0x14], 4));
AppendRecord(records, MakeReplayRecord(1077, &this->equipmentViewState[0x18], 4));
AppendRecord(records, MakeReplayRecord(1078, &this->equipmentViewState[0x1C], 4));
AppendRecord(records, MakeReplayRecord(1079, &this->equipmentViewState[0x20], 4));
AppendRecord(records, MakeReplayRecord(1080, &this->equipmentViewState[0x24], 4));
AppendRecord(records, MakeReplayRecord(1081, &this->equipmentViewState[0x2C], 0x18));
AppendRecord(records, MakeReplayRecord(1082, this->playerTeam, 1));
AppendRecord(records, MakeReplayRecord(1083, &this->m_score1, sizeof(this->m_score1)));
AppendRecord(records, MakeReplayRecord(1084, &this->m_score2, sizeof(this->m_score2)));
AppendRecord(records, MakeReplayRecord(1085, this->chatFont, 1));
AppendRecord(records, MakeReplayRecord(1086, &this->cartCount, sizeof(this->cartCount)));
AppendRecord(records, MakeReplayRecord(1087, &this->cartMaxCount, sizeof(this->cartMaxCount)));
AppendRecord(records, MakeReplayRecord(1088, &this->cartWeight, sizeof(this->cartWeight)));
AppendRecord(records, MakeReplayRecord(1089, &this->cartMaxWeight, sizeof(this->cartMaxWeight)));
AppendRecord(records, MakeReplayRecord(1090, this->storeSlotCounts, 4));
AppendRecord(records, MakeReplayRecord(1091, &this->storeSlotCounts[0x4], 4));
AppendRecord(records, MakeReplayRecord(1092, &this->itemSendState[0x8], 4));
AppendRecord(records, MakeReplayRecord(1093, &this->itemSendState[0xC], 4));
AppendRecord(records, MakeReplayRecord(1094, &this->partyExpShare, sizeof(this->partyExpShare)));
AppendRecord(records, MakeReplayRecord(1095, &this->partyItemCollect, sizeof(this->partyItemCollect)));
AppendRecord(records, MakeReplayRecord(1096, &this->partyItemDistribute, sizeof(this->partyItemDistribute)));
AppendRecord(records, BuildReplayTaggedRecord(1097, this->storeTitle.c_str(), this->storeTitle.size()));
const unsigned char option = g_serviceOptions.Get(0x40);
AppendRecord(records, MakeReplayRecord(1098, &option, sizeof(option)));
AppendRecord(records, MakeReplayRecord(1099, &this->adoptionSkillAvailable, sizeof(this->adoptionSkillAvailable)));
AppendRecord(records, MakeReplayRecord(1100, &this->m_bodyCostume, sizeof(this->m_bodyCostume)));
AppendRecord(records, MakeReplayRecord(1101, &this->extraInventorySlots, sizeof(this->extraInventorySlots)));
AppendRecord(records, MakeReplayRecord(1001, nullptr, 0));
return records;
}
// OFFSET: 0x00E41E60
// FUNCTION: fn_24201
std::vector<unsigned char> CSession::BuildShopReplayRecords() {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(7500, nullptr, 0));
for (auto it = this->m_nameToId.begin(); it != this->m_nameToId.end(); ++it) {
ReplayShopNameRecord value;
value.unknown = 0;
value.shopId = it->second;
std::memset(value.name, 0, sizeof(value.name));
std::memcpy(value.name, it->first.c_str(), (std::min)(it->first.size(), sizeof(value.name)));
AppendRecord(records, MakeReplayRecord(7502, &value, sizeof(value)));
}
for (auto shop = this->m_shops.begin(); shop != this->m_shops.end(); ++shop) {
for (unsigned int index = 0; index < shop->second.items.size(); ++index) {
const std::map<int, std::pair<int, int>>& items = shop->second.items[index];
for (auto item = items.begin(); item != items.end(); ++item) {
ReplayShopItemRecord value;
value.shopId = shop->first;
value.index = index;
value.itemId = item->first;
value.first = item->second.first;
value.second = item->second.second;
AppendRecord(records, MakeReplayRecord(7504, &value, sizeof(value)));
}
}
ReplayShopFlagRecord value;
value.shopId = shop->first;
value.flag = shop->second.flag;
AppendRecord(records, MakeReplayRecord(7505, &value, sizeof(value)));
}
AppendRecord(records, MakeReplayRecord(7501, nullptr, 0));
return records;
}
// OFFSET: 0x00E42600
// FUNCTION: fn_24202
std::vector<unsigned char> CSession::BuildExtraReplayRecords() {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(5500, nullptr, 0));
AppendRecord(records, MakeReplayRecord(5510, &this->memorialWindowOpen, sizeof(this->memorialWindowOpen)));
AppendRecord(records, MakeReplayRecord(5511, &this->memorialWindowX, sizeof(this->memorialWindowX)));
AppendRecord(records, MakeReplayRecord(5512, &this->memorialWindowY, sizeof(this->memorialWindowY)));
AppendRecord(records, MakeReplayRecord(5513, &this->memorialDungeonState, sizeof(this->memorialDungeonState)));
AppendRecord(records, MakeReplayRecord(5514, &this->memorialReplayValue, sizeof(this->memorialReplayValue)));
AppendRecord(records, BuildReplayTaggedRecord(5515, this->memorialDungeonName.c_str(), this->memorialDungeonName.size()));
AppendRecord(records, MakeReplayRecord(5516, &this->memorialQueuePosition, sizeof(this->memorialQueuePosition)));
AppendRecord(records, MakeReplayRecord(5517, &this->memorialCreationTime, sizeof(this->memorialCreationTime)));
AppendRecord(records, MakeReplayRecord(5518, &this->memorialExpirationTime, sizeof(this->memorialExpirationTime)));
AppendRecord(records, MakeReplayRecord(5501, nullptr, 0));
return records;
}
// OFFSET: 0x00E3FF30
// FUNCTION: fn_24198
std::vector<unsigned char> CSession::BuildStatsReplayRecords() {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(2000, nullptr, 0));
AppendRecord(records, MakeReplayRecord(2010, &this->strengthBonus, sizeof(this->strengthBonus)));
AppendRecord(records, MakeReplayRecord(2011, &this->agilityBonus, sizeof(this->agilityBonus)));
AppendRecord(records, MakeReplayRecord(2012, &this->vitalityBonus, sizeof(this->vitalityBonus)));
AppendRecord(records, MakeReplayRecord(2013, &this->intelligenceBonus, sizeof(this->intelligenceBonus)));
AppendRecord(records, MakeReplayRecord(2014, &this->dexterityBonus, sizeof(this->dexterityBonus)));
AppendRecord(records, MakeReplayRecord(2015, &this->luckBonus, sizeof(this->luckBonus)));
AppendRecord(records, BuildReplayTaggedRecord(2016, &this->storeItemLimit, sizeof(this->storeItemLimit)));
AppendRecord(records, BuildReplayTaggedRecord(2017, &this->m_weight, sizeof(this->m_weight)));
AppendRecord(records, BuildReplayTaggedRecord(2018, &this->m_maxWeight, sizeof(this->m_maxWeight)));
AppendRecord(records, BuildReplayTaggedRecord(2019, &this->randomStatIndexA, sizeof(this->randomStatIndexA)));
AppendRecord(records, BuildReplayTaggedRecord(2020, &this->randomStatIndexB, sizeof(this->randomStatIndexB)));
AppendRecord(records, BuildReplayTaggedRecord(2021, &this->randomStatIndexC, sizeof(this->randomStatIndexC)));
AppendRecord(records, BuildReplayTaggedRecord(2022, &this->randomStatIndexD, sizeof(this->randomStatIndexD)));
AppendRecord(records, BuildReplayTaggedRecord(2023, &this->randomStatIndexE, sizeof(this->randomStatIndexE)));
AppendRecord(records, BuildReplayTaggedRecord(2024, this->randomStatValues, sizeof(this->randomStatValues)));
AppendRecord(records, BuildReplayTaggedRecord(2025, this->replayStatValuesA, sizeof(this->replayStatValuesA)));
AppendRecord(records, BuildReplayTaggedRecord(2026, this->replayStatValuesB, sizeof(this->replayStatValuesB)));
AppendRecord(records, BuildReplayTaggedRecord(2027, this->replayStatValuesC, sizeof(this->replayStatValuesC)));
AppendRecord(records, BuildReplayTaggedRecord(2028, this->minimapTokenStorage, 0x30));
AppendRecord(records, BuildReplayTaggedRecord(2029, &this->hp, sizeof(this->hp)));
AppendRecord(records, BuildReplayTaggedRecord(2030, &this->maxHp, sizeof(this->maxHp)));
AppendRecord(records, BuildReplayTaggedRecord(2031, &this->spAndHomunculusStats[0], 4));
AppendRecord(records, BuildReplayTaggedRecord(2032, &this->spAndHomunculusStats[4], 4));
AppendRecord(records, BuildReplayTaggedRecord(2033, &this->m_prohibitionTarget, sizeof(this->m_prohibitionTarget)));
AppendRecord(records, BuildReplayTaggedRecord(2034, &this->quitBlocked, sizeof(this->quitBlocked)));
AppendRecord(records, BuildReplayTaggedRecord(2035, &this->battleChat, sizeof(this->battleChat)));
AppendRecord(records, BuildReplayTaggedRecord(2036, &this->quitAllowed, sizeof(this->quitAllowed)));
AppendRecord(records, BuildReplayTaggedRecord(2037, &this->sessionFlag, sizeof(this->sessionFlag)));
AppendRecord(records, BuildReplayTaggedRecord(2038, this->m_accountName.c_str(), this->m_accountName.size()));
AppendRecord(records, BuildReplayTaggedRecord(2052, &this->madogearType, sizeof(this->madogearType)));
AppendRecord(records, MakeReplayRecord(2001, nullptr, 0));
return records;
}
// OFFSET: 0x00E40C60
// FUNCTION: fn_24199
std::vector<unsigned char> CSession::BuildMessengerReplayRecords() {
std::vector<unsigned char> records;
AppendRecord(records, MakeReplayRecord(4000, nullptr, 0));
AppendRecord(records, MakeReplayRecord(4010, &this->replaySessionFlag, 1));
AppendRecord(records, MakeReplayRecord(4011, &this->partyBlocked, sizeof(this->partyBlocked)));
AppendRecord(records, MakeReplayRecord(4012, &this->partyLeader, sizeof(this->partyLeader)));
AppendRecord(records, BuildReplayTaggedRecord(4013, this->partyRequestName.c_str(), this->partyRequestName.size()));
AppendRecord(records, BuildReplayTaggedRecord(4014, this->partyName.c_str(), this->partyName.size()));
AppendRecord(records, MakeFramedMessage(4015, SerializeMessengerMembers(this->m_partyMembers)));
AppendRecord(records, MakeFramedMessage(4016, SerializeMessengerMembers(this->m_friends)));
AppendRecord(records, MakeReplayRecord(4001, nullptr, 0));
return records;
}