mirror of https://github.com/PCSX2/pcsx2.git
GSDump: Add embedded screenshot
This commit is contained in:
parent
4ed748fa30
commit
89c79aa6c3
|
@ -33,7 +33,9 @@ GSDumpBase::~GSDumpBase()
|
||||||
fclose(m_gs);
|
fclose(m_gs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GSDumpBase::AddHeader(const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs)
|
void GSDumpBase::AddHeader(const std::string& serial, u32 crc,
|
||||||
|
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
|
||||||
|
const freezeData& fd, const GSPrivRegSet* regs)
|
||||||
{
|
{
|
||||||
// New header: CRC of FFFFFFFF, secondary header, full header follows.
|
// New header: CRC of FFFFFFFF, secondary header, full header follows.
|
||||||
const u32 fake_crc = 0xFFFFFFFFu;
|
const u32 fake_crc = 0xFFFFFFFFu;
|
||||||
|
@ -41,7 +43,8 @@ void GSDumpBase::AddHeader(const std::string& serial, u32 crc, const freezeData&
|
||||||
|
|
||||||
// Compute full header size (with serial).
|
// Compute full header size (with serial).
|
||||||
// This acts as the state size for loading older dumps.
|
// This acts as the state size for loading older dumps.
|
||||||
const u32 header_size = sizeof(GSDumpHeader) + static_cast<u32>(serial.size());
|
const u32 screenshot_size = screenshot_width * screenshot_height * sizeof(screenshot_pixels[0]);
|
||||||
|
const u32 header_size = sizeof(GSDumpHeader) + static_cast<u32>(serial.size()) + screenshot_size;
|
||||||
AppendRawData(&header_size, 4);
|
AppendRawData(&header_size, 4);
|
||||||
|
|
||||||
// Write hader.
|
// Write hader.
|
||||||
|
@ -51,9 +54,15 @@ void GSDumpBase::AddHeader(const std::string& serial, u32 crc, const freezeData&
|
||||||
header.crc = crc;
|
header.crc = crc;
|
||||||
header.serial_offset = sizeof(header);
|
header.serial_offset = sizeof(header);
|
||||||
header.serial_size = static_cast<u32>(serial.size());
|
header.serial_size = static_cast<u32>(serial.size());
|
||||||
|
header.screenshot_width = screenshot_width;
|
||||||
|
header.screenshot_height = screenshot_height;
|
||||||
|
header.screenshot_offset = header.serial_offset + header.serial_size;
|
||||||
|
header.screenshot_size = screenshot_size;
|
||||||
AppendRawData(&header, sizeof(header));
|
AppendRawData(&header, sizeof(header));
|
||||||
if (!serial.empty())
|
if (!serial.empty())
|
||||||
AppendRawData(serial.data(), serial.size());
|
AppendRawData(serial.data(), serial.size());
|
||||||
|
if (screenshot_pixels)
|
||||||
|
AppendRawData(screenshot_pixels, screenshot_size);
|
||||||
|
|
||||||
// Then the real state data.
|
// Then the real state data.
|
||||||
AppendRawData(fd.data, fd.size);
|
AppendRawData(fd.data, fd.size);
|
||||||
|
@ -112,10 +121,12 @@ void GSDumpBase::Write(const void* data, size_t size)
|
||||||
// GSDump implementation
|
// GSDump implementation
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
GSDumpUncompressed::GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs)
|
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")
|
: GSDumpBase(fn + ".gs")
|
||||||
{
|
{
|
||||||
AddHeader(serial, crc, fd, regs);
|
AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GSDumpUncompressed::AppendRawData(const void* data, size_t size)
|
void GSDumpUncompressed::AppendRawData(const void* data, size_t size)
|
||||||
|
@ -132,7 +143,9 @@ void GSDumpUncompressed::AppendRawData(u8 c)
|
||||||
// GSDumpXz implementation
|
// GSDumpXz implementation
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
GSDumpXz::GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs)
|
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")
|
: GSDumpBase(fn + ".gs.xz")
|
||||||
{
|
{
|
||||||
m_strm = LZMA_STREAM_INIT;
|
m_strm = LZMA_STREAM_INIT;
|
||||||
|
@ -143,7 +156,7 @@ GSDumpXz::GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, co
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddHeader(serial, crc, fd, regs);
|
AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
GSDumpXz::~GSDumpXz()
|
GSDumpXz::~GSDumpXz()
|
||||||
|
|
|
@ -47,6 +47,10 @@ struct GSDumpHeader
|
||||||
u32 serial_offset;
|
u32 serial_offset;
|
||||||
u32 serial_size;
|
u32 serial_size;
|
||||||
u32 crc;
|
u32 crc;
|
||||||
|
u32 screenshot_width;
|
||||||
|
u32 screenshot_height;
|
||||||
|
u32 screenshot_offset;
|
||||||
|
u32 screenshot_size;
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
@ -57,7 +61,9 @@ class GSDumpBase
|
||||||
FILE* m_gs;
|
FILE* m_gs;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void AddHeader(const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs);
|
void AddHeader(const std::string& serial, u32 crc,
|
||||||
|
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
|
||||||
|
const freezeData& fd, const GSPrivRegSet* regs);
|
||||||
void Write(const void* data, size_t size);
|
void Write(const void* data, size_t size);
|
||||||
|
|
||||||
virtual void AppendRawData(const void* data, size_t size) = 0;
|
virtual void AppendRawData(const void* data, size_t size) = 0;
|
||||||
|
@ -78,7 +84,9 @@ class GSDumpUncompressed final : public GSDumpBase
|
||||||
void AppendRawData(u8 c) final;
|
void AppendRawData(u8 c) final;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs);
|
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;
|
virtual ~GSDumpUncompressed() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,6 +102,8 @@ class GSDumpXz final : public GSDumpBase
|
||||||
void AppendRawData(u8 c);
|
void AppendRawData(u8 c);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs);
|
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);
|
||||||
virtual ~GSDumpXz();
|
virtual ~GSDumpXz();
|
||||||
};
|
};
|
||||||
|
|
|
@ -466,10 +466,26 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
||||||
const std::string serial(VMManager::GetGameSerial());
|
const std::string serial(VMManager::GetGameSerial());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// keep the screenshot relatively small so we don't bloat the dump
|
||||||
|
static constexpr u32 DUMP_SCREENSHOT_WIDTH = 640;
|
||||||
|
static constexpr u32 DUMP_SCREENSHOT_HEIGHT = 480;
|
||||||
|
std::vector<u32> screenshot_pixels;
|
||||||
|
SaveSnapshotToMemory(DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT, &screenshot_pixels);
|
||||||
|
|
||||||
if (m_control_key)
|
if (m_control_key)
|
||||||
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpUncompressed(m_snapshot, serial, m_crc, fd, m_regs));
|
{
|
||||||
|
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpUncompressed(m_snapshot, serial, m_crc,
|
||||||
|
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
|
||||||
|
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
|
||||||
|
fd, m_regs));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpXz(m_snapshot, serial, m_crc, fd, m_regs));
|
{
|
||||||
|
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpXz(m_snapshot, serial, m_crc,
|
||||||
|
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
|
||||||
|
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
|
||||||
|
fd, m_regs));
|
||||||
|
}
|
||||||
|
|
||||||
delete[] fd.data;
|
delete[] fd.data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
|
|
||||||
|
|
||||||
#include "common/EmbeddedImage.h"
|
#include "common/EmbeddedImage.h"
|
||||||
|
#include "common/FileSystem.h"
|
||||||
|
#include "common/StringUtil.h"
|
||||||
#include "gui/Resources/NoIcon.h"
|
#include "gui/Resources/NoIcon.h"
|
||||||
#include "GS.h"
|
#include "GS.h"
|
||||||
#include "GS/GSDump.h"
|
#include "GS/GSDump.h"
|
||||||
|
@ -47,6 +49,7 @@
|
||||||
#include <wx/wfstream.h>
|
#include <wx/wfstream.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
template <typename Output, typename Input, typename std::enable_if<sizeof(Input) == sizeof(Output), bool>::type = true>
|
template <typename Output, typename Input, typename std::enable_if<sizeof(Input) == sizeof(Output), bool>::type = true>
|
||||||
static constexpr Output BitCast(Input input)
|
static constexpr Output BitCast(Input input)
|
||||||
|
@ -168,6 +171,94 @@ static constexpr const char* GetNameTEXCPSM(u8 psm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::unique_ptr<GSDumpFile> GetDumpFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
std::FILE* fp = FileSystem::OpenCFile(filename.c_str(), "rb");
|
||||||
|
if (!fp)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
if (StringUtil::EndsWith(filename, ".xz"))
|
||||||
|
return std::make_unique<GSDumpLzma>(fp, nullptr);
|
||||||
|
else
|
||||||
|
return std::make_unique<GSDumpRaw>(fp, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool GetPreviewImageFromDump(const std::string& filename, u32* width, u32* height, std::vector<u32>* pixels)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::unique_ptr<GSDumpFile> dump = GetDumpFile(filename);
|
||||||
|
if (!dump)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
u32 crc;
|
||||||
|
dump->Read(&crc, sizeof(crc));
|
||||||
|
if (crc != 0xFFFFFFFFu)
|
||||||
|
{
|
||||||
|
// not new header dump, so no preview
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 header_size;
|
||||||
|
dump->Read(&header_size, sizeof(header_size));
|
||||||
|
if (header_size < sizeof(GSDumpHeader))
|
||||||
|
{
|
||||||
|
// doesn't have the screenshot fields
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<u8[]> header_bits = std::make_unique<u8[]>(header_size);
|
||||||
|
dump->Read(header_bits.get(), header_size);
|
||||||
|
|
||||||
|
GSDumpHeader header;
|
||||||
|
std::memcpy(&header, header_bits.get(), sizeof(header));
|
||||||
|
if (header.screenshot_size == 0 ||
|
||||||
|
header.screenshot_size < (header.screenshot_width * header.screenshot_height * sizeof(u32)) ||
|
||||||
|
(static_cast<u64>(header.screenshot_offset) + header.screenshot_size) > header_size)
|
||||||
|
{
|
||||||
|
// doesn't have a screenshot
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*width = header.screenshot_width;
|
||||||
|
*height = header.screenshot_height;
|
||||||
|
pixels->resize(header.screenshot_width * header.screenshot_height);
|
||||||
|
std::memcpy(pixels->data(), header_bits.get() + header.screenshot_offset, header.screenshot_size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<wxImage> GetPreviewImageFromDump(const std::string& filename)
|
||||||
|
{
|
||||||
|
std::vector<u32> pixels;
|
||||||
|
u32 width, height;
|
||||||
|
if (!GetPreviewImageFromDump(filename, &width, &height, &pixels))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
// strip alpha bytes because wx is dumb and stores on a separate plane
|
||||||
|
// apparently this isn't aligned? stupidity...
|
||||||
|
const u32 pitch = width * 3;
|
||||||
|
u8* wxpixels = static_cast<u8*>(std::malloc(pitch * height));
|
||||||
|
for (u32 y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
const u8* in = reinterpret_cast<const u8*>(pixels.data() + y * width);
|
||||||
|
u8* out = wxpixels + y * pitch;
|
||||||
|
for (u32 x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
*(out++) = in[0];
|
||||||
|
*(out++) = in[1];
|
||||||
|
*(out++) = in[2];
|
||||||
|
in += sizeof(u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wxImage(wxSize(width, height), wxpixels);
|
||||||
|
}
|
||||||
|
|
||||||
namespace GSDump
|
namespace GSDump
|
||||||
{
|
{
|
||||||
bool isRunning = false;
|
bool isRunning = false;
|
||||||
|
@ -335,8 +426,17 @@ void Dialogs::GSDumpDialog::SelectedDump(wxListEvent& evt)
|
||||||
img.Rescale(400,250, wxIMAGE_QUALITY_HIGH);
|
img.Rescale(400,250, wxIMAGE_QUALITY_HIGH);
|
||||||
m_preview_image->SetBitmap(wxBitmap(img));
|
m_preview_image->SetBitmap(wxBitmap(img));
|
||||||
}
|
}
|
||||||
|
else if (std::optional<wxImage> img = GetPreviewImageFromDump(StringUtil::wxStringToUTF8String(filename)); img.has_value())
|
||||||
|
{
|
||||||
|
// try embedded dump
|
||||||
|
img->Rescale(400, 250, wxIMAGE_QUALITY_HIGH);
|
||||||
|
m_preview_image->SetBitmap(img.value());
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
m_preview_image->SetBitmap(EmbeddedImage<res_NoIcon>().Get());
|
m_preview_image->SetBitmap(EmbeddedImage<res_NoIcon>().Get());
|
||||||
|
}
|
||||||
|
|
||||||
m_selected_dump = wxString(filename);
|
m_selected_dump = wxString(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,18 +470,16 @@ void Dialogs::GSDumpDialog::RunDump(wxCommandEvent& event)
|
||||||
{
|
{
|
||||||
if (!m_run->IsEnabled())
|
if (!m_run->IsEnabled())
|
||||||
return;
|
return;
|
||||||
FILE* dumpfile = wxFopen(m_selected_dump, L"rb");
|
|
||||||
if (!dumpfile)
|
m_thread->m_dump_file = GetDumpFile(StringUtil::wxStringToUTF8String(m_selected_dump));
|
||||||
|
if (!m_thread->m_dump_file)
|
||||||
{
|
{
|
||||||
wxString s;
|
wxString s;
|
||||||
s.Printf(_("Failed to load the dump %s !"), m_selected_dump);
|
s.Printf(_("Failed to load the dump %s !"), m_selected_dump);
|
||||||
wxMessageBox(s, _("GS Debugger"), wxICON_ERROR);
|
wxMessageBox(s, _("GS Debugger"), wxICON_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_selected_dump.EndsWith(".xz"))
|
|
||||||
m_thread->m_dump_file = std::make_unique<GSDumpLzma>(dumpfile, nullptr);
|
|
||||||
else
|
|
||||||
m_thread->m_dump_file = std::make_unique<GSDumpRaw >(dumpfile, nullptr);
|
|
||||||
m_run->Disable();
|
m_run->Disable();
|
||||||
m_settings->Disable();
|
m_settings->Disable();
|
||||||
m_debug_mode->Enable();
|
m_debug_mode->Enable();
|
||||||
|
|
Loading…
Reference in New Issue