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);
|
||||
}
|
||||
|
||||
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.
|
||||
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).
|
||||
// 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);
|
||||
|
||||
// Write hader.
|
||||
|
@ -51,9 +54,15 @@ void GSDumpBase::AddHeader(const std::string& serial, u32 crc, const freezeData&
|
|||
header.crc = crc;
|
||||
header.serial_offset = sizeof(header);
|
||||
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));
|
||||
if (!serial.empty())
|
||||
AppendRawData(serial.data(), serial.size());
|
||||
if (screenshot_pixels)
|
||||
AppendRawData(screenshot_pixels, screenshot_size);
|
||||
|
||||
// Then the real state data.
|
||||
AppendRawData(fd.data, fd.size);
|
||||
|
@ -112,10 +121,12 @@ void GSDumpBase::Write(const void* data, size_t size)
|
|||
// 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")
|
||||
{
|
||||
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)
|
||||
|
@ -132,7 +143,9 @@ void GSDumpUncompressed::AppendRawData(u8 c)
|
|||
// 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")
|
||||
{
|
||||
m_strm = LZMA_STREAM_INIT;
|
||||
|
@ -143,7 +156,7 @@ GSDumpXz::GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, co
|
|||
return;
|
||||
}
|
||||
|
||||
AddHeader(serial, crc, fd, regs);
|
||||
AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs);
|
||||
}
|
||||
|
||||
GSDumpXz::~GSDumpXz()
|
||||
|
|
|
@ -47,6 +47,10 @@ struct GSDumpHeader
|
|||
u32 serial_offset;
|
||||
u32 serial_size;
|
||||
u32 crc;
|
||||
u32 screenshot_width;
|
||||
u32 screenshot_height;
|
||||
u32 screenshot_offset;
|
||||
u32 screenshot_size;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
|
@ -57,7 +61,9 @@ class GSDumpBase
|
|||
FILE* m_gs;
|
||||
|
||||
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);
|
||||
|
||||
virtual void AppendRawData(const void* data, size_t size) = 0;
|
||||
|
@ -78,7 +84,9 @@ class GSDumpUncompressed final : public GSDumpBase
|
|||
void AppendRawData(u8 c) final;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -94,6 +102,8 @@ class GSDumpXz final : public GSDumpBase
|
|||
void AppendRawData(u8 c);
|
||||
|
||||
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();
|
||||
};
|
||||
|
|
|
@ -466,10 +466,26 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
|||
const std::string serial(VMManager::GetGameSerial());
|
||||
#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)
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
|
||||
#include "common/EmbeddedImage.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "gui/Resources/NoIcon.h"
|
||||
#include "GS.h"
|
||||
#include "GS/GSDump.h"
|
||||
|
@ -47,6 +49,7 @@
|
|||
#include <wx/wfstream.h>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
template <typename Output, typename Input, typename std::enable_if<sizeof(Input) == sizeof(Output), bool>::type = true>
|
||||
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
|
||||
{
|
||||
bool isRunning = false;
|
||||
|
@ -335,8 +426,17 @@ void Dialogs::GSDumpDialog::SelectedDump(wxListEvent& evt)
|
|||
img.Rescale(400,250, wxIMAGE_QUALITY_HIGH);
|
||||
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
|
||||
{
|
||||
m_preview_image->SetBitmap(EmbeddedImage<res_NoIcon>().Get());
|
||||
}
|
||||
|
||||
m_selected_dump = wxString(filename);
|
||||
}
|
||||
|
||||
|
@ -370,18 +470,16 @@ void Dialogs::GSDumpDialog::RunDump(wxCommandEvent& event)
|
|||
{
|
||||
if (!m_run->IsEnabled())
|
||||
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;
|
||||
s.Printf(_("Failed to load the dump %s !"), m_selected_dump);
|
||||
wxMessageBox(s, _("GS Debugger"), wxICON_ERROR);
|
||||
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_settings->Disable();
|
||||
m_debug_mode->Enable();
|
||||
|
|
Loading…
Reference in New Issue