CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/GPU/Debugger/Playback.cpp
Views: 1401
// Copyright (c) 2017- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <algorithm>18#include <cstring>19#include <functional>20#include <mutex>21#include <vector>22#include <snappy-c.h>23#include <zstd.h>24#include "Common/Profiler/Profiler.h"25#include "Common/CommonTypes.h"26#include "Common/Log.h"27#include "Core/Config.h"28#include "Core/Core.h"29#include "Core/CoreTiming.h"30#include "Core/Debugger/MemBlockInfo.h"31#include "Core/ELF/ParamSFO.h"32#include "Core/FileSystems/MetaFileSystem.h"33#include "Core/HLE/sceDisplay.h"34#include "Core/HLE/sceKernelMemory.h"35#include "Core/MemMap.h"36#include "Core/MIPS/MIPS.h"37#include "Core/System.h"38#include "GPU/GPUInterface.h"39#include "GPU/GPUState.h"40#include "GPU/ge_constants.h"41#include "GPU/Debugger/Playback.h"42#include "GPU/Debugger/Record.h"43#include "GPU/Debugger/RecordFormat.h"4445namespace GPURecord {4647static std::string lastExecFilename;48static uint32_t lastExecVersion;49static std::vector<Command> lastExecCommands;50static std::vector<u8> lastExecPushbuf;51static std::mutex executeLock;5253// This class maps pushbuffer (dump data) sections to PSP memory.54// Dumps can be larger than available PSP memory, because they include generated data too.55//56// If possible, it maps to dynamically allocated "slabs" so nearby access is fast.57// Otherwise it uses "extra" allocations to manage sections that straddle two slabs.58// Slabs are managed with LRU, extra buffers are round-robin.59class BufMapping {60public:61BufMapping(const std::vector<u8> &pushbuf) : pushbuf_(pushbuf) {62}6364// Returns a pointer to contiguous memory for this access, or else 0 (failure).65u32 Map(u32 bufpos, u32 sz, const std::function<void()> &flush);6667// Clear and reset allocations made.68void Reset() {69slabGeneration_ = 0;70extraOffset_ = 0;71for (int i = 0; i < SLAB_COUNT; ++i) {72slabs_[i].Free();73}74for (int i = 0; i < EXTRA_COUNT; ++i) {75extra_[i].Free();76}77}7879protected:80u32 MapSlab(u32 bufpos, const std::function<void()> &flush);81u32 MapExtra(u32 bufpos, u32 sz, const std::function<void()> &flush);8283enum {84// These numbers kept low because we only have 24 MB of user memory to map into.85SLAB_SIZE = 1 * 1024 * 1024,86// 10 is the number of texture units + verts + inds.87// In the worst case, we could concurrently need 10 slabs/extras at the same time.88SLAB_COUNT = 10,89EXTRA_COUNT = 10,90};9192// The current "generation". Static simply as a convenience for access.93// This increments on every allocation, for a simple LRU.94static int slabGeneration_;9596// An aligned large mapping of the pushbuffer in PSP RAM.97struct SlabInfo {98u32 psp_pointer_ = 0;99u32 buf_pointer_ = 0;100int last_used_ = 0;101102bool Matches(u32 bufpos) {103// We check psp_pointer_ because bufpos = 0 is valid, and the initial value.104return buf_pointer_ == bufpos && psp_pointer_ != 0;105}106107// Automatically marks used for LRU purposes.108u32 Ptr(u32 bufpos) {109last_used_ = slabGeneration_;110return psp_pointer_ + (bufpos - buf_pointer_);111}112113int Age() const {114// If not allocated, it's as expired as it's gonna get.115if (psp_pointer_ == 0)116return std::numeric_limits<int>::max();117return slabGeneration_ - last_used_;118}119120bool Alloc();121void Free();122bool Setup(u32 bufpos, const std::vector<u8> &pushbuf_);123};124125// An adhoc mapping of the pushbuffer (either larger than a slab or straddling slabs.)126// Remember: texture data, verts, etc. must be contiguous.127struct ExtraInfo {128u32 psp_pointer_ = 0;129u32 buf_pointer_ = 0;130u32 size_ = 0;131132bool Matches(u32 bufpos, u32 sz) {133// We check psp_pointer_ because bufpos = 0 is valid, and the initial value.134return buf_pointer_ == bufpos && psp_pointer_ != 0 && size_ >= sz;135}136137u32 Ptr() {138return psp_pointer_;139}140141bool Alloc(u32 bufpos, u32 sz, const std::vector<u8> &pushbuf_);142void Free();143};144145SlabInfo slabs_[SLAB_COUNT]{};146u32 lastSlab_ = 0;147u32 extraOffset_ = 0;148ExtraInfo extra_[EXTRA_COUNT]{};149150const std::vector<u8> &pushbuf_;151};152153u32 BufMapping::Map(u32 bufpos, u32 sz, const std::function<void()> &flush) {154int slab1 = bufpos / SLAB_SIZE;155int slab2 = (bufpos + sz - 1) / SLAB_SIZE;156157if (slab1 == slab2) {158// Shortcut in case it's simply the most recent slab.159if (slabs_[lastSlab_].Matches(slab1 * SLAB_SIZE))160return slabs_[lastSlab_].Ptr(bufpos);161// Doesn't straddle, so we can just map to a slab.162return MapSlab(bufpos, flush);163} else {164// We need contiguous, so we'll just allocate separately.165return MapExtra(bufpos, sz, flush);166}167}168169u32 BufMapping::MapSlab(u32 bufpos, const std::function<void()> &flush) {170u32 slab_pos = (bufpos / SLAB_SIZE) * SLAB_SIZE;171172int best = 0;173for (int i = 0; i < SLAB_COUNT; ++i) {174if (slabs_[i].Matches(slab_pos)) {175return slabs_[i].Ptr(bufpos);176}177178if (slabs_[i].Age() > slabs_[best].Age()) {179best = i;180}181}182183// Stall before mapping a new slab.184flush();185186// Okay, we need to allocate.187if (!slabs_[best].Setup(slab_pos, pushbuf_)) {188return 0;189}190lastSlab_ = best;191return slabs_[best].Ptr(bufpos);192}193194u32 BufMapping::MapExtra(u32 bufpos, u32 sz, const std::function<void()> &flush) {195for (int i = 0; i < EXTRA_COUNT; ++i) {196// Might be likely to reuse larger buffers straddling slabs.197if (extra_[i].Matches(bufpos, sz)) {198return extra_[i].Ptr();199}200}201202// Stall first, so we don't stomp existing RAM.203flush();204205int i = extraOffset_;206extraOffset_ = (extraOffset_ + 1) % EXTRA_COUNT;207208if (!extra_[i].Alloc(bufpos, sz, pushbuf_)) {209// Let's try to power on - hopefully none of these are still in use.210for (int i = 0; i < EXTRA_COUNT; ++i) {211extra_[i].Free();212}213if (!extra_[i].Alloc(bufpos, sz, pushbuf_)) {214return 0;215}216}217return extra_[i].Ptr();218}219220bool BufMapping::SlabInfo::Alloc() {221u32 sz = SLAB_SIZE;222psp_pointer_ = userMemory.Alloc(sz, false, "Slab");223if (psp_pointer_ == -1) {224psp_pointer_ = 0;225}226return psp_pointer_ != 0;227}228229void BufMapping::SlabInfo::Free() {230if (psp_pointer_) {231userMemory.Free(psp_pointer_);232psp_pointer_ = 0;233buf_pointer_ = 0;234last_used_ = 0;235}236}237238bool BufMapping::ExtraInfo::Alloc(u32 bufpos, u32 sz, const std::vector<u8> &pushbuf_) {239// Make sure we've freed any previous allocation first.240Free();241242u32 allocSize = sz;243psp_pointer_ = userMemory.Alloc(allocSize, false, "Straddle extra");244if (psp_pointer_ == -1) {245psp_pointer_ = 0;246}247if (psp_pointer_ == 0) {248return false;249}250251buf_pointer_ = bufpos;252size_ = sz;253Memory::MemcpyUnchecked(psp_pointer_, pushbuf_.data() + bufpos, sz);254return true;255}256257void BufMapping::ExtraInfo::Free() {258if (psp_pointer_) {259userMemory.Free(psp_pointer_);260psp_pointer_ = 0;261buf_pointer_ = 0;262}263}264265bool BufMapping::SlabInfo::Setup(u32 bufpos, const std::vector<u8> &pushbuf_) {266// If it already has RAM, we're simply taking it over. Slabs come only in one size.267if (psp_pointer_ == 0) {268if (!Alloc()) {269return false;270}271}272273buf_pointer_ = bufpos;274u32 sz = std::min((u32)SLAB_SIZE, (u32)pushbuf_.size() - bufpos);275Memory::MemcpyUnchecked(psp_pointer_, pushbuf_.data() + bufpos, sz);276277slabGeneration_++;278last_used_ = slabGeneration_;279return true;280}281282int BufMapping::slabGeneration_ = 0;283284class DumpExecute {285public:286DumpExecute(const std::vector<u8> &pushbuf, const std::vector<Command> &commands, uint32_t version)287: pushbuf_(pushbuf), commands_(commands), mapping_(pushbuf), version_(version) {288}289~DumpExecute();290291bool Run();292293private:294void SyncStall();295bool SubmitCmds(const void *p, u32 sz);296void SubmitListEnd();297298void Init(u32 ptr, u32 sz);299void Registers(u32 ptr, u32 sz);300void Vertices(u32 ptr, u32 sz);301void Indices(u32 ptr, u32 sz);302void ClutAddr(u32 ptr, u32 sz);303void Clut(u32 ptr, u32 sz);304void TransferSrc(u32 ptr, u32 sz);305void Memset(u32 ptr, u32 sz);306void MemcpyDest(u32 ptr, u32 sz);307void Memcpy(u32 ptr, u32 sz);308void Texture(int level, u32 ptr, u32 sz);309void Framebuf(int level, u32 ptr, u32 sz);310void Display(u32 ptr, u32 sz, bool allowFlip);311void EdramTrans(u32 ptr, u32 sz);312313u32 execMemcpyDest = 0;314u32 execClutAddr = 0;315u32 execClutFlags = 0;316u32 execListBuf = 0;317u32 execListPos = 0;318u32 execListID = 0;319const int LIST_BUF_SIZE = 256 * 1024;320std::vector<u32> execListQueue;321u16 lastBufw_[8]{};322u32 lastTex_[8]{};323u32 lastBase_ = 0;324325const std::vector<u8> &pushbuf_;326const std::vector<Command> &commands_;327BufMapping mapping_;328uint32_t version_ = 0;329};330331void DumpExecute::SyncStall() {332if (execListBuf == 0) {333return;334}335336gpu->UpdateStall(execListID, execListPos);337s64 listTicks = gpu->GetListTicks(execListID);338if (listTicks != -1) {339s64 nowTicks = CoreTiming::GetTicks();340if (listTicks > nowTicks) {341currentMIPS->downcount -= listTicks - nowTicks;342}343}344345// Make sure downcount doesn't overflow.346CoreTiming::ForceCheck();347}348349bool DumpExecute::SubmitCmds(const void *p, u32 sz) {350if (execListBuf == 0) {351u32 allocSize = LIST_BUF_SIZE;352execListBuf = userMemory.Alloc(allocSize, true, "List buf");353if (execListBuf == -1) {354execListBuf = 0;355}356if (execListBuf == 0) {357ERROR_LOG(Log::System, "Unable to allocate for display list");358return false;359}360361execListPos = execListBuf;362Memory::Write_U32(GE_CMD_NOP << 24, execListPos);363execListPos += 4;364365gpu->EnableInterrupts(false);366auto optParam = PSPPointer<PspGeListArgs>::Create(0);367execListID = gpu->EnqueueList(execListBuf, execListPos, -1, optParam, false);368gpu->EnableInterrupts(true);369}370371u32 pendingSize = (u32)execListQueue.size() * sizeof(u32);372// Validate space for jump.373u32 allocSize = pendingSize + sz + 8;374if (execListPos + allocSize >= execListBuf + LIST_BUF_SIZE) {375Memory::Write_U32((GE_CMD_BASE << 24) | ((execListBuf >> 8) & 0x00FF0000), execListPos);376Memory::Write_U32((GE_CMD_JUMP << 24) | (execListBuf & 0x00FFFFFF), execListPos + 4);377378execListPos = execListBuf;379lastBase_ = execListBuf & 0xFF000000;380381// Don't continue until we've stalled.382SyncStall();383}384385Memory::MemcpyUnchecked(execListPos, execListQueue.data(), pendingSize);386execListPos += pendingSize;387u32 writePos = execListPos;388Memory::MemcpyUnchecked(execListPos, p, sz);389execListPos += sz;390391// TODO: Unfortunate. Maybe Texture commands should contain the bufw instead.392// The goal here is to realistically combine prims in dumps. Stalling for the bufw flushes.393u32_le *ops = (u32_le *)Memory::GetPointerUnchecked(writePos);394395u32 lastTexHigh[8]{};396for (int i = 0; i < 8; ++i)397lastTexHigh[i] = ((lastTex_[i] & 0xFF000000) >> 8) | ((GE_CMD_TEXBUFWIDTH0 + i) << 24);398399for (u32 i = 0; i < sz / 4; ++i) {400u32 cmd = ops[i] >> 24;401if (cmd >= GE_CMD_TEXBUFWIDTH0 && cmd <= GE_CMD_TEXBUFWIDTH7) {402int level = cmd - GE_CMD_TEXBUFWIDTH0;403u16 bufw = ops[i] & 0xFFFF;404405// NOP the address part of the command to avoid a flush too.406if (bufw == lastBufw_[level])407ops[i] = GE_CMD_NOP << 24;408else409ops[i] = lastTexHigh[level] | bufw;410lastBufw_[level] = bufw;411}412413// Since we're here anyway, also NOP out texture addresses.414// This makes Step Tex not hit phantom textures, but we rely on it for lastTex_[].415if (cmd >= GE_CMD_TEXADDR0 && cmd <= GE_CMD_TEXADDR7) {416ops[i] = GE_CMD_NOP << 24;417}418if (cmd == GE_CMD_SIGNAL || cmd == GE_CMD_BASE) {419lastBase_ = 0xFFFFFFFF;420}421}422423execListQueue.clear();424425return true;426}427428void DumpExecute::SubmitListEnd() {429if (execListPos == 0) {430return;431}432433// There's always space for the end, same size as a jump.434Memory::Write_U32(GE_CMD_FINISH << 24, execListPos);435Memory::Write_U32(GE_CMD_END << 24, execListPos + 4);436execListPos += 8;437438for (int i = 0; i < 8; ++i)439lastTex_[i] = 0;440lastBase_ = 0xFFFFFFFF;441442SyncStall();443gpu->ListSync(execListID, 0);444}445446void DumpExecute::Init(u32 ptr, u32 sz) {447gstate.Restore((u32_le *)(pushbuf_.data() + ptr));448gpu->ReapplyGfxState();449450for (int i = 0; i < 8; ++i) {451lastBufw_[i] = 0;452lastTex_[i] = 0;453}454lastBase_ = 0xFFFFFFFF;455}456457void DumpExecute::Registers(u32 ptr, u32 sz) {458SubmitCmds(pushbuf_.data() + ptr, sz);459}460461void DumpExecute::Vertices(u32 ptr, u32 sz) {462u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this));463if (psp == 0) {464ERROR_LOG(Log::System, "Unable to allocate for vertices");465return;466}467468if (lastBase_ != (psp & 0xFF000000)) {469execListQueue.push_back((GE_CMD_BASE << 24) | ((psp >> 8) & 0x00FF0000));470lastBase_ = psp & 0xFF000000;471}472execListQueue.push_back((GE_CMD_VADDR << 24) | (psp & 0x00FFFFFF));473}474475void DumpExecute::Indices(u32 ptr, u32 sz) {476u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this));477if (psp == 0) {478ERROR_LOG(Log::System, "Unable to allocate for indices");479return;480}481482if (lastBase_ != (psp & 0xFF000000)) {483execListQueue.push_back((GE_CMD_BASE << 24) | ((psp >> 8) & 0x00FF0000));484lastBase_ = psp & 0xFF000000;485}486execListQueue.push_back((GE_CMD_IADDR << 24) | (psp & 0x00FFFFFF));487}488489void DumpExecute::ClutAddr(u32 ptr, u32 sz) {490struct ClutAddrData {491u32 addr;492u32 flags;493};494const ClutAddrData *data = (const ClutAddrData *)(pushbuf_.data() + ptr);495execClutAddr = data->addr;496execClutFlags = data->flags;497}498499void DumpExecute::Clut(u32 ptr, u32 sz) {500// This is always run when we have the actual address set.501if (execClutAddr != 0) {502const bool isTarget = (execClutFlags & 1) != 0;503504// Could potentially always skip if !isTarget, but playing it safe for offset texture behavior.505if (Memory::IsValidRange(execClutAddr, sz) && (!isTarget || !g_Config.bSoftwareRendering)) {506// Intentionally don't trigger an upload here.507Memory::MemcpyUnchecked(execClutAddr, pushbuf_.data() + ptr, sz);508NotifyMemInfo(MemBlockFlags::WRITE, execClutAddr, sz, "ReplayClut");509}510511execClutAddr = 0;512} else {513u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this));514if (psp == 0) {515ERROR_LOG(Log::System, "Unable to allocate for clut");516return;517}518519execListQueue.push_back((GE_CMD_CLUTADDRUPPER << 24) | ((psp >> 8) & 0x00FF0000));520execListQueue.push_back((GE_CMD_CLUTADDR << 24) | (psp & 0x00FFFFFF));521}522}523524void DumpExecute::TransferSrc(u32 ptr, u32 sz) {525u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this));526if (psp == 0) {527ERROR_LOG(Log::System, "Unable to allocate for transfer");528return;529}530531// Need to sync in order to access gstate.transfersrcw.532SyncStall();533534execListQueue.push_back((gstate.transfersrcw & 0xFF00FFFF) | ((psp >> 8) & 0x00FF0000));535execListQueue.push_back(((GE_CMD_TRANSFERSRC) << 24) | (psp & 0x00FFFFFF));536}537538void DumpExecute::Memset(u32 ptr, u32 sz) {539PROFILE_THIS_SCOPE("ReplayMemset");540struct MemsetCommand {541u32 dest;542int value;543u32 sz;544};545546const MemsetCommand *data = (const MemsetCommand *)(pushbuf_.data() + ptr);547548if (Memory::IsVRAMAddress(data->dest)) {549SyncStall();550gpu->PerformMemorySet(data->dest, (u8)data->value, data->sz);551}552}553554void DumpExecute::MemcpyDest(u32 ptr, u32 sz) {555execMemcpyDest = *(const u32 *)(pushbuf_.data() + ptr);556}557558void DumpExecute::Memcpy(u32 ptr, u32 sz) {559PROFILE_THIS_SCOPE("ReplayMemcpy");560if (Memory::IsVRAMAddress(execMemcpyDest)) {561SyncStall();562Memory::MemcpyUnchecked(execMemcpyDest, pushbuf_.data() + ptr, sz);563NotifyMemInfo(MemBlockFlags::WRITE, execMemcpyDest, sz, "ReplayMemcpy");564gpu->PerformWriteColorFromMemory(execMemcpyDest, sz);565}566}567568void DumpExecute::Texture(int level, u32 ptr, u32 sz) {569u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this));570if (psp == 0) {571ERROR_LOG(Log::System, "Unable to allocate for texture");572return;573}574575if (lastTex_[level] != psp) {576u32 bufwCmd = GE_CMD_TEXBUFWIDTH0 + level;577u32 addrCmd = GE_CMD_TEXADDR0 + level;578execListQueue.push_back((bufwCmd << 24) | ((psp >> 8) & 0x00FF0000) | lastBufw_[level]);579execListQueue.push_back((addrCmd << 24) | (psp & 0x00FFFFFF));580lastTex_[level] = psp;581}582}583584void DumpExecute::Framebuf(int level, u32 ptr, u32 sz) {585PROFILE_THIS_SCOPE("ReplayFramebuf");586struct FramebufData {587u32 addr;588int bufw;589u32 flags;590u32 pad;591};592593FramebufData *framebuf = (FramebufData *)(pushbuf_.data() + ptr);594595if (lastTex_[level] != framebuf->addr || lastBufw_[level] != framebuf->bufw) {596u32 bufwCmd = GE_CMD_TEXBUFWIDTH0 + level;597u32 addrCmd = GE_CMD_TEXADDR0 + level;598execListQueue.push_back((bufwCmd << 24) | ((framebuf->addr >> 8) & 0x00FF0000) | framebuf->bufw);599execListQueue.push_back((addrCmd << 24) | (framebuf->addr & 0x00FFFFFF));600lastTex_[level] = framebuf->addr;601lastBufw_[level] = framebuf->bufw;602}603604// And now also copy the data into VRAM (in case it wasn't actually rendered.)605u32 headerSize = (u32)sizeof(FramebufData);606u32 pspSize = sz - headerSize;607const bool isTarget = (framebuf->flags & 1) != 0;608const bool unchangedVRAM = version_ >= 6 && (framebuf->flags & 2) != 0;609// TODO: Could use drawnVRAM flag, but it can be wrong.610// Could potentially always skip if !isTarget, but playing it safe for offset texture behavior.611if (Memory::IsValidRange(framebuf->addr, pspSize) && !unchangedVRAM && (!isTarget || !g_Config.bSoftwareRendering)) {612// Intentionally don't trigger an upload here.613Memory::MemcpyUnchecked(framebuf->addr, pushbuf_.data() + ptr + headerSize, pspSize);614NotifyMemInfo(MemBlockFlags::WRITE, framebuf->addr, pspSize, "ReplayTex");615}616}617618void DumpExecute::Display(u32 ptr, u32 sz, bool allowFlip) {619struct DisplayBufData {620PSPPointer<u8> topaddr;621int linesize, pixelFormat;622};623624DisplayBufData *disp = (DisplayBufData *)(pushbuf_.data() + ptr);625626// Sync up drawing.627SyncStall();628629__DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 1);630if (allowFlip) {631__DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 0);632}633}634635void DumpExecute::EdramTrans(u32 ptr, u32 sz) {636uint32_t value;637memcpy(&value, pushbuf_.data() + ptr, 4);638639// Sync up drawing.640SyncStall();641642if (gpu)643gpu->SetAddrTranslation(value);644}645646DumpExecute::~DumpExecute() {647execMemcpyDest = 0;648if (execListBuf) {649userMemory.Free(execListBuf);650execListBuf = 0;651}652execListPos = 0;653mapping_.Reset();654}655656bool DumpExecute::Run() {657// Start with the default value.658if (gpu)659gpu->SetAddrTranslation(0x400);660661for (size_t i = 0; i < commands_.size(); i++) {662const Command &cmd = commands_[i];663switch (cmd.type) {664case CommandType::INIT:665Init(cmd.ptr, cmd.sz);666break;667668case CommandType::REGISTERS:669Registers(cmd.ptr, cmd.sz);670break;671672case CommandType::VERTICES:673Vertices(cmd.ptr, cmd.sz);674break;675676case CommandType::INDICES:677Indices(cmd.ptr, cmd.sz);678break;679680case CommandType::CLUTADDR:681ClutAddr(cmd.ptr, cmd.sz);682break;683684case CommandType::CLUT:685Clut(cmd.ptr, cmd.sz);686break;687688case CommandType::TRANSFERSRC:689TransferSrc(cmd.ptr, cmd.sz);690break;691692case CommandType::MEMSET:693Memset(cmd.ptr, cmd.sz);694break;695696case CommandType::MEMCPYDEST:697MemcpyDest(cmd.ptr, cmd.sz);698break;699700case CommandType::MEMCPYDATA:701Memcpy(cmd.ptr, cmd.sz);702break;703704case CommandType::EDRAMTRANS:705EdramTrans(cmd.ptr, cmd.sz);706break;707708case CommandType::TEXTURE0:709case CommandType::TEXTURE1:710case CommandType::TEXTURE2:711case CommandType::TEXTURE3:712case CommandType::TEXTURE4:713case CommandType::TEXTURE5:714case CommandType::TEXTURE6:715case CommandType::TEXTURE7:716Texture((int)cmd.type - (int)CommandType::TEXTURE0, cmd.ptr, cmd.sz);717break;718719case CommandType::FRAMEBUF0:720case CommandType::FRAMEBUF1:721case CommandType::FRAMEBUF2:722case CommandType::FRAMEBUF3:723case CommandType::FRAMEBUF4:724case CommandType::FRAMEBUF5:725case CommandType::FRAMEBUF6:726case CommandType::FRAMEBUF7:727Framebuf((int)cmd.type - (int)CommandType::FRAMEBUF0, cmd.ptr, cmd.sz);728break;729730case CommandType::DISPLAY:731Display(cmd.ptr, cmd.sz, i == commands_.size() - 1);732break;733734default:735ERROR_LOG(Log::System, "Unsupported GE dump command: %d", (int)cmd.type);736return false;737}738}739740SubmitListEnd();741return true;742}743744static bool ReadCompressed(u32 fp, void *dest, size_t sz, uint32_t version) {745u32 compressed_size = 0;746if (pspFileSystem.ReadFile(fp, (u8 *)&compressed_size, sizeof(compressed_size)) != sizeof(compressed_size)) {747return false;748}749750u8 *compressed = new u8[compressed_size];751if (pspFileSystem.ReadFile(fp, compressed, compressed_size) != compressed_size) {752delete[] compressed;753return false;754}755756size_t real_size = sz;757if (version < 5)758snappy_uncompress((const char *)compressed, compressed_size, (char *)dest, &real_size);759else760real_size = ZSTD_decompress(dest, real_size, compressed, compressed_size);761delete[] compressed;762763return real_size == sz;764}765766static void ReplayStop() {767// This can happen from a separate thread.768std::lock_guard<std::mutex> guard(executeLock);769lastExecFilename.clear();770lastExecCommands.clear();771lastExecPushbuf.clear();772lastExecVersion = 0;773}774775bool RunMountedReplay(const std::string &filename) {776_assert_msg_(!GPURecord::IsActivePending(), "Cannot run replay while recording.");777778std::lock_guard<std::mutex> guard(executeLock);779Core_ListenStopRequest(&ReplayStop);780781uint32_t version = lastExecVersion;782if (lastExecFilename != filename) {783PROFILE_THIS_SCOPE("ReplayLoad");784u32 fp = pspFileSystem.OpenFile(filename, FILEACCESS_READ);785Header header;786pspFileSystem.ReadFile(fp, (u8 *)&header, sizeof(header));787version = header.version;788789if (memcmp(header.magic, HEADER_MAGIC, sizeof(header.magic)) != 0 || header.version > VERSION || header.version < MIN_VERSION) {790ERROR_LOG(Log::System, "Invalid GE dump or unsupported version");791pspFileSystem.CloseFile(fp);792return false;793}794if (header.version <= 3) {795pspFileSystem.SeekFile(fp, 12, FILEMOVE_BEGIN);796memset(header.gameID, 0, sizeof(header.gameID));797}798799size_t gameIDLength = strnlen(header.gameID, sizeof(header.gameID));800if (gameIDLength != 0) {801g_paramSFO.SetValue("DISC_ID", std::string(header.gameID, gameIDLength), (int)sizeof(header.gameID));802}803804u32 sz = 0;805pspFileSystem.ReadFile(fp, (u8 *)&sz, sizeof(sz));806u32 bufsz = 0;807pspFileSystem.ReadFile(fp, (u8 *)&bufsz, sizeof(bufsz));808809lastExecCommands.resize(sz);810lastExecPushbuf.resize(bufsz);811812bool truncated = false;813truncated = truncated || !ReadCompressed(fp, lastExecCommands.data(), sizeof(Command) * sz, header.version);814truncated = truncated || !ReadCompressed(fp, lastExecPushbuf.data(), bufsz, header.version);815816pspFileSystem.CloseFile(fp);817818if (truncated) {819ERROR_LOG(Log::System, "Truncated GE dump");820return false;821}822823lastExecFilename = filename;824lastExecVersion = version;825}826827DumpExecute executor(lastExecPushbuf, lastExecCommands, version);828return executor.Run();829}830831};832833834