diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 12f045719..2baf47b27 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -3,6 +3,7 @@ #include "common/heap_array.h" #include "common/log.h" #include "common/state_wrapper.h" +#include "common/string_util.h" #include "dma.h" #include "host_display.h" #include "host_interface.h" @@ -1453,6 +1454,26 @@ void GPU::SetTextureWindow(u32 value) m_draw_mode.texture_window_changed = true; } +bool GPU::DumpVRAMToFile(const char* filename) +{ + ReadVRAM(0, 0, VRAM_WIDTH, VRAM_HEIGHT); + + const char* extension = std::strrchr(filename, '.'); + if (extension && StringUtil::Strcasecmp(extension, ".png") == 0) + { + return DumpVRAMToFile(filename, VRAM_WIDTH, VRAM_HEIGHT, sizeof(u16) * VRAM_WIDTH, m_vram_ptr, true); + } + else if (extension && StringUtil::Strcasecmp(extension, ".bin") == 0) + { + return FileSystem::WriteBinaryFile(filename, m_vram_ptr, VRAM_WIDTH * VRAM_HEIGHT * sizeof(u16)); + } + else + { + Log_ErrorPrintf("Unknown extension: '%s'", filename); + return false; + } +} + bool GPU::DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer, bool remove_alpha) { auto fp = FileSystem::OpenManagedCFile(filename, "wb"); diff --git a/src/core/gpu.h b/src/core/gpu.h index 96fe55c37..73b24971b 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -160,6 +160,9 @@ public: // Returns the video clock frequency. TickCount GetCRTCFrequency() const; + // Dumps raw VRAM to a file. + bool DumpVRAMToFile(const char* filename); + protected: TickCount CRTCTicksToSystemTicks(TickCount crtc_ticks, TickCount fractional_ticks) const; TickCount SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const; diff --git a/src/core/spu.h b/src/core/spu.h index d718232b4..f93cff22d 100644 --- a/src/core/spu.h +++ b/src/core/spu.h @@ -17,6 +17,12 @@ class TimingEvent; class SPU { public: + enum : u32 + { + RAM_SIZE = 512 * 1024, + RAM_MASK = RAM_SIZE - 1, + }; + SPU(); ~SPU(); @@ -47,9 +53,11 @@ public: /// Stops dumping audio to file, if started. bool StopDumpingAudio(); + /// Access to SPU RAM. + const std::array& GetRAM() const { return m_ram; } + std::array& GetRAM() { return m_ram; } + private: - static constexpr u32 RAM_SIZE = 512 * 1024; - static constexpr u32 RAM_MASK = RAM_SIZE - 1; static constexpr u32 SPU_BASE = 0x1F801C00; static constexpr u32 NUM_VOICES = 24; static constexpr u32 NUM_VOICE_REGISTERS = 8; diff --git a/src/core/system.cpp b/src/core/system.cpp index d287515b8..afa9b5f23 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1635,11 +1635,30 @@ void UpdateMemoryCards() bool DumpRAM(const char* filename) { - auto fp = FileSystem::OpenManagedCFile(filename, "wb"); - if (!fp) + if (!IsValid()) return false; - return std::fwrite(Bus::g_ram, Bus::RAM_SIZE, 1, fp.get()) == 1; + return FileSystem::WriteBinaryFile(filename, Bus::g_ram, Bus::RAM_SIZE); +} + +bool DumpVRAM(const char* filename) +{ + if (!IsValid()) + return false; + + g_gpu->RestoreGraphicsAPIState(); + const bool result = g_gpu->DumpVRAMToFile(filename); + g_gpu->ResetGraphicsAPIState(); + + return result; +} + +bool DumpSPURAM(const char* filename) +{ + if (!IsValid()) + return false; + + return FileSystem::WriteBinaryFile(filename, g_spu.GetRAM().data(), SPU::RAM_SIZE); } bool HasMedia() diff --git a/src/core/system.h b/src/core/system.h index 3e4261979..a718e4e35 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -175,6 +175,12 @@ void UpdateMemoryCards(); /// Dumps RAM to a file. bool DumpRAM(const char* filename); +/// Dumps video RAM to a file. +bool DumpVRAM(const char* filename); + +/// Dumps sound RAM to a file. +bool DumpSPURAM(const char* filename); + bool HasMedia(); bool InsertMedia(const char* path); void RemoveMedia(); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 247441f77..84bb5a784 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -821,6 +821,9 @@ void MainWindow::updateEmulationActions(bool starting, bool running) m_ui.menuCheats->setDisabled(starting || !running); m_ui.actionCheatManager->setDisabled(starting || !running); m_ui.actionCPUDebugger->setDisabled(starting || !running); + m_ui.actionDumpRAM->setDisabled(starting || !running); + m_ui.actionDumpVRAM->setDisabled(starting || !running); + m_ui.actionDumpSPURAM->setDisabled(starting || !running); m_ui.actionSaveState->setDisabled(starting || !running); m_ui.menuSaveState->setDisabled(starting || !running); @@ -1041,12 +1044,29 @@ void MainWindow::connectSignals() m_host_interface->stopDumpingAudio(); }); connect(m_ui.actionDumpRAM, &QAction::triggered, [this]() { - const QString filename = QFileDialog::getSaveFileName(this, tr("Destination File")); + const QString filename = + QFileDialog::getSaveFileName(this, tr("Destination File"), QString(), tr("Binary Files (*.bin)")); if (filename.isEmpty()) return; m_host_interface->dumpRAM(filename); }); + connect(m_ui.actionDumpVRAM, &QAction::triggered, [this]() { + const QString filename = QFileDialog::getSaveFileName(this, tr("Destination File"), QString(), + tr("Binary Files (*.bin);;PNG Images (*.png)")); + if (filename.isEmpty()) + return; + + m_host_interface->dumpVRAM(filename); + }); + connect(m_ui.actionDumpSPURAM, &QAction::triggered, [this]() { + const QString filename = + QFileDialog::getSaveFileName(this, tr("Destination File"), QString(), tr("Binary Files (*.bin)")); + if (filename.isEmpty()) + return; + + m_host_interface->dumpSPURAM(filename); + }); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.actionDebugShowVRAM, "Debug", "ShowVRAM"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.actionDebugShowGPUState, "Debug", "ShowGPUState"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.actionDebugShowCDROMState, "Debug", diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui index 2edf3b0e7..62100ee30 100644 --- a/src/duckstation-qt/mainwindow.ui +++ b/src/duckstation-qt/mainwindow.ui @@ -174,13 +174,16 @@ + + + + + + - - - @@ -611,6 +614,16 @@ Dump RAM... + + + Dump VRAM... + + + + + Dump SPU RAM... + + true diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index c3487e63b..ee225aef5 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -1292,9 +1292,6 @@ void QtHostInterface::dumpRAM(const QString& filename) return; } - if (System::IsShutdown()) - return; - const std::string filename_str = filename.toStdString(); if (System::DumpRAM(filename_str.c_str())) ReportFormattedMessage("RAM dumped to '%s'", filename_str.c_str()); @@ -1302,6 +1299,36 @@ void QtHostInterface::dumpRAM(const QString& filename) ReportFormattedMessage("Failed to dump RAM to '%s'", filename_str.c_str()); } +void QtHostInterface::dumpVRAM(const QString& filename) +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "dumpVRAM", Qt::QueuedConnection, Q_ARG(const QString&, filename)); + return; + } + + const std::string filename_str = filename.toStdString(); + if (System::DumpVRAM(filename_str.c_str())) + ReportFormattedMessage("VRAM dumped to '%s'", filename_str.c_str()); + else + ReportFormattedMessage("Failed to dump VRAM to '%s'", filename_str.c_str()); +} + +void QtHostInterface::dumpSPURAM(const QString& filename) +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "dumpSPURAM", Qt::QueuedConnection, Q_ARG(const QString&, filename)); + return; + } + + const std::string filename_str = filename.toStdString(); + if (System::DumpSPURAM(filename_str.c_str())) + ReportFormattedMessage("SPU RAM dumped to '%s'", filename_str.c_str()); + else + ReportFormattedMessage("Failed to dump SPU RAM to '%s'", filename_str.c_str()); +} + void QtHostInterface::saveScreenshot() { if (!isOnWorkerThread()) diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 19a9f7383..2795eec97 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -169,6 +169,8 @@ public Q_SLOTS: void stopDumpingAudio(); void singleStepCPU(); void dumpRAM(const QString& filename); + void dumpVRAM(const QString& filename); + void dumpSPURAM(const QString& filename); void saveScreenshot(); void redrawDisplayWindow(); void toggleFullscreen();