From 4ed748fa309d5c131f5b1ac2a5da7d8db2ad155e Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 5 Mar 2022 14:25:43 +1000 Subject: [PATCH] GSDump: Add extensible header and serial Serial is used to apply hw fixes. --- pcsx2/GS/GSDump.cpp | 38 ++++++++++---- pcsx2/GS/GSDump.h | 23 ++++++--- pcsx2/GS/GSState.cpp | 4 +- pcsx2/GS/GSState.h | 4 +- pcsx2/GS/Renderers/Common/GSRenderer.cpp | 16 +++++- pcsx2/gui/Dialogs/GSDumpDialog.cpp | 64 ++++++++++++++++++++---- pcsx2/gui/Dialogs/ModalPopups.h | 6 +-- 7 files changed, 123 insertions(+), 32 deletions(-) diff --git a/pcsx2/GS/GSDump.cpp b/pcsx2/GS/GSDump.cpp index 981efe4090..83a0218764 100644 --- a/pcsx2/GS/GSDump.cpp +++ b/pcsx2/GS/GSDump.cpp @@ -16,6 +16,7 @@ #include "PrecompiledHeader.h" #include "GSDump.h" #include "GSExtra.h" +#include "GSState.h" GSDumpBase::GSDumpBase(const std::string& fn) : m_frames(0) @@ -32,10 +33,29 @@ GSDumpBase::~GSDumpBase() fclose(m_gs); } -void GSDumpBase::AddHeader(u32 crc, const freezeData& fd, const GSPrivRegSet* regs) +void GSDumpBase::AddHeader(const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs) { - AppendRawData(&crc, 4); - AppendRawData(&fd.size, 4); + // New header: CRC of FFFFFFFF, secondary header, full header follows. + const u32 fake_crc = 0xFFFFFFFFu; + AppendRawData(&fake_crc, 4); + + // Compute full header size (with serial). + // This acts as the state size for loading older dumps. + const u32 header_size = sizeof(GSDumpHeader) + static_cast(serial.size()); + AppendRawData(&header_size, 4); + + // Write hader. + GSDumpHeader header = {}; + header.state_version = GSState::STATE_VERSION; + header.state_size = fd.size; + header.crc = crc; + header.serial_offset = sizeof(header); + header.serial_size = static_cast(serial.size()); + AppendRawData(&header, sizeof(header)); + if (!serial.empty()) + AppendRawData(serial.data(), serial.size()); + + // Then the real state data. AppendRawData(fd.data, fd.size); AppendRawData(regs, sizeof(*regs)); } @@ -92,18 +112,18 @@ void GSDumpBase::Write(const void* data, size_t size) // GSDump implementation ////////////////////////////////////////////////////////////////////// -GSDump::GSDump(const std::string& fn, u32 crc, const freezeData& fd, const GSPrivRegSet* regs) +GSDumpUncompressed::GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs) : GSDumpBase(fn + ".gs") { - AddHeader(crc, fd, regs); + AddHeader(serial, crc, fd, regs); } -void GSDump::AppendRawData(const void* data, size_t size) +void GSDumpUncompressed::AppendRawData(const void* data, size_t size) { Write(data, size); } -void GSDump::AppendRawData(u8 c) +void GSDumpUncompressed::AppendRawData(u8 c) { Write(&c, 1); } @@ -112,7 +132,7 @@ void GSDump::AppendRawData(u8 c) // GSDumpXz implementation ////////////////////////////////////////////////////////////////////// -GSDumpXz::GSDumpXz(const std::string& fn, u32 crc, const freezeData& fd, const GSPrivRegSet* regs) +GSDumpXz::GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs) : GSDumpBase(fn + ".gs.xz") { m_strm = LZMA_STREAM_INIT; @@ -123,7 +143,7 @@ GSDumpXz::GSDumpXz(const std::string& fn, u32 crc, const freezeData& fd, const G return; } - AddHeader(crc, fd, regs); + AddHeader(serial, crc, fd, regs); } GSDumpXz::~GSDumpXz() diff --git a/pcsx2/GS/GSDump.h b/pcsx2/GS/GSDump.h index 7590e5388f..65aa624881 100644 --- a/pcsx2/GS/GSDump.h +++ b/pcsx2/GS/GSDump.h @@ -23,7 +23,7 @@ /* Dump file format: -- [crc/4] [state size/4] [state data/size] [PMODE/0x2000] [id/1] [data/?] .. [id/1] [data/?] +- [0xFFFFFFFF] [Header] [state size/4] [state data/size] [PMODE/0x2000] [id/1] [data/?] .. [id/1] [data/?] Transfer data (id == 0) - [0/1] [path index/1] [size/4] [data/size] @@ -39,6 +39,17 @@ Regs data (id == 3) */ +#pragma pack(push, 4) +struct GSDumpHeader +{ + u32 state_version; ///< Must always be first in struct to safely prevent old PCSX2 versions from crashing. + u32 state_size; + u32 serial_offset; + u32 serial_size; + u32 crc; +}; +#pragma pack(pop) + class GSDumpBase { int m_frames; @@ -46,7 +57,7 @@ class GSDumpBase FILE* m_gs; protected: - void AddHeader(u32 crc, const freezeData& fd, const GSPrivRegSet* regs); + void AddHeader(const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs); void Write(const void* data, size_t size); virtual void AppendRawData(const void* data, size_t size) = 0; @@ -61,14 +72,14 @@ public: bool VSync(int field, bool last, const GSPrivRegSet* regs); }; -class GSDump final : public GSDumpBase +class GSDumpUncompressed final : public GSDumpBase { void AppendRawData(const void* data, size_t size) final; void AppendRawData(u8 c) final; public: - GSDump(const std::string& fn, u32 crc, const freezeData& fd, const GSPrivRegSet* regs); - virtual ~GSDump() = default; + GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs); + virtual ~GSDumpUncompressed() = default; }; class GSDumpXz final : public GSDumpBase @@ -83,6 +94,6 @@ class GSDumpXz final : public GSDumpBase void AppendRawData(u8 c); public: - GSDumpXz(const std::string& fn, u32 crc, const freezeData& fd, const GSPrivRegSet* regs); + GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs); virtual ~GSDumpXz(); }; diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index c3c59c0193..ae73a26aaa 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -29,7 +29,7 @@ static __fi bool IsAutoFlushEnabled() } GSState::GSState() - : m_version(7) + : m_version(STATE_VERSION) , m_gsc(NULL) , m_skip(0) , m_skip_offset(0) @@ -2173,7 +2173,7 @@ int GSState::Defrost(const freezeData* fd) u8* data = fd->data; - int version; + u32 version; ReadState(&version, data); diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index a5d5b5b086..7dcbf7deca 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -123,7 +123,7 @@ class GSState : public GSAlignedClass<32> template void SetPrimHandlers(); - int m_version; + u32 m_version; int m_sssize; struct GSTransferBuffer @@ -243,6 +243,8 @@ public: int s_savel; std::string m_dump_root; + static constexpr u32 STATE_VERSION = 8; + enum PRIM_OVERLAP { PRIM_OVERLAP_UNKNOW, diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index 2da495c045..0282aad787 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -21,9 +21,15 @@ #include "PerformanceMetrics.h" #include "pcsx2/Config.h" #include "common/StringUtil.h" + +#ifndef PCSX2_CORE +#include "gui/AppCoreThread.h" #if defined(__unix__) #include #endif +#else +#include "VMManager.h" +#endif GSRenderer::GSRenderer() : m_shift_key(false) @@ -454,10 +460,16 @@ void GSRenderer::VSync(u32 field, bool registers_written) fd.data = new u8[fd.size]; Freeze(&fd, false); +#ifndef PCSX2_CORE + const std::string serial(StringUtil::wxStringToUTF8String(GameInfo::gameSerial)); +#else + const std::string serial(VMManager::GetGameSerial()); +#endif + if (m_control_key) - m_dump = std::unique_ptr(new GSDump(m_snapshot, m_crc, fd, m_regs)); + m_dump = std::unique_ptr(new GSDumpUncompressed(m_snapshot, serial, m_crc, fd, m_regs)); else - m_dump = std::unique_ptr(new GSDumpXz(m_snapshot, m_crc, fd, m_regs)); + m_dump = std::unique_ptr(new GSDumpXz(m_snapshot, serial, m_crc, fd, m_regs)); delete[] fd.data; } diff --git a/pcsx2/gui/Dialogs/GSDumpDialog.cpp b/pcsx2/gui/Dialogs/GSDumpDialog.cpp index 4b4291853b..c025344e28 100644 --- a/pcsx2/gui/Dialogs/GSDumpDialog.cpp +++ b/pcsx2/gui/Dialogs/GSDumpDialog.cpp @@ -24,6 +24,7 @@ #include "common/EmbeddedImage.h" #include "gui/Resources/NoIcon.h" #include "GS.h" +#include "GS/GSDump.h" #include "HostDisplay.h" #include "PathDefs.h" @@ -31,6 +32,7 @@ #include "gui/GSFrame.h" #include "Counters.h" #include "PerformanceMetrics.h" +#include "GameDatabase.h" #include #include @@ -514,7 +516,7 @@ void Dialogs::GSDumpDialog::GenPacketInfo(GSData& dump) { case GSType::Transfer: { - char* data = dump.data.get(); + u8* data = dump.data.get(); u32 remaining = dump.length; int idx = 0; while (remaining >= 16) @@ -532,7 +534,7 @@ void Dialogs::GSDumpDialog::GenPacketInfo(GSData& dump) case GSType::VSync: { wxString s; - s.Printf("Field = %u", *(u8*)(dump.data.get())); + s.Printf("Field = %u", dump.data[0]); m_gif_packet->AppendItem(rootId, s); break; } @@ -549,7 +551,7 @@ void Dialogs::GSDumpDialog::GenPacketInfo(GSData& dump) } } -void Dialogs::GSDumpDialog::ParseTransfer(wxTreeItemId& trootId, char* data) +void Dialogs::GSDumpDialog::ParseTransfer(wxTreeItemId& trootId, u8* data) { u64 tag = *(u64*)data; u64 regs = *(u64*)(data + 8); @@ -815,7 +817,7 @@ void Dialogs::GSDumpDialog::ParseTreePrim(wxTreeItemId& id, u32 prim) m_gif_packet->Expand(id); } -void Dialogs::GSDumpDialog::ProcessDumpEvent(const GSData& event, char* regs) +void Dialogs::GSDumpDialog::ProcessDumpEvent(const GSData& event, u8* regs) { switch (event.id) { @@ -921,14 +923,46 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread() default: break; } - char regs[8192]; + u8 regs[8192]; m_dump_file->Read(&crc, 4); m_dump_file->Read(&ss, 4); - - std::unique_ptr state_data(new char[ss]); + std::unique_ptr state_data = std::make_unique(ss); m_dump_file->Read(state_data.get(), ss); + + // Pull serial out of new header, if present. + std::string serial; + if (crc == 0xFFFFFFFFu) + { + GSDumpHeader header; + if (ss < sizeof(header)) + { + Console.Error("GSDump header is corrupted."); + GSDump::isRunning = false; + return; + } + + std::memcpy(&header, state_data.get(), sizeof(header)); + if (header.serial_size > 0) + { + if (header.serial_offset > ss || (static_cast(header.serial_offset) + header.serial_size) > ss) + { + Console.Error("GSDump header is corrupted."); + GSDump::isRunning = false; + return; + } + + if (header.serial_size > 0) + serial.assign(reinterpret_cast(state_data.get()) + header.serial_offset, header.serial_size); + } + + // Read the real state data + ss = header.state_size; + state_data = std::make_unique(ss); + m_dump_file->Read(state_data.get(), ss); + } + m_dump_file->Read(®s, 8192); freezeData fd = {(int)ss, (u8*)state_data.get()}; @@ -956,7 +990,9 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread() size = 8192; break; } - std::unique_ptr data(new char[size]); + // make_unique would zero the data out, which is pointless since we're reading it anyway, + // and this loop is executed a *ton* of times. + std::unique_ptr data(new u8[size]); m_dump_file->Read(data.get(), size); m_root_window->m_dump_packets.push_back({id, std::move(data), size, id_transfer}); } @@ -974,7 +1010,17 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread() g_FrameCount = 0; } - if (!GSopen(g_Conf->EmuOptions.GS, renderer, (u8*)regs)) + Pcsx2Config::GSOptions config(g_Conf->EmuOptions.GS); + if (!serial.empty()) + { + if (const GameDatabaseSchema::GameEntry* entry = GameDatabase::findGame(serial); entry) + { + // apply hardware fixes to config before opening (e.g. tex in rt) + entry->applyGSHardwareFixes(config); + } + } + + if (!GSopen(config, renderer, regs)) { OnStop(); return; diff --git a/pcsx2/gui/Dialogs/ModalPopups.h b/pcsx2/gui/Dialogs/ModalPopups.h index f90949ce63..a0b4615812 100644 --- a/pcsx2/gui/Dialogs/ModalPopups.h +++ b/pcsx2/gui/Dialogs/ModalPopups.h @@ -239,7 +239,7 @@ namespace Dialogs struct GSData { GSType id; - std::unique_ptr data; + std::unique_ptr data; int length; GSTransferPath path; }; @@ -260,11 +260,11 @@ namespace Dialogs std::vector m_gif_items; float m_stored_q = 1.0; - void ProcessDumpEvent(const GSData& event, char* regs); + void ProcessDumpEvent(const GSData& event, u8* regs); u32 ReadPacketSize(const void* packet); void GenPacketList(); void GenPacketInfo(GSData& dump); - void ParseTransfer(wxTreeItemId& id, char* data); + void ParseTransfer(wxTreeItemId& id, u8* data); void ParseTreeReg(wxTreeItemId& id, GIFReg reg, u128 data, bool packed); void ParseTreePrim(wxTreeItemId& id, u32 prim); void CloseDump(wxCommandEvent& event);