#include #include "config.h" #include "replay.h" #include "myfilesystem.h" #include "deck_manager.h" #define LZMA_API_STATIC #include namespace ygo { Replay::Replay() { replay_data = new unsigned char[MAX_REPLAY_SIZE]; comp_data = new unsigned char[MAX_COMP_SIZE]; } Replay::~Replay() { delete[] replay_data; delete[] comp_data; } void Replay::BeginRecord() { if(!FileSystem::IsDirExists(L"./replay") && !FileSystem::MakeDir(L"./replay")) return; if(is_recording) std::fclose(fp); fp = myfopen("./replay/_LastReplay.yrp", "wb"); if(!fp) return; Reset(); is_recording = true; } void Replay::WriteHeader(ExtendedReplayHeader& header) { pheader = header; std::fwrite(&header, sizeof header, 1, fp); std::fflush(fp); } void Replay::WriteData(const void* data, size_t length, bool flush) { if(!is_recording) return; if (length > MAX_REPLAY_SIZE - replay_size) return; std::memcpy(replay_data + replay_size, data, length); replay_size += length; std::fwrite(data, length, 1, fp); if(flush) std::fflush(fp); } void Replay::WriteInt32(int32_t data, bool flush) { Write(data, flush); } void Replay::Flush() { if(!is_recording) return; std::fflush(fp); } void Replay::EndRecord() { if(!is_recording) return; std::fclose(fp); pheader.base.datasize = replay_size; pheader.base.flag |= REPLAY_COMPRESSED; lzma_options_lzma opt; lzma_lzma_preset(&opt, 5); opt.dict_size = 0x1U << 24; opt.lc = 3; opt.lp = 0; opt.pb = 2; opt.nice_len = 32; opt.mf = LZMA_MF_BT4; opt.ext_flags = 0; lzma_filter filters[2]; filters[0].id = LZMA_FILTER_LZMA1EXT; filters[0].options = &opt; filters[1].id = LZMA_VLI_UNKNOWN; filters[1].options = nullptr; lzma_properties_encode(&filters[0], pheader.base.props); size_t out_pos = 0; lzma_ret lret = lzma_raw_buffer_encode(filters, nullptr, replay_data, replay_size, comp_data, &out_pos, MAX_COMP_SIZE); if (lret != LZMA_OK) { std::memcpy(comp_data, &lret, sizeof lret); comp_size = sizeof lret; } else { comp_size = out_pos; } is_recording = false; } bool Replay::SaveReplay(const wchar_t* base_name) { if(!FileSystem::IsDirExists(L"./replay") && !FileSystem::MakeDir(L"./replay")) return false; wchar_t filename[256]{}; wchar_t path[256]{}; BufferIO::CopyWideString(base_name, filename); FileSystem::SafeFileName(filename); if (myswprintf(path, L"./replay/%ls.yrp", filename) <= 0) return false; FILE* rfp = mywfopen(path, "wb"); if(!rfp) return false; std::fwrite(&pheader, sizeof pheader, 1, rfp); std::fwrite(comp_data, comp_size, 1, rfp); std::fclose(rfp); return true; } bool Replay::OpenReplay(const wchar_t* name) { FILE* rfp = mywfopen(name, "rb"); if(!rfp) { wchar_t fname[256]; if (myswprintf(fname, L"./replay/%ls", name) <= 0) return false; rfp = mywfopen(fname, "rb"); } if(!rfp) return false; Reset(); bool correct_header = true; if (std::fread(&pheader, sizeof pheader.base, 1, rfp) < 1) correct_header = false; else if (pheader.base.id != REPLAY_ID_YRP1 && pheader.base.id != REPLAY_ID_YRP2) correct_header = false; else if (pheader.base.version < 0x12d0u) correct_header = false; else if (pheader.base.version >= 0x1353u && !(pheader.base.flag & REPLAY_UNIFORM)) correct_header = false; if (!correct_header) { std::fclose(rfp); return false; } if (pheader.base.id == REPLAY_ID_YRP2 && std::fread(reinterpret_cast(&pheader) + sizeof pheader.base, sizeof pheader - sizeof pheader.base, 1, rfp) < 1) { std::fclose(rfp); return false; } if(pheader.base.flag & REPLAY_COMPRESSED) { comp_size = std::fread(comp_data, 1, MAX_COMP_SIZE, rfp); std::fclose(rfp); if (pheader.base.datasize > MAX_REPLAY_SIZE) return false; replay_size = pheader.base.datasize; lzma_filter filter; filter.id = LZMA_FILTER_LZMA1; filter.options = nullptr; if (lzma_properties_decode(&filter, nullptr, pheader.base.props, 5) != LZMA_OK) return false; lzma_options_lzma *lzma_opt = static_cast(filter.options); lzma_set_ext_size(*lzma_opt, replay_size); lzma_opt->ext_flags = LZMA_LZMA1EXT_ALLOW_EOPM; filter.id = LZMA_FILTER_LZMA1EXT; lzma_filter filters[2] = { filter, { LZMA_VLI_UNKNOWN, nullptr } }; size_t in_pos = 0, out_pos = 0; lzma_ret lret = lzma_raw_buffer_decode(filters, nullptr, comp_data, &in_pos, comp_size, replay_data, &out_pos, replay_size); lzma_filters_free(filters, nullptr); if (lret != LZMA_OK || out_pos != replay_size) { replay_size = 0; return false; } } else { replay_size = std::fread(replay_data, 1, MAX_REPLAY_SIZE, rfp); std::fclose(rfp); comp_size = 0; } is_replaying = true; can_read = true; if (!ReadInfo()) { Reset(); return false; } info_offset = data_position; data_position = 0; return true; } bool Replay::DeleteReplay(const wchar_t* name) { if (std::wcschr(name, L'/') || std::wcschr(name, L'\\')) return false; wchar_t fname[256]; if(myswprintf(fname, L"./replay/%ls", name) <= 0) return false; return FileSystem::RemoveFile(fname); } bool Replay::RenameReplay(const wchar_t* oldname, const wchar_t* newname) { wchar_t old_path[256]; wchar_t new_path[256]; if (std::wcschr(oldname, L'/') || std::wcschr(oldname, L'\\')) return false; if (std::wcschr(newname, L'/') || std::wcschr(newname, L'\\')) return false; if (myswprintf(old_path, L"./replay/%ls", oldname) <= 0) return false; if (myswprintf(new_path, L"./replay/%ls", newname) <= 0) return false; return FileSystem::Rename(old_path, new_path); } bool Replay::ReadNextResponse(unsigned char resp[]) { unsigned char len{}; if (!ReadData(&len, sizeof len)) return false; if (!ReadData(resp, len)) return false; return true; } bool Replay::ReadName(wchar_t* data) { uint16_t buffer[20]{}; if (!ReadData(buffer, sizeof buffer)) { return false; } BufferIO::CopyWStr(buffer, data, 20); return true; } void Replay::ReadHeader(ExtendedReplayHeader& header) { header = pheader; } bool Replay::ReadData(void* data, size_t length) { if (!is_replaying || !can_read) return false; if (length > replay_size - data_position) { can_read = false; return false; } if (length) std::memcpy(data, replay_data + data_position, length); data_position += length; return true; } int32_t Replay::ReadInt32() { return Read(); } void Replay::Rewind() { data_position = 0; can_read = true; } void Replay::Reset() { is_recording = false; is_replaying = false; can_read = false; replay_size = 0; comp_size = 0; data_position = 0; info_offset = 0; players.clear(); params = { 0 }; decks.clear(); script_name.clear(); } void Replay::SkipInfo(){ if (data_position == 0) data_position += info_offset; } bool Replay::IsReplaying() const { return is_replaying; } bool Replay::SaveDeck(size_t index, const wchar_t* filename) { if(index >= decks.size()) return false; DeckArray deck = decks[index]; std::reverse(deck.main.begin(), deck.main.end()); std::reverse(deck.extra.begin(), deck.extra.end()); return DeckManager::SaveDeckArray(deck, filename); } bool Replay::ReadInfo() { int player_count = (pheader.base.flag & REPLAY_TAG) ? 4 : 2; for (int i = 0; i < player_count; ++i) { wchar_t name[20]{}; if (!ReadName(name)) return false; players.push_back(name); } if (!ReadData(¶ms, sizeof params)) return false; bool is_tag1 = pheader.base.flag & REPLAY_TAG; bool is_tag2 = params.duel_flag & DUEL_TAG_MODE; if (is_tag1 != is_tag2) return false; if (pheader.base.flag & REPLAY_SINGLE_MODE) { uint16_t slen = Read(); char filename[256]{}; if (slen == 0 || slen > sizeof(filename) - 1) return false; if (!ReadData(filename, slen)) return false; filename[slen] = 0; if (std::strncmp(filename, "./single/", 9)) return false; script_name = filename + 9; if (script_name.find_first_of(R"(/\)") != std::string::npos) return false; } else { for (int p = 0; p < player_count; ++p) { DeckArray deck; uint32_t main = Read(); if (main > MAINC_MAX) return false; if (main) deck.main.resize(main); if (!ReadData(deck.main.data(), main * sizeof(uint32_t))) return false; uint32_t extra = Read(); if (extra > MAINC_MAX) return false; if (extra) deck.extra.resize(extra); if (!ReadData(deck.extra.data(), extra * sizeof(uint32_t))) return false; decks.push_back(deck); } } return true; } }