From 4331ae1925336357066fcb741bea05bf1aa49bed Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 12 Mar 2022 23:20:23 +1000 Subject: [PATCH] VMManager: Support playing back GS dumps --- pcsx2-qt/EmuThread.cpp | 4 + pcsx2-qt/MainWindow.cpp | 12 +- pcsx2/CMakeLists.txt | 2 + pcsx2/Frontend/ImGuiManager.cpp | 14 ++ pcsx2/Frontend/ImGuiManager.h | 11 + pcsx2/GSDumpReplayer.cpp | 342 ++++++++++++++++++++++++++++++++ pcsx2/GSDumpReplayer.h | 15 ++ pcsx2/Memory.cpp | 12 +- pcsx2/System.cpp | 10 + pcsx2/VMManager.cpp | 68 ++++++- pcsx2/VMManager.h | 3 + pcsx2/pcsx2core.vcxproj | 2 + pcsx2/pcsx2core.vcxproj.filters | 2 + 13 files changed, 482 insertions(+), 15 deletions(-) create mode 100644 pcsx2/GSDumpReplayer.cpp create mode 100644 pcsx2/GSDumpReplayer.h diff --git a/pcsx2-qt/EmuThread.cpp b/pcsx2-qt/EmuThread.cpp index 96a2aa1e60..49dd652a22 100644 --- a/pcsx2-qt/EmuThread.cpp +++ b/pcsx2-qt/EmuThread.cpp @@ -28,6 +28,7 @@ #include "pcsx2/Frontend/InputManager.h" #include "pcsx2/Frontend/ImGuiManager.h" #include "pcsx2/GS.h" +#include "pcsx2/GSDumpReplayer.h" #include "pcsx2/HostDisplay.h" #include "pcsx2/PAD/Host/PAD.h" #include "pcsx2/PerformanceMetrics.h" @@ -677,6 +678,9 @@ bool Host::BeginPresentFrame(bool frame_skip) void Host::EndPresentFrame() { + if (GSDumpReplayer::IsReplayingDump()) + GSDumpReplayer::RenderUI(); + ImGuiManager::RenderOSD(); s_host_display->EndPresent(); ImGuiManager::NewFrame(); diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index c6b786bc60..fad60120b7 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -44,9 +44,15 @@ #include "svnrev.h" static constexpr char DISC_IMAGE_FILTER[] = - QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.elf *.irx *.m3u);;Single-Track Raw Images (*.bin " - "*.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;CSO Images (*.cso);;" - "ELF Executables (*.elf);;IRX Executables (*.irx);;Playlists (*.m3u)"); + QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.elf *.irx *.m3u *.gs *.gs.xz);;" + "Single-Track Raw Images (*.bin *.iso);;" + "Cue Sheets (*.cue);;" + "MAME CHD Images (*.chd);;" + "CSO Images (*.cso);;" + "ELF Executables (*.elf);;" + "IRX Executables (*.irx);;" + "Playlists (*.m3u);;" + "GS Dumps (*.gs *.gs.xz)"); const char* MainWindow::DEFAULT_THEME_NAME = "darkfusion"; diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index cc4b7c9088..4343266741 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -983,6 +983,7 @@ if(PCSX2_CORE) Frontend/InputManager.cpp Frontend/InputSource.cpp Frontend/LayeredSettingsInterface.cpp + GSDumpReplayer.cpp HostSettings.cpp VMManager.cpp ) @@ -992,6 +993,7 @@ if(PCSX2_CORE) Frontend/InputManager.h Frontend/InputSource.h Frontend/LayeredSettingsInterface.h + GSDumpReplayer.h HostSettings.h VMManager.h) endif() diff --git a/pcsx2/Frontend/ImGuiManager.cpp b/pcsx2/Frontend/ImGuiManager.cpp index ec60c3a973..ffda4a34b0 100644 --- a/pcsx2/Frontend/ImGuiManager.cpp +++ b/pcsx2/Frontend/ImGuiManager.cpp @@ -630,3 +630,17 @@ void ImGuiManager::RenderOSD() DrawOSDMessages(); } +float ImGuiManager::GetGlobalScale() +{ + return s_global_scale; +} + +ImFont* ImGuiManager::GetStandardFont() +{ + return s_standard_font; +} + +ImFont* ImGuiManager::GetFixedFont() +{ + return s_fixed_font; +} diff --git a/pcsx2/Frontend/ImGuiManager.h b/pcsx2/Frontend/ImGuiManager.h index 8b8cbadd2f..1fb928c9b6 100644 --- a/pcsx2/Frontend/ImGuiManager.h +++ b/pcsx2/Frontend/ImGuiManager.h @@ -15,6 +15,8 @@ #pragma once +struct ImFont; + namespace ImGuiManager { /// Initializes ImGui, creates fonts, etc. @@ -34,5 +36,14 @@ namespace ImGuiManager /// Renders any on-screen display elements. void RenderOSD(); + + /// Returns the scale of all on-screen elements. + float GetGlobalScale(); + + /// Returns the standard font for external drawing. + ImFont* GetStandardFont(); + + /// Returns the fixed-width font for external drawing. + ImFont* GetFixedFont(); } // namespace ImGuiManager diff --git a/pcsx2/GSDumpReplayer.cpp b/pcsx2/GSDumpReplayer.cpp new file mode 100644 index 0000000000..db78fb0041 --- /dev/null +++ b/pcsx2/GSDumpReplayer.cpp @@ -0,0 +1,342 @@ +#include "PrecompiledHeader.h" + +#include + +#include "common/FileSystem.h" +#include "common/StringUtil.h" +#include "common/Timer.h" + +#include "imgui.h" + +// Has to come before Gif.h +#include "MemoryTypes.h" + +#include "Frontend/ImGuiManager.h" +#include "Frontend/GameList.h" +#include "Gif.h" +#include "Gif_Unit.h" +#include "GSDumpReplayer.h" +#include "GS/GSLzma.h" +#include "GS.h" +#include "Host.h" +#include "R3000A.h" +#include "R5900.h" +#include "VMManager.h" +#include "VUmicro.h" + +static void GSDumpReplayerCpuReserve(); +static void GSDumpReplayerCpuShutdown(); +static void GSDumpReplayerCpuReset(); +static void GSDumpReplayerCpuStep(); +static void GSDumpReplayerCpuExecute(); +static void GSDumpReplayerCpuCheckExecutionState(); +static void GSDumpReplayerCpuThrowException(const BaseException& ex); +static void GSDumpReplayerCpuThrowCpuException(const BaseR5900Exception& ex); +static void GSDumpReplayerCpuClear(u32 addr, u32 size); +static uint GSDumpReplayerCpuGetCacheReserve(); +static void GSDumpReplayerCpuSetCacheReserve(uint reserveInMegs); + +static std::unique_ptr s_dump_file; +static u32 s_current_packet = 0; +static u32 s_dump_frame_number = 0; +static bool s_dump_running = false; +static bool s_needs_state_loaded = false; +static u64 s_frame_ticks = 0; +static u64 s_next_frame_time = 0; + +R5900cpu GSDumpReplayerCpu = { + GSDumpReplayerCpuReserve, + GSDumpReplayerCpuShutdown, + GSDumpReplayerCpuReset, + GSDumpReplayerCpuStep, + GSDumpReplayerCpuExecute, + GSDumpReplayerCpuCheckExecutionState, + GSDumpReplayerCpuThrowException, + GSDumpReplayerCpuThrowCpuException, + GSDumpReplayerCpuClear, + GSDumpReplayerCpuGetCacheReserve, + GSDumpReplayerCpuSetCacheReserve}; + +static InterpVU0 gsDumpVU0; +static InterpVU1 gsDumpVU1; + +bool GSDumpReplayer::IsReplayingDump() +{ + return static_cast(s_dump_file); +} + +bool GSDumpReplayer::Initialize(const char* filename) +{ + Common::Timer timer; + Console.WriteLn("(GSDumpReplayer) Reading file..."); + + s_dump_file = GSDumpFile::OpenGSDump(filename); + if (!s_dump_file || !s_dump_file->ReadFile()) + { + Host::ReportFormattedErrorAsync("GSDumpReplayer", "Failed to open or read '%s'.", filename); + s_dump_file.reset(); + return false; + } + + Console.WriteLn("(GSDumpReplayer) Read file in %.2f ms.", timer.GetTimeMilliseconds()); + + // We replace all CPUs. + Cpu = &GSDumpReplayerCpu; + psxCpu = &psxInt; + CpuVU0 = &gsDumpVU0; + CpuVU1 = &gsDumpVU1; + + return true; +} + +void GSDumpReplayer::Reset() +{ + GSDumpReplayerCpuReset(); +} + +void GSDumpReplayer::Shutdown() +{ + Console.WriteLn("(GSDumpReplayer) Shutting down."); + + Cpu = nullptr; + psxCpu = nullptr; + CpuVU0 = nullptr; + CpuVU1 = nullptr; + s_dump_file.reset(); +} + +std::string GSDumpReplayer::GetDumpSerial() +{ + std::string ret; + + if (!s_dump_file->GetSerial().empty()) + { + ret = s_dump_file->GetSerial(); + } + else if (s_dump_file->GetCRC() != 0) + { + // old dump files don't have serials, but we have the crc... + // so, let's try searching the game list for a crc match. + auto lock = GameList::GetLock(); + const GameList::Entry* entry = GameList::GetEntryByCRC(s_dump_file->GetCRC()); + if (entry) + ret = entry->serial; + } + + return ret; +} + +u32 GSDumpReplayer::GetDumpCRC() +{ + return s_dump_file->GetCRC(); +} + +void GSDumpReplayerCpuReserve() +{ +} + +void GSDumpReplayerCpuShutdown() +{ +} + +void GSDumpReplayerCpuReset() +{ + s_needs_state_loaded = true; + s_current_packet = 0; + s_dump_frame_number = 0; +} + +static void GSDumpReplayerLoadInitialState() +{ + // reset GS registers to initial dump values + std::memcpy(PS2MEM_GS, s_dump_file->GetRegsData().data(), + std::min(Ps2MemSize::GSregs, static_cast(s_dump_file->GetRegsData().size()))); + + // load GS state + freezeData fd = {static_cast(s_dump_file->GetStateData().size()), + const_cast(s_dump_file->GetStateData().data())}; + MTGS_FreezeData mfd = {&fd, 0}; + GetMTGS().Freeze(FreezeAction::Load, mfd); + if (mfd.retval != 0) + Host::ReportFormattedErrorAsync("GSDumpReplayer", "Failed to load GS state."); +} + +static void GSDumpReplayerSendPacketToMTGS(GIF_PATH path, const u8* data, u32 length) +{ + pxAssert((length % 16) == 0); + + Gif_Path& gifPath = gifUnit.gifPath[path]; + gifPath.CopyGSPacketData(const_cast(data), length); + + GS_Packet gsPack; + gsPack.offset = gifPath.curOffset; + gsPack.size = length; + gifPath.curOffset += length; + Gif_AddCompletedGSPacket(gsPack, path); +} + +static void GSDumpReplayerUpdateFrameLimit() +{ + constexpr u32 default_frame_limit = 60; + const u32 frame_limit = static_cast(default_frame_limit * EmuConfig.GS.LimitScalar); + + if (frame_limit > 0) + s_frame_ticks = (GetTickFrequency() + (frame_limit / 2)) / frame_limit; + else + s_frame_ticks = 0; +} + +static void GSDumpReplayerFrameLimit() +{ + if (s_frame_ticks == 0) + return; + + // Frame limiter + u64 now = GetCPUTicks(); + const s64 ms = GetTickFrequency() / 1000; + const s64 sleep = s_next_frame_time - now - ms; + if (sleep > ms) + Threading::Sleep(sleep / ms); + while ((now = GetCPUTicks()) < s_next_frame_time) + ShortSpin(); + s_next_frame_time = std::max(now, s_next_frame_time + s_frame_ticks); +} + +void GSDumpReplayerCpuStep() +{ + if (s_needs_state_loaded) + { + GSDumpReplayerLoadInitialState(); + s_needs_state_loaded = false; + } + + const GSDumpFile::GSData& packet = s_dump_file->GetPackets()[s_current_packet]; + s_current_packet = (s_current_packet + 1) % static_cast(s_dump_file->GetPackets().size()); + if (s_current_packet == 0) + s_dump_frame_number = 0; + + switch (packet.id) + { + case GSDumpTypes::GSType::Transfer: + { + switch (packet.path) + { + case GSDumpTypes::GSTransferPath::Path1Old: + { + std::unique_ptr data(new u8[16384]); + const s32 addr = 16384 - packet.length; + std::memcpy(data.get(), packet.data.get() + addr, packet.length); + GSDumpReplayerSendPacketToMTGS(GIF_PATH_1, data.get(), packet.length); + } + break; + + case GSDumpTypes::GSTransferPath::Path1New: + case GSDumpTypes::GSTransferPath::Path2: + case GSDumpTypes::GSTransferPath::Path3: + { + GSDumpReplayerSendPacketToMTGS(static_cast(static_cast(packet.path) - 1), + reinterpret_cast(packet.data.get()), packet.length); + } + break; + + default: + break; + } + break; + } + + case GSDumpTypes::GSType::VSync: + { + s_dump_frame_number++; + GSDumpReplayerCpuCheckExecutionState(); + GSDumpReplayerUpdateFrameLimit(); + GSDumpReplayerFrameLimit(); + GetMTGS().PostVsyncStart(false); + VMManager::Internal::VSyncOnCPUThread(); + } + break; + + case GSDumpTypes::GSType::ReadFIFO2: + { + std::unique_ptr arr(new char[*((int*)packet.data.get())]); + GSreadFIFO2((u8*)arr.get(), *((int*)packet.data.get())); + } + break; + + case GSDumpTypes::GSType::Registers: + { + std::memcpy(PS2MEM_GS, packet.data.get(), std::min(packet.length, Ps2MemSize::GSregs)); + } + break; + } +} + +void GSDumpReplayerCpuExecute() +{ + s_dump_running = true; + s_next_frame_time = GetCPUTicks(); + + while (s_dump_running) + { + GSDumpReplayerCpuStep(); + } +} + +void GSDumpReplayerCpuCheckExecutionState() +{ + if (VMManager::Internal::IsExecutionInterrupted()) + s_dump_running = false; +} + +void GSDumpReplayerCpuThrowException(const BaseException& ex) +{ +} + +void GSDumpReplayerCpuThrowCpuException(const BaseR5900Exception& ex) +{ +} + +void GSDumpReplayerCpuClear(u32 addr, u32 size) +{ +} + +uint GSDumpReplayerCpuGetCacheReserve() +{ + return 0; +} + +void GSDumpReplayerCpuSetCacheReserve(uint reserveInMegs) +{ +} + +void GSDumpReplayer::RenderUI() +{ + const float scale = ImGuiManager::GetGlobalScale(); + const float shadow_offset = std::ceil(1.0f * scale); + const float margin = std::ceil(10.0f * scale); + const float spacing = std::ceil(5.0f * scale); + float position_y = margin; + + ImDrawList* dl = ImGui::GetBackgroundDrawList(); + ImFont* font = ImGuiManager::GetFixedFont(); + FastFormatAscii text; + ImVec2 text_size; + +#define DRAW_LINE(font, text, color) \ + do \ + { \ + text_size = font->CalcTextSizeA(font->FontSize, std::numeric_limits::max(), -1.0f, (text), nullptr, nullptr); \ + dl->AddText(font, font->FontSize, ImVec2(margin + shadow_offset, position_y + shadow_offset), IM_COL32(0, 0, 0, 100), (text)); \ + dl->AddText(font, font->FontSize, ImVec2(margin, position_y), color, (text)); \ + position_y += text_size.y + spacing; \ + } while (0) + + text.Write("Dump Frame: %u", s_dump_frame_number); + DRAW_LINE(font, text.c_str(), IM_COL32(255, 255, 255, 255)); + + text.Clear(); + text.Write("Packet Number: %u/%u", s_current_packet, static_cast(s_dump_file->GetPackets().size())); + DRAW_LINE(font, text.c_str(), IM_COL32(255, 255, 255, 255)); + +#undef DRAW_LINE +} diff --git a/pcsx2/GSDumpReplayer.h b/pcsx2/GSDumpReplayer.h new file mode 100644 index 0000000000..1eca2497f2 --- /dev/null +++ b/pcsx2/GSDumpReplayer.h @@ -0,0 +1,15 @@ +#pragma once + +namespace GSDumpReplayer +{ +bool IsReplayingDump(); + +bool Initialize(const char* filename); +void Reset(); +void Shutdown(); + +std::string GetDumpSerial(); +u32 GetDumpCRC(); + +void RenderUI(); +} \ No newline at end of file diff --git a/pcsx2/Memory.cpp b/pcsx2/Memory.cpp index 384f9d927a..1a1a5cf743 100644 --- a/pcsx2/Memory.cpp +++ b/pcsx2/Memory.cpp @@ -49,6 +49,10 @@ BIOS #include "common/PageFaultSource.h" +#ifdef PCSX2_CORE +#include "GSDumpReplayer.h" +#endif + #ifdef ENABLECACHE #include "Cache.h" #endif @@ -850,7 +854,13 @@ void eeMemoryReserve::Reset() vtlb_VMap(0x00000000,0x00000000,0x20000000); vtlb_VMapUnmap(0x20000000,0x60000000); - if (!LoadBIOS()) +#ifdef PCSX2_CORE + const bool needs_bios = !GSDumpReplayer::IsReplayingDump(); +#else + constexpr bool needs_bios = true; +#endif + + if (needs_bios && !LoadBIOS()) pxFailRel("Failed to load BIOS"); } diff --git a/pcsx2/System.cpp b/pcsx2/System.cpp index cef9c11be1..2446ed4032 100644 --- a/pcsx2/System.cpp +++ b/pcsx2/System.cpp @@ -27,6 +27,11 @@ #include "common/MemsetFast.inl" #include "common/Perf.h" +#ifdef PCSX2_CORE +#include "GSDumpReplayer.h" + +extern R5900cpu GSDumpReplayerCpu; +#endif // -------------------------------------------------------------------------------------- // RecompiledCodeReserve (implementations) @@ -576,6 +581,11 @@ void SysCpuProviderPack::ApplyConfig() const if( EmuConfig.Cpu.Recompiler.EnableVU1 ) CpuVU1 = (BaseVUmicroCPU*)CpuProviders->microVU1; + +#ifdef PCSX2_CORE + if (GSDumpReplayer::IsReplayingDump()) + Cpu = &GSDumpReplayerCpu; +#endif } // Resets all PS2 cpu execution caches, which does not affect that actual PS2 state/condition. diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index a3059b68e2..b81d6787bc 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -35,6 +35,7 @@ #include "Elfheader.h" #include "FW.h" #include "GS.h" +#include "GSDumpReplayer.h" #include "HostDisplay.h" #include "HostSettings.h" #include "IopBios.h" @@ -222,6 +223,10 @@ void VMManager::LoadSettings() EmuConfig.GS.MaskUserHacks(); EmuConfig.GS.MaskUpscalingHacks(); + // Force MTVU off when playing back GS dumps, it doesn't get used. + if (GSDumpReplayer::IsReplayingDump()) + EmuConfig.Speedhacks.vuThread = false; + if (HasValidVM()) ApplyGameFixes(); } @@ -454,11 +459,20 @@ void VMManager::UpdateRunningGame(bool force) // we have the CRC but we're still at the bios and the settings are changed // (e.g. the user presses TAB to speed up emulation), we don't want to apply the // settings as if the game is already running (title, loadeding patches, etc). - bool ingame = (ElfCRC && (g_GameLoading || g_GameStarted)); - const u32 new_crc = ingame ? ElfCRC : 0; - const std::string crc_string(StringUtil::StdStringFromFormat("%08X", new_crc)); + u32 new_crc; + std::string new_serial; + if (!GSDumpReplayer::IsReplayingDump()) + { + const bool ingame = (ElfCRC && (g_GameLoading || g_GameStarted)); + new_crc = ingame ? ElfCRC : 0; + new_serial = ingame ? SysGetDiscID().ToStdString() : SysGetBiosDiscID().ToStdString(); + } + else + { + new_crc = GSDumpReplayer::GetDumpCRC(); + new_serial = GSDumpReplayer::GetDumpSerial(); + } - std::string new_serial(ingame ? SysGetDiscID().ToStdString() : SysGetBiosDiscID().ToStdString()); if (!force && s_game_crc == new_crc && s_game_serial == new_serial) return; @@ -488,7 +502,7 @@ void VMManager::UpdateRunningGame(bool force) ApplySettings(); ForgetLoadedPatches(); - LoadPatches(crc_string, true, false); + LoadPatches(StringUtil::StdStringFromFormat("%08X", new_crc), true, false); GetMTGS().SendGameCRC(new_crc); Host::OnGameChanged(s_disc_path, s_game_serial, s_game_name, s_game_crc); @@ -544,7 +558,18 @@ bool VMManager::Initialize(const VMBootParameters& boot_params) }; LoadSettings(); - ApplyBootParameters(boot_params); + + if (IsGSDumpFileName(boot_params.source)) + { + CDVDsys_ChangeSource(CDVD_SourceType::NoDisc); + if (!GSDumpReplayer::Initialize(boot_params.source.c_str())) + return false; + } + else + { + ApplyBootParameters(boot_params); + } + EmuConfig.LimiterMode = GetInitialLimiterMode(); Console.WriteLn("Allocating memory map..."); @@ -661,17 +686,18 @@ bool VMManager::Initialize(const VMBootParameters& boot_params) frameLimitReset(); cpuReset(); + Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds()); + s_state.store(VMState::Paused); + Host::OnVMStarted(); + UpdateRunningGame(true); SetEmuThreadAffinities(true); PerformanceMetrics::Clear(); - Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds()); - s_state.store(VMState::Paused); - Host::OnVMStarted(); // do we want to load state? - if (!boot_params.save_state.empty()) + if (!GSDumpReplayer::IsReplayingDump() && !boot_params.save_state.empty()) { if (!DoLoadState(boot_params.save_state.c_str())) { @@ -692,12 +718,16 @@ void VMManager::Shutdown(bool allow_save_resume_state /* = true */) vu1Thread.WaitVU(); GetMTGS().WaitGS(); - if (allow_save_resume_state && ShouldSaveResumeState()) + if (!GSDumpReplayer::IsReplayingDump() && allow_save_resume_state && ShouldSaveResumeState()) { std::string resume_file_name(GetCurrentSaveStateFileName(-1)); if (!resume_file_name.empty() && !DoSaveState(resume_file_name.c_str(), -1)) Console.Error("Failed to save resume state"); } + else if (GSDumpReplayer::IsReplayingDump()) + { + GSDumpReplayer::Shutdown(); + } { std::unique_lock lock(s_info_mutex); @@ -790,6 +820,9 @@ std::string VMManager::GetCurrentSaveStateFileName(s32 slot) bool VMManager::DoLoadState(const char* filename) { + if (GSDumpReplayer::IsReplayingDump()) + return false; + try { Host::OnSaveStateLoading(filename); @@ -808,6 +841,9 @@ bool VMManager::DoLoadState(const char* filename) bool VMManager::DoSaveState(const char* filename, s32 slot_for_message) { + if (GSDumpReplayer::IsReplayingDump()) + return false; + try { std::unique_ptr elist = std::make_unique(new VmStateBuffer(L"Zippable Savestate")); @@ -920,6 +956,11 @@ bool VMManager::IsElfFileName(const std::string& path) return (StringUtil::Strcasecmp(&path[pos], ".elf") == 0); } +bool VMManager::IsGSDumpFileName(const std::string& path) +{ + return (StringUtil::EndsWithNoCase(path, ".gs") || StringUtil::EndsWithNoCase(path, ".gs.xz")); +} + void VMManager::SetBootParametersForPath(const std::string& path, VMBootParameters* params) { if (IsElfFileName(path)) @@ -927,6 +968,11 @@ void VMManager::SetBootParametersForPath(const std::string& path, VMBootParamete params->elf_override = path; params->source_type = CDVD_SourceType::NoDisc; } + else if (IsGSDumpFileName(path)) + { + params->source_type = CDVD_SourceType::NoDisc; + params->source = path; + } else if (!path.empty()) { params->source_type = CDVD_SourceType::Iso; diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index a0bc684e9a..6eaa2dad20 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -135,6 +135,9 @@ namespace VMManager /// Returns true if the specified path is an ELF. bool IsElfFileName(const std::string& path); + /// Returns true if the specified path is a GS Dump. + bool IsGSDumpFileName(const std::string& path); + /// Updates boot parameters for a given start filename. If it's an elf, it'll set elf_override, otherwise source. void SetBootParametersForPath(const std::string& path, VMBootParameters* params); diff --git a/pcsx2/pcsx2core.vcxproj b/pcsx2/pcsx2core.vcxproj index a2a247d973..734c5aca0a 100644 --- a/pcsx2/pcsx2core.vcxproj +++ b/pcsx2/pcsx2core.vcxproj @@ -186,6 +186,7 @@ + @@ -488,6 +489,7 @@ + diff --git a/pcsx2/pcsx2core.vcxproj.filters b/pcsx2/pcsx2core.vcxproj.filters index c93397b16a..dd359a2b63 100644 --- a/pcsx2/pcsx2core.vcxproj.filters +++ b/pcsx2/pcsx2core.vcxproj.filters @@ -1202,6 +1202,7 @@ System\Ps2\EmotionEngine\EE\Dynarec + @@ -1985,6 +1986,7 @@ System\Ps2\EmotionEngine\EE\Dynarec +