VMManager: Support playing back GS dumps

This commit is contained in:
Connor McLaughlin 2022-03-12 23:20:23 +10:00 committed by refractionpcsx2
parent 4d85d916b7
commit 4331ae1925
13 changed files with 482 additions and 15 deletions

View File

@ -28,6 +28,7 @@
#include "pcsx2/Frontend/InputManager.h" #include "pcsx2/Frontend/InputManager.h"
#include "pcsx2/Frontend/ImGuiManager.h" #include "pcsx2/Frontend/ImGuiManager.h"
#include "pcsx2/GS.h" #include "pcsx2/GS.h"
#include "pcsx2/GSDumpReplayer.h"
#include "pcsx2/HostDisplay.h" #include "pcsx2/HostDisplay.h"
#include "pcsx2/PAD/Host/PAD.h" #include "pcsx2/PAD/Host/PAD.h"
#include "pcsx2/PerformanceMetrics.h" #include "pcsx2/PerformanceMetrics.h"
@ -677,6 +678,9 @@ bool Host::BeginPresentFrame(bool frame_skip)
void Host::EndPresentFrame() void Host::EndPresentFrame()
{ {
if (GSDumpReplayer::IsReplayingDump())
GSDumpReplayer::RenderUI();
ImGuiManager::RenderOSD(); ImGuiManager::RenderOSD();
s_host_display->EndPresent(); s_host_display->EndPresent();
ImGuiManager::NewFrame(); ImGuiManager::NewFrame();

View File

@ -44,9 +44,15 @@
#include "svnrev.h" #include "svnrev.h"
static constexpr char DISC_IMAGE_FILTER[] = 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 " QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.elf *.irx *.m3u *.gs *.gs.xz);;"
"*.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;CSO Images (*.cso);;" "Single-Track Raw Images (*.bin *.iso);;"
"ELF Executables (*.elf);;IRX Executables (*.irx);;Playlists (*.m3u)"); "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"; const char* MainWindow::DEFAULT_THEME_NAME = "darkfusion";

View File

@ -983,6 +983,7 @@ if(PCSX2_CORE)
Frontend/InputManager.cpp Frontend/InputManager.cpp
Frontend/InputSource.cpp Frontend/InputSource.cpp
Frontend/LayeredSettingsInterface.cpp Frontend/LayeredSettingsInterface.cpp
GSDumpReplayer.cpp
HostSettings.cpp HostSettings.cpp
VMManager.cpp VMManager.cpp
) )
@ -992,6 +993,7 @@ if(PCSX2_CORE)
Frontend/InputManager.h Frontend/InputManager.h
Frontend/InputSource.h Frontend/InputSource.h
Frontend/LayeredSettingsInterface.h Frontend/LayeredSettingsInterface.h
GSDumpReplayer.h
HostSettings.h HostSettings.h
VMManager.h) VMManager.h)
endif() endif()

View File

@ -630,3 +630,17 @@ void ImGuiManager::RenderOSD()
DrawOSDMessages(); DrawOSDMessages();
} }
float ImGuiManager::GetGlobalScale()
{
return s_global_scale;
}
ImFont* ImGuiManager::GetStandardFont()
{
return s_standard_font;
}
ImFont* ImGuiManager::GetFixedFont()
{
return s_fixed_font;
}

View File

@ -15,6 +15,8 @@
#pragma once #pragma once
struct ImFont;
namespace ImGuiManager namespace ImGuiManager
{ {
/// Initializes ImGui, creates fonts, etc. /// Initializes ImGui, creates fonts, etc.
@ -34,5 +36,14 @@ namespace ImGuiManager
/// Renders any on-screen display elements. /// Renders any on-screen display elements.
void RenderOSD(); 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 } // namespace ImGuiManager

342
pcsx2/GSDumpReplayer.cpp Normal file
View File

@ -0,0 +1,342 @@
#include "PrecompiledHeader.h"
#include <atomic>
#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<GSDumpFile> 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<bool>(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<u32>(s_dump_file->GetRegsData().size())));
// load GS state
freezeData fd = {static_cast<int>(s_dump_file->GetStateData().size()),
const_cast<u8*>(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<u8*>(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<u32>(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<u32>(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<u8[]> 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<GIF_PATH>(static_cast<u8>(packet.path) - 1),
reinterpret_cast<const u8*>(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<char[]> 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<s32>(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<float>::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<u32>(s_dump_file->GetPackets().size()));
DRAW_LINE(font, text.c_str(), IM_COL32(255, 255, 255, 255));
#undef DRAW_LINE
}

15
pcsx2/GSDumpReplayer.h Normal file
View File

@ -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();
}

View File

@ -49,6 +49,10 @@ BIOS
#include "common/PageFaultSource.h" #include "common/PageFaultSource.h"
#ifdef PCSX2_CORE
#include "GSDumpReplayer.h"
#endif
#ifdef ENABLECACHE #ifdef ENABLECACHE
#include "Cache.h" #include "Cache.h"
#endif #endif
@ -850,7 +854,13 @@ void eeMemoryReserve::Reset()
vtlb_VMap(0x00000000,0x00000000,0x20000000); vtlb_VMap(0x00000000,0x00000000,0x20000000);
vtlb_VMapUnmap(0x20000000,0x60000000); 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"); pxFailRel("Failed to load BIOS");
} }

View File

@ -27,6 +27,11 @@
#include "common/MemsetFast.inl" #include "common/MemsetFast.inl"
#include "common/Perf.h" #include "common/Perf.h"
#ifdef PCSX2_CORE
#include "GSDumpReplayer.h"
extern R5900cpu GSDumpReplayerCpu;
#endif
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// RecompiledCodeReserve (implementations) // RecompiledCodeReserve (implementations)
@ -576,6 +581,11 @@ void SysCpuProviderPack::ApplyConfig() const
if( EmuConfig.Cpu.Recompiler.EnableVU1 ) if( EmuConfig.Cpu.Recompiler.EnableVU1 )
CpuVU1 = (BaseVUmicroCPU*)CpuProviders->microVU1; 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. // Resets all PS2 cpu execution caches, which does not affect that actual PS2 state/condition.

View File

@ -35,6 +35,7 @@
#include "Elfheader.h" #include "Elfheader.h"
#include "FW.h" #include "FW.h"
#include "GS.h" #include "GS.h"
#include "GSDumpReplayer.h"
#include "HostDisplay.h" #include "HostDisplay.h"
#include "HostSettings.h" #include "HostSettings.h"
#include "IopBios.h" #include "IopBios.h"
@ -222,6 +223,10 @@ void VMManager::LoadSettings()
EmuConfig.GS.MaskUserHacks(); EmuConfig.GS.MaskUserHacks();
EmuConfig.GS.MaskUpscalingHacks(); 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()) if (HasValidVM())
ApplyGameFixes(); 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 // 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 // (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). // settings as if the game is already running (title, loadeding patches, etc).
bool ingame = (ElfCRC && (g_GameLoading || g_GameStarted)); u32 new_crc;
const u32 new_crc = ingame ? ElfCRC : 0; std::string new_serial;
const std::string crc_string(StringUtil::StdStringFromFormat("%08X", new_crc)); 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) if (!force && s_game_crc == new_crc && s_game_serial == new_serial)
return; return;
@ -488,7 +502,7 @@ void VMManager::UpdateRunningGame(bool force)
ApplySettings(); ApplySettings();
ForgetLoadedPatches(); ForgetLoadedPatches();
LoadPatches(crc_string, true, false); LoadPatches(StringUtil::StdStringFromFormat("%08X", new_crc), true, false);
GetMTGS().SendGameCRC(new_crc); GetMTGS().SendGameCRC(new_crc);
Host::OnGameChanged(s_disc_path, s_game_serial, s_game_name, s_game_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(); LoadSettings();
if (IsGSDumpFileName(boot_params.source))
{
CDVDsys_ChangeSource(CDVD_SourceType::NoDisc);
if (!GSDumpReplayer::Initialize(boot_params.source.c_str()))
return false;
}
else
{
ApplyBootParameters(boot_params); ApplyBootParameters(boot_params);
}
EmuConfig.LimiterMode = GetInitialLimiterMode(); EmuConfig.LimiterMode = GetInitialLimiterMode();
Console.WriteLn("Allocating memory map..."); Console.WriteLn("Allocating memory map...");
@ -661,17 +686,18 @@ bool VMManager::Initialize(const VMBootParameters& boot_params)
frameLimitReset(); frameLimitReset();
cpuReset(); cpuReset();
Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds());
s_state.store(VMState::Paused);
Host::OnVMStarted();
UpdateRunningGame(true); UpdateRunningGame(true);
SetEmuThreadAffinities(true); SetEmuThreadAffinities(true);
PerformanceMetrics::Clear(); 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? // 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())) if (!DoLoadState(boot_params.save_state.c_str()))
{ {
@ -692,12 +718,16 @@ void VMManager::Shutdown(bool allow_save_resume_state /* = true */)
vu1Thread.WaitVU(); vu1Thread.WaitVU();
GetMTGS().WaitGS(); GetMTGS().WaitGS();
if (allow_save_resume_state && ShouldSaveResumeState()) if (!GSDumpReplayer::IsReplayingDump() && allow_save_resume_state && ShouldSaveResumeState())
{ {
std::string resume_file_name(GetCurrentSaveStateFileName(-1)); std::string resume_file_name(GetCurrentSaveStateFileName(-1));
if (!resume_file_name.empty() && !DoSaveState(resume_file_name.c_str(), -1)) if (!resume_file_name.empty() && !DoSaveState(resume_file_name.c_str(), -1))
Console.Error("Failed to save resume state"); Console.Error("Failed to save resume state");
} }
else if (GSDumpReplayer::IsReplayingDump())
{
GSDumpReplayer::Shutdown();
}
{ {
std::unique_lock lock(s_info_mutex); std::unique_lock lock(s_info_mutex);
@ -790,6 +820,9 @@ std::string VMManager::GetCurrentSaveStateFileName(s32 slot)
bool VMManager::DoLoadState(const char* filename) bool VMManager::DoLoadState(const char* filename)
{ {
if (GSDumpReplayer::IsReplayingDump())
return false;
try try
{ {
Host::OnSaveStateLoading(filename); Host::OnSaveStateLoading(filename);
@ -808,6 +841,9 @@ bool VMManager::DoLoadState(const char* filename)
bool VMManager::DoSaveState(const char* filename, s32 slot_for_message) bool VMManager::DoSaveState(const char* filename, s32 slot_for_message)
{ {
if (GSDumpReplayer::IsReplayingDump())
return false;
try try
{ {
std::unique_ptr<ArchiveEntryList> elist = std::make_unique<ArchiveEntryList>(new VmStateBuffer(L"Zippable Savestate")); std::unique_ptr<ArchiveEntryList> elist = std::make_unique<ArchiveEntryList>(new VmStateBuffer(L"Zippable Savestate"));
@ -920,6 +956,11 @@ bool VMManager::IsElfFileName(const std::string& path)
return (StringUtil::Strcasecmp(&path[pos], ".elf") == 0); 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) void VMManager::SetBootParametersForPath(const std::string& path, VMBootParameters* params)
{ {
if (IsElfFileName(path)) if (IsElfFileName(path))
@ -927,6 +968,11 @@ void VMManager::SetBootParametersForPath(const std::string& path, VMBootParamete
params->elf_override = path; params->elf_override = path;
params->source_type = CDVD_SourceType::NoDisc; params->source_type = CDVD_SourceType::NoDisc;
} }
else if (IsGSDumpFileName(path))
{
params->source_type = CDVD_SourceType::NoDisc;
params->source = path;
}
else if (!path.empty()) else if (!path.empty())
{ {
params->source_type = CDVD_SourceType::Iso; params->source_type = CDVD_SourceType::Iso;

View File

@ -135,6 +135,9 @@ namespace VMManager
/// Returns true if the specified path is an ELF. /// Returns true if the specified path is an ELF.
bool IsElfFileName(const std::string& path); 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. /// 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); void SetBootParametersForPath(const std::string& path, VMBootParameters* params);

View File

@ -186,6 +186,7 @@
<ClCompile Include="GameDatabase.cpp" /> <ClCompile Include="GameDatabase.cpp" />
<ClCompile Include="Gif_Logger.cpp" /> <ClCompile Include="Gif_Logger.cpp" />
<ClCompile Include="Gif_Unit.cpp" /> <ClCompile Include="Gif_Unit.cpp" />
<ClCompile Include="GSDumpReplayer.cpp" />
<ClCompile Include="GS\Renderers\DX11\D3D.cpp" /> <ClCompile Include="GS\Renderers\DX11\D3D.cpp" />
<ClCompile Include="GS\Renderers\HW\GSTextureReplacementLoaders.cpp" /> <ClCompile Include="GS\Renderers\HW\GSTextureReplacementLoaders.cpp" />
<ClCompile Include="GS\Renderers\HW\GSTextureReplacements.cpp" /> <ClCompile Include="GS\Renderers\HW\GSTextureReplacements.cpp" />
@ -488,6 +489,7 @@
<ClInclude Include="Frontend\XInputSource.h" /> <ClInclude Include="Frontend\XInputSource.h" />
<ClInclude Include="GameDatabase.h" /> <ClInclude Include="GameDatabase.h" />
<ClInclude Include="Gif_Unit.h" /> <ClInclude Include="Gif_Unit.h" />
<ClInclude Include="GSDumpReplayer.h" />
<ClInclude Include="GS\Renderers\DX11\D3D.h" /> <ClInclude Include="GS\Renderers\DX11\D3D.h" />
<ClInclude Include="GS\Renderers\HW\GSTextureReplacements.h" /> <ClInclude Include="GS\Renderers\HW\GSTextureReplacements.h" />
<ClInclude Include="GS\Window\GSwxDialog.h" /> <ClInclude Include="GS\Window\GSwxDialog.h" />

View File

@ -1202,6 +1202,7 @@
<ClCompile Include="x86\iR5900Analysis.cpp"> <ClCompile Include="x86\iR5900Analysis.cpp">
<Filter>System\Ps2\EmotionEngine\EE\Dynarec</Filter> <Filter>System\Ps2\EmotionEngine\EE\Dynarec</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="GSDumpReplayer.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Patch.h"> <ClInclude Include="Patch.h">
@ -1985,6 +1986,7 @@
<ClInclude Include="x86\iR5900Analysis.h"> <ClInclude Include="x86\iR5900Analysis.h">
<Filter>System\Ps2\EmotionEngine\EE\Dynarec</Filter> <Filter>System\Ps2\EmotionEngine\EE\Dynarec</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="GSDumpReplayer.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="GS\GS.rc"> <ResourceCompile Include="GS\GS.rc">