diff --git a/common/HeapArray.h b/common/HeapArray.h index e360ecd710..e57c5f699a 100644 --- a/common/HeapArray.h +++ b/common/HeapArray.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin , 2024 PCSX2 Dev Team -// SPDX-License-Identifier: GPL-3.0 +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ #pragma once diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index fb1ab1f8b8..fef0c5a423 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -47,7 +47,6 @@ if(WIN32) WINVER=${MIN_WIN32} _WIN32_WINNT=${MIN_WIN32} WIN32_LEAN_AND_MEAN - LZMA_API_STATIC WIL_SUPPRESS_EXCEPTIONS ) endif(WIN32) @@ -1114,7 +1113,7 @@ target_link_libraries(PCSX2_FLAGS INTERFACE LZ4::LZ4 SoundTouch::SoundTouch PNG::PNG - LibLZMA::LibLZMA + LZMA::LZMA Zstd::Zstd ${LIBC_LIBRARIES} ) diff --git a/pcsx2/GS/GSDump.cpp b/pcsx2/GS/GSDump.cpp index b09afff53c..7c17d9e121 100644 --- a/pcsx2/GS/GSDump.cpp +++ b/pcsx2/GS/GSDump.cpp @@ -1,11 +1,20 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ -#include "GSDump.h" -#include "GSExtra.h" -#include "GSState.h" +#include "GS/GSDump.h" +#include "GS/GSExtra.h" +#include "GS/GSLzma.h" +#include "GS/GSState.h" + #include "common/Console.h" #include "common/FileSystem.h" +#include "common/HeapArray.h" +#include "common/ScopedGuard.h" + +#include <7zCrc.h> +#include +#include +#include GSDumpBase::GSDumpBase(std::string fn) : m_filename(std::move(fn)) @@ -14,13 +23,13 @@ GSDumpBase::GSDumpBase(std::string fn) { m_gs = FileSystem::OpenCFile(m_filename.c_str(), "wb"); if (!m_gs) - Console.Error("GSDump: Error failed to open %s", m_filename.c_str()); + Console.ErrorFmt("GSDump: Error failed to open {}", m_filename); } GSDumpBase::~GSDumpBase() { if (m_gs) - fclose(m_gs); + std::fclose(m_gs); } void GSDumpBase::AddHeader(const std::string& serial, u32 crc, @@ -104,201 +113,315 @@ void GSDumpBase::Write(const void* data, size_t size) size_t written = fwrite(data, 1, size, m_gs); if (written != size) - fprintf(stderr, "GSDump: Error failed to write data\n"); + Console.Error("GSDump: Error failed to write data"); } ////////////////////////////////////////////////////////////////////// // GSDump implementation ////////////////////////////////////////////////////////////////////// -GSDumpUncompressed::GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc, +namespace +{ + class GSDumpUncompressed final : public GSDumpBase + { + void AppendRawData(const void* data, size_t size) final; + void AppendRawData(u8 c) final; + + public: + GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc, + u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, + const freezeData& fd, const GSPrivRegSet* regs); + virtual ~GSDumpUncompressed() = default; + }; + + GSDumpUncompressed::GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc, + u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, + const freezeData& fd, const GSPrivRegSet* regs) + : GSDumpBase(fn + ".gs") + { + AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs); + } + + void GSDumpUncompressed::AppendRawData(const void* data, size_t size) + { + Write(data, size); + } + + void GSDumpUncompressed::AppendRawData(u8 c) + { + Write(&c, 1); + } +} // namespace + +std::unique_ptr GSDumpBase::CreateUncompressedDump( + const std::string& fn, const std::string& serial, u32 crc, u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, const freezeData& fd, const GSPrivRegSet* regs) - : GSDumpBase(fn + ".gs") { - AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs); + return std::make_unique(fn, serial, crc, + screenshot_width, screenshot_height, screenshot_pixels, + fd, regs); } -void GSDumpUncompressed::AppendRawData(const void* data, size_t size) +namespace { - Write(data, size); -} + class GsDumpBuffered : public GSDumpBase + { + protected: + void AppendRawData(const void* data, size_t size) override final; + void AppendRawData(u8 c) override final; -void GSDumpUncompressed::AppendRawData(u8 c) -{ - Write(&c, 1); -} + void EnsureSpace(size_t size); + + DynamicHeapArray m_buffer; + size_t m_buffer_size = 0; + + public: + GsDumpBuffered(std::string fn); + virtual ~GsDumpBuffered() override = default; + }; + + GsDumpBuffered::GsDumpBuffered(std::string fn) + : GSDumpBase(std::move(fn)) + { + m_buffer.resize(_1mb); + } + + void GsDumpBuffered::AppendRawData(const void* data, size_t size) + { + if (size == 0) [[unlikely]] + return; + + EnsureSpace(size); + std::memcpy(&m_buffer[m_buffer_size], data, size); + m_buffer_size += size; + } + + void GsDumpBuffered::AppendRawData(u8 c) + { + EnsureSpace(1); + m_buffer[m_buffer_size++] = c; + } + + void GsDumpBuffered::EnsureSpace(size_t size) + { + const size_t new_size = m_buffer_size + size; + if (new_size <= m_buffer.size()) + return; + + const size_t alloc_size = std::max(m_buffer.size() * 2, new_size); + m_buffer.resize(alloc_size); + } +} // namespace ////////////////////////////////////////////////////////////////////// // GSDumpXz implementation ////////////////////////////////////////////////////////////////////// - -GSDumpXz::GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, - u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, - const freezeData& fd, const GSPrivRegSet* regs) - : GSDumpBase(fn + ".gs.xz") +namespace { - m_strm = LZMA_STREAM_INIT; - lzma_ret ret = lzma_easy_encoder(&m_strm, 6 /*level*/, LZMA_CHECK_CRC64); - if (ret != LZMA_OK) + class GSDumpXz final : public GsDumpBuffered { - fprintf(stderr, "GSDumpXz: Error initializing LZMA encoder ! (error code %u)\n", ret); - return; + void Compress(); + + public: + GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, + u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, + const freezeData& fd, const GSPrivRegSet* regs); + ~GSDumpXz() override; + }; + + GSDumpXz::GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, + u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, + const freezeData& fd, const GSPrivRegSet* regs) + : GsDumpBuffered(fn + ".gs.xz") + { + AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs); } - AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs); -} - -GSDumpXz::~GSDumpXz() -{ - Flush(); - - // Finish the stream - m_strm.avail_in = 0; - Compress(LZMA_FINISH, LZMA_STREAM_END); - - lzma_end(&m_strm); -} - -void GSDumpXz::AppendRawData(const void* data, size_t size) -{ - size_t old_size = m_in_buff.size(); - m_in_buff.resize(old_size + size); - memcpy(&m_in_buff[old_size], data, size); - - // Enough data was accumulated, time to write/compress it. If compression - // is enabled, it will freeze PCSX2. 1GB should be enough for long dump. - // - // Note: long dumps are currently not supported so this path won't be executed - if (m_in_buff.size() > 1024 * 1024 * 1024) - Flush(); -} - -void GSDumpXz::AppendRawData(u8 c) -{ - m_in_buff.push_back(c); -} - -void GSDumpXz::Flush() -{ - if (m_in_buff.empty()) - return; - - m_strm.next_in = m_in_buff.data(); - m_strm.avail_in = m_in_buff.size(); - - Compress(LZMA_RUN, LZMA_OK); - - m_in_buff.clear(); -} - -void GSDumpXz::Compress(lzma_action action, lzma_ret expected_status) -{ - std::vector out_buff(1024 * 1024); - do + GSDumpXz::~GSDumpXz() { - m_strm.next_out = out_buff.data(); - m_strm.avail_out = out_buff.size(); + Compress(); + } - lzma_ret ret = lzma_code(&m_strm, action); - - if (ret != expected_status) + void GSDumpXz::Compress() + { + struct MemoryInStream { - fprintf(stderr, "GSDumpXz: Error %d\n", (int)ret); + ISeqInStream vt; + const u8* buffer; + size_t buffer_size; + size_t read_pos; + }; + MemoryInStream mis = { + {.Read = [](const ISeqInStream* p, void* buf, size_t* size) -> SRes { + MemoryInStream* mis = CONTAINER_FROM_VTBL(p, MemoryInStream, vt); + const size_t avail = mis->buffer_size - mis->read_pos; + const size_t copy = std::min(avail, *size); + + std::memcpy(buf, &mis->buffer[mis->read_pos], copy); + mis->read_pos += copy; + *size = copy; + return SZ_OK; + }}, + m_buffer.data(), + m_buffer_size, + 0}; + + struct DumpOutStream + { + ISeqOutStream vt; + GSDumpXz* real; + }; + DumpOutStream dos = { + {.Write = [](const ISeqOutStream* p, const void* buf, size_t size) -> size_t { + DumpOutStream* dos = CONTAINER_FROM_VTBL(p, DumpOutStream, vt); + dos->real->Write(buf, size); + return size; + }}, + this}; + + pxAssert(m_buffer_size > 0); + + GSInit7ZCRCTables(); + + CXzProps props; + XzProps_Init(&props); + const SRes res = Xz_Encode(&dos.vt, &mis.vt, &props, nullptr); + if (res != SZ_OK) + { + Console.ErrorFmt("Xz_Encode() failed: {}", static_cast(res)); return; } + } +} // namespace - size_t write_size = out_buff.size() - m_strm.avail_out; - Write(out_buff.data(), write_size); - - } while (m_strm.avail_out == 0); +std::unique_ptr GSDumpBase::CreateXzDump( + const std::string& fn, const std::string& serial, u32 crc, + u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, + const freezeData& fd, const GSPrivRegSet* regs) +{ + return std::make_unique(fn, serial, crc, + screenshot_width, screenshot_height, screenshot_pixels, + fd, regs); } ////////////////////////////////////////////////////////////////////// // GSDumpZstd implementation ////////////////////////////////////////////////////////////////////// -GSDumpZst::GSDumpZst(const std::string& fn, const std::string& serial, u32 crc, - u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, - const freezeData& fd, const GSPrivRegSet* regs) - : GSDumpBase(fn + ".gs.zst") +namespace { - m_strm = ZSTD_createCStream(); - - // Compression level 6 provides a good balance between speed and ratio. - ZSTD_CCtx_setParameter(m_strm, ZSTD_c_compressionLevel, 6); - - m_in_buff.reserve(_1mb); - m_out_buff.resize(_1mb); - - AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs); -} - -GSDumpZst::~GSDumpZst() -{ - // Finish the stream - Compress(ZSTD_e_end); - - ZSTD_freeCStream(m_strm); -} - -void GSDumpZst::AppendRawData(const void* data, size_t size) -{ - size_t old_size = m_in_buff.size(); - m_in_buff.resize(old_size + size); - memcpy(&m_in_buff[old_size], data, size); - MayFlush(); -} - -void GSDumpZst::AppendRawData(u8 c) -{ - m_in_buff.push_back(c); - MayFlush(); -} - -void GSDumpZst::MayFlush() -{ - if (m_in_buff.size() >= _1mb) - Compress(ZSTD_e_continue); -} - -void GSDumpZst::Compress(ZSTD_EndDirective action) -{ - if (m_in_buff.empty()) - return; - - ZSTD_inBuffer inbuf = {m_in_buff.data(), m_in_buff.size(), 0}; - - for (;;) + class GSDumpZst final : public GSDumpBase { - ZSTD_outBuffer outbuf = {m_out_buff.data(), m_out_buff.size(), 0}; + ZSTD_CStream* m_strm; - const size_t remaining = ZSTD_compressStream2(m_strm, &outbuf, &inbuf, action); - if (ZSTD_isError(remaining)) - { - fprintf(stderr, "GSDumpZstd: Error %s\n", ZSTD_getErrorName(remaining)); - return; - } + std::vector m_in_buff; + std::vector m_out_buff; - if (outbuf.pos > 0) - { - Write(m_out_buff.data(), outbuf.pos); - outbuf.pos = 0; - } + void MayFlush(); + void Compress(ZSTD_EndDirective action); + void AppendRawData(const void* data, size_t size); + void AppendRawData(u8 c); - if (action == ZSTD_e_end) - { - // break when compression output has finished - if (remaining == 0) - break; - } - else - { - // break when all input data is consumed - if (inbuf.pos == inbuf.size) - break; - } + public: + GSDumpZst(const std::string& fn, const std::string& serial, u32 crc, + u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, + const freezeData& fd, const GSPrivRegSet* regs); + virtual ~GSDumpZst(); + }; + + GSDumpZst::GSDumpZst(const std::string& fn, const std::string& serial, u32 crc, + u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, + const freezeData& fd, const GSPrivRegSet* regs) + : GSDumpBase(fn + ".gs.zst") + { + m_strm = ZSTD_createCStream(); + + // Compression level 6 provides a good balance between speed and ratio. + ZSTD_CCtx_setParameter(m_strm, ZSTD_c_compressionLevel, 6); + + m_in_buff.reserve(_1mb); + m_out_buff.resize(_1mb); + + AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs); } - m_in_buff.clear(); + GSDumpZst::~GSDumpZst() + { + // Finish the stream + Compress(ZSTD_e_end); + + ZSTD_freeCStream(m_strm); + } + + void GSDumpZst::AppendRawData(const void* data, size_t size) + { + size_t old_size = m_in_buff.size(); + m_in_buff.resize(old_size + size); + memcpy(&m_in_buff[old_size], data, size); + MayFlush(); + } + + void GSDumpZst::AppendRawData(u8 c) + { + m_in_buff.push_back(c); + MayFlush(); + } + + void GSDumpZst::MayFlush() + { + if (m_in_buff.size() >= _1mb) + Compress(ZSTD_e_continue); + } + + void GSDumpZst::Compress(ZSTD_EndDirective action) + { + if (m_in_buff.empty()) + return; + + ZSTD_inBuffer inbuf = {m_in_buff.data(), m_in_buff.size(), 0}; + + for (;;) + { + ZSTD_outBuffer outbuf = {m_out_buff.data(), m_out_buff.size(), 0}; + + const size_t remaining = ZSTD_compressStream2(m_strm, &outbuf, &inbuf, action); + if (ZSTD_isError(remaining)) + { + Console.ErrorFmt("GSDumpZstd: Error {}", ZSTD_getErrorName(remaining)); + return; + } + + if (outbuf.pos > 0) + { + Write(m_out_buff.data(), outbuf.pos); + outbuf.pos = 0; + } + + if (action == ZSTD_e_end) + { + // break when compression output has finished + if (remaining == 0) + break; + } + else + { + // break when all input data is consumed + if (inbuf.pos == inbuf.size) + break; + } + } + + m_in_buff.clear(); + } +} // namespace + +std::unique_ptr GSDumpBase::CreateZstDump( + const std::string& fn, const std::string& serial, u32 crc, + u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, + const freezeData& fd, const GSPrivRegSet* regs) +{ + return std::make_unique(fn, serial, crc, + screenshot_width, screenshot_height, screenshot_pixels, + fd, regs); } diff --git a/pcsx2/GS/GSDump.h b/pcsx2/GS/GSDump.h index 4a5bceef58..60cf5690f7 100644 --- a/pcsx2/GS/GSDump.h +++ b/pcsx2/GS/GSDump.h @@ -1,13 +1,11 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #pragma once #include "SaveState.h" -#include "GSRegs.h" -#include "Renderers/SW/GSVertexSW.h" -#include -#include +#include "GS/GSRegs.h" +#include "GS/Renderers/SW/GSVertexSW.h" /* @@ -68,53 +66,17 @@ public: void ReadFIFO(u32 size); void Transfer(int index, const u8* mem, size_t size); bool VSync(int field, bool last, const GSPrivRegSet* regs); -}; -class GSDumpUncompressed final : public GSDumpBase -{ - void AppendRawData(const void* data, size_t size) final; - void AppendRawData(u8 c) final; - -public: - GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc, + static std::unique_ptr CreateUncompressedDump( + const std::string& fn, const std::string& serial, u32 crc, u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, const freezeData& fd, const GSPrivRegSet* regs); - virtual ~GSDumpUncompressed() = default; -}; - -class GSDumpXz final : public GSDumpBase -{ - lzma_stream m_strm; - - std::vector m_in_buff; - - void Flush(); - void Compress(lzma_action action, lzma_ret expected_status); - void AppendRawData(const void* data, size_t size); - void AppendRawData(u8 c); - -public: - GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, + static std::unique_ptr CreateXzDump( + const std::string& fn, const std::string& serial, u32 crc, u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, const freezeData& fd, const GSPrivRegSet* regs); - virtual ~GSDumpXz(); -}; - -class GSDumpZst final : public GSDumpBase -{ - ZSTD_CStream* m_strm; - - std::vector m_in_buff; - std::vector m_out_buff; - - void MayFlush(); - void Compress(ZSTD_EndDirective action); - void AppendRawData(const void* data, size_t size); - void AppendRawData(u8 c); - -public: - GSDumpZst(const std::string& fn, const std::string& serial, u32 crc, + static std::unique_ptr CreateZstDump( + const std::string& fn, const std::string& serial, u32 crc, u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels, const freezeData& fd, const GSPrivRegSet* regs); - virtual ~GSDumpZst(); }; diff --git a/pcsx2/GS/GSLzma.cpp b/pcsx2/GS/GSLzma.cpp index ba95bae61b..ccf737b514 100644 --- a/pcsx2/GS/GSLzma.cpp +++ b/pcsx2/GS/GSLzma.cpp @@ -1,65 +1,32 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #include "common/AlignedMalloc.h" #include "common/Console.h" #include "common/FileSystem.h" +#include "common/ScopedGuard.h" #include "common/StringUtil.h" +#include "common/BitUtils.h" +#include "common/Error.h" +#include "common/HeapArray.h" #include "GS/GSDump.h" #include "GS/GSLzma.h" #include "GS/GSExtra.h" +#include +#include <7zCrc.h> +#include +#include +#include + +#include + using namespace GSDumpTypes; -GSDumpFile::GSDumpFile(FILE* file, FILE* repack_file) - : m_fp(file) - , m_repack_fp(repack_file) -{ -} +GSDumpFile::GSDumpFile() = default; -void GSDumpFile::Repack(void* ptr, size_t size) -{ - if (m_repack_fp == nullptr) - return; - - size_t ret = fwrite(ptr, 1, size, m_repack_fp); - if (ret != size) - fprintf(stderr, "Failed to repack\n"); -} - -GSDumpFile::~GSDumpFile() -{ - if (m_fp) - fclose(m_fp); - if (m_repack_fp) - fclose(m_repack_fp); -} - -std::unique_ptr GSDumpFile::OpenGSDump(const char* filename, const char* repack_filename /*= nullptr*/) -{ - std::FILE* fp = FileSystem::OpenCFile(filename, "rb"); - if (!fp) - return nullptr; - - std::FILE* repack_fp = nullptr; - if (repack_filename && std::strlen(repack_filename) > 0) - { - repack_fp = FileSystem::OpenCFile(repack_filename, "wb"); - if (!repack_fp) - { - std::fclose(fp); - return nullptr; - } - } - - if (StringUtil::EndsWithNoCase(filename, ".xz")) - return std::make_unique(fp, nullptr); - else if (StringUtil::EndsWithNoCase(filename, ".zst")) - return std::make_unique(fp, nullptr); - else - return std::make_unique(fp, nullptr); -} +GSDumpFile::~GSDumpFile() = default; bool GSDumpFile::GetPreviewImageFromDump(const char* filename, u32* width, u32* height, std::vector* pixels) { @@ -102,15 +69,21 @@ bool GSDumpFile::GetPreviewImageFromDump(const char* filename, u32* width, u32* return true; } -bool GSDumpFile::ReadFile() +bool GSDumpFile::ReadFile(Error* error) { u32 ss; if (Read(&m_crc, sizeof(m_crc)) != sizeof(m_crc) || Read(&ss, sizeof(ss)) != sizeof(ss)) + { + Error::SetString(error, "Failed to read header"); return false; + } m_state_data.resize(ss); if (Read(m_state_data.data(), ss) != ss) + { + Error::SetString(error, "Failed to read state data"); return false; + } // Pull serial out of new header, if present. if (m_crc == 0xFFFFFFFFu) @@ -118,7 +91,7 @@ bool GSDumpFile::ReadFile() GSDumpHeader header; if (m_state_data.size() < sizeof(header)) { - Console.Error("GSDump header is corrupted."); + Error::SetString(error, "GSDump header is corrupted."); return false; } @@ -130,7 +103,7 @@ bool GSDumpFile::ReadFile() { if (header.serial_offset > ss || (static_cast(header.serial_offset) + header.serial_size) > ss) { - Console.Error("GSDump header is corrupted."); + Error::SetString(error, "GSDump header is corrupted."); return false; } @@ -141,12 +114,18 @@ bool GSDumpFile::ReadFile() // Read the real state data m_state_data.resize(header.state_size); if (Read(m_state_data.data(), header.state_size) != header.state_size) + { + Error::SetString(error, "Failed to read real state data"); return false; + } } m_regs_data.resize(8192); if (Read(m_regs_data.data(), m_regs_data.size()) != m_regs_data.size()) + { + Error::SetString(error, "Failed to read regs data"); return false; + } // read all the packet data in // TODO: make this suck less by getting the full/extracted size and preallocating @@ -160,7 +139,10 @@ bool GSDumpFile::ReadFile() if (read != read_size) { if (!IsEof()) + { + Error::SetString(error, "Failed to read packet"); return false; + } m_packet_data.resize(packet_data_size + read); m_packet_data.shrink_to_fit(); @@ -175,7 +157,10 @@ bool GSDumpFile::ReadFile() do \ { \ if (remaining < sizeof(u8)) \ + { \ + Error::SetString(error, "Failed to read byte"); \ return false; \ + } \ std::memcpy(dst, data, sizeof(u8)); \ data++; \ remaining--; \ @@ -184,7 +169,10 @@ bool GSDumpFile::ReadFile() do \ { \ if (remaining < sizeof(u32)) \ + { \ + Error::SetString(error, "Failed to read word"); \ return false; \ + } \ std::memcpy(dst, data, sizeof(u32)); \ data += sizeof(u32); \ remaining -= sizeof(u32); \ @@ -212,6 +200,7 @@ bool GSDumpFile::ReadFile() packet.length = 8192; break; default: + Error::SetString(error, fmt::format("Unknown packet type {}", static_cast(packet.id))); return false; } @@ -242,224 +231,423 @@ bool GSDumpFile::ReadFile() } /******************************************************************/ -GSDumpLzma::GSDumpLzma(FILE* file, FILE* repack_file) - : GSDumpFile(file, repack_file) + +static std::once_flag s_lzma_crc_table_init; + +void GSInit7ZCRCTables() { - Initialize(); + std::call_once(s_lzma_crc_table_init, []() { + CrcGenerateTable(); + Crc64GenerateTable(); + }); } -void GSDumpLzma::Initialize() +namespace { - memset(&m_strm, 0, sizeof(lzma_stream)); - - lzma_ret ret = lzma_stream_decoder(&m_strm, UINT32_MAX, 0); - - if (ret != LZMA_OK) + class GSDumpLzma final : public GSDumpFile { - Console.Error("Error initializing the decoder! (error code %u)", ret); - pxFailRel("Failed to initialize LZMA decoder."); + public: + GSDumpLzma(); + ~GSDumpLzma() override; + + protected: + bool Open(std::FILE* fp, Error* error) override; + bool IsEof() override; + size_t Read(void* ptr, size_t size) override; + + private: + static constexpr size_t kInputBufSize = static_cast(1) << 18; + + struct Block + { + size_t file_offset; + size_t stream_offset; + size_t compressed_size; + size_t uncompressed_size; + CXzStreamFlags stream_flags; + }; + + bool DecompressNextBlock(); + + std::FILE* m_fp = nullptr; + std::vector m_blocks; + size_t m_stream_size = 0; + + DynamicHeapArray m_block_buffer; + size_t m_block_index = 0; + size_t m_block_size = 0; + size_t m_block_pos = 0; + + DynamicHeapArray m_block_read_buffer; + alignas(64) CXzUnpacker m_unpacker = {}; + }; + + GSDumpLzma::GSDumpLzma() = default; + + GSDumpLzma::~GSDumpLzma() + { + + + XzUnpacker_Free(&m_unpacker); } - m_buff_size = 1024*1024; - m_area = (uint8_t*)_aligned_malloc(m_buff_size, VECTOR_ALIGNMENT); - m_inbuf = (uint8_t*)_aligned_malloc(BUFSIZ, VECTOR_ALIGNMENT); - m_avail = 0; - m_start = 0; - - m_strm.avail_in = 0; - m_strm.next_in = m_inbuf; - - m_strm.avail_out = m_buff_size; - m_strm.next_out = m_area; -} - -void GSDumpLzma::Decompress() -{ - lzma_action action = LZMA_RUN; - - m_strm.next_out = m_area; - m_strm.avail_out = m_buff_size; - - // Nothing left in the input buffer. Read data from the file - if (m_strm.avail_in == 0 && !feof(m_fp)) + bool GSDumpLzma::Open(std::FILE* fp, Error* error) { - m_strm.next_in = m_inbuf; - m_strm.avail_in = fread(m_inbuf, 1, BUFSIZ, m_fp); + m_fp = fp; - if (ferror(m_fp)) + GSInit7ZCRCTables(); + + struct MyFileInStream { - Console.Error("Read error: %s", strerror(errno)); - pxFailRel("LZMA read error."); + ISeekInStream vt; + std::FILE* fp; + }; + + MyFileInStream fis = { + {.Read = [](const ISeekInStream* p, void* buf, size_t* size) -> SRes { + MyFileInStream* fis = CONTAINER_FROM_VTBL(p, MyFileInStream, vt); + const size_t size_to_read = *size; + const auto bytes_read = std::fread(buf, 1, size_to_read, fis->fp); + *size = (bytes_read >= 0) ? bytes_read : 0; + return (bytes_read == size_to_read) ? SZ_OK : SZ_ERROR_READ; + }, + .Seek = [](const ISeekInStream* p, Int64* pos, ESzSeek origin) -> SRes { + MyFileInStream* fis = CONTAINER_FROM_VTBL(p, MyFileInStream, vt); + static_assert(SZ_SEEK_CUR == SEEK_CUR && SZ_SEEK_SET == SEEK_SET && SZ_SEEK_END == SEEK_END); + if (FileSystem::FSeek64(fis->fp, *pos, static_cast(origin)) != 0) + return SZ_ERROR_READ; + + const s64 new_pos = FileSystem::FTell64(fis->fp); + if (new_pos < 0) + return SZ_ERROR_READ; + + *pos = new_pos; + return SZ_OK; + }}, + m_fp}; + + CLookToRead2 look_stream = {}; + LookToRead2_Init(&look_stream); + LookToRead2_CreateVTable(&look_stream, False); + look_stream.realStream = &fis.vt; + look_stream.bufSize = kInputBufSize; + look_stream.buf = static_cast(ISzAlloc_Alloc(&g_Alloc, kInputBufSize)); + if (!look_stream.buf) + { + Error::SetString(error, "Failed to allocate lookahead buffer"); + return false; } - } + ScopedGuard guard = [&look_stream]() { + if (look_stream.buf) + ISzAlloc_Free(&g_Alloc, look_stream.buf); + }; - lzma_ret ret = lzma_code(&m_strm, action); + // Read blocks + CXzs xzs; + Xzs_Construct(&xzs); + const ScopedGuard xzs_guard([&xzs]() { + Xzs_Free(&xzs, &g_Alloc); + }); - if (ret != LZMA_OK) - { - if (ret != LZMA_STREAM_END) + const s64 file_size = FileSystem::FSize64(m_fp); + Int64 start_pos = file_size; + SRes res = Xzs_ReadBackward(&xzs, &look_stream.vt, &start_pos, nullptr, &g_Alloc); + if (res != SZ_OK) { - Console.Error("Decoder error: (error code %u)", ret); - pxFailRel("LZMA decoder error."); - } - } - - m_start = 0; - m_avail = m_buff_size - m_strm.avail_out; -} - -bool GSDumpLzma::IsEof() -{ - return feof(m_fp) && m_avail == 0 && m_strm.avail_in == 0; -} - -size_t GSDumpLzma::Read(void* ptr, size_t size) -{ - size_t off = 0; - uint8_t* dst = (uint8_t*)ptr; - while (size && !IsEof()) - { - if (m_avail == 0) - { - Decompress(); + Error::SetString(error, fmt::format("Xzs_ReadBackward() failed: {}", res)); + return false; } - size_t l = std::min(size, m_avail); - memcpy(dst + off, m_area + m_start, l); - m_avail -= l; - size -= l; - m_start += l; - off += l; - } - - if (off > 0) - Repack(ptr, off); - - return off; -} - -GSDumpLzma::~GSDumpLzma() -{ - lzma_end(&m_strm); - - if (m_inbuf) - _aligned_free(m_inbuf); - if (m_area) - _aligned_free(m_area); -} - -/******************************************************************/ -GSDumpDecompressZst::GSDumpDecompressZst(FILE* file, FILE* repack_file) - : GSDumpFile(file, repack_file) -{ - Initialize(); -} - -void GSDumpDecompressZst::Initialize() -{ - m_strm = ZSTD_createDStream(); - - m_area = (uint8_t*)_aligned_malloc(OUTPUT_BUFFER_SIZE, 32); - m_inbuf.src = (uint8_t*)_aligned_malloc(INPUT_BUFFER_SIZE, 32); - m_inbuf.pos = 0; - m_inbuf.size = 0; - m_avail = 0; - m_start = 0; -} - -void GSDumpDecompressZst::Decompress() -{ - ZSTD_outBuffer outbuf = { m_area, OUTPUT_BUFFER_SIZE, 0 }; - while (outbuf.pos == 0) - { - // Nothing left in the input buffer. Read data from the file - if (m_inbuf.pos == m_inbuf.size && !feof(m_fp)) + const size_t num_blocks = Xzs_GetNumBlocks(&xzs); + if (num_blocks == 0) { - m_inbuf.size = fread((void*)m_inbuf.src, 1, INPUT_BUFFER_SIZE, m_fp); - m_inbuf.pos = 0; + Error::SetString(error, "Stream has no blocks."); + return false; + } - if (ferror(m_fp)) + m_blocks.reserve(num_blocks); + for (int sn = xzs.num - 1; sn >= 0; sn--) + { + const CXzStream& stream = xzs.streams[sn]; + size_t src_offset = stream.startOffset + XZ_STREAM_HEADER_SIZE; + for (size_t bn = 0; bn < stream.numBlocks; bn++) { - Console.Error("Zst read error: %s", strerror(errno)); - pxFailRel("Zst read error."); + const CXzBlockSizes& block = stream.blocks[bn]; + + Block out_block; + out_block.file_offset = src_offset; + out_block.stream_offset = m_stream_size; + out_block.compressed_size = std::min(Common::AlignUpPow2(block.totalSize, 4), + static_cast(file_size - static_cast(src_offset))); // LZMA blocks are 4 byte aligned? + out_block.uncompressed_size = block.unpackSize; + out_block.stream_flags = stream.flags; + m_stream_size += out_block.uncompressed_size; + src_offset += out_block.compressed_size; + m_blocks.push_back(std::move(out_block)); } } - size_t ret = ZSTD_decompressStream(m_strm, &outbuf, &m_inbuf); - if (ZSTD_isError(ret)) - { - Console.Error("Decoder error: (error code %s)", ZSTD_getErrorName(ret)); - pxFailRel("Zst decoder error."); - } + DevCon.WriteLnFmt("XZ stream is {} bytes across {} blocks", m_stream_size, m_blocks.size()); + XzUnpacker_Construct(&m_unpacker, &g_Alloc); + return true; } - m_start = 0; - m_avail = outbuf.pos; -} - -bool GSDumpDecompressZst::IsEof() -{ - return feof(m_fp) && m_avail == 0 && m_inbuf.pos == m_inbuf.size; -} - -size_t GSDumpDecompressZst::Read(void* ptr, size_t size) -{ - size_t off = 0; - uint8_t* dst = (uint8_t*)ptr; - while (size && !IsEof()) + bool GSDumpLzma::DecompressNextBlock() { - if (m_avail == 0) + if (m_block_index == m_blocks.size()) + return false; + + const Block& block = m_blocks[m_block_index]; + + if (block.compressed_size > m_block_read_buffer.size()) + m_block_read_buffer.resize(Common::AlignUpPow2(block.compressed_size, _128kb)); + + if (FileSystem::FSeek64(m_fp, static_cast(block.file_offset), SEEK_SET) != 0 || + std::fread(m_block_read_buffer.data(), block.compressed_size, 1, m_fp) != 1) { - Decompress(); + Console.ErrorFmt("Failed to read {} bytes from offset {}", block.file_offset, block.compressed_size); + return false; } - size_t l = std::min(size, m_avail); - memcpy(dst + off, m_area + m_start, l); - m_avail -= l; - size -= l; - m_start += l; - off += l; + if (block.uncompressed_size > m_block_buffer.size()) + m_block_buffer.resize(Common::AlignUpPow2(block.uncompressed_size, _128kb)); + + XzUnpacker_Init(&m_unpacker); + m_unpacker.streamFlags = block.stream_flags; + XzUnpacker_PrepareToRandomBlockDecoding(&m_unpacker); + XzUnpacker_SetOutBuf(&m_unpacker, m_block_buffer.data(), block.uncompressed_size); + SizeT out_uncompressed_size = block.uncompressed_size; + SizeT out_compressed_size = block.compressed_size; + + ECoderStatus status; + const SRes res = XzUnpacker_Code(&m_unpacker, nullptr, &out_uncompressed_size, + m_block_read_buffer.data(), &out_compressed_size, true, CODER_FINISH_END, &status); + if (res != SZ_OK || status != CODER_STATUS_FINISHED_WITH_MARK) [[unlikely]] + { + Console.ErrorFmt("XzUnpacker_Code() failed: {} (status {})", res, static_cast(status)); + return false; + } + + if (out_compressed_size != block.compressed_size || out_uncompressed_size != block.uncompressed_size) + { + Console.WarningFmt("Decompress size mismatch: {}/{} vs {}/{}", out_compressed_size, out_uncompressed_size, + block.compressed_size, block.uncompressed_size); + } + + m_block_size = out_uncompressed_size; + m_block_pos = 0; + m_block_index++; + return true; } - if (off > 0) - Repack(ptr, off); + bool GSDumpLzma::IsEof() + { + return (m_block_pos == m_block_size && m_block_index == m_blocks.size()); + } - return off; -} + size_t GSDumpLzma::Read(void* ptr, size_t size) + { + u8* dst = static_cast(ptr); + size_t remain = size; + while (remain > 0) + { + if (m_block_size == m_block_pos && !DecompressNextBlock()) [[unlikely]] + break; -GSDumpDecompressZst::~GSDumpDecompressZst() -{ - ZSTD_freeDStream(m_strm); + const size_t avail = (m_block_size - m_block_pos); + const size_t read = std::min(avail, remain); + pxAssert(avail > 0 && read > 0); + std::memcpy(dst, &m_block_buffer[m_block_pos], read); + dst += read; + remain -= read; + m_block_pos += read; + } - if (m_inbuf.src) - _aligned_free((void*)m_inbuf.src); - if (m_area) - _aligned_free(m_area); -} + return size - remain; + } + + /******************************************************************/ + + class GSDumpDecompressZst final : public GSDumpFile + { + static constexpr u32 INPUT_BUFFER_SIZE = 512 * _1kb; + static constexpr u32 OUTPUT_BUFFER_SIZE = 2 * _1mb; + + std::FILE* m_fp = nullptr; + + ZSTD_DStream* m_strm = nullptr; + ZSTD_inBuffer m_inbuf = {}; + + uint8_t* m_area = nullptr; + + size_t m_avail = 0; + size_t m_start = 0; + + bool Decompress(); + + public: + GSDumpDecompressZst(); + ~GSDumpDecompressZst() override; + + bool Open(std::FILE* fp, Error* error) override; + bool IsEof() override; + size_t Read(void* ptr, size_t size) override; + }; + + GSDumpDecompressZst::GSDumpDecompressZst() = default; + + GSDumpDecompressZst::~GSDumpDecompressZst() + { + if (m_strm) + ZSTD_freeDStream(m_strm); + + if (m_inbuf.src) + _aligned_free((void*)m_inbuf.src); + if (m_area) + _aligned_free(m_area); + + if (m_fp) + std::fclose(m_fp); + } + + bool GSDumpDecompressZst::Open(std::FILE* fp, Error* error) + { + m_fp = fp; + m_strm = ZSTD_createDStream(); + + m_area = (uint8_t*)_aligned_malloc(OUTPUT_BUFFER_SIZE, 32); + m_inbuf.src = (uint8_t*)_aligned_malloc(INPUT_BUFFER_SIZE, 32); + m_inbuf.pos = 0; + m_inbuf.size = 0; + m_avail = 0; + m_start = 0; + return true; + } + + bool GSDumpDecompressZst::Decompress() + { + ZSTD_outBuffer outbuf = {m_area, OUTPUT_BUFFER_SIZE, 0}; + while (outbuf.pos == 0) + { + // Nothing left in the input buffer. Read data from the file + if (m_inbuf.pos == m_inbuf.size && !std::feof(m_fp)) + { + m_inbuf.size = fread((void*)m_inbuf.src, 1, INPUT_BUFFER_SIZE, m_fp); + m_inbuf.pos = 0; + + if (ferror(m_fp)) + { + Console.Error("Zst read error: %s", strerror(errno)); + return false; + } + } + + const size_t ret = ZSTD_decompressStream(m_strm, &outbuf, &m_inbuf); + if (ZSTD_isError(ret)) + { + Console.Error("Decoder error: (error code %s)", ZSTD_getErrorName(ret)); + return false; + } + } + + m_start = 0; + m_avail = outbuf.pos; + return true; + } + + bool GSDumpDecompressZst::IsEof() + { + return feof(m_fp) && m_avail == 0 && m_inbuf.pos == m_inbuf.size; + } + + size_t GSDumpDecompressZst::Read(void* ptr, size_t size) + { + size_t off = 0; + uint8_t* dst = (uint8_t*)ptr; + while (size && !IsEof()) + { + if (m_avail == 0) + { + if (!Decompress()) [[unlikely]] + break; + } + + const size_t l = std::min(size, m_avail); + std::memcpy(dst + off, m_area + m_start, l); + m_avail -= l; + size -= l; + m_start += l; + off += l; + } + + return off; + } + + /******************************************************************/ + + class GSDumpRaw final : public GSDumpFile + { + std::FILE* m_fp; + + public: + GSDumpRaw(); + ~GSDumpRaw() override; + + bool Open(std::FILE* fp, Error* error) override; + bool IsEof() override; + size_t Read(void* ptr, size_t size) override; + }; + + GSDumpRaw::GSDumpRaw() = default; + + GSDumpRaw::~GSDumpRaw() + { + if (m_fp) + std::fclose(m_fp); + } + + bool GSDumpRaw::Open(std::FILE* fp, Error* error) + { + m_fp = fp; + return true; + } + + bool GSDumpRaw::IsEof() + { + return !!feof(m_fp); + } + + size_t GSDumpRaw::Read(void* ptr, size_t size) + { + size_t ret = fread(ptr, 1, size, m_fp); + if (ret != size && ferror(m_fp)) + { + fprintf(stderr, "GSDumpRaw:: Read error (%zu/%zu)\n", ret, size); + return ret; + } + + return ret; + } +} // namespace /******************************************************************/ -GSDumpRaw::GSDumpRaw(FILE* file, FILE* repack_file) - : GSDumpFile(file, repack_file) +std::unique_ptr GSDumpFile::OpenGSDump(const char* filename, Error* error) { -} - -bool GSDumpRaw::IsEof() -{ - return !!feof(m_fp); -} - -size_t GSDumpRaw::Read(void* ptr, size_t size) -{ - size_t ret = fread(ptr, 1, size, m_fp); - if (ret != size && ferror(m_fp)) - { - fprintf(stderr, "GSDumpRaw:: Read error (%zu/%zu)\n", ret, size); - return ret; - } - - if (ret > 0) - Repack(ptr, ret); - - return ret; + std::FILE* fp = FileSystem::OpenCFile(filename, "rb", error); + if (!fp) + return nullptr; + + std::unique_ptr file; + if (StringUtil::EndsWithNoCase(filename, ".xz")) + file = std::make_unique(); + else if (StringUtil::EndsWithNoCase(filename, ".zst")) + file = std::make_unique(); + else + file = std::make_unique(); + + if (!file->Open(fp, error)) + file = {}; + + return file; } diff --git a/pcsx2/GS/GSLzma.h b/pcsx2/GS/GSLzma.h index 577e09963c..a552b5c630 100644 --- a/pcsx2/GS/GSLzma.h +++ b/pcsx2/GS/GSLzma.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #pragma once @@ -7,8 +7,7 @@ #include #include -#include -#include +class Error; #define GEN_REG_ENUM_CLASS_CONTENT(ClassName, EntryName, Value) \ EntryName = Value, @@ -291,7 +290,7 @@ public: virtual ~GSDumpFile(); - static std::unique_ptr OpenGSDump(const char* filename, const char* repack_filename = nullptr); + static std::unique_ptr OpenGSDump(const char* filename, Error* error = nullptr); static bool GetPreviewImageFromDump(const char* filename, u32* width, u32* height, std::vector* pixels); __fi const std::string& GetSerial() const { return m_serial; } @@ -301,21 +300,16 @@ public: __fi const ByteArray& GetStateData() const { return m_state_data; } __fi const GSDataArray& GetPackets() const { return m_dump_packets; } - bool ReadFile(); + bool ReadFile(Error* error); protected: - GSDumpFile(FILE* file, FILE* repack_file); + GSDumpFile(); + virtual bool Open(std::FILE* fp, Error* error) = 0; virtual bool IsEof() = 0; virtual size_t Read(void* ptr, size_t size) = 0; - void Repack(void* ptr, size_t size); - - FILE* m_fp = nullptr; - private: - FILE* m_repack_fp = nullptr; - std::string m_serial; u32 m_crc = 0; @@ -326,58 +320,5 @@ private: GSDataArray m_dump_packets; }; -class GSDumpLzma : public GSDumpFile -{ - lzma_stream m_strm; - - size_t m_buff_size; - uint8_t* m_area; - uint8_t* m_inbuf; - - size_t m_avail; - size_t m_start; - - void Decompress(); - void Initialize(); - -public: - GSDumpLzma(FILE* file, FILE* repack_file); - virtual ~GSDumpLzma(); - - bool IsEof() final; - size_t Read(void* ptr, size_t size) final; -}; - -class GSDumpDecompressZst : public GSDumpFile -{ - static constexpr u32 INPUT_BUFFER_SIZE = 512 * _1kb; - static constexpr u32 OUTPUT_BUFFER_SIZE = 2 * _1mb; - - ZSTD_DStream* m_strm; - ZSTD_inBuffer m_inbuf; - - uint8_t* m_area; - - size_t m_avail; - size_t m_start; - - void Decompress(); - void Initialize(); - -public: - GSDumpDecompressZst(FILE* file, FILE* repack_file); - virtual ~GSDumpDecompressZst(); - - bool IsEof() final; - size_t Read(void* ptr, size_t size) final; -}; - -class GSDumpRaw : public GSDumpFile -{ -public: - GSDumpRaw(FILE* file, FILE* repack_file); - virtual ~GSDumpRaw() = default; - - bool IsEof() final; - size_t Read(void* ptr, size_t size) final; -}; +// Initializes CRC tables used by LZMA SDK. +void GSInit7ZCRCTables(); diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index 9002f34a12..71f4dde226 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -676,23 +676,23 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame) std::string_view compression_str; if (GSConfig.GSDumpCompression == GSDumpCompressionMethod::Uncompressed) { - m_dump = std::unique_ptr(new GSDumpUncompressed(m_snapshot, VMManager::GetDiscSerial(), + m_dump = GSDumpBase::CreateUncompressedDump(m_snapshot, VMManager::GetDiscSerial(), VMManager::GetDiscCRC(), screenshot_width, screenshot_height, - screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(), fd, m_regs)); + screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(), fd, m_regs); compression_str = TRANSLATE_SV("GS", "with no compression"); } else if (GSConfig.GSDumpCompression == GSDumpCompressionMethod::LZMA) { - m_dump = std::unique_ptr( - new GSDumpXz(m_snapshot, VMManager::GetDiscSerial(), VMManager::GetDiscCRC(), screenshot_width, - screenshot_height, screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(), fd, m_regs)); + m_dump = GSDumpBase::CreateXzDump(m_snapshot, VMManager::GetDiscSerial(), + VMManager::GetDiscCRC(), screenshot_width, screenshot_height, + screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(), fd, m_regs); compression_str = TRANSLATE_SV("GS", "with LZMA compression"); } else { - m_dump = std::unique_ptr( - new GSDumpZst(m_snapshot, VMManager::GetDiscSerial(), VMManager::GetDiscCRC(), screenshot_width, - screenshot_height, screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(), fd, m_regs)); + m_dump = GSDumpBase::CreateZstDump(m_snapshot, VMManager::GetDiscSerial(), + VMManager::GetDiscCRC(), screenshot_width, screenshot_height, + screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(), fd, m_regs); compression_str = TRANSLATE_SV("GS", "with Zstandard compression"); } diff --git a/pcsx2/GSDumpReplayer.cpp b/pcsx2/GSDumpReplayer.cpp index c53a752e08..47a3019967 100644 --- a/pcsx2/GSDumpReplayer.cpp +++ b/pcsx2/GSDumpReplayer.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #include "GS.h" @@ -18,7 +18,9 @@ #include "fmt/core.h" +#include "common/Error.h" #include "common/FileSystem.h" +#include "common/Path.h" #include "common/StringUtil.h" #include "common/Threading.h" #include "common/Timer.h" @@ -87,10 +89,12 @@ bool GSDumpReplayer::Initialize(const char* filename) Common::Timer timer; Console.WriteLn("(GSDumpReplayer) Reading file '%s'...", filename); - s_dump_file = GSDumpFile::OpenGSDump(filename); - if (!s_dump_file || !s_dump_file->ReadFile()) + Error error; + s_dump_file = GSDumpFile::OpenGSDump(filename, &error); + if (!s_dump_file || !s_dump_file->ReadFile(&error)) { - Host::ReportFormattedErrorAsync("GSDumpReplayer", "Failed to open or read '%s'.", filename); + Host::ReportErrorAsync("GSDumpReplayer", fmt::format("Failed to open or read '{}': {}", + Path::GetFileName(filename), error.GetDescription())); s_dump_file.reset(); return false; } @@ -119,10 +123,12 @@ bool GSDumpReplayer::ChangeDump(const char* filename) return false; } + Error error; std::unique_ptr new_dump(GSDumpFile::OpenGSDump(filename)); - if (!new_dump || !new_dump->ReadFile()) + if (!new_dump || !new_dump->ReadFile(&error)) { - Host::ReportFormattedErrorAsync("GSDumpReplayer", "Failed to open or read '%s'.", filename); + Host::ReportErrorAsync("GSDumpReplayer", fmt::format("Failed to open or read '{}': {}", + Path::GetFileName(filename), error.GetDescription())); return false; } diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index 124bb925b3..f67290762e 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -36,7 +36,6 @@ %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fmt\fmt\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\wil\include - %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\xz\xz\src\liblzma\api %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zlib %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\lz4\lz4\lib %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\libpng @@ -60,6 +59,7 @@ %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\glslang\glslang %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\vulkan-headers\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\d3d12memalloc\include + %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\lzma\include %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\xbyak %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zydis\include;$(SolutionDir)3rdparty\zydis\dependencies\zycore\include Use @@ -810,9 +810,6 @@ {e9b51944-7e6d-4bcd-83f2-7bbd5a46182d} - - {12728250-16ec-4dc6-94d7-e21dd88947f8} - {a0d2b3ad-1f72-4ee3-8b5c-f2c358da35f0} @@ -849,6 +846,9 @@ {39098635-446a-4fc3-9b1c-8609d94598a8} + + {a4323327-3f2b-4271-83d9-7f9a3c66b6b2} + {95dd0a0c-d14d-4cff-a593-820ef26efcc8}