From 7b8bc901571eacf06d23046bbed0ea8ff87834b3 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 19 Sep 2024 18:46:07 +0300 Subject: [PATCH 01/11] MiscFixes11 (#984) * fixed an issue in sceSaveDataSetSaveDataMemory , makes chariot CUSA00739 goes ingame * added branch name into sdl window title --- src/core/libraries/save_data/savedata.cpp | 7 ++++--- src/emulator.cpp | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 52349b35510..10a89f66943 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -730,12 +730,13 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, const size_t bufSize, const int64_t offset) { LOG_INFO(Lib_SaveData, "called"); const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "sdmemory/save_mem1.sav"; + std::to_string(userId) / game_serial / "sdmemory"; - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write); + Common::FS::IOFile file(mount_dir / "save_mem1.sav", Common::FS::FileAccessMode::Write); + if (!file.IsOpen()) + return -1; file.Seek(offset); file.WriteRaw(buf, bufSize); - return ORBIS_OK; } diff --git a/src/emulator.cpp b/src/emulator.cpp index cc9cbbd9864..2d3460a7402 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -141,8 +141,8 @@ void Emulator::Run(const std::filesystem::path& file) { if (Common::isRelease) { window_title = fmt::format("shadPS4 v{} | {}", Common::VERSION, game_title); } else { - window_title = - fmt::format("shadPS4 v{} {} | {}", Common::VERSION, Common::g_scm_desc, game_title); + window_title = fmt::format("shadPS4 v{} {} {} | {}", Common::VERSION, Common::g_scm_branch, + Common::g_scm_desc, game_title); } window = std::make_unique( Config::getScreenWidth(), Config::getScreenHeight(), controller, window_title); From c0f7de9fdee04f391f015b09bacd56610f8d4890 Mon Sep 17 00:00:00 2001 From: "Daniel R." <47796739+polybiusproxy@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:19:29 +0200 Subject: [PATCH 02/11] core: name avplayer threads --- src/core/libraries/avplayer/avplayer_source.cpp | 14 +++++++++++--- src/core/libraries/avplayer/avplayer_state.cpp | 3 +++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index 51c37df0427..fcae180e752 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -7,6 +7,8 @@ #include "common/alignment.h" #include "common/singleton.h" +#include "common/thread.h" + #include "core/file_sys/fs.h" #include "core/libraries/kernel/time_management.h" @@ -432,6 +434,8 @@ void AvPlayerSource::ReleaseAVFormatContext(AVFormatContext* context) { void AvPlayerSource::DemuxerThread(std::stop_token stop) { using namespace std::chrono; + Common::SetCurrentThreadName("shadPS4:AvDemuxer"); + if (!m_audio_stream_index.has_value() && !m_video_stream_index.has_value()) { LOG_WARNING(Lib_AvPlayer, "Could not start DEMUXER thread. No streams enabled."); return; @@ -499,7 +503,7 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) { } m_state.OnEOF(); - LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normally"); } AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) { @@ -598,6 +602,8 @@ Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { using namespace std::chrono; + Common::SetCurrentThreadName("shadPS4:AvVideoDecoder"); + LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started"); while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) { if (!m_video_packets_cv.Wait(stop, @@ -653,7 +659,7 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { } } - LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normally"); } AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) { @@ -718,6 +724,8 @@ Frame AvPlayerSource::PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { using namespace std::chrono; + Common::SetCurrentThreadName("shadPS4:AvAudioDecoder"); + LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started"); while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) { if (!m_audio_packets_cv.Wait(stop, @@ -773,7 +781,7 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { } } - LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normally"); } bool AvPlayerSource::HasRunningThreads() const { diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index a512063f2b8..654e04836fe 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -5,6 +5,8 @@ #include "avplayer_source.h" #include "avplayer_state.h" +#include "common/thread.h" + #include "core/libraries/error_codes.h" #include "core/libraries/kernel/time_management.h" @@ -178,6 +180,7 @@ bool AvPlayerState::Start() { void AvPlayerState::AvControllerThread(std::stop_token stop) { using std::chrono::milliseconds; + Common::SetCurrentThreadName("shadPS4:AvController"); while (!stop.stop_requested()) { if (m_event_queue.Size() != 0) { From 07de1ee97702d2dc4103d8262c956c0467d56a0b Mon Sep 17 00:00:00 2001 From: korenkonder Date: Thu, 19 Sep 2024 21:29:56 +0300 Subject: [PATCH 03/11] Sort opcodes by their indices. Group them too when applicable (#945) --- .../frontend/translate/data_share.cpp | 255 +-- .../frontend/translate/scalar_alu.cpp | 563 +++---- .../frontend/translate/scalar_memory.cpp | 3 + .../frontend/translate/translate.h | 248 +-- .../frontend/translate/vector_alu.cpp | 1367 +++++++++-------- .../translate/vector_interpolation.cpp | 29 +- .../frontend/translate/vector_memory.cpp | 578 +++---- 7 files changed, 1567 insertions(+), 1476 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index 0c3d21a9685..247ad65c2f0 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -7,135 +7,90 @@ namespace Shader::Gcn { void Translator::EmitDataShare(const GcnInst& inst) { switch (inst.opcode) { - case Opcode::DS_SWIZZLE_B32: - return DS_SWIZZLE_B32(inst); - case Opcode::DS_READ_B32: - return DS_READ(32, false, false, false, inst); - case Opcode::DS_READ2ST64_B32: - return DS_READ(32, false, true, true, inst); - case Opcode::DS_READ_B64: - return DS_READ(64, false, false, false, inst); - case Opcode::DS_READ2_B32: - return DS_READ(32, false, true, false, inst); - case Opcode::DS_READ2_B64: - return DS_READ(64, false, true, false, inst); - case Opcode::DS_WRITE_B32: - return DS_WRITE(32, false, false, false, inst); - case Opcode::DS_WRITE2ST64_B32: - return DS_WRITE(32, false, true, true, inst); - case Opcode::DS_WRITE_B64: - return DS_WRITE(64, false, false, false, inst); - case Opcode::DS_WRITE2_B32: - return DS_WRITE(32, false, true, false, inst); - case Opcode::DS_WRITE2_B64: - return DS_WRITE(64, false, true, false, inst); + // DS case Opcode::DS_ADD_U32: return DS_ADD_U32(inst, false); - case Opcode::DS_MIN_U32: - return DS_MIN_U32(inst, false, false); case Opcode::DS_MIN_I32: return DS_MIN_U32(inst, true, false); - case Opcode::DS_MAX_U32: - return DS_MAX_U32(inst, false, false); case Opcode::DS_MAX_I32: return DS_MAX_U32(inst, true, false); + case Opcode::DS_MIN_U32: + return DS_MIN_U32(inst, false, false); + case Opcode::DS_MAX_U32: + return DS_MAX_U32(inst, false, false); + case Opcode::DS_WRITE_B32: + return DS_WRITE(32, false, false, false, inst); + case Opcode::DS_WRITE2_B32: + return DS_WRITE(32, false, true, false, inst); + case Opcode::DS_WRITE2ST64_B32: + return DS_WRITE(32, false, true, true, inst); case Opcode::DS_ADD_RTN_U32: return DS_ADD_U32(inst, true); case Opcode::DS_MIN_RTN_U32: return DS_MIN_U32(inst, false, true); case Opcode::DS_MAX_RTN_U32: return DS_MAX_U32(inst, false, true); - case Opcode::DS_APPEND: - return DS_APPEND(inst); + case Opcode::DS_SWIZZLE_B32: + return DS_SWIZZLE_B32(inst); + case Opcode::DS_READ_B32: + return DS_READ(32, false, false, false, inst); + case Opcode::DS_READ2_B32: + return DS_READ(32, false, true, false, inst); + case Opcode::DS_READ2ST64_B32: + return DS_READ(32, false, true, true, inst); case Opcode::DS_CONSUME: return DS_CONSUME(inst); + case Opcode::DS_APPEND: + return DS_APPEND(inst); + case Opcode::DS_WRITE_B64: + return DS_WRITE(64, false, false, false, inst); + case Opcode::DS_WRITE2_B64: + return DS_WRITE(64, false, true, false, inst); + case Opcode::DS_READ_B64: + return DS_READ(64, false, false, false, inst); + case Opcode::DS_READ2_B64: + return DS_READ(64, false, true, false, inst); default: LogMissingOpcode(inst); } } -void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { - const u8 offset0 = inst.control.ds.offset0; - const u8 offset1 = inst.control.ds.offset1; - const IR::U32 src{GetSrc(inst.src[1])}; - ASSERT(offset1 & 0x80); - const IR::U32 lane_id = ir.LaneId(); - const IR::U32 id_in_group = ir.BitwiseAnd(lane_id, ir.Imm32(0b11)); - const IR::U32 base = ir.ShiftLeftLogical(id_in_group, ir.Imm32(1)); - const IR::U32 index = - ir.IAdd(lane_id, ir.BitFieldExtract(ir.Imm32(offset0), base, ir.Imm32(2))); - SetDst(inst.dst[0], ir.QuadShuffle(src, index)); +// SOPP + +void Translator::S_BARRIER() { + ir.Barrier(); } -void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, - const GcnInst& inst) { - const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; - IR::VectorReg dst_reg{inst.dst[0].code}; - if (is_pair) { - // Pair loads are either 32 or 64-bit - const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); - const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0); - if (bit_size == 32) { - ir.SetVectorReg(dst_reg++, IR::U32{data0}); - } else { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 0)}); - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 1)}); - } - const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); - const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1); - if (bit_size == 32) { - ir.SetVectorReg(dst_reg++, IR::U32{data1}); - } else { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 0)}); - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 1)}); - } - } else if (bit_size == 64) { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0); - ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)}); - ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(data, 1)}); +// VOP2 + +void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) { + const IR::ScalarReg dst{inst.dst[0].code}; + const IR::U32 value{GetSrc(inst.src[0])}; + + if (info.stage != Stage::Compute) { + SetDst(inst.dst[0], value); } else { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)}; - ir.SetVectorReg(dst_reg, data); + SetDst(inst.dst[0], ir.ReadFirstLane(value)); } } -void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, - const GcnInst& inst) { - const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; - const IR::VectorReg data0{inst.src[1].code}; - const IR::VectorReg data1{inst.src[2].code}; - if (is_pair) { - const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); - if (bit_size == 32) { - ir.WriteShared(32, ir.GetVectorReg(data0), addr0); - } else { - ir.WriteShared( - 64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)), - addr0); - } - const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); - if (bit_size == 32) { - ir.WriteShared(32, ir.GetVectorReg(data1), addr1); - } else { - ir.WriteShared( - 64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)), - addr1); - } - } else if (bit_size == 64) { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - const IR::Value data = - ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); - ir.WriteShared(bit_size, data, addr0); - } else { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); - } +void Translator::V_READLANE_B32(const GcnInst& inst) { + const IR::ScalarReg dst{inst.dst[0].code}; + const IR::U32 value{GetSrc(inst.src[0])}; + const IR::U32 lane{GetSrc(inst.src[1])}; + ir.SetScalarReg(dst, ir.ReadLane(value, lane)); +} + +void Translator::V_WRITELANE_B32(const GcnInst& inst) { + const IR::VectorReg dst{inst.dst[0].code}; + const IR::U32 value{GetSrc(inst.src[0])}; + const IR::U32 lane{GetSrc(inst.src[1])}; + const IR::U32 old_value{GetSrc(inst.dst[0])}; + ir.SetVectorReg(dst, ir.WriteLane(old_value, value, lane)); } +// DS + void Translator::DS_ADD_U32(const GcnInst& inst, bool rtn) { const IR::U32 addr{GetSrc(inst.src[0])}; const IR::U32 data{GetSrc(inst.src[1])}; @@ -169,34 +124,86 @@ void Translator::DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn) { } } -void Translator::S_BARRIER() { - ir.Barrier(); -} - -void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) { - const IR::ScalarReg dst{inst.dst[0].code}; - const IR::U32 value{GetSrc(inst.src[0])}; - - if (info.stage != Stage::Compute) { - SetDst(inst.dst[0], value); +void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, + const GcnInst& inst) { + const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; + const IR::VectorReg data0{inst.src[1].code}; + const IR::VectorReg data1{inst.src[2].code}; + if (is_pair) { + const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); + if (bit_size == 32) { + ir.WriteShared(32, ir.GetVectorReg(data0), addr0); + } else { + ir.WriteShared( + 64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)), + addr0); + } + const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); + if (bit_size == 32) { + ir.WriteShared(32, ir.GetVectorReg(data1), addr1); + } else { + ir.WriteShared( + 64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)), + addr1); + } + } else if (bit_size == 64) { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const IR::Value data = + ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); + ir.WriteShared(bit_size, data, addr0); } else { - SetDst(inst.dst[0], ir.ReadFirstLane(value)); + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); } } -void Translator::V_READLANE_B32(const GcnInst& inst) { - const IR::ScalarReg dst{inst.dst[0].code}; - const IR::U32 value{GetSrc(inst.src[0])}; - const IR::U32 lane{GetSrc(inst.src[1])}; - ir.SetScalarReg(dst, ir.ReadLane(value, lane)); +void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { + const u8 offset0 = inst.control.ds.offset0; + const u8 offset1 = inst.control.ds.offset1; + const IR::U32 src{GetSrc(inst.src[1])}; + ASSERT(offset1 & 0x80); + const IR::U32 lane_id = ir.LaneId(); + const IR::U32 id_in_group = ir.BitwiseAnd(lane_id, ir.Imm32(0b11)); + const IR::U32 base = ir.ShiftLeftLogical(id_in_group, ir.Imm32(1)); + const IR::U32 index = + ir.IAdd(lane_id, ir.BitFieldExtract(ir.Imm32(offset0), base, ir.Imm32(2))); + SetDst(inst.dst[0], ir.QuadShuffle(src, index)); } -void Translator::V_WRITELANE_B32(const GcnInst& inst) { - const IR::VectorReg dst{inst.dst[0].code}; - const IR::U32 value{GetSrc(inst.src[0])}; - const IR::U32 lane{GetSrc(inst.src[1])}; - const IR::U32 old_value{GetSrc(inst.dst[0])}; - ir.SetVectorReg(dst, ir.WriteLane(old_value, value, lane)); +void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, + const GcnInst& inst) { + const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; + IR::VectorReg dst_reg{inst.dst[0].code}; + if (is_pair) { + // Pair loads are either 32 or 64-bit + const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); + const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0); + if (bit_size == 32) { + ir.SetVectorReg(dst_reg++, IR::U32{data0}); + } else { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 0)}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 1)}); + } + const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); + const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1); + if (bit_size == 32) { + ir.SetVectorReg(dst_reg++, IR::U32{data1}); + } else { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 0)}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 1)}); + } + } else if (bit_size == 64) { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0); + ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)}); + ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(data, 1)}); + } else { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)}; + ir.SetVectorReg(dst_reg, data); + } } void Translator::DS_APPEND(const GcnInst& inst) { diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index e246b5c516a..0c9efdc48f7 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -17,79 +17,83 @@ void Translator::EmitScalarAlu(const GcnInst& inst) { } default: switch (inst.opcode) { - case Opcode::S_MOV_B32: - return S_MOV(inst); - case Opcode::S_MUL_I32: - return S_MUL_I32(inst); - case Opcode::S_AND_SAVEEXEC_B64: - return S_AND_SAVEEXEC_B64(inst); - case Opcode::S_MOV_B64: - return S_MOV_B64(inst); - case Opcode::S_OR_B64: - return S_OR_B64(NegateMode::None, false, inst); - case Opcode::S_NOR_B64: - return S_OR_B64(NegateMode::Result, false, inst); - case Opcode::S_XOR_B64: - return S_OR_B64(NegateMode::None, true, inst); - case Opcode::S_XNOR_B64: - return S_OR_B64(NegateMode::Result, true, inst); - case Opcode::S_ORN2_B64: - return S_OR_B64(NegateMode::Src1, false, inst); - case Opcode::S_AND_B64: - return S_AND_B64(NegateMode::None, inst); - case Opcode::S_NAND_B64: - return S_AND_B64(NegateMode::Result, inst); - case Opcode::S_ANDN2_B64: - return S_AND_B64(NegateMode::Src1, inst); - case Opcode::S_NOT_B64: - return S_NOT_B64(inst); + // SOP2 + case Opcode::S_ADD_U32: + return S_ADD_U32(inst); + case Opcode::S_SUB_U32: + return S_SUB_U32(inst); case Opcode::S_ADD_I32: return S_ADD_I32(inst); + case Opcode::S_SUB_I32: + return S_SUB_U32(inst); + case Opcode::S_ADDC_U32: + return S_ADDC_U32(inst); + case Opcode::S_MIN_I32: + return S_MIN_U32(true, inst); + case Opcode::S_MIN_U32: + return S_MIN_U32(false, inst); + case Opcode::S_MAX_I32: + return S_MAX_U32(true, inst); + case Opcode::S_MAX_U32: + return S_MAX_U32(false, inst); + case Opcode::S_CSELECT_B32: + return S_CSELECT_B32(inst); + case Opcode::S_CSELECT_B64: + return S_CSELECT_B64(inst); case Opcode::S_AND_B32: return S_AND_B32(NegateMode::None, inst); - case Opcode::S_NAND_B32: - return S_AND_B32(NegateMode::Result, inst); - case Opcode::S_ANDN2_B32: - return S_AND_B32(NegateMode::Src1, inst); - case Opcode::S_ASHR_I32: - return S_ASHR_I32(inst); + case Opcode::S_AND_B64: + return S_AND_B64(NegateMode::None, inst); case Opcode::S_OR_B32: return S_OR_B32(inst); + case Opcode::S_OR_B64: + return S_OR_B64(NegateMode::None, false, inst); case Opcode::S_XOR_B32: return S_XOR_B32(inst); + case Opcode::S_XOR_B64: + return S_OR_B64(NegateMode::None, true, inst); + case Opcode::S_ANDN2_B32: + return S_AND_B32(NegateMode::Src1, inst); + case Opcode::S_ANDN2_B64: + return S_AND_B64(NegateMode::Src1, inst); + case Opcode::S_ORN2_B64: + return S_OR_B64(NegateMode::Src1, false, inst); + case Opcode::S_NAND_B32: + return S_AND_B32(NegateMode::Result, inst); + case Opcode::S_NAND_B64: + return S_AND_B64(NegateMode::Result, inst); + case Opcode::S_NOR_B64: + return S_OR_B64(NegateMode::Result, false, inst); + case Opcode::S_XNOR_B64: + return S_OR_B64(NegateMode::Result, true, inst); case Opcode::S_LSHL_B32: return S_LSHL_B32(inst); case Opcode::S_LSHR_B32: return S_LSHR_B32(inst); - case Opcode::S_CSELECT_B32: - return S_CSELECT_B32(inst); - case Opcode::S_CSELECT_B64: - return S_CSELECT_B64(inst); - case Opcode::S_BFE_U32: - return S_BFE_U32(inst); + case Opcode::S_ASHR_I32: + return S_ASHR_I32(inst); case Opcode::S_BFM_B32: return S_BFM_B32(inst); - case Opcode::S_BREV_B32: - return S_BREV_B32(inst); - case Opcode::S_ADD_U32: - return S_ADD_U32(inst); - case Opcode::S_ADDC_U32: - return S_ADDC_U32(inst); - case Opcode::S_SUB_U32: - case Opcode::S_SUB_I32: - return S_SUB_U32(inst); - case Opcode::S_MIN_U32: - return S_MIN_U32(false, inst); - case Opcode::S_MIN_I32: - return S_MIN_U32(true, inst); - case Opcode::S_MAX_U32: - return S_MAX_U32(false, inst); - case Opcode::S_MAX_I32: - return S_MAX_U32(true, inst); + case Opcode::S_MUL_I32: + return S_MUL_I32(inst); + case Opcode::S_BFE_U32: + return S_BFE_U32(inst); case Opcode::S_ABSDIFF_I32: return S_ABSDIFF_I32(inst); + + // SOP1 + case Opcode::S_MOV_B32: + return S_MOV(inst); + case Opcode::S_MOV_B64: + return S_MOV_B64(inst); + case Opcode::S_NOT_B64: + return S_NOT_B64(inst); case Opcode::S_WQM_B64: break; + case Opcode::S_BREV_B32: + return S_BREV_B32(inst); + case Opcode::S_AND_SAVEEXEC_B64: + return S_AND_SAVEEXEC_B64(inst); default: LogMissingOpcode(inst); } @@ -131,6 +135,7 @@ void Translator::EmitSOPC(const GcnInst& inst) { void Translator::EmitSOPK(const GcnInst& inst) { switch (inst.opcode) { + // SOPK case Opcode::S_MOVK_I32: return S_MOVK(inst); @@ -169,165 +174,123 @@ void Translator::EmitSOPK(const GcnInst& inst) { } } -void Translator::S_MOVK(const GcnInst& inst) { - const auto simm16 = inst.control.sopk.simm; - if (simm16 & (1 << 15)) { - // TODO: need to verify the case of imm sign extension - UNREACHABLE(); - } - SetDst(inst.dst[0], ir.Imm32(simm16)); -} +// SOP2 -void Translator::S_ADDK_I32(const GcnInst& inst) { - const s32 simm16 = inst.control.sopk.simm; - SetDst(inst.dst[0], ir.IAdd(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +void Translator::S_ADD_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IAdd(src0, src1)); + // TODO: Carry out + ir.SetScc(ir.Imm1(false)); } -void Translator::S_MULK_I32(const GcnInst& inst) { - const s32 simm16 = inst.control.sopk.simm; - SetDst(inst.dst[0], ir.IMul(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +void Translator::S_SUB_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ISub(src0, src1)); + // TODO: Carry out + ir.SetScc(ir.Imm1(false)); } -void Translator::S_MOV(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0])); +void Translator::S_ADD_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IAdd(src0, src1)); + // TODO: Overflow flag } -void Translator::S_MUL_I32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.IMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); +void Translator::S_ADDC_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 carry{ir.Select(ir.GetScc(), ir.Imm32(1U), ir.Imm32(0U))}; + SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), carry)); } -void Translator::S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst) { - const IR::U32 lhs = GetSrc(inst.src[0]); - const IR::U32 rhs = GetSrc(inst.src[1]); - const IR::U1 result = [&] { - switch (cond) { - case ConditionOp::EQ: - return ir.IEqual(lhs, rhs); - case ConditionOp::LG: - return ir.INotEqual(lhs, rhs); - case ConditionOp::GT: - return ir.IGreaterThan(lhs, rhs, is_signed); - case ConditionOp::GE: - return ir.IGreaterThanEqual(lhs, rhs, is_signed); - case ConditionOp::LT: - return ir.ILessThan(lhs, rhs, is_signed); - case ConditionOp::LE: - return ir.ILessThanEqual(lhs, rhs, is_signed); - default: - UNREACHABLE(); - } - }(); - ir.SetScc(result); +void Translator::S_MIN_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result = ir.IMin(src0, src1, is_signed); + SetDst(inst.dst[0], result); + ir.SetScc(ir.IEqual(result, src0)); } -void Translator::S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst) { - const s32 simm16 = inst.control.sopk.simm; - const IR::U32 lhs = GetSrc(inst.dst[0]); - const IR::U32 rhs = ir.Imm32(simm16); - const IR::U1 result = [&] { - switch (cond) { - case ConditionOp::EQ: - return ir.IEqual(lhs, rhs); - case ConditionOp::LG: - return ir.INotEqual(lhs, rhs); - case ConditionOp::GT: - return ir.IGreaterThan(lhs, rhs, is_signed); - case ConditionOp::GE: - return ir.IGreaterThanEqual(lhs, rhs, is_signed); - case ConditionOp::LT: - return ir.ILessThan(lhs, rhs, is_signed); - case ConditionOp::LE: - return ir.ILessThanEqual(lhs, rhs, is_signed); - default: - UNREACHABLE(); - } - }(); - ir.SetScc(result); +void Translator::S_MAX_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result = ir.IMax(src0, src1, is_signed); + SetDst(inst.dst[0], result); + ir.SetScc(ir.IEqual(result, src0)); } -void Translator::S_AND_SAVEEXEC_B64(const GcnInst& inst) { - // This instruction normally operates on 64-bit data (EXEC, VCC, SGPRs) - // However here we flatten it to 1-bit EXEC and 1-bit VCC. For the destination - // SGPR we have a special IR opcode for SPGRs that act as thread masks. - const IR::U1 exec{ir.GetExec()}; - const IR::U1 src = [&] { - switch (inst.src[0].field) { - case OperandField::VccLo: - return ir.GetVcc(); - case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); - default: - UNREACHABLE(); - } - }(); - - switch (inst.dst[0].field) { - case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), exec); - break; - case OperandField::VccLo: - ir.SetVcc(exec); - break; - default: - UNREACHABLE(); - } - - // Update EXEC. - const IR::U1 result = ir.LogicalAnd(exec, src); - ir.SetExec(result); - ir.SetScc(result); +void Translator::S_CSELECT_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], IR::U32{ir.Select(ir.GetScc(), src0, src1)}); } -void Translator::S_MOV_B64(const GcnInst& inst) { - const IR::U1 src = [&] { - switch (inst.src[0].field) { +void Translator::S_CSELECT_B64(const GcnInst& inst) { + const auto get_src = [&](const InstOperand& operand) { + switch (operand.field) { case OperandField::VccLo: return ir.GetVcc(); case OperandField::ExecLo: return ir.GetExec(); case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); + return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); case OperandField::ConstZero: return ir.Imm1(false); default: UNREACHABLE(); } - }(); + }; + const IR::U1 src0{get_src(inst.src[0])}; + const IR::U1 src1{get_src(inst.src[1])}; + const IR::U1 result{ir.Select(ir.GetScc(), src0, src1)}; switch (inst.dst[0].field) { - case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), src); - break; - case OperandField::ExecLo: - ir.SetExec(src); - break; case OperandField::VccLo: - ir.SetVcc(src); + ir.SetVcc(result); + break; + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); break; default: UNREACHABLE(); } } -void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) { +void Translator::S_AND_B32(NegateMode negate, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + IR::U32 src1{GetSrc(inst.src[1])}; + if (negate == NegateMode::Src1) { + src1 = ir.BitwiseNot(src1); + } + IR::U32 result{ir.BitwiseAnd(src0, src1)}; + if (negate == NegateMode::Result) { + result = ir.BitwiseNot(result); + } + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + +void Translator::S_AND_B64(NegateMode negate, const GcnInst& inst) { const auto get_src = [&](const InstOperand& operand) { switch (operand.field) { - case OperandField::ExecLo: - return ir.GetExec(); case OperandField::VccLo: return ir.GetVcc(); + case OperandField::ExecLo: + return ir.GetExec(); case OperandField::ScalarGPR: return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); default: UNREACHABLE(); } }; - const IR::U1 src0{get_src(inst.src[0])}; IR::U1 src1{get_src(inst.src[1])}; if (negate == NegateMode::Src1) { src1 = ir.LogicalNot(src1); } - IR::U1 result = is_xor ? ir.LogicalXor(src0, src1) : ir.LogicalOr(src0, src1); + IR::U1 result = ir.LogicalAnd(src0, src1); if (negate == NegateMode::Result) { result = ir.LogicalNot(result); } @@ -339,30 +302,42 @@ void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) { case OperandField::ScalarGPR: ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); break; + case OperandField::ExecLo: + ir.SetExec(result); + break; default: UNREACHABLE(); } } -void Translator::S_AND_B64(NegateMode negate, const GcnInst& inst) { +void Translator::S_OR_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result{ir.BitwiseOr(src0, src1)}; + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + +void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) { const auto get_src = [&](const InstOperand& operand) { switch (operand.field) { - case OperandField::VccLo: - return ir.GetVcc(); case OperandField::ExecLo: return ir.GetExec(); + case OperandField::VccLo: + return ir.GetVcc(); case OperandField::ScalarGPR: return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); default: UNREACHABLE(); } }; + const IR::U1 src0{get_src(inst.src[0])}; IR::U1 src1{get_src(inst.src[1])}; if (negate == NegateMode::Src1) { src1 = ir.LogicalNot(src1); } - IR::U1 result = ir.LogicalAnd(src0, src1); + IR::U1 result = is_xor ? ir.LogicalXor(src0, src1) : ir.LogicalOr(src0, src1); if (negate == NegateMode::Result) { result = ir.LogicalNot(result); } @@ -374,128 +349,154 @@ void Translator::S_AND_B64(NegateMode negate, const GcnInst& inst) { case OperandField::ScalarGPR: ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); break; - case OperandField::ExecLo: - ir.SetExec(result); - break; default: UNREACHABLE(); } } -void Translator::S_ADD_I32(const GcnInst& inst) { +void Translator::S_XOR_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IAdd(src0, src1)); - // TODO: Overflow flag + const IR::U32 result{ir.BitwiseXor(src0, src1)}; + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_AND_B32(NegateMode negate, const GcnInst& inst) { +void Translator::S_LSHL_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - IR::U32 src1{GetSrc(inst.src[1])}; - if (negate == NegateMode::Src1) { - src1 = ir.BitwiseNot(src1); - } - IR::U32 result{ir.BitwiseAnd(src0, src1)}; - if (negate == NegateMode::Result) { - result = ir.BitwiseNot(result); - } + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result = ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F))); SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_ASHR_I32(const GcnInst& inst) { +void Translator::S_LSHR_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.ShiftRightArithmetic(src0, src1)}; + const IR::U32 result{ir.ShiftRightLogical(src0, src1)}; SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_OR_B32(const GcnInst& inst) { +void Translator::S_ASHR_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.BitwiseOr(src0, src1)}; + const IR::U32 result{ir.ShiftRightArithmetic(src0, src1)}; SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_XOR_B32(const GcnInst& inst) { +void Translator::S_BFM_B32(const GcnInst& inst) { + const IR::U32 src0{ir.BitwiseAnd(GetSrc(inst.src[0]), ir.Imm32(0x1F))}; + const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; + const IR::U32 mask{ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1u), src0), ir.Imm32(1))}; + SetDst(inst.dst[0], ir.ShiftLeftLogical(mask, src1)); +} + +void Translator::S_MUL_I32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.IMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); +} + +void Translator::S_BFE_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.BitwiseXor(src0, src1)}; + const IR::U32 offset{ir.BitwiseAnd(src1, ir.Imm32(0x1F))}; + const IR::U32 count{ir.BitFieldExtract(src1, ir.Imm32(16), ir.Imm32(7))}; + const IR::U32 result{ir.BitFieldExtract(src0, offset, count)}; SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_LSHR_B32(const GcnInst& inst) { +void Translator::S_ABSDIFF_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.ShiftRightLogical(src0, src1)}; + const IR::U32 result{ir.IAbs(ir.ISub(src0, src1))}; SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_CSELECT_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], IR::U32{ir.Select(ir.GetScc(), src0, src1)}); +// SOPK + +void Translator::S_MOVK(const GcnInst& inst) { + const auto simm16 = inst.control.sopk.simm; + if (simm16 & (1 << 15)) { + // TODO: need to verify the case of imm sign extension + UNREACHABLE(); + } + SetDst(inst.dst[0], ir.Imm32(simm16)); } -void Translator::S_CSELECT_B64(const GcnInst& inst) { - const auto get_src = [&](const InstOperand& operand) { - switch (operand.field) { +void Translator::S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + const IR::U32 lhs = GetSrc(inst.dst[0]); + const IR::U32 rhs = ir.Imm32(simm16); + const IR::U1 result = [&] { + switch (cond) { + case ConditionOp::EQ: + return ir.IEqual(lhs, rhs); + case ConditionOp::LG: + return ir.INotEqual(lhs, rhs); + case ConditionOp::GT: + return ir.IGreaterThan(lhs, rhs, is_signed); + case ConditionOp::GE: + return ir.IGreaterThanEqual(lhs, rhs, is_signed); + case ConditionOp::LT: + return ir.ILessThan(lhs, rhs, is_signed); + case ConditionOp::LE: + return ir.ILessThanEqual(lhs, rhs, is_signed); + default: + UNREACHABLE(); + } + }(); + ir.SetScc(result); +} + +void Translator::S_ADDK_I32(const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + SetDst(inst.dst[0], ir.IAdd(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +} + +void Translator::S_MULK_I32(const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + SetDst(inst.dst[0], ir.IMul(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +} + +// SOP1 + +void Translator::S_MOV(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[0])); +} + +void Translator::S_MOV_B64(const GcnInst& inst) { + const IR::U1 src = [&] { + switch (inst.src[0].field) { case OperandField::VccLo: return ir.GetVcc(); case OperandField::ExecLo: return ir.GetExec(); case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); + return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); case OperandField::ConstZero: return ir.Imm1(false); default: UNREACHABLE(); } - }; - const IR::U1 src0{get_src(inst.src[0])}; - const IR::U1 src1{get_src(inst.src[1])}; - const IR::U1 result{ir.Select(ir.GetScc(), src0, src1)}; + }(); switch (inst.dst[0].field) { - case OperandField::VccLo: - ir.SetVcc(result); - break; case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), src); + break; + case OperandField::ExecLo: + ir.SetExec(src); + break; + case OperandField::VccLo: + ir.SetVcc(src); break; default: UNREACHABLE(); } } -void Translator::S_BFE_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 offset{ir.BitwiseAnd(src1, ir.Imm32(0x1F))}; - const IR::U32 count{ir.BitFieldExtract(src1, ir.Imm32(16), ir.Imm32(7))}; - const IR::U32 result{ir.BitFieldExtract(src0, offset, count)}; - SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); -} - -void Translator::S_LSHL_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result = ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F))); - SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); -} - -void Translator::S_BFM_B32(const GcnInst& inst) { - const IR::U32 src0{ir.BitwiseAnd(GetSrc(inst.src[0]), ir.Imm32(0x1F))}; - const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; - const IR::U32 mask{ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1u), src0), ir.Imm32(1))}; - SetDst(inst.dst[0], ir.ShiftLeftLogical(mask, src1)); -} - void Translator::S_NOT_B64(const GcnInst& inst) { const auto get_src = [&](const InstOperand& operand) { switch (operand.field) { @@ -528,22 +529,6 @@ void Translator::S_BREV_B32(const GcnInst& inst) { SetDst(inst.dst[0], ir.BitReverse(GetSrc(inst.src[0]))); } -void Translator::S_ADD_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IAdd(src0, src1)); - // TODO: Carry out - ir.SetScc(ir.Imm1(false)); -} - -void Translator::S_SUB_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src0, src1)); - // TODO: Carry out - ir.SetScc(ir.Imm1(false)); -} - void Translator::S_GETPC_B64(u32 pc, const GcnInst& inst) { // This only really exists to let resource tracking pass know // there is an inline cbuf. @@ -552,35 +537,63 @@ void Translator::S_GETPC_B64(u32 pc, const GcnInst& inst) { ir.SetScalarReg(dst + 1, ir.Imm32(0)); } -void Translator::S_ADDC_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 carry{ir.Select(ir.GetScc(), ir.Imm32(1U), ir.Imm32(0U))}; - SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), carry)); -} +void Translator::S_AND_SAVEEXEC_B64(const GcnInst& inst) { + // This instruction normally operates on 64-bit data (EXEC, VCC, SGPRs) + // However here we flatten it to 1-bit EXEC and 1-bit VCC. For the destination + // SGPR we have a special IR opcode for SPGRs that act as thread masks. + const IR::U1 exec{ir.GetExec()}; + const IR::U1 src = [&] { + switch (inst.src[0].field) { + case OperandField::VccLo: + return ir.GetVcc(); + case OperandField::ScalarGPR: + return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); + default: + UNREACHABLE(); + } + }(); -void Translator::S_MAX_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result = ir.IMax(src0, src1, is_signed); - SetDst(inst.dst[0], result); - ir.SetScc(ir.IEqual(result, src0)); -} + switch (inst.dst[0].field) { + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), exec); + break; + case OperandField::VccLo: + ir.SetVcc(exec); + break; + default: + UNREACHABLE(); + } -void Translator::S_MIN_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result = ir.IMin(src0, src1, is_signed); - SetDst(inst.dst[0], result); - ir.SetScc(ir.IEqual(result, src0)); + // Update EXEC. + const IR::U1 result = ir.LogicalAnd(exec, src); + ir.SetExec(result); + ir.SetScc(result); } -void Translator::S_ABSDIFF_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.IAbs(ir.ISub(src0, src1))}; - SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +// SOPC + +void Translator::S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst) { + const IR::U32 lhs = GetSrc(inst.src[0]); + const IR::U32 rhs = GetSrc(inst.src[1]); + const IR::U1 result = [&] { + switch (cond) { + case ConditionOp::EQ: + return ir.IEqual(lhs, rhs); + case ConditionOp::LG: + return ir.INotEqual(lhs, rhs); + case ConditionOp::GT: + return ir.IGreaterThan(lhs, rhs, is_signed); + case ConditionOp::GE: + return ir.IGreaterThanEqual(lhs, rhs, is_signed); + case ConditionOp::LT: + return ir.ILessThan(lhs, rhs, is_signed); + case ConditionOp::LE: + return ir.ILessThanEqual(lhs, rhs, is_signed); + default: + UNREACHABLE(); + } + }(); + ir.SetScc(result); } } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/scalar_memory.cpp b/src/shader_recompiler/frontend/translate/scalar_memory.cpp index 29f2acc2771..a6f8cafd7a3 100644 --- a/src/shader_recompiler/frontend/translate/scalar_memory.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_memory.cpp @@ -9,6 +9,7 @@ static constexpr u32 SQ_SRC_LITERAL = 0xFF; void Translator::EmitScalarMemory(const GcnInst& inst) { switch (inst.opcode) { + // SMRD case Opcode::S_LOAD_DWORDX4: return S_LOAD_DWORD(4, inst); case Opcode::S_LOAD_DWORDX8: @@ -30,6 +31,8 @@ void Translator::EmitScalarMemory(const GcnInst& inst) { } } +// SMRD + void Translator::S_LOAD_DWORD(int num_dwords, const GcnInst& inst) { const auto& smrd = inst.control.smrd; const u32 dword_offset = [&] -> u32 { diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 9527e9f99da..efa27cbd7c1 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -61,180 +61,201 @@ class Translator { // Instruction categories void EmitPrologue(); void EmitFetch(const GcnInst& inst); - void EmitDataShare(const GcnInst& inst); - void EmitVectorInterpolation(const GcnInst& inst); - void EmitScalarMemory(const GcnInst& inst); - void EmitVectorMemory(const GcnInst& inst); void EmitExport(const GcnInst& inst); void EmitFlowControl(u32 pc, const GcnInst& inst); void EmitScalarAlu(const GcnInst& inst); + void EmitScalarMemory(const GcnInst& inst); void EmitVectorAlu(const GcnInst& inst); + void EmitVectorInterpolation(const GcnInst& inst); + void EmitDataShare(const GcnInst& inst); + void EmitVectorMemory(const GcnInst& inst); // Instruction encodings void EmitSOPC(const GcnInst& inst); void EmitSOPK(const GcnInst& inst); // Scalar ALU - void S_MOVK(const GcnInst& inst); - void S_MOV(const GcnInst& inst); - void S_MUL_I32(const GcnInst& inst); - void S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst); - void S_AND_SAVEEXEC_B64(const GcnInst& inst); - void S_MOV_B64(const GcnInst& inst); - void S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst); - void S_AND_B64(NegateMode negate, const GcnInst& inst); + // SOP2 + void S_ADD_U32(const GcnInst& inst); + void S_SUB_U32(const GcnInst& inst); void S_ADD_I32(const GcnInst& inst); + void S_ADDC_U32(const GcnInst& inst); + void S_MIN_U32(bool is_signed, const GcnInst& inst); + void S_MAX_U32(bool is_signed, const GcnInst& inst); + void S_CSELECT_B32(const GcnInst& inst); + void S_CSELECT_B64(const GcnInst& inst); void S_AND_B32(NegateMode negate, const GcnInst& inst); - void S_ASHR_I32(const GcnInst& inst); + void S_AND_B64(NegateMode negate, const GcnInst& inst); void S_OR_B32(const GcnInst& inst); + void S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst); void S_XOR_B32(const GcnInst& inst); - void S_LSHR_B32(const GcnInst& inst); - void S_CSELECT_B32(const GcnInst& inst); - void S_CSELECT_B64(const GcnInst& inst); - void S_BFE_U32(const GcnInst& inst); void S_LSHL_B32(const GcnInst& inst); + void S_LSHR_B32(const GcnInst& inst); + void S_ASHR_I32(const GcnInst& inst); void S_BFM_B32(const GcnInst& inst); + void S_MUL_I32(const GcnInst& inst); + void S_BFE_U32(const GcnInst& inst); + void S_ABSDIFF_I32(const GcnInst& inst); + + // SOPK + void S_MOVK(const GcnInst& inst); + void S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst); + void S_ADDK_I32(const GcnInst& inst); + void S_MULK_I32(const GcnInst& inst); + + // SOP1 + void S_MOV(const GcnInst& inst); + void S_MOV_B64(const GcnInst& inst); void S_NOT_B64(const GcnInst& inst); void S_BREV_B32(const GcnInst& inst); - void S_ADD_U32(const GcnInst& inst); - void S_SUB_U32(const GcnInst& inst); void S_GETPC_B64(u32 pc, const GcnInst& inst); - void S_ADDC_U32(const GcnInst& inst); - void S_MULK_I32(const GcnInst& inst); - void S_ADDK_I32(const GcnInst& inst); - void S_MAX_U32(bool is_signed, const GcnInst& inst); - void S_MIN_U32(bool is_signed, const GcnInst& inst); - void S_ABSDIFF_I32(const GcnInst& inst); - void S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst); + void S_AND_SAVEEXEC_B64(const GcnInst& inst); + + // SOPC + void S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst); + + // SOPP + void S_BARRIER(); // Scalar Memory + // SMRD void S_LOAD_DWORD(int num_dwords, const GcnInst& inst); void S_BUFFER_LOAD_DWORD(int num_dwords, const GcnInst& inst); // Vector ALU - void V_MOV(const GcnInst& inst); - void V_SAD(const GcnInst& inst); - void V_MAC_F32(const GcnInst& inst); - void V_CVT_PKRTZ_F16_F32(const GcnInst& inst); - void V_CVT_F32_F16(const GcnInst& inst); - void V_CVT_F16_F32(const GcnInst& inst); - void V_MUL_F32(const GcnInst& inst); + // VOP2 void V_CNDMASK_B32(const GcnInst& inst); - void V_OR_B32(bool is_xor, const GcnInst& inst); - void V_AND_B32(const GcnInst& inst); - void V_LSHLREV_B32(const GcnInst& inst); + void V_READLANE_B32(const GcnInst& inst); + void V_WRITELANE_B32(const GcnInst& inst); + void V_ADD_F32(const GcnInst& inst); + void V_SUB_F32(const GcnInst& inst); + void V_SUBREV_F32(const GcnInst& inst); + void V_MUL_F32(const GcnInst& inst); + void V_MUL_I32_I24(const GcnInst& inst); + void V_MIN_F32(const GcnInst& inst, bool is_legacy = false); + void V_MAX_F32(const GcnInst& inst, bool is_legacy = false); + void V_MIN_I32(const GcnInst& inst); + void V_MIN_U32(const GcnInst& inst); + void V_MAX_U32(bool is_signed, const GcnInst& inst); + void V_LSHR_B32(const GcnInst& inst); + void V_LSHRREV_B32(const GcnInst& inst); + void V_ASHR_I32(const GcnInst& inst); + void V_ASHRREV_I32(const GcnInst& inst); void V_LSHL_B32(const GcnInst& inst); - void V_LSHL_B64(const GcnInst& inst); + void V_LSHLREV_B32(const GcnInst& inst); + void V_AND_B32(const GcnInst& inst); + void V_OR_B32(bool is_xor, const GcnInst& inst); + void V_BFM_B32(const GcnInst& inst); + void V_MAC_F32(const GcnInst& inst); + void V_MADMK_F32(const GcnInst& inst); + void V_BCNT_U32_B32(const GcnInst& inst); + void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst); void V_ADD_I32(const GcnInst& inst); + void V_SUB_I32(const GcnInst& inst); + void V_SUBREV_I32(const GcnInst& inst); void V_ADDC_U32(const GcnInst& inst); + void V_LDEXP_F32(const GcnInst& inst); + void V_CVT_PKRTZ_F16_F32(const GcnInst& inst); + + // VOP1 + void V_MOV(const GcnInst& inst); + void V_READFIRSTLANE_B32(const GcnInst& inst); void V_CVT_F32_I32(const GcnInst& inst); void V_CVT_F32_U32(const GcnInst& inst); - void V_MAD_F32(const GcnInst& inst); - void V_FRACT_F32(const GcnInst& inst); - void V_ADD_F32(const GcnInst& inst); + void V_CVT_U32_F32(const GcnInst& inst); + void V_CVT_I32_F32(const GcnInst& inst); + void V_CVT_F16_F32(const GcnInst& inst); + void V_CVT_F32_F16(const GcnInst& inst); + void V_CVT_FLR_I32_F32(const GcnInst& inst); void V_CVT_OFF_F32_I4(const GcnInst& inst); - void V_MED3_F32(const GcnInst& inst); - void V_MED3_I32(const GcnInst& inst); + void V_CVT_F32_UBYTE(u32 index, const GcnInst& inst); + void V_FRACT_F32(const GcnInst& inst); + void V_TRUNC_F32(const GcnInst& inst); + void V_CEIL_F32(const GcnInst& inst); + void V_RNDNE_F32(const GcnInst& inst); void V_FLOOR_F32(const GcnInst& inst); - void V_SUB_F32(const GcnInst& inst); + void V_EXP_F32(const GcnInst& inst); + void V_LOG_F32(const GcnInst& inst); void V_RCP_F32(const GcnInst& inst); void V_RCP_F64(const GcnInst& inst); - void V_FMA_F32(const GcnInst& inst); - void V_FMA_F64(const GcnInst& inst); - void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst); - void V_MAX_F32(const GcnInst& inst, bool is_legacy = false); - void V_MUL_F64(const GcnInst& inst); - void V_MAX_F64(const GcnInst& inst); - void V_MAX_U32(bool is_signed, const GcnInst& inst); void V_RSQ_F32(const GcnInst& inst); - void V_SIN_F32(const GcnInst& inst); - void V_LOG_F32(const GcnInst& inst); - void V_EXP_F32(const GcnInst& inst); void V_SQRT_F32(const GcnInst& inst); - void V_MIN_F32(const GcnInst& inst, bool is_legacy = false); - void V_MIN3_F32(const GcnInst& inst); - void V_MIN3_I32(const GcnInst& inst); - void V_MADMK_F32(const GcnInst& inst); - void V_CUBEMA_F32(const GcnInst& inst); - void V_CUBESC_F32(const GcnInst& inst); - void V_CUBETC_F32(const GcnInst& inst); - void V_CUBEID_F32(const GcnInst& inst); - void V_CVT_U32_F32(const GcnInst& inst); - void V_SUBREV_F32(const GcnInst& inst); - void V_SUBREV_I32(const GcnInst& inst); - void V_MAD_U64_U32(const GcnInst& inst); - void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst); - void V_LSHRREV_B32(const GcnInst& inst); - void V_MUL_HI_U32(bool is_signed, const GcnInst& inst); - void V_SAD_U32(const GcnInst& inst); - void V_BFE_U32(bool is_signed, const GcnInst& inst); - void V_MAD_I32_I24(const GcnInst& inst, bool is_signed = true); - void V_MUL_I32_I24(const GcnInst& inst); - void V_SUB_I32(const GcnInst& inst); - void V_LSHR_B32(const GcnInst& inst); - void V_ASHRREV_I32(const GcnInst& inst); - void V_ASHR_I32(const GcnInst& inst); - void V_MAD_U32_U24(const GcnInst& inst); - void V_RNDNE_F32(const GcnInst& inst); - void V_BCNT_U32_B32(const GcnInst& inst); + void V_SIN_F32(const GcnInst& inst); void V_COS_F32(const GcnInst& inst); - void V_MAX3_F32(const GcnInst& inst); - void V_MAX3_U32(bool is_signed, const GcnInst& inst); - void V_CVT_I32_F32(const GcnInst& inst); - void V_MIN_I32(const GcnInst& inst); - void V_MUL_LO_U32(const GcnInst& inst); - void V_TRUNC_F32(const GcnInst& inst); - void V_CEIL_F32(const GcnInst& inst); - void V_MIN_U32(const GcnInst& inst); - void V_CMP_NE_U64(const GcnInst& inst); - void V_BFI_B32(const GcnInst& inst); void V_NOT_B32(const GcnInst& inst); - void V_CVT_F32_UBYTE(u32 index, const GcnInst& inst); void V_BFREV_B32(const GcnInst& inst); - void V_LDEXP_F32(const GcnInst& inst); - void V_CVT_FLR_I32_F32(const GcnInst& inst); - void V_CMP_CLASS_F32(const GcnInst& inst); - void V_FFBL_B32(const GcnInst& inst); - void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst); - void V_BFM_B32(const GcnInst& inst); void V_FFBH_U32(const GcnInst& inst); - void V_MOVRELS_B32(const GcnInst& inst); + void V_FFBL_B32(const GcnInst& inst); void V_MOVRELD_B32(const GcnInst& inst); + void V_MOVRELS_B32(const GcnInst& inst); void V_MOVRELSD_B32(const GcnInst& inst); - // Vector Memory - void BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst); - void BUFFER_LOAD_FORMAT(u32 num_dwords, const GcnInst& inst); - void BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst); - void BUFFER_STORE_FORMAT(u32 num_dwords, const GcnInst& inst); - void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst); + // VOPC + void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst); + void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst); + void V_CMP_NE_U64(const GcnInst& inst); + void V_CMP_CLASS_F32(const GcnInst& inst); + + // VOP3a + void V_MAD_F32(const GcnInst& inst); + void V_MAD_I32_I24(const GcnInst& inst, bool is_signed = false); + void V_MAD_U32_U24(const GcnInst& inst); + void V_CUBEID_F32(const GcnInst& inst); + void V_CUBESC_F32(const GcnInst& inst); + void V_CUBETC_F32(const GcnInst& inst); + void V_CUBEMA_F32(const GcnInst& inst); + void V_BFE_U32(bool is_signed, const GcnInst& inst); + void V_BFI_B32(const GcnInst& inst); + void V_FMA_F32(const GcnInst& inst); + void V_FMA_F64(const GcnInst& inst); + void V_MIN3_F32(const GcnInst& inst); + void V_MIN3_I32(const GcnInst& inst); + void V_MAX3_F32(const GcnInst& inst); + void V_MAX3_U32(bool is_signed, const GcnInst& inst); + void V_MED3_F32(const GcnInst& inst); + void V_MED3_I32(const GcnInst& inst); + void V_SAD(const GcnInst& inst); + void V_SAD_U32(const GcnInst& inst); + void V_LSHL_B64(const GcnInst& inst); + void V_MUL_F64(const GcnInst& inst); + void V_MAX_F64(const GcnInst& inst); + void V_MUL_LO_U32(const GcnInst& inst); + void V_MUL_HI_U32(bool is_signed, const GcnInst& inst); + void V_MAD_U64_U32(const GcnInst& inst); // Vector interpolation + // VINTRP void V_INTERP_P2_F32(const GcnInst& inst); void V_INTERP_MOV_F32(const GcnInst& inst); // Data share - void DS_SWIZZLE_B32(const GcnInst& inst); - void DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); - void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); + // DS void DS_ADD_U32(const GcnInst& inst, bool rtn); void DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn); void DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn); - void V_READFIRSTLANE_B32(const GcnInst& inst); - void V_READLANE_B32(const GcnInst& inst); - void V_WRITELANE_B32(const GcnInst& inst); + void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); + void DS_SWIZZLE_B32(const GcnInst& inst); + void DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); void DS_APPEND(const GcnInst& inst); void DS_CONSUME(const GcnInst& inst); - void S_BARRIER(); + // Buffer Memory + // MUBUF / MTBUF + void BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst); + void BUFFER_LOAD_FORMAT(u32 num_dwords, const GcnInst& inst); + void BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst); + void BUFFER_STORE_FORMAT(u32 num_dwords, const GcnInst& inst); + void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst); + + // Image Memory // MIMG + void IMAGE_LOAD(bool has_mip, const GcnInst& inst); + void IMAGE_STORE(const GcnInst& inst); void IMAGE_GET_RESINFO(const GcnInst& inst); + void IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst); void IMAGE_SAMPLE(const GcnInst& inst); void IMAGE_GATHER(const GcnInst& inst); - void IMAGE_STORE(const GcnInst& inst); - void IMAGE_LOAD(bool has_mip, const GcnInst& inst); void IMAGE_GET_LOD(const GcnInst& inst); - void IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst); private: template @@ -244,6 +265,7 @@ class Translator { void SetDst(const InstOperand& operand, const IR::U32F32& value); void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw); + // Vector ALU Helprers IR::U32 VMovRelSHelper(u32 src_vgprno, const IR::U32 m0); void VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index dc832fbfb11..2d85d9bfb18 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -8,55 +8,111 @@ namespace Shader::Gcn { void Translator::EmitVectorAlu(const GcnInst& inst) { switch (inst.opcode) { - case Opcode::V_LSHLREV_B32: - return V_LSHLREV_B32(inst); - case Opcode::V_LSHL_B32: - return V_LSHL_B32(inst); - case Opcode::V_LSHL_B64: - return V_LSHL_B64(inst); - case Opcode::V_BFREV_B32: - return V_BFREV_B32(inst); - case Opcode::V_BFE_U32: - return V_BFE_U32(false, inst); - case Opcode::V_BFE_I32: - return V_BFE_U32(true, inst); - case Opcode::V_BFI_B32: - return V_BFI_B32(inst); + // VOP2 + case Opcode::V_CNDMASK_B32: + return V_CNDMASK_B32(inst); + case Opcode::V_READLANE_B32: + return V_READLANE_B32(inst); + case Opcode::V_WRITELANE_B32: + return V_WRITELANE_B32(inst); + case Opcode::V_ADD_F32: + return V_ADD_F32(inst); + case Opcode::V_SUB_F32: + return V_SUB_F32(inst); + case Opcode::V_SUBREV_F32: + return V_SUBREV_F32(inst); + case Opcode::V_MAC_LEGACY_F32: + return V_MAC_F32(inst); + case Opcode::V_MUL_LEGACY_F32: + return V_MUL_F32(inst); + case Opcode::V_MUL_F32: + return V_MUL_F32(inst); + case Opcode::V_MUL_I32_I24: + return V_MUL_I32_I24(inst); + case Opcode::V_MUL_U32_U24: + return V_MUL_I32_I24(inst); + case Opcode::V_MIN_LEGACY_F32: + return V_MIN_F32(inst, true); + case Opcode::V_MAX_LEGACY_F32: + return V_MAX_F32(inst, true); + case Opcode::V_MIN_F32: + return V_MIN_F32(inst, false); + case Opcode::V_MAX_F32: + return V_MAX_F32(inst); + case Opcode::V_MIN_I32: + return V_MIN_I32(inst); + case Opcode::V_MAX_I32: + return V_MAX_U32(true, inst); + case Opcode::V_MIN_U32: + return V_MIN_U32(inst); + case Opcode::V_MAX_U32: + return V_MAX_U32(false, inst); case Opcode::V_LSHR_B32: return V_LSHR_B32(inst); - case Opcode::V_ASHRREV_I32: - return V_ASHRREV_I32(inst); - case Opcode::V_ASHR_I32: - return V_ASHR_I32(inst); case Opcode::V_LSHRREV_B32: return V_LSHRREV_B32(inst); - case Opcode::V_NOT_B32: - return V_NOT_B32(inst); + case Opcode::V_ASHR_I32: + return V_ASHR_I32(inst); + case Opcode::V_ASHRREV_I32: + return V_ASHRREV_I32(inst); + case Opcode::V_LSHL_B32: + return V_LSHL_B32(inst); + case Opcode::V_LSHLREV_B32: + return V_LSHLREV_B32(inst); case Opcode::V_AND_B32: return V_AND_B32(inst); case Opcode::V_OR_B32: return V_OR_B32(false, inst); case Opcode::V_XOR_B32: return V_OR_B32(true, inst); - case Opcode::V_FFBL_B32: - return V_FFBL_B32(inst); - - case Opcode::V_MOV_B32: - return V_MOV(inst); + case Opcode::V_BFM_B32: + return V_BFM_B32(inst); + case Opcode::V_MAC_F32: + return V_MAC_F32(inst); + case Opcode::V_MADMK_F32: + return V_MADMK_F32(inst); + case Opcode::V_MADAK_F32: + return V_FMA_F32(inst); + case Opcode::V_BCNT_U32_B32: + return V_BCNT_U32_B32(inst); + case Opcode::V_MBCNT_LO_U32_B32: + return V_MBCNT_U32_B32(true, inst); + case Opcode::V_MBCNT_HI_U32_B32: + return V_MBCNT_U32_B32(false, inst); case Opcode::V_ADD_I32: return V_ADD_I32(inst); + case Opcode::V_SUB_I32: + return V_SUB_I32(inst); + case Opcode::V_SUBREV_I32: + return V_SUBREV_I32(inst); case Opcode::V_ADDC_U32: return V_ADDC_U32(inst); + case Opcode::V_LDEXP_F32: + return V_LDEXP_F32(inst); + case Opcode::V_CVT_PKRTZ_F16_F32: + return V_CVT_PKRTZ_F16_F32(inst); + + // VOP1 + case Opcode::V_MOV_B32: + return V_MOV(inst); + case Opcode::V_READFIRSTLANE_B32: + return V_READFIRSTLANE_B32(inst); case Opcode::V_CVT_F32_I32: return V_CVT_F32_I32(inst); case Opcode::V_CVT_F32_U32: return V_CVT_F32_U32(inst); - case Opcode::V_CVT_PKRTZ_F16_F32: - return V_CVT_PKRTZ_F16_F32(inst); - case Opcode::V_CVT_F32_F16: - return V_CVT_F32_F16(inst); + case Opcode::V_CVT_U32_F32: + return V_CVT_U32_F32(inst); + case Opcode::V_CVT_I32_F32: + return V_CVT_I32_F32(inst); case Opcode::V_CVT_F16_F32: return V_CVT_F16_F32(inst); + case Opcode::V_CVT_F32_F16: + return V_CVT_F32_F16(inst); + case Opcode::V_CVT_FLR_I32_F32: + return V_CVT_FLR_I32_F32(inst); + case Opcode::V_CVT_OFF_F32_I4: + return V_CVT_OFF_F32_I4(inst); case Opcode::V_CVT_F32_UBYTE0: return V_CVT_F32_UBYTE(0, inst); case Opcode::V_CVT_F32_UBYTE1: @@ -65,197 +121,83 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CVT_F32_UBYTE(2, inst); case Opcode::V_CVT_F32_UBYTE3: return V_CVT_F32_UBYTE(3, inst); - case Opcode::V_CVT_OFF_F32_I4: - return V_CVT_OFF_F32_I4(inst); - case Opcode::V_MAD_U64_U32: - return V_MAD_U64_U32(inst); - case Opcode::V_CMP_GE_I32: - return V_CMP_U32(ConditionOp::GE, true, false, inst); - case Opcode::V_CMP_EQ_I32: - return V_CMP_U32(ConditionOp::EQ, true, false, inst); - case Opcode::V_CMP_LE_I32: - return V_CMP_U32(ConditionOp::LE, true, false, inst); - case Opcode::V_CMP_NE_I32: - return V_CMP_U32(ConditionOp::LG, true, false, inst); - case Opcode::V_CMP_NE_U32: - return V_CMP_U32(ConditionOp::LG, false, false, inst); - case Opcode::V_CMP_EQ_U32: - return V_CMP_U32(ConditionOp::EQ, false, false, inst); - case Opcode::V_CMP_F_U32: - return V_CMP_U32(ConditionOp::F, false, false, inst); - case Opcode::V_CMP_LT_U32: - return V_CMP_U32(ConditionOp::LT, false, false, inst); - case Opcode::V_CMP_GT_U32: - return V_CMP_U32(ConditionOp::GT, false, false, inst); - case Opcode::V_CMP_GE_U32: - return V_CMP_U32(ConditionOp::GE, false, false, inst); - case Opcode::V_CMP_TRU_U32: - return V_CMP_U32(ConditionOp::TRU, false, false, inst); - case Opcode::V_CMP_NEQ_F32: - return V_CMP_F32(ConditionOp::LG, false, inst); - case Opcode::V_CMP_F_F32: - return V_CMP_F32(ConditionOp::F, false, inst); - case Opcode::V_CMP_LT_F32: - return V_CMP_F32(ConditionOp::LT, false, inst); - case Opcode::V_CMP_EQ_F32: - return V_CMP_F32(ConditionOp::EQ, false, inst); - case Opcode::V_CMP_LE_F32: - return V_CMP_F32(ConditionOp::LE, false, inst); - case Opcode::V_CMP_GT_F32: - return V_CMP_F32(ConditionOp::GT, false, inst); - case Opcode::V_CMP_LG_F32: - return V_CMP_F32(ConditionOp::LG, false, inst); - case Opcode::V_CMP_GE_F32: - return V_CMP_F32(ConditionOp::GE, false, inst); - case Opcode::V_CMP_NLE_F32: - return V_CMP_F32(ConditionOp::GT, false, inst); - case Opcode::V_CMP_NLT_F32: - return V_CMP_F32(ConditionOp::GE, false, inst); - case Opcode::V_CMP_NGT_F32: - return V_CMP_F32(ConditionOp::LE, false, inst); - case Opcode::V_CMP_NGE_F32: - return V_CMP_F32(ConditionOp::LT, false, inst); - case Opcode::V_CMP_U_F32: - return V_CMP_F32(ConditionOp::U, false, inst); - case Opcode::V_CNDMASK_B32: - return V_CNDMASK_B32(inst); - case Opcode::V_MAX_I32: - return V_MAX_U32(true, inst); - case Opcode::V_MAX_U32: - return V_MAX_U32(false, inst); - case Opcode::V_MIN_I32: - return V_MIN_I32(inst); - case Opcode::V_CUBEMA_F32: - return V_CUBEMA_F32(inst); - case Opcode::V_CUBESC_F32: - return V_CUBESC_F32(inst); - case Opcode::V_CUBETC_F32: - return V_CUBETC_F32(inst); - case Opcode::V_CUBEID_F32: - return V_CUBEID_F32(inst); - case Opcode::V_CVT_U32_F32: - return V_CVT_U32_F32(inst); - case Opcode::V_CVT_I32_F32: - return V_CVT_I32_F32(inst); - case Opcode::V_CVT_FLR_I32_F32: - return V_CVT_FLR_I32_F32(inst); - case Opcode::V_SUBREV_I32: - return V_SUBREV_I32(inst); - case Opcode::V_MUL_HI_U32: - return V_MUL_HI_U32(false, inst); - case Opcode::V_MUL_LO_I32: - return V_MUL_LO_U32(inst); - case Opcode::V_SAD_U32: - return V_SAD_U32(inst); - case Opcode::V_SUB_I32: - return V_SUB_I32(inst); - case Opcode::V_MAD_I32_I24: - return V_MAD_I32_I24(inst); - case Opcode::V_MUL_I32_I24: - case Opcode::V_MUL_U32_U24: - return V_MUL_I32_I24(inst); - case Opcode::V_MAD_U32_U24: - return V_MAD_U32_U24(inst); - case Opcode::V_BCNT_U32_B32: - return V_BCNT_U32_B32(inst); - case Opcode::V_MUL_LO_U32: - return V_MUL_LO_U32(inst); - case Opcode::V_MIN_U32: - return V_MIN_U32(inst); - case Opcode::V_CMP_NE_U64: - return V_CMP_NE_U64(inst); - case Opcode::V_READFIRSTLANE_B32: - return V_READFIRSTLANE_B32(inst); - case Opcode::V_READLANE_B32: - return V_READLANE_B32(inst); - case Opcode::V_WRITELANE_B32: - return V_WRITELANE_B32(inst); - - case Opcode::V_MAD_F32: - return V_MAD_F32(inst); - case Opcode::V_MAC_F32: - return V_MAC_F32(inst); - case Opcode::V_MUL_F32: - return V_MUL_F32(inst); - case Opcode::V_RCP_F32: - return V_RCP_F32(inst); - case Opcode::V_RCP_F64: - return V_RCP_F64(inst); - case Opcode::V_LDEXP_F32: - return V_LDEXP_F32(inst); case Opcode::V_FRACT_F32: return V_FRACT_F32(inst); - case Opcode::V_ADD_F32: - return V_ADD_F32(inst); - case Opcode::V_MED3_F32: - return V_MED3_F32(inst); - case Opcode::V_MED3_I32: - return V_MED3_I32(inst); + case Opcode::V_TRUNC_F32: + return V_TRUNC_F32(inst); + case Opcode::V_CEIL_F32: + return V_CEIL_F32(inst); + case Opcode::V_RNDNE_F32: + return V_RNDNE_F32(inst); case Opcode::V_FLOOR_F32: return V_FLOOR_F32(inst); - case Opcode::V_SUB_F32: - return V_SUB_F32(inst); - case Opcode::V_FMA_F32: - case Opcode::V_MADAK_F32: - return V_FMA_F32(inst); - case Opcode::V_FMA_F64: - return V_FMA_F64(inst); - case Opcode::V_MAX_F32: - return V_MAX_F32(inst); - case Opcode::V_MUL_F64: - return V_MUL_F64(inst); - case Opcode::V_MAX_F64: - return V_MAX_F64(inst); + case Opcode::V_EXP_F32: + return V_EXP_F32(inst); + case Opcode::V_LOG_F32: + return V_LOG_F32(inst); + case Opcode::V_RCP_F32: + return V_RCP_F32(inst); + case Opcode::V_RCP_F64: + return V_RCP_F64(inst); + case Opcode::V_RCP_IFLAG_F32: + return V_RCP_F32(inst); + case Opcode::V_RSQ_CLAMP_F32: + return V_RSQ_F32(inst); + case Opcode::V_RSQ_LEGACY_F32: + return V_RSQ_F32(inst); case Opcode::V_RSQ_F32: return V_RSQ_F32(inst); + case Opcode::V_SQRT_F32: + return V_SQRT_F32(inst); case Opcode::V_SIN_F32: return V_SIN_F32(inst); case Opcode::V_COS_F32: return V_COS_F32(inst); - case Opcode::V_LOG_F32: - return V_LOG_F32(inst); - case Opcode::V_EXP_F32: - return V_EXP_F32(inst); - case Opcode::V_SQRT_F32: - return V_SQRT_F32(inst); - case Opcode::V_MIN_F32: - return V_MIN_F32(inst, false); - case Opcode::V_MIN3_F32: - return V_MIN3_F32(inst); - case Opcode::V_MIN3_I32: - return V_MIN3_I32(inst); - case Opcode::V_MIN_LEGACY_F32: - return V_MIN_F32(inst, true); - case Opcode::V_MADMK_F32: - return V_MADMK_F32(inst); - case Opcode::V_SUBREV_F32: - return V_SUBREV_F32(inst); - case Opcode::V_RNDNE_F32: - return V_RNDNE_F32(inst); - case Opcode::V_MAX3_F32: - return V_MAX3_F32(inst); - case Opcode::V_MAX3_U32: - return V_MAX3_U32(false, inst); - case Opcode::V_MAX3_I32: - return V_MAX_U32(true, inst); - case Opcode::V_TRUNC_F32: - return V_TRUNC_F32(inst); - case Opcode::V_CEIL_F32: - return V_CEIL_F32(inst); - case Opcode::V_MUL_LEGACY_F32: - return V_MUL_F32(inst); - case Opcode::V_MAC_LEGACY_F32: - return V_MAC_F32(inst); - case Opcode::V_MAD_LEGACY_F32: - return V_MAD_F32(inst); - case Opcode::V_MAX_LEGACY_F32: - return V_MAX_F32(inst, true); - case Opcode::V_RSQ_LEGACY_F32: - case Opcode::V_RSQ_CLAMP_F32: - return V_RSQ_F32(inst); - case Opcode::V_RCP_IFLAG_F32: - return V_RCP_F32(inst); + case Opcode::V_NOT_B32: + return V_NOT_B32(inst); + case Opcode::V_BFREV_B32: + return V_BFREV_B32(inst); + case Opcode::V_FFBH_U32: + return V_FFBH_U32(inst); + case Opcode::V_FFBL_B32: + return V_FFBL_B32(inst); + case Opcode::V_MOVRELD_B32: + return V_MOVRELD_B32(inst); + case Opcode::V_MOVRELS_B32: + return V_MOVRELS_B32(inst); + case Opcode::V_MOVRELSD_B32: + return V_MOVRELSD_B32(inst); + + // VOPC + // V_CMP_{OP16}_F32 + case Opcode::V_CMP_F_F32: + return V_CMP_F32(ConditionOp::F, false, inst); + case Opcode::V_CMP_LT_F32: + return V_CMP_F32(ConditionOp::LT, false, inst); + case Opcode::V_CMP_EQ_F32: + return V_CMP_F32(ConditionOp::EQ, false, inst); + case Opcode::V_CMP_LE_F32: + return V_CMP_F32(ConditionOp::LE, false, inst); + case Opcode::V_CMP_GT_F32: + return V_CMP_F32(ConditionOp::GT, false, inst); + case Opcode::V_CMP_LG_F32: + return V_CMP_F32(ConditionOp::LG, false, inst); + case Opcode::V_CMP_GE_F32: + return V_CMP_F32(ConditionOp::GE, false, inst); + case Opcode::V_CMP_U_F32: + return V_CMP_F32(ConditionOp::U, false, inst); + case Opcode::V_CMP_NGE_F32: + return V_CMP_F32(ConditionOp::LT, false, inst); + case Opcode::V_CMP_NGT_F32: + return V_CMP_F32(ConditionOp::LE, false, inst); + case Opcode::V_CMP_NLE_F32: + return V_CMP_F32(ConditionOp::GT, false, inst); + case Opcode::V_CMP_NEQ_F32: + return V_CMP_F32(ConditionOp::LG, false, inst); + case Opcode::V_CMP_NLT_F32: + return V_CMP_F32(ConditionOp::GE, false, inst); + // V_CMPX_{OP16}_F32 case Opcode::V_CMPX_F_F32: return V_CMP_F32(ConditionOp::F, true, inst); case Opcode::V_CMPX_LT_F32: @@ -284,19 +226,48 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CMP_F32(ConditionOp::GE, true, inst); case Opcode::V_CMPX_TRU_F32: return V_CMP_F32(ConditionOp::TRU, true, inst); - case Opcode::V_CMP_CLASS_F32: - return V_CMP_CLASS_F32(inst); - case Opcode::V_CMP_LE_U32: - return V_CMP_U32(ConditionOp::LE, false, false, inst); - case Opcode::V_CMP_GT_I32: - return V_CMP_U32(ConditionOp::GT, true, false, inst); + // V_CMP_{OP8}_I32 case Opcode::V_CMP_LT_I32: return V_CMP_U32(ConditionOp::LT, true, false, inst); - case Opcode::V_CMPX_GT_I32: - return V_CMP_U32(ConditionOp::GT, true, true, inst); + case Opcode::V_CMP_EQ_I32: + return V_CMP_U32(ConditionOp::EQ, true, false, inst); + case Opcode::V_CMP_LE_I32: + return V_CMP_U32(ConditionOp::LE, true, false, inst); + case Opcode::V_CMP_GT_I32: + return V_CMP_U32(ConditionOp::GT, true, false, inst); + case Opcode::V_CMP_NE_I32: + return V_CMP_U32(ConditionOp::LG, true, false, inst); + case Opcode::V_CMP_GE_I32: + return V_CMP_U32(ConditionOp::GE, true, false, inst); + + // V_CMPX_{OP8}_I32 case Opcode::V_CMPX_LT_I32: return V_CMP_U32(ConditionOp::LT, true, true, inst); + case Opcode::V_CMPX_GT_I32: + return V_CMP_U32(ConditionOp::GT, true, true, inst); + case Opcode::V_CMPX_LG_I32: + return V_CMP_U32(ConditionOp::LG, true, true, inst); + + // V_CMP_{OP8}_U32 + case Opcode::V_CMP_F_U32: + return V_CMP_U32(ConditionOp::F, false, false, inst); + case Opcode::V_CMP_LT_U32: + return V_CMP_U32(ConditionOp::LT, false, false, inst); + case Opcode::V_CMP_EQ_U32: + return V_CMP_U32(ConditionOp::EQ, false, false, inst); + case Opcode::V_CMP_LE_U32: + return V_CMP_U32(ConditionOp::LE, false, false, inst); + case Opcode::V_CMP_GT_U32: + return V_CMP_U32(ConditionOp::GT, false, false, inst); + case Opcode::V_CMP_NE_U32: + return V_CMP_U32(ConditionOp::LG, false, false, inst); + case Opcode::V_CMP_GE_U32: + return V_CMP_U32(ConditionOp::GE, false, false, inst); + case Opcode::V_CMP_TRU_U32: + return V_CMP_U32(ConditionOp::TRU, false, false, inst); + + // V_CMPX_{OP8}_U32 case Opcode::V_CMPX_F_U32: return V_CMP_U32(ConditionOp::F, false, true, inst); case Opcode::V_CMPX_LT_U32: @@ -313,85 +284,184 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CMP_U32(ConditionOp::GE, false, true, inst); case Opcode::V_CMPX_TRU_U32: return V_CMP_U32(ConditionOp::TRU, false, true, inst); - case Opcode::V_CMPX_LG_I32: - return V_CMP_U32(ConditionOp::LG, true, true, inst); - case Opcode::V_MBCNT_LO_U32_B32: - return V_MBCNT_U32_B32(true, inst); - case Opcode::V_MBCNT_HI_U32_B32: - return V_MBCNT_U32_B32(false, inst); - case Opcode::V_MOVRELS_B32: - return V_MOVRELS_B32(inst); - case Opcode::V_MOVRELD_B32: - return V_MOVRELD_B32(inst); - case Opcode::V_MOVRELSD_B32: - return V_MOVRELSD_B32(inst); + // V_CMP_{OP8}_U64 + case Opcode::V_CMP_NE_U64: + return V_CMP_NE_U64(inst); + + case Opcode::V_CMP_CLASS_F32: + return V_CMP_CLASS_F32(inst); + + // VOP3a + case Opcode::V_MAD_LEGACY_F32: + return V_MAD_F32(inst); + case Opcode::V_MAD_F32: + return V_MAD_F32(inst); + case Opcode::V_MAD_I32_I24: + return V_MAD_I32_I24(inst); + case Opcode::V_MAD_U32_U24: + return V_MAD_U32_U24(inst); + case Opcode::V_CUBEID_F32: + return V_CUBEID_F32(inst); + case Opcode::V_CUBESC_F32: + return V_CUBESC_F32(inst); + case Opcode::V_CUBETC_F32: + return V_CUBETC_F32(inst); + case Opcode::V_CUBEMA_F32: + return V_CUBEMA_F32(inst); + case Opcode::V_BFE_U32: + return V_BFE_U32(false, inst); + case Opcode::V_BFE_I32: + return V_BFE_U32(true, inst); + case Opcode::V_BFI_B32: + return V_BFI_B32(inst); + case Opcode::V_FMA_F32: + return V_FMA_F32(inst); + case Opcode::V_FMA_F64: + return V_FMA_F64(inst); + case Opcode::V_MIN3_F32: + return V_MIN3_F32(inst); + case Opcode::V_MIN3_I32: + return V_MIN3_I32(inst); + case Opcode::V_MAX3_F32: + return V_MAX3_F32(inst); + case Opcode::V_MAX3_I32: + return V_MAX3_U32(true, inst); + case Opcode::V_MAX3_U32: + return V_MAX3_U32(false, inst); + case Opcode::V_MED3_F32: + return V_MED3_F32(inst); + case Opcode::V_MED3_I32: + return V_MED3_I32(inst); + case Opcode::V_SAD_U32: + return V_SAD_U32(inst); + case Opcode::V_LSHL_B64: + return V_LSHL_B64(inst); + case Opcode::V_MUL_F64: + return V_MUL_F64(inst); + case Opcode::V_MAX_F64: + return V_MAX_F64(inst); + case Opcode::V_MUL_LO_U32: + return V_MUL_LO_U32(inst); + case Opcode::V_MUL_HI_U32: + return V_MUL_HI_U32(false, inst); + case Opcode::V_MUL_LO_I32: + return V_MUL_LO_U32(inst); + case Opcode::V_MAD_U64_U32: + return V_MAD_U64_U32(inst); case Opcode::V_NOP: return; - - case Opcode::V_BFM_B32: - return V_BFM_B32(inst); - case Opcode::V_FFBH_U32: - return V_FFBH_U32(inst); default: LogMissingOpcode(inst); } } -void Translator::V_MOV(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0])); +// VOP2 + +void Translator::V_CNDMASK_B32(const GcnInst& inst) { + const IR::VectorReg dst_reg{inst.dst[0].code}; + const IR::ScalarReg flag_reg{inst.src[2].code}; + const IR::U1 flag = inst.src[2].field == OperandField::ScalarGPR + ? ir.GetThreadBitScalarReg(flag_reg) + : ir.GetVcc(); + const IR::Value result = + ir.Select(flag, GetSrc(inst.src[1]), GetSrc(inst.src[0])); + ir.SetVectorReg(dst_reg, IR::U32F32{result}); } -void Translator::V_SAD(const GcnInst& inst) { - const IR::U32 abs_diff = ir.IAbs(ir.ISub(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); - SetDst(inst.dst[0], ir.IAdd(abs_diff, GetSrc(inst.src[2]))); +void Translator::V_ADD_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPAdd(src0, src1)); } -void Translator::V_MAC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.FPFma(GetSrc(inst.src[0]), GetSrc(inst.src[1]), - GetSrc(inst.dst[0]))); +void Translator::V_SUB_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPSub(src0, src1)); } -void Translator::V_CVT_PKRTZ_F16_F32(const GcnInst& inst) { - const IR::VectorReg dst_reg{inst.dst[0].code}; - const IR::Value vec_f32 = - ir.CompositeConstruct(GetSrc(inst.src[0]), GetSrc(inst.src[1])); - ir.SetVectorReg(dst_reg, ir.PackHalf2x16(vec_f32)); +void Translator::V_SUBREV_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPSub(src1, src0)); } -void Translator::V_CVT_F32_F16(const GcnInst& inst) { - const IR::U32 src0 = GetSrc(inst.src[0]); - const IR::U16 src0l = ir.UConvert(16, src0); - SetDst(inst.dst[0], ir.FPConvert(32, ir.BitCast(src0l))); +void Translator::V_MUL_F32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); } -void Translator::V_CVT_F16_F32(const GcnInst& inst) { - const IR::F32 src0 = GetSrc(inst.src[0]); - const IR::F16 src0fp16 = ir.FPConvert(16, src0); - SetDst(inst.dst[0], ir.UConvert(32, ir.BitCast(src0fp16))); +void Translator::V_MUL_I32_I24(const GcnInst& inst) { + const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(24), true)}; + const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(24), true)}; + SetDst(inst.dst[0], ir.IMul(src0, src1)); } -void Translator::V_MUL_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); +void Translator::V_MIN_F32(const GcnInst& inst, bool is_legacy) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPMin(src0, src1, is_legacy)); } -void Translator::V_CNDMASK_B32(const GcnInst& inst) { - const IR::VectorReg dst_reg{inst.dst[0].code}; - const IR::ScalarReg flag_reg{inst.src[2].code}; - const IR::U1 flag = inst.src[2].field == OperandField::ScalarGPR - ? ir.GetThreadBitScalarReg(flag_reg) - : ir.GetVcc(); - const IR::Value result = - ir.Select(flag, GetSrc(inst.src[1]), GetSrc(inst.src[0])); - ir.SetVectorReg(dst_reg, IR::U32F32{result}); +void Translator::V_MAX_F32(const GcnInst& inst, bool is_legacy) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPMax(src0, src1, is_legacy)); +} + +void Translator::V_MIN_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.SMin(src0, src1)); +} + +void Translator::V_MIN_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IMin(src0, src1, false)); +} + +void Translator::V_MAX_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IMax(src0, src1, is_signed)); +} + +void Translator::V_LSHR_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +} + +void Translator::V_LSHRREV_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); +} + +void Translator::V_ASHR_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightArithmetic(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +} + +void Translator::V_ASHRREV_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightArithmetic(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); } -void Translator::V_OR_B32(bool is_xor, const GcnInst& inst) { +void Translator::V_LSHL_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +} + +void Translator::V_LSHLREV_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, - is_xor ? ir.BitwiseXor(src0, src1) : IR::U32(ir.BitwiseOr(src0, src1))); + ir.SetVectorReg(dst_reg, ir.ShiftLeftLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); } void Translator::V_AND_B32(const GcnInst& inst) { @@ -401,27 +471,65 @@ void Translator::V_AND_B32(const GcnInst& inst) { ir.SetVectorReg(dst_reg, ir.BitwiseAnd(src0, src1)); } -void Translator::V_LSHLREV_B32(const GcnInst& inst) { +void Translator::V_OR_B32(bool is_xor, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.ShiftLeftLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); + ir.SetVectorReg(dst_reg, + is_xor ? ir.BitwiseXor(src0, src1) : IR::U32(ir.BitwiseOr(src0, src1))); } -void Translator::V_LSHL_B32(const GcnInst& inst) { +void Translator::V_BFM_B32(const GcnInst& inst) { + // bitmask width + const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(4))}; + // bitmask offset + const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(4))}; + const IR::U32 ones = ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1), src0), ir.Imm32(1)); + SetDst(inst.dst[0], ir.ShiftLeftLogical(ones, src1)); +} + +void Translator::V_MAC_F32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.FPFma(GetSrc(inst.src[0]), GetSrc(inst.src[1]), + GetSrc(inst.dst[0]))); +} + +void Translator::V_MADMK_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 k{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPFma(src0, k, src1)); +} + +void Translator::V_BCNT_U32_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); + SetDst(inst.dst[0], ir.IAdd(ir.BitCount(src0), src1)); } -void Translator::V_LSHL_B64(const GcnInst& inst) { - const IR::U64 src0{GetSrc64(inst.src[0])}; - const IR::U64 src1{GetSrc64(inst.src[1])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ASSERT_MSG(src0.IsImmediate() && src0.U64() == 0 && src1.IsImmediate() && src1.U64() == 0, - "V_LSHL_B64 with non-zero src0 or src1 is not supported"); - ir.SetVectorReg(dst_reg, ir.Imm32(0)); - ir.SetVectorReg(dst_reg + 1, ir.Imm32(0)); +void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { + if (!is_low) { + // v_mbcnt_hi_u32_b32 v2, -1, 0 + if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193 && + inst.src[1].field == OperandField::ConstZero) { + return; + } + // v_mbcnt_hi_u32_b32 vX, exec_hi, 0 + if (inst.src[0].field == OperandField::ExecHi && + inst.src[1].field == OperandField::ConstZero) { + return; + } + } else { + // v_mbcnt_lo_u32_b32 v2, -1, vX + // used combined with above to fetch lane id in non-compute stages + if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193) { + SetDst(inst.dst[0], ir.LaneId()); + } + // v_mbcnt_lo_u32_b32 v20, exec_lo, vX + // used combined in above for append buffer indexing. + if (inst.src[0].field == OperandField::ExecLo) { + SetDst(inst.dst[0], ir.Imm32(0)); + } + } } void Translator::V_ADD_I32(const GcnInst& inst) { @@ -432,8 +540,20 @@ void Translator::V_ADD_I32(const GcnInst& inst) { // TODO: Carry } -void Translator::V_ADDC_U32(const GcnInst& inst) { +void Translator::V_SUB_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ISub(src0, src1)); +} + +void Translator::V_SUBREV_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ISub(src1, src0)); + // TODO: Carry-out +} +void Translator::V_ADDC_U32(const GcnInst& inst) { const auto src0 = GetSrc(inst.src[0]); const auto src1 = GetSrc(inst.src[1]); @@ -462,6 +582,25 @@ void Translator::V_ADDC_U32(const GcnInst& inst) { ir.SetVcc(did_overflow); } +void Translator::V_LDEXP_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPLdexp(src0, src1)); +} + +void Translator::V_CVT_PKRTZ_F16_F32(const GcnInst& inst) { + const IR::VectorReg dst_reg{inst.dst[0].code}; + const IR::Value vec_f32 = + ir.CompositeConstruct(GetSrc(inst.src[0]), GetSrc(inst.src[1])); + ir.SetVectorReg(dst_reg, ir.PackHalf2x16(vec_f32)); +} + +// VOP1 + +void Translator::V_MOV(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[0])); +} + void Translator::V_CVT_F32_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::VectorReg dst_reg{inst.dst[0].code}; @@ -474,23 +613,31 @@ void Translator::V_CVT_F32_U32(const GcnInst& inst) { ir.SetVectorReg(dst_reg, ir.ConvertUToF(32, 32, src0)); } -void Translator::V_MAD_F32(const GcnInst& inst) { +void Translator::V_CVT_U32_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); + SetDst(inst.dst[0], ir.ConvertFToU(32, src0)); } -void Translator::V_FRACT_F32(const GcnInst& inst) { +void Translator::V_CVT_I32_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.Fract(src0)); + SetDst(inst.dst[0], ir.ConvertFToS(32, src0)); } -void Translator::V_ADD_F32(const GcnInst& inst) { +void Translator::V_CVT_F16_F32(const GcnInst& inst) { + const IR::F32 src0 = GetSrc(inst.src[0]); + const IR::F16 src0fp16 = ir.FPConvert(16, src0); + SetDst(inst.dst[0], ir.UConvert(32, ir.BitCast(src0fp16))); +} + +void Translator::V_CVT_F32_F16(const GcnInst& inst) { + const IR::U32 src0 = GetSrc(inst.src[0]); + const IR::U16 src0l = ir.UConvert(16, src0); + SetDst(inst.dst[0], ir.FPConvert(32, ir.BitCast(src0l))); +} + +void Translator::V_CVT_FLR_I32_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPAdd(src0, src1)); + SetDst(inst.dst[0], ir.ConvertFToI(32, true, ir.FPFloor(src0))); } void Translator::V_CVT_OFF_F32_I4(const GcnInst& inst) { @@ -503,20 +650,31 @@ void Translator::V_CVT_OFF_F32_I4(const GcnInst& inst) { ir.SetVectorReg(dst_reg, ir.Imm32(IntToFloat[src0.U32() & 0xF])); } -void Translator::V_MED3_F32(const GcnInst& inst) { +void Translator::V_CVT_F32_UBYTE(u32 index, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 byte = ir.BitFieldExtract(src0, ir.Imm32(8 * index), ir.Imm32(8)); + SetDst(inst.dst[0], ir.ConvertUToF(32, 32, byte)); +} + +void Translator::V_FRACT_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - const IR::F32 mmx = ir.FPMin(ir.FPMax(src0, src1), src2); - SetDst(inst.dst[0], ir.FPMax(ir.FPMin(src0, src1), mmx)); + const IR::VectorReg dst_reg{inst.dst[0].code}; + ir.SetVectorReg(dst_reg, ir.Fract(src0)); } -void Translator::V_MED3_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - const IR::U32 mmx = ir.SMin(ir.SMax(src0, src1), src2); - SetDst(inst.dst[0], ir.SMax(ir.SMin(src0, src1), mmx)); +void Translator::V_TRUNC_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPTrunc(src0)); +} + +void Translator::V_CEIL_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPCeil(src0)); +} + +void Translator::V_RNDNE_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPRoundEven(src0)); } void Translator::V_FLOOR_F32(const GcnInst& inst) { @@ -525,22 +683,19 @@ void Translator::V_FLOOR_F32(const GcnInst& inst) { ir.SetVectorReg(dst_reg, ir.FPFloor(src0)); } -void Translator::V_SUB_F32(const GcnInst& inst) { +void Translator::V_EXP_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPSub(src0, src1)); + SetDst(inst.dst[0], ir.FPExp2(src0)); } -void Translator::V_RCP_F32(const GcnInst& inst) { +void Translator::V_LOG_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPRecip(src0)); + SetDst(inst.dst[0], ir.FPLog2(src0)); } -void Translator::V_FMA_F32(const GcnInst& inst) { +void Translator::V_RCP_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); + SetDst(inst.dst[0], ir.FPRecip(src0)); } void Translator::V_RCP_F64(const GcnInst& inst) { @@ -548,13 +703,79 @@ void Translator::V_RCP_F64(const GcnInst& inst) { SetDst64(inst.dst[0], ir.FPRecip(src0)); } -void Translator::V_FMA_F64(const GcnInst& inst) { - const IR::F64 src0{GetSrc64(inst.src[0])}; - const IR::F64 src1{GetSrc64(inst.src[1])}; - const IR::F64 src2{GetSrc64(inst.src[2])}; - SetDst64(inst.dst[0], ir.FPFma(src0, src1, src2)); +void Translator::V_RSQ_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPRecipSqrt(src0)); +} + +void Translator::V_SQRT_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPSqrt(src0)); +} + +void Translator::V_SIN_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPSin(src0)); +} + +void Translator::V_COS_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPCos(src0)); +} + +void Translator::V_NOT_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.BitwiseNot(src0)); +} + +void Translator::V_BFREV_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.BitReverse(src0)); +} + +void Translator::V_FFBH_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + // Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB) + // position + const IR::U32 msb_pos = ir.FindUMsb(src0); + const IR::U32 pos_from_left = ir.ISub(ir.Imm32(31), msb_pos); + // Select 0xFFFFFFFF if src0 was 0 + const IR::U1 cond = ir.INotEqual(src0, ir.Imm32(0)); + SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))}); +} + +void Translator::V_FFBL_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FindILsb(src0)); +} + +void Translator::V_MOVRELD_B32(const GcnInst& inst) { + const IR::U32 src_val{GetSrc(inst.src[0])}; + u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); + IR::U32 m0 = ir.GetM0(); + + VMovRelDHelper(dst_vgprno, src_val, m0); +} + +void Translator::V_MOVRELS_B32(const GcnInst& inst) { + u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); + const IR::U32 m0 = ir.GetM0(); + + const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); + SetDst(inst.dst[0], src_val); +} + +void Translator::V_MOVRELSD_B32(const GcnInst& inst) { + u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); + u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); + IR::U32 m0 = ir.GetM0(); + + const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); + VMovRelDHelper(dst_vgprno, src_val, m0); } +// VOPC + void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::F32 src1{GetSrc(inst.src[1])}; @@ -596,134 +817,6 @@ void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) { } } -void Translator::V_MAX_F32(const GcnInst& inst, bool is_legacy) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPMax(src0, src1, is_legacy)); -} - -void Translator::V_MUL_F64(const GcnInst& inst) { - const IR::F64 src0{GetSrc64(inst.src[0])}; - const IR::F64 src1{GetSrc64(inst.src[1])}; - SetDst64(inst.dst[0], ir.FPMul(src0, src1)); -} - -void Translator::V_MAX_F64(const GcnInst& inst) { - const IR::F64 src0{GetSrc64(inst.src[0])}; - const IR::F64 src1{GetSrc64(inst.src[1])}; - SetDst64(inst.dst[0], ir.FPMax(src0, src1)); -} - -void Translator::V_MAX_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IMax(src0, src1, is_signed)); -} - -void Translator::V_RSQ_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPRecipSqrt(src0)); -} - -void Translator::V_SIN_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPSin(src0)); -} - -void Translator::V_LOG_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPLog2(src0)); -} - -void Translator::V_EXP_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPExp2(src0)); -} - -void Translator::V_SQRT_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPSqrt(src0)); -} - -void Translator::V_MIN_F32(const GcnInst& inst, bool is_legacy) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPMin(src0, src1, is_legacy)); -} - -void Translator::V_MIN3_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPMin(src0, ir.FPMin(src1, src2))); -} - -void Translator::V_MIN3_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.SMin(src0, ir.SMin(src1, src2))); -} - -void Translator::V_MADMK_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 k{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPFma(src0, k, src1)); -} - -void Translator::V_CUBEMA_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.Imm32(1.f)); -} - -void Translator::V_CUBESC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0])); -} - -void Translator::V_CUBETC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[1])); -} - -void Translator::V_CUBEID_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[2])); -} - -void Translator::V_CVT_U32_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.ConvertFToU(32, src0)); -} - -void Translator::V_SUBREV_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPSub(src1, src0)); -} - -void Translator::V_SUBREV_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src1, src0)); - // TODO: Carry-out -} - -void Translator::V_MAD_U64_U32(const GcnInst& inst) { - const auto src0 = GetSrc(inst.src[0]); - const auto src1 = GetSrc(inst.src[1]); - const auto src2 = GetSrc64(inst.src[2]); - - // const IR::U64 mul_result = ir.UConvert(64, ir.IMul(src0, src1)); - const IR::U64 mul_result = - ir.PackUint2x32(ir.CompositeConstruct(ir.IMul(src0, src1), ir.Imm32(0U))); - const IR::U64 sum_result = ir.IAdd(mul_result, src2); - - SetDst64(inst.dst[0], sum_result); - - const IR::U1 less_src0 = ir.ILessThan(sum_result, mul_result, false); - const IR::U1 less_src1 = ir.ILessThan(sum_result, src2, false); - const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); - ir.SetVcc(did_overflow); -} - void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; @@ -762,41 +855,68 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const } } -void Translator::V_LSHRREV_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); -} - -void Translator::V_MUL_HI_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 hi{ir.CompositeExtract(ir.IMulExt(src0, src1, is_signed), 1)}; - SetDst(inst.dst[0], hi); +void Translator::V_CMP_NE_U64(const GcnInst& inst) { + const auto get_src = [&](const InstOperand& operand) { + switch (operand.field) { + case OperandField::VccLo: + return ir.GetVcc(); + case OperandField::ExecLo: + return ir.GetExec(); + case OperandField::ScalarGPR: + return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); + case OperandField::ConstZero: + return ir.Imm1(false); + default: + UNREACHABLE(); + } + }; + const IR::U1 src0{get_src(inst.src[0])}; + ASSERT(inst.src[1].field == OperandField::ConstZero); // src0 != 0 + switch (inst.dst[1].field) { + case OperandField::VccLo: + ir.SetVcc(src0); + break; + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), src0); + break; + default: + UNREACHABLE(); + } } -void Translator::V_SAD_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; +void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - IR::U32 result; - if (src0.IsImmediate() && src0.U32() == 0U) { - result = src1; - } else if (src1.IsImmediate() && src1.U32() == 0U) { - result = src0; + IR::U1 value; + if (src1.IsImmediate()) { + const auto class_mask = static_cast(src1.U32()); + if ((class_mask & IR::FloatClassFunc::NaN) == IR::FloatClassFunc::NaN) { + value = ir.FPIsNan(src0); + } else if ((class_mask & IR::FloatClassFunc::Infinity) == IR::FloatClassFunc::Infinity) { + value = ir.FPIsInf(src0); + } else { + UNREACHABLE(); + } } else { - const IR::U32 max{ir.IMax(src0, src1, false)}; - const IR::U32 min{ir.IMin(src0, src1, false)}; - result = ir.ISub(max, min); + // We don't know the type yet, delay its resolution. + value = ir.FPCmpClass32(src0, src1); + } + + switch (inst.dst[1].field) { + case OperandField::VccLo: + return ir.SetVcc(value); + default: + UNREACHABLE(); } - SetDst(inst.dst[0], ir.IAdd(result, src2)); } -void Translator::V_BFE_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; - const IR::U32 src2{ir.BitwiseAnd(GetSrc(inst.src[2]), ir.Imm32(0x1F))}; - SetDst(inst.dst[0], ir.BitFieldExtract(src0, src1, src2, is_signed)); +// VOP3a + +void Translator::V_MAD_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); } void Translator::V_MAD_I32_I24(const GcnInst& inst, bool is_signed) { @@ -808,242 +928,172 @@ void Translator::V_MAD_I32_I24(const GcnInst& inst, bool is_signed) { SetDst(inst.dst[0], ir.IAdd(ir.IMul(src0, src1), src2)); } -void Translator::V_MUL_I32_I24(const GcnInst& inst) { - const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(24), true)}; - const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(24), true)}; - SetDst(inst.dst[0], ir.IMul(src0, src1)); -} - -void Translator::V_SUB_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src0, src1)); +void Translator::V_MAD_U32_U24(const GcnInst& inst) { + V_MAD_I32_I24(inst, false); } -void Translator::V_LSHR_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +void Translator::V_CUBEID_F32(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[2])); } -void Translator::V_ASHRREV_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightArithmetic(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); +void Translator::V_CUBESC_F32(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[0])); } -void Translator::V_ASHR_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightArithmetic(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +void Translator::V_CUBETC_F32(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[1])); } -void Translator::V_MAD_U32_U24(const GcnInst& inst) { - V_MAD_I32_I24(inst, false); +void Translator::V_CUBEMA_F32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.Imm32(1.f)); } -void Translator::V_RNDNE_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPRoundEven(src0)); +void Translator::V_BFE_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; + const IR::U32 src2{ir.BitwiseAnd(GetSrc(inst.src[2]), ir.Imm32(0x1F))}; + SetDst(inst.dst[0], ir.BitFieldExtract(src0, src1, src2, is_signed)); } -void Translator::V_BCNT_U32_B32(const GcnInst& inst) { +void Translator::V_BFI_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IAdd(ir.BitCount(src0), src1)); + const IR::U32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], + ir.BitwiseOr(ir.BitwiseAnd(src0, src1), ir.BitwiseAnd(ir.BitwiseNot(src0), src2))); } -void Translator::V_COS_F32(const GcnInst& inst) { +void Translator::V_FMA_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPCos(src0)); + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); } -void Translator::V_MAX3_F32(const GcnInst& inst) { +void Translator::V_FMA_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + const IR::F64 src1{GetSrc64(inst.src[1])}; + const IR::F64 src2{GetSrc64(inst.src[2])}; + SetDst64(inst.dst[0], ir.FPFma(src0, src1, src2)); +} + +void Translator::V_MIN3_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::F32 src1{GetSrc(inst.src[1])}; const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPMax(src0, ir.FPMax(src1, src2))); + SetDst(inst.dst[0], ir.FPMin(src0, ir.FPMin(src1, src2))); } -void Translator::V_MAX3_U32(bool is_signed, const GcnInst& inst) { +void Translator::V_MIN3_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.IMax(src0, ir.IMax(src1, src2, is_signed), is_signed)); + SetDst(inst.dst[0], ir.SMin(src0, ir.SMin(src1, src2))); } -void Translator::V_CVT_I32_F32(const GcnInst& inst) { +void Translator::V_MAX3_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.ConvertFToS(32, src0)); -} - -void Translator::V_MIN_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.SMin(src0, src1)); + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPMax(src0, ir.FPMax(src1, src2))); } -void Translator::V_MUL_LO_U32(const GcnInst& inst) { +void Translator::V_MAX3_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IMul(src0, src1)); -} - -void Translator::V_TRUNC_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPTrunc(src0)); + const IR::U32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.IMax(src0, ir.IMax(src1, src2, is_signed), is_signed)); } -void Translator::V_CEIL_F32(const GcnInst& inst) { +void Translator::V_MED3_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPCeil(src0)); + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + const IR::F32 mmx = ir.FPMin(ir.FPMax(src0, src1), src2); + SetDst(inst.dst[0], ir.FPMax(ir.FPMin(src0, src1), mmx)); } -void Translator::V_MIN_U32(const GcnInst& inst) { +void Translator::V_MED3_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IMin(src0, src1, false)); + const IR::U32 src2{GetSrc(inst.src[2])}; + const IR::U32 mmx = ir.SMin(ir.SMax(src0, src1), src2); + SetDst(inst.dst[0], ir.SMax(ir.SMin(src0, src1), mmx)); } -void Translator::V_CMP_NE_U64(const GcnInst& inst) { - const auto get_src = [&](const InstOperand& operand) { - switch (operand.field) { - case OperandField::VccLo: - return ir.GetVcc(); - case OperandField::ExecLo: - return ir.GetExec(); - case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); - case OperandField::ConstZero: - return ir.Imm1(false); - default: - UNREACHABLE(); - } - }; - const IR::U1 src0{get_src(inst.src[0])}; - ASSERT(inst.src[1].field == OperandField::ConstZero); // src0 != 0 - switch (inst.dst[1].field) { - case OperandField::VccLo: - ir.SetVcc(src0); - break; - case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), src0); - break; - default: - UNREACHABLE(); - } +void Translator::V_SAD(const GcnInst& inst) { + const IR::U32 abs_diff = ir.IAbs(ir.ISub(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); + SetDst(inst.dst[0], ir.IAdd(abs_diff, GetSrc(inst.src[2]))); } -void Translator::V_BFI_B32(const GcnInst& inst) { +void Translator::V_SAD_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], - ir.BitwiseOr(ir.BitwiseAnd(src0, src1), ir.BitwiseAnd(ir.BitwiseNot(src0), src2))); + IR::U32 result; + if (src0.IsImmediate() && src0.U32() == 0U) { + result = src1; + } else if (src1.IsImmediate() && src1.U32() == 0U) { + result = src0; + } else { + const IR::U32 max{ir.IMax(src0, src1, false)}; + const IR::U32 min{ir.IMin(src0, src1, false)}; + result = ir.ISub(max, min); + } + SetDst(inst.dst[0], ir.IAdd(result, src2)); } -void Translator::V_NOT_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.BitwiseNot(src0)); +void Translator::V_LSHL_B64(const GcnInst& inst) { + const IR::U64 src0{GetSrc64(inst.src[0])}; + const IR::U64 src1{GetSrc64(inst.src[1])}; + const IR::VectorReg dst_reg{inst.dst[0].code}; + ASSERT_MSG(src0.IsImmediate() && src0.U64() == 0 && src1.IsImmediate() && src1.U64() == 0, + "V_LSHL_B64 with non-zero src0 or src1 is not supported"); + ir.SetVectorReg(dst_reg, ir.Imm32(0)); + ir.SetVectorReg(dst_reg + 1, ir.Imm32(0)); } -void Translator::V_CVT_F32_UBYTE(u32 index, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 byte = ir.BitFieldExtract(src0, ir.Imm32(8 * index), ir.Imm32(8)); - SetDst(inst.dst[0], ir.ConvertUToF(32, 32, byte)); +void Translator::V_MUL_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + const IR::F64 src1{GetSrc64(inst.src[1])}; + SetDst64(inst.dst[0], ir.FPMul(src0, src1)); } -void Translator::V_BFREV_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.BitReverse(src0)); +void Translator::V_MAX_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + const IR::F64 src1{GetSrc64(inst.src[1])}; + SetDst64(inst.dst[0], ir.FPMax(src0, src1)); } -void Translator::V_LDEXP_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; +void Translator::V_MUL_LO_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPLdexp(src0, src1)); -} - -void Translator::V_CVT_FLR_I32_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.ConvertFToI(32, true, ir.FPFloor(src0))); + SetDst(inst.dst[0], ir.IMul(src0, src1)); } -void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; +void Translator::V_MUL_HI_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - IR::U1 value; - if (src1.IsImmediate()) { - const auto class_mask = static_cast(src1.U32()); - if ((class_mask & IR::FloatClassFunc::NaN) == IR::FloatClassFunc::NaN) { - value = ir.FPIsNan(src0); - } else if ((class_mask & IR::FloatClassFunc::Infinity) == IR::FloatClassFunc::Infinity) { - value = ir.FPIsInf(src0); - } else { - UNREACHABLE(); - } - } else { - // We don't know the type yet, delay its resolution. - value = ir.FPCmpClass32(src0, src1); - } - - switch (inst.dst[1].field) { - case OperandField::VccLo: - return ir.SetVcc(value); - default: - UNREACHABLE(); - } + const IR::U32 hi{ir.CompositeExtract(ir.IMulExt(src0, src1, is_signed), 1)}; + SetDst(inst.dst[0], hi); } -void Translator::V_FFBL_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FindILsb(src0)); -} +void Translator::V_MAD_U64_U32(const GcnInst& inst) { + const auto src0 = GetSrc(inst.src[0]); + const auto src1 = GetSrc(inst.src[1]); + const auto src2 = GetSrc64(inst.src[2]); -void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { - if (!is_low) { - // v_mbcnt_hi_u32_b32 v2, -1, 0 - if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193 && - inst.src[1].field == OperandField::ConstZero) { - return; - } - // v_mbcnt_hi_u32_b32 vX, exec_hi, 0 - if (inst.src[0].field == OperandField::ExecHi && - inst.src[1].field == OperandField::ConstZero) { - return; - } - } else { - // v_mbcnt_lo_u32_b32 v2, -1, vX - // used combined with above to fetch lane id in non-compute stages - if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193) { - SetDst(inst.dst[0], ir.LaneId()); - } - // v_mbcnt_lo_u32_b32 v20, exec_lo, vX - // used combined in above for append buffer indexing. - if (inst.src[0].field == OperandField::ExecLo) { - SetDst(inst.dst[0], ir.Imm32(0)); - } - } -} + // const IR::U64 mul_result = ir.UConvert(64, ir.IMul(src0, src1)); + const IR::U64 mul_result = + ir.PackUint2x32(ir.CompositeConstruct(ir.IMul(src0, src1), ir.Imm32(0U))); + const IR::U64 sum_result = ir.IAdd(mul_result, src2); -void Translator::V_BFM_B32(const GcnInst& inst) { - // bitmask width - const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(4))}; - // bitmask offset - const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(4))}; - const IR::U32 ones = ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1), src0), ir.Imm32(1)); - SetDst(inst.dst[0], ir.ShiftLeftLogical(ones, src1)); -} + SetDst64(inst.dst[0], sum_result); -void Translator::V_FFBH_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - // Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB) - // position - const IR::U32 msb_pos = ir.FindUMsb(src0); - const IR::U32 pos_from_left = ir.ISub(ir.Imm32(31), msb_pos); - // Select 0xFFFFFFFF if src0 was 0 - const IR::U1 cond = ir.INotEqual(src0, ir.Imm32(0)); - SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))}); + const IR::U1 less_src0 = ir.ILessThan(sum_result, mul_result, false); + const IR::U1 less_src1 = ir.ILessThan(sum_result, src2, false); + const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); + ir.SetVcc(did_overflow); } // TODO: add range analysis pass to hopefully put an upper bound on m0, and only select one of @@ -1069,29 +1119,4 @@ void Translator::VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR: } } -void Translator::V_MOVRELS_B32(const GcnInst& inst) { - u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); - const IR::U32 m0 = ir.GetM0(); - - const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); - SetDst(inst.dst[0], src_val); -} - -void Translator::V_MOVRELD_B32(const GcnInst& inst) { - const IR::U32 src_val{GetSrc(inst.src[0])}; - u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); - IR::U32 m0 = ir.GetM0(); - - VMovRelDHelper(dst_vgprno, src_val, m0); -} - -void Translator::V_MOVRELSD_B32(const GcnInst& inst) { - u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); - u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); - IR::U32 m0 = ir.GetM0(); - - const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); - VMovRelDHelper(dst_vgprno, src_val, m0); -} - } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp index c12ae8f57b9..8617370ac35 100644 --- a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp +++ b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp @@ -5,6 +5,22 @@ namespace Shader::Gcn { +void Translator::EmitVectorInterpolation(const GcnInst& inst) { + switch (inst.opcode) { + // VINTRP + case Opcode::V_INTERP_P1_F32: + return; + case Opcode::V_INTERP_P2_F32: + return V_INTERP_P2_F32(inst); + case Opcode::V_INTERP_MOV_F32: + return V_INTERP_MOV_F32(inst); + default: + LogMissingOpcode(inst); + } +} + +// VINTRP + void Translator::V_INTERP_P2_F32(const GcnInst& inst) { const IR::VectorReg dst_reg{inst.dst[0].code}; auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); @@ -19,17 +35,4 @@ void Translator::V_INTERP_MOV_F32(const GcnInst& inst) { ir.SetVectorReg(dst_reg, ir.GetAttribute(attrib, inst.control.vintrp.chan)); } -void Translator::EmitVectorInterpolation(const GcnInst& inst) { - switch (inst.opcode) { - case Opcode::V_INTERP_P1_F32: - return; - case Opcode::V_INTERP_P2_F32: - return V_INTERP_P2_F32(inst); - case Opcode::V_INTERP_MOV_F32: - return V_INTERP_MOV_F32(inst); - default: - LogMissingOpcode(inst); - } -} - } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index e0f35fb90b4..212d7fdc55e 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -7,55 +7,7 @@ namespace Shader::Gcn { void Translator::EmitVectorMemory(const GcnInst& inst) { switch (inst.opcode) { - case Opcode::IMAGE_SAMPLE_LZ_O: - case Opcode::IMAGE_SAMPLE_O: - case Opcode::IMAGE_SAMPLE_C: - case Opcode::IMAGE_SAMPLE_C_LZ: - case Opcode::IMAGE_SAMPLE_LZ: - case Opcode::IMAGE_SAMPLE: - case Opcode::IMAGE_SAMPLE_L: - case Opcode::IMAGE_SAMPLE_L_O: - case Opcode::IMAGE_SAMPLE_C_O: - case Opcode::IMAGE_SAMPLE_B: - case Opcode::IMAGE_SAMPLE_C_LZ_O: - case Opcode::IMAGE_SAMPLE_D: - case Opcode::IMAGE_SAMPLE_CD: - return IMAGE_SAMPLE(inst); - case Opcode::IMAGE_GATHER4_LZ: - case Opcode::IMAGE_GATHER4_C: - case Opcode::IMAGE_GATHER4_C_LZ: - case Opcode::IMAGE_GATHER4_LZ_O: - return IMAGE_GATHER(inst); - case Opcode::IMAGE_ATOMIC_ADD: - return IMAGE_ATOMIC(AtomicOp::Add, inst); - case Opcode::IMAGE_ATOMIC_AND: - return IMAGE_ATOMIC(AtomicOp::And, inst); - case Opcode::IMAGE_ATOMIC_OR: - return IMAGE_ATOMIC(AtomicOp::Or, inst); - case Opcode::IMAGE_ATOMIC_XOR: - return IMAGE_ATOMIC(AtomicOp::Xor, inst); - case Opcode::IMAGE_ATOMIC_UMAX: - return IMAGE_ATOMIC(AtomicOp::Umax, inst); - case Opcode::IMAGE_ATOMIC_SMAX: - return IMAGE_ATOMIC(AtomicOp::Smax, inst); - case Opcode::IMAGE_ATOMIC_UMIN: - return IMAGE_ATOMIC(AtomicOp::Umin, inst); - case Opcode::IMAGE_ATOMIC_SMIN: - return IMAGE_ATOMIC(AtomicOp::Smin, inst); - case Opcode::IMAGE_ATOMIC_INC: - return IMAGE_ATOMIC(AtomicOp::Inc, inst); - case Opcode::IMAGE_ATOMIC_DEC: - return IMAGE_ATOMIC(AtomicOp::Dec, inst); - case Opcode::IMAGE_GET_LOD: - return IMAGE_GET_LOD(inst); - case Opcode::IMAGE_STORE: - return IMAGE_STORE(inst); - case Opcode::IMAGE_LOAD_MIP: - return IMAGE_LOAD(true, inst); - case Opcode::IMAGE_LOAD: - return IMAGE_LOAD(false, inst); - case Opcode::IMAGE_GET_RESINFO: - return IMAGE_GET_RESINFO(inst); + // MUBUF / MTBUF // Buffer load operations case Opcode::TBUFFER_LOAD_FORMAT_X: @@ -137,237 +89,72 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { case Opcode::BUFFER_ATOMIC_DEC: return BUFFER_ATOMIC(AtomicOp::Dec, inst); - default: - LogMissingOpcode(inst); - } -} - -void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) { - IR::VectorReg dst_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - const auto flags = ImageResFlags(inst.control.mimg.dmask); - const bool has_mips = flags.test(ImageResComponent::MipCount); - const IR::U32 lod = ir.GetVectorReg(IR::VectorReg(inst.src[0].code)); - const IR::Value tsharp = ir.GetScalarReg(tsharp_reg); - const IR::Value size = ir.ImageQueryDimension(tsharp, lod, ir.Imm1(has_mips)); - - if (flags.test(ImageResComponent::Width)) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 0)}); - } - if (flags.test(ImageResComponent::Height)) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 1)}); - } - if (flags.test(ImageResComponent::Depth)) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 2)}); - } - if (has_mips) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 3)}); - } -} - -void Translator::IMAGE_SAMPLE(const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg dest_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; - const auto flags = MimgModifierFlags(mimg.mod); - - // Load first dword of T# and S#. We will use them as the handle that will guide resource - // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture - // binding index. - const IR::Value handle = - ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); - - // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction - // Set Architecture - const IR::U32 offset = - flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::U32{}; - const IR::F32 bias = - flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - const IR::F32 dref = - flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - const IR::Value derivatives = [&] -> IR::Value { - if (!flags.test(MimgModifier::Derivative)) { - return {}; - } - addr_reg = addr_reg + 4; - return ir.CompositeConstruct( - ir.GetVectorReg(addr_reg - 4), ir.GetVectorReg(addr_reg - 3), - ir.GetVectorReg(addr_reg - 2), ir.GetVectorReg(addr_reg - 1)); - }(); - - // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler - // Since these are at most 4 dwords, we load them into a single uvec4 and place them - // in coords field of the instruction. Then the resource tracking pass will patch the - // IR instruction to fill in lod_clamp field. - const IR::Value body = ir.CompositeConstruct( - ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); - - // Derivatives are tricky because their number depends on the texture type which is located in - // T#. We don't have access to T# though until resource tracking pass. For now assume if - // derivatives are present, that a 2D image is bound. - const bool has_derivatives = flags.test(MimgModifier::Derivative); - const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod); - - IR::TextureInstInfo info{}; - info.is_depth.Assign(flags.test(MimgModifier::Pcf)); - info.has_bias.Assign(flags.test(MimgModifier::LodBias)); - info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); - info.force_level0.Assign(flags.test(MimgModifier::Level0)); - info.has_offset.Assign(flags.test(MimgModifier::Offset)); - info.explicit_lod.Assign(explicit_lod); - info.has_derivatives.Assign(has_derivatives); - - // Issue IR instruction, leaving unknown fields blank to patch later. - const IR::Value texel = [&]() -> IR::Value { - if (has_derivatives) { - return ir.ImageGradient(handle, body, derivatives, offset, {}, info); - } - if (!flags.test(MimgModifier::Pcf)) { - if (explicit_lod) { - return ir.ImageSampleExplicitLod(handle, body, offset, info); - } else { - return ir.ImageSampleImplicitLod(handle, body, bias, offset, info); - } - } - if (explicit_lod) { - return ir.ImageSampleDrefExplicitLod(handle, body, dref, offset, info); - } - return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, info); - }(); - - for (u32 i = 0; i < 4; i++) { - if (((mimg.dmask >> i) & 1) == 0) { - continue; - } - IR::F32 value; - if (flags.test(MimgModifier::Pcf)) { - value = i < 3 ? IR::F32{texel} : ir.Imm32(1.0f); - } else { - value = IR::F32{ir.CompositeExtract(texel, i)}; - } - ir.SetVectorReg(dest_reg++, value); - } -} - -void Translator::IMAGE_GATHER(const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - if (mimg.da) { - LOG_WARNING(Render_Vulkan, "Image instruction declares an array"); - } - - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg dest_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; - const auto flags = MimgModifierFlags(mimg.mod); - - // Load first dword of T# and S#. We will use them as the handle that will guide resource - // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture - // binding index. - const IR::Value handle = - ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); - - // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction - // Set Architecture - const IR::Value offset = - flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::Value{}; - const IR::F32 bias = - flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - const IR::F32 dref = - flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - - // Derivatives are tricky because their number depends on the texture type which is located in - // T#. We don't have access to T# though until resource tracking pass. For now assume no - // derivatives are present, otherwise we don't know where coordinates are placed in the address - // stream. - ASSERT_MSG(!flags.test(MimgModifier::Derivative), "Derivative image instruction"); - - // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler - // Since these are at most 4 dwords, we load them into a single uvec4 and place them - // in coords field of the instruction. Then the resource tracking pass will patch the - // IR instruction to fill in lod_clamp field. - const IR::Value body = ir.CompositeConstruct( - ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); - - const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod); - - IR::TextureInstInfo info{}; - info.is_depth.Assign(flags.test(MimgModifier::Pcf)); - info.has_bias.Assign(flags.test(MimgModifier::LodBias)); - info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); - info.force_level0.Assign(flags.test(MimgModifier::Level0)); - info.has_offset.Assign(flags.test(MimgModifier::Offset)); - // info.explicit_lod.Assign(explicit_lod); - info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1); - - // Issue IR instruction, leaving unknown fields blank to patch later. - const IR::Value texel = [&]() -> IR::Value { - const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{}; - if (!flags.test(MimgModifier::Pcf)) { - return ir.ImageGather(handle, body, offset, info); - } - ASSERT(mimg.dmask & 1); // should be always 1st (R) component - return ir.ImageGatherDref(handle, body, offset, dref, info); - }(); - - // For gather4 instructions dmask selects which component to read and must have - // only one bit set to 1 - ASSERT_MSG(std::popcount(mimg.dmask) == 1, "Unexpected bits in gather dmask"); - for (u32 i = 0; i < 4; i++) { - const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; - ir.SetVectorReg(dest_reg++, value); - } -} + // MIMG + // Image load operations + case Opcode::IMAGE_LOAD: + return IMAGE_LOAD(false, inst); + case Opcode::IMAGE_LOAD_MIP: + return IMAGE_LOAD(true, inst); -void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg dest_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + // Buffer store operations + case Opcode::IMAGE_STORE: + return IMAGE_STORE(inst); - const IR::Value handle = ir.GetScalarReg(tsharp_reg); - const IR::Value body = - ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + // Image misc operations + case Opcode::IMAGE_GET_RESINFO: + return IMAGE_GET_RESINFO(inst); - IR::TextureInstInfo info{}; - info.explicit_lod.Assign(has_mip); - const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info); + // Image atomic operations + case Opcode::IMAGE_ATOMIC_ADD: + return IMAGE_ATOMIC(AtomicOp::Add, inst); + case Opcode::IMAGE_ATOMIC_SMIN: + return IMAGE_ATOMIC(AtomicOp::Smin, inst); + case Opcode::IMAGE_ATOMIC_UMIN: + return IMAGE_ATOMIC(AtomicOp::Umin, inst); + case Opcode::IMAGE_ATOMIC_SMAX: + return IMAGE_ATOMIC(AtomicOp::Smax, inst); + case Opcode::IMAGE_ATOMIC_UMAX: + return IMAGE_ATOMIC(AtomicOp::Umax, inst); + case Opcode::IMAGE_ATOMIC_AND: + return IMAGE_ATOMIC(AtomicOp::And, inst); + case Opcode::IMAGE_ATOMIC_OR: + return IMAGE_ATOMIC(AtomicOp::Or, inst); + case Opcode::IMAGE_ATOMIC_XOR: + return IMAGE_ATOMIC(AtomicOp::Xor, inst); + case Opcode::IMAGE_ATOMIC_INC: + return IMAGE_ATOMIC(AtomicOp::Inc, inst); + case Opcode::IMAGE_ATOMIC_DEC: + return IMAGE_ATOMIC(AtomicOp::Dec, inst); - for (u32 i = 0; i < 4; i++) { - if (((mimg.dmask >> i) & 1) == 0) { - continue; - } - IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; - ir.SetVectorReg(dest_reg++, value); - } -} + case Opcode::IMAGE_SAMPLE: + case Opcode::IMAGE_SAMPLE_D: + case Opcode::IMAGE_SAMPLE_L: + case Opcode::IMAGE_SAMPLE_B: + case Opcode::IMAGE_SAMPLE_LZ: + case Opcode::IMAGE_SAMPLE_C: + case Opcode::IMAGE_SAMPLE_C_LZ: + case Opcode::IMAGE_SAMPLE_O: + case Opcode::IMAGE_SAMPLE_L_O: + case Opcode::IMAGE_SAMPLE_LZ_O: + case Opcode::IMAGE_SAMPLE_C_O: + case Opcode::IMAGE_SAMPLE_C_LZ_O: + case Opcode::IMAGE_SAMPLE_CD: + return IMAGE_SAMPLE(inst); -void Translator::IMAGE_STORE(const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg data_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + // Image gather operations + case Opcode::IMAGE_GATHER4_LZ: + case Opcode::IMAGE_GATHER4_C: + case Opcode::IMAGE_GATHER4_C_LZ: + case Opcode::IMAGE_GATHER4_LZ_O: + return IMAGE_GATHER(inst); - const IR::Value handle = ir.GetScalarReg(tsharp_reg); - const IR::Value body = - ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + // Image misc operations + case Opcode::IMAGE_GET_LOD: + return IMAGE_GET_LOD(inst); - boost::container::static_vector comps; - for (u32 i = 0; i < 4; i++) { - if (((mimg.dmask >> i) & 1) == 0) { - comps.push_back(ir.Imm32(0.f)); - continue; - } - comps.push_back(ir.GetVectorReg(data_reg++)); + default: + LogMissingOpcode(inst); } - const IR::Value value = ir.CompositeConstruct(comps[0], comps[1], comps[2], comps[3]); - ir.ImageWrite(handle, body, value, {}); } void Translator::BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst) { @@ -588,19 +375,77 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { } } -void Translator::IMAGE_GET_LOD(const GcnInst& inst) { +// Image Memory +// MIMG + +void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) { const auto& mimg = inst.control.mimg; - IR::VectorReg dst_reg{inst.dst[0].code}; IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg dest_reg{inst.dst[0].code}; const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; const IR::Value handle = ir.GetScalarReg(tsharp_reg); - const IR::Value body = ir.CompositeConstruct( - ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); - const IR::Value lod = ir.ImageQueryLod(handle, body, {}); - ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 0)}); - ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 1)}); + const IR::Value body = + ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), + ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + + IR::TextureInstInfo info{}; + info.explicit_lod.Assign(has_mip); + const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info); + + for (u32 i = 0; i < 4; i++) { + if (((mimg.dmask >> i) & 1) == 0) { + continue; + } + IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; + ir.SetVectorReg(dest_reg++, value); + } +} + +void Translator::IMAGE_STORE(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg data_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + + const IR::Value handle = ir.GetScalarReg(tsharp_reg); + const IR::Value body = + ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), + ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + + boost::container::static_vector comps; + for (u32 i = 0; i < 4; i++) { + if (((mimg.dmask >> i) & 1) == 0) { + comps.push_back(ir.Imm32(0.f)); + continue; + } + comps.push_back(ir.GetVectorReg(data_reg++)); + } + const IR::Value value = ir.CompositeConstruct(comps[0], comps[1], comps[2], comps[3]); + ir.ImageWrite(handle, body, value, {}); +} + +void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) { + IR::VectorReg dst_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + const auto flags = ImageResFlags(inst.control.mimg.dmask); + const bool has_mips = flags.test(ImageResComponent::MipCount); + const IR::U32 lod = ir.GetVectorReg(IR::VectorReg(inst.src[0].code)); + const IR::Value tsharp = ir.GetScalarReg(tsharp_reg); + const IR::Value size = ir.ImageQueryDimension(tsharp, lod, ir.Imm1(has_mips)); + + if (flags.test(ImageResComponent::Width)) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 0)}); + } + if (flags.test(ImageResComponent::Height)) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 1)}); + } + if (flags.test(ImageResComponent::Depth)) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 2)}); + } + if (has_mips) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 3)}); + } } void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) { @@ -647,4 +492,177 @@ void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) { } } +void Translator::IMAGE_SAMPLE(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg dest_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; + const auto flags = MimgModifierFlags(mimg.mod); + + // Load first dword of T# and S#. We will use them as the handle that will guide resource + // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture + // binding index. + const IR::Value handle = + ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); + + // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction + // Set Architecture + const IR::U32 offset = + flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::U32{}; + const IR::F32 bias = + flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; + const IR::F32 dref = + flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; + const IR::Value derivatives = [&] -> IR::Value { + if (!flags.test(MimgModifier::Derivative)) { + return {}; + } + addr_reg = addr_reg + 4; + return ir.CompositeConstruct( + ir.GetVectorReg(addr_reg - 4), ir.GetVectorReg(addr_reg - 3), + ir.GetVectorReg(addr_reg - 2), ir.GetVectorReg(addr_reg - 1)); + }(); + + // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler + // Since these are at most 4 dwords, we load them into a single uvec4 and place them + // in coords field of the instruction. Then the resource tracking pass will patch the + // IR instruction to fill in lod_clamp field. + const IR::Value body = ir.CompositeConstruct( + ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), + ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + + // Derivatives are tricky because their number depends on the texture type which is located in + // T#. We don't have access to T# though until resource tracking pass. For now assume if + // derivatives are present, that a 2D image is bound. + const bool has_derivatives = flags.test(MimgModifier::Derivative); + const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod); + + IR::TextureInstInfo info{}; + info.is_depth.Assign(flags.test(MimgModifier::Pcf)); + info.has_bias.Assign(flags.test(MimgModifier::LodBias)); + info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); + info.force_level0.Assign(flags.test(MimgModifier::Level0)); + info.has_offset.Assign(flags.test(MimgModifier::Offset)); + info.explicit_lod.Assign(explicit_lod); + info.has_derivatives.Assign(has_derivatives); + + // Issue IR instruction, leaving unknown fields blank to patch later. + const IR::Value texel = [&]() -> IR::Value { + if (has_derivatives) { + return ir.ImageGradient(handle, body, derivatives, offset, {}, info); + } + if (!flags.test(MimgModifier::Pcf)) { + if (explicit_lod) { + return ir.ImageSampleExplicitLod(handle, body, offset, info); + } else { + return ir.ImageSampleImplicitLod(handle, body, bias, offset, info); + } + } + if (explicit_lod) { + return ir.ImageSampleDrefExplicitLod(handle, body, dref, offset, info); + } + return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, info); + }(); + + for (u32 i = 0; i < 4; i++) { + if (((mimg.dmask >> i) & 1) == 0) { + continue; + } + IR::F32 value; + if (flags.test(MimgModifier::Pcf)) { + value = i < 3 ? IR::F32{texel} : ir.Imm32(1.0f); + } else { + value = IR::F32{ir.CompositeExtract(texel, i)}; + } + ir.SetVectorReg(dest_reg++, value); + } +} + +void Translator::IMAGE_GATHER(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + if (mimg.da) { + LOG_WARNING(Render_Vulkan, "Image instruction declares an array"); + } + + IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg dest_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; + const auto flags = MimgModifierFlags(mimg.mod); + + // Load first dword of T# and S#. We will use them as the handle that will guide resource + // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture + // binding index. + const IR::Value handle = + ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); + + // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction + // Set Architecture + const IR::Value offset = + flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::Value{}; + const IR::F32 bias = + flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; + const IR::F32 dref = + flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; + + // Derivatives are tricky because their number depends on the texture type which is located in + // T#. We don't have access to T# though until resource tracking pass. For now assume no + // derivatives are present, otherwise we don't know where coordinates are placed in the address + // stream. + ASSERT_MSG(!flags.test(MimgModifier::Derivative), "Derivative image instruction"); + + // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler + // Since these are at most 4 dwords, we load them into a single uvec4 and place them + // in coords field of the instruction. Then the resource tracking pass will patch the + // IR instruction to fill in lod_clamp field. + const IR::Value body = ir.CompositeConstruct( + ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), + ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + + const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod); + + IR::TextureInstInfo info{}; + info.is_depth.Assign(flags.test(MimgModifier::Pcf)); + info.has_bias.Assign(flags.test(MimgModifier::LodBias)); + info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); + info.force_level0.Assign(flags.test(MimgModifier::Level0)); + info.has_offset.Assign(flags.test(MimgModifier::Offset)); + // info.explicit_lod.Assign(explicit_lod); + info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1); + + // Issue IR instruction, leaving unknown fields blank to patch later. + const IR::Value texel = [&]() -> IR::Value { + const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{}; + if (!flags.test(MimgModifier::Pcf)) { + return ir.ImageGather(handle, body, offset, info); + } + ASSERT(mimg.dmask & 1); // should be always 1st (R) component + return ir.ImageGatherDref(handle, body, offset, dref, info); + }(); + + // For gather4 instructions dmask selects which component to read and must have + // only one bit set to 1 + ASSERT_MSG(std::popcount(mimg.dmask) == 1, "Unexpected bits in gather dmask"); + for (u32 i = 0; i < 4; i++) { + const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; + ir.SetVectorReg(dest_reg++, value); + } +} + +void Translator::IMAGE_GET_LOD(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + IR::VectorReg dst_reg{inst.dst[0].code}; + IR::VectorReg addr_reg{inst.src[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + + const IR::Value handle = ir.GetScalarReg(tsharp_reg); + const IR::Value body = ir.CompositeConstruct( + ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), + ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + const IR::Value lod = ir.ImageQueryLod(handle, body, {}); + ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 0)}); + ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 1)}); +} + } // namespace Shader::Gcn From 7b8f0d2a19fd430b3760f3deb0a08cbf6b6ba148 Mon Sep 17 00:00:00 2001 From: psucien Date: Thu, 19 Sep 2024 21:42:19 +0200 Subject: [PATCH 04/11] core: file_sys: fix for racing in fs mount --- src/core/file_sys/fs.cpp | 1 + src/core/file_sys/fs.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 199e42a046e..da631d6377d 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -15,6 +15,7 @@ void MntPoints::Mount(const std::filesystem::path& host_folder, const std::strin } void MntPoints::Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder) { + std::scoped_lock lock{m_mutex}; auto it = std::remove_if(m_mnt_pairs.begin(), m_mnt_pairs.end(), [&](const MntPair& pair) { return pair.mount == guest_folder; }); m_mnt_pairs.erase(it, m_mnt_pairs.end()); diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index 2c55b0513cf..ddd02f5313b 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -34,6 +34,7 @@ class MntPoints { std::filesystem::path GetHostPath(std::string_view guest_directory); const MntPair* GetMount(const std::string& guest_path) { + std::scoped_lock lock{m_mutex}; const auto it = std::ranges::find_if( m_mnt_pairs, [&](const auto& mount) { return guest_path.starts_with(mount.mount); }); return it == m_mnt_pairs.end() ? nullptr : &*it; From 60f315a54dee5b24d789de3f114a74fadab6a80b Mon Sep 17 00:00:00 2001 From: korenkonder Date: Thu, 19 Sep 2024 22:43:03 +0300 Subject: [PATCH 05/11] video_core: stride fix (#986) I don't know why it was forced to be 1 while in reality it should be as is --- src/video_core/amdgpu/resource.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index 1721c1aeac9..41dbe801db1 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -66,7 +66,7 @@ struct Buffer { } u32 GetStride() const noexcept { - return stride == 0 ? 1U : stride; + return stride; } u32 NumDwords() const noexcept { @@ -74,7 +74,7 @@ struct Buffer { } u32 GetSize() const noexcept { - return GetStride() * num_records; + return stride == 0 ? num_records : (stride * num_records); } }; static_assert(sizeof(Buffer) == 16); // 128bits From 048b8aef5856117214839a75e7decb8b7cb1f91e Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:28:35 -0700 Subject: [PATCH 06/11] videoout: Make present thread realtime on macOS. (#990) --- src/common/thread.cpp | 43 ++++++++++++++++++++++++++ src/common/thread.h | 3 ++ src/core/libraries/videoout/driver.cpp | 6 ++-- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/common/thread.cpp b/src/common/thread.cpp index f08b36faae9..d1b2254727f 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -9,6 +9,7 @@ #include "common/thread.h" #ifdef __APPLE__ #include +#include #include #elif defined(_WIN32) #include @@ -31,6 +32,48 @@ namespace Common { +#ifdef __APPLE__ + +void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) { + // CPU time to grant. + const std::chrono::nanoseconds computation_ns = period_ns / 2; + + // Determine the timebase for converting time to ticks. + struct mach_timebase_info timebase {}; + mach_timebase_info(&timebase); + const auto ticks_per_ns = + static_cast(timebase.denom) / static_cast(timebase.numer); + + const auto period_ticks = + static_cast(static_cast(period_ns.count()) * ticks_per_ns); + const auto computation_ticks = + static_cast(static_cast(computation_ns.count()) * ticks_per_ns); + + thread_time_constraint_policy policy = { + .period = period_ticks, + .computation = computation_ticks, + // Should not matter since preemptible is false, but needs to be >= computation regardless. + .constraint = computation_ticks, + .preemptible = false, + }; + + int ret = thread_policy_set( + pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY, + reinterpret_cast(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (ret != KERN_SUCCESS) { + LOG_ERROR(Common, "Could not set thread to real-time with period {} ns: {}", + period_ns.count(), ret); + } +} + +#else + +void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) { + // Not implemented +} + +#endif + #ifdef _WIN32 void SetCurrentThreadPriority(ThreadPriority new_priority) { diff --git a/src/common/thread.h b/src/common/thread.h index 39acc1db551..3ee60c72fc4 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -4,6 +4,7 @@ #pragma once +#include #include "common/types.h" namespace Common { @@ -16,6 +17,8 @@ enum class ThreadPriority : u32 { Critical = 4, }; +void SetCurrentThreadRealtime(std::chrono::nanoseconds period_ns); + void SetCurrentThreadPriority(ThreadPriority new_priority); void SetCurrentThreadName(const char* name); diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 8fcdd118be3..f04fb505db4 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -261,8 +261,11 @@ void VideoOutDriver::SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_ } void VideoOutDriver::PresentThread(std::stop_token token) { - static constexpr std::chrono::milliseconds VblankPeriod{16}; + static constexpr std::chrono::nanoseconds VblankPeriod{16666667}; + const auto vblank_period = VblankPeriod / Config::vblankDiv(); + Common::SetCurrentThreadName("PresentThread"); + Common::SetCurrentThreadRealtime(vblank_period); const auto receive_request = [this] -> Request { std::scoped_lock lk{mutex}; @@ -274,7 +277,6 @@ void VideoOutDriver::PresentThread(std::stop_token token) { return {}; }; - auto vblank_period = VblankPeriod / Config::vblankDiv(); auto delay = std::chrono::microseconds{0}; while (!token.stop_requested()) { // Sleep for most of the vblank duration. From 5638735af60ed547e6fa1d71dbaeacec7f947a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Fri, 20 Sep 2024 06:54:30 +0200 Subject: [PATCH 07/11] Set High Priority for Windows (#961) --- src/emulator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/emulator.cpp b/src/emulator.cpp index 2d3460a7402..bf2d4588a5c 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -42,9 +42,10 @@ Emulator::Emulator() { const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(config_dir / "config.toml"); - // Initialize NT API functions + // Initialize NT API functions and set high priority #ifdef _WIN32 Common::NtApi::Initialize(); + SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); #endif // Start logger. From 079d90211fa17ea512f7459c08ebc1bac197c873 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Fri, 20 Sep 2024 01:54:23 -0700 Subject: [PATCH 08/11] ci: Refresh CMake cache on build. (#996) --- .github/workflows/linux-qt.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/macos-qt.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/windows-qt.yml | 2 +- .github/workflows/windows.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linux-qt.yml b/.github/workflows/linux-qt.yml index b074cbca866..fc9755fa4fa 100644 --- a/.github/workflows/linux-qt.yml +++ b/.github/workflows/linux-qt.yml @@ -45,7 +45,7 @@ jobs: key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e496eb37ec7..27bb8a40113 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -45,7 +45,7 @@ jobs: key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel diff --git a/.github/workflows/macos-qt.yml b/.github/workflows/macos-qt.yml index 5895ce79cb7..4cbfb9652ae 100644 --- a/.github/workflows/macos-qt.yml +++ b/.github/workflows/macos-qt.yml @@ -62,7 +62,7 @@ jobs: variant: sccache - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f60cf98a52d..99c85b7b82f 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -53,7 +53,7 @@ jobs: variant: sccache - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) diff --git a/.github/workflows/windows-qt.yml b/.github/workflows/windows-qt.yml index 7d0f46de26a..65faa7507d4 100644 --- a/.github/workflows/windows-qt.yml +++ b/.github/workflows/windows-qt.yml @@ -55,7 +55,7 @@ jobs: arch: amd64 - name: Configure CMake - run: cmake -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index a837f00c291..9c65a7444c7 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -45,7 +45,7 @@ jobs: arch: amd64 - name: Configure CMake - run: cmake -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel From 077f8981a73f4e0212ba20e3daf0c5c54771b287 Mon Sep 17 00:00:00 2001 From: ElBread3 <92335081+ElBread3@users.noreply.github.com> Date: Fri, 20 Sep 2024 04:07:45 -0500 Subject: [PATCH 09/11] QOL: Set Log Filter On Save (#991) * set log filter on reset * clang format --- src/common/logging/backend.cpp | 8 ++++++++ src/common/logging/backend.h | 2 ++ src/qt_gui/settings_dialog.cpp | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index a21af8bbaab..7802977f5f7 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -144,6 +144,10 @@ class Impl { initialization_in_progress_suppress_logging = false; } + static bool IsActive() { + return instance != nullptr; + } + static void Start() { instance->StartBackendThread(); } @@ -275,6 +279,10 @@ void Initialize(std::string_view log_file) { Impl::Initialize(log_file.empty() ? LOG_FILE : log_file); } +bool IsActive() { + return Impl::IsActive(); +} + void Start() { Impl::Start(); } diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index 91c9da8327a..a1ad663691e 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -13,6 +13,8 @@ class Filter; /// Initializes the logging system. This should be the first thing called in main. void Initialize(std::string_view log_file = ""); +bool IsActive(); + /// Starts the logging threads. void Start(); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 4206c446323..720c68b78ef 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -4,6 +4,8 @@ #include #include +#include "common/logging/backend.h" +#include "common/logging/filter.h" #include "main_window.h" #include "settings_dialog.h" #include "ui_settings_dialog.h" @@ -78,6 +80,11 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge Config::setDefaultValues(); LoadValuesFromConfig(); } + if (Common::Log::IsActive()) { + Common::Log::Filter filter; + filter.ParseFilterString(Config::getLogFilter()); + Common::Log::SetGlobalFilter(filter); + } }); ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); From 0f4bcd8c8399d47f7f7bf774a5450c7f0a6c8c00 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Fri, 20 Sep 2024 06:34:19 -0300 Subject: [PATCH 10/11] Rewrite Save Data & Impl Save Data Dialog (#824) * core: Rewrite PSF parser & add encoder add .sfo hex pattern to /scripts * core/fs: allow to mount path as read-only * common: Add CString wrapper to handle native null-terminated strings * SaveData: rewrite to implement full functionality * mock value for SYSTEM_VER * SavaData: backup features * SavaData: SaveDataMemory features * imgui Ref-counted textures - has a background thread to decode textures * imgui: rework gamepad navigation * PSF: fixed psf not using enum class for PSFEntryFmt (was a standard old ugly enum) - Add null check to CString when itself is used in a nullable field * SaveDataDialog implementation - Fix Mounting/Unmounting check of SaveInstance --- CMakeLists.txt | 17 +- scripts/file_formats/sfo.hexpat | 52 + src/common/cstring.h | 133 ++ src/common/io_file.cpp | 14 + src/common/io_file.h | 2 + src/common/string_util.cpp | 11 +- src/common/string_util.h | 4 +- src/core/file_format/psf.cpp | 290 ++- src/core/file_format/psf.h | 75 +- src/core/file_sys/fs.cpp | 11 +- src/core/file_sys/fs.h | 7 +- .../libraries/app_content/app_content.cpp | 27 +- src/core/libraries/kernel/file_system.cpp | 34 +- src/core/libraries/kernel/libkernel.cpp | 2 +- src/core/libraries/libs.cpp | 4 +- src/core/libraries/np_trophy/trophy_ui.h | 4 - .../save_data/dialog/savedatadialog.cpp | 163 ++ .../save_data/dialog/savedatadialog.h | 33 + .../save_data/dialog/savedatadialog_ui.cpp | 802 ++++++++ .../save_data/dialog/savedatadialog_ui.h | 317 ++++ src/core/libraries/save_data/error_codes.h | 28 - src/core/libraries/save_data/save_backup.cpp | 207 +++ src/core/libraries/save_data/save_backup.h | 64 + .../libraries/save_data/save_instance.cpp | 228 +++ src/core/libraries/save_data/save_instance.h | 138 ++ src/core/libraries/save_data/save_memory.cpp | 287 +++ src/core/libraries/save_data/save_memory.h | 49 + src/core/libraries/save_data/savedata.cpp | 1631 ++++++++++++----- src/core/libraries/save_data/savedata.h | 356 +--- src/core/libraries/system/msgdialog.cpp | 5 - src/core/libraries/system/msgdialog_ui.cpp | 60 +- src/core/libraries/system/msgdialog_ui.h | 16 +- src/core/libraries/system/savedatadialog.cpp | 84 - src/core/libraries/system/savedatadialog.h | 26 - src/emulator.cpp | 12 +- src/imgui/imgui_config.h | 5 +- src/imgui/imgui_layer.h | 4 - src/imgui/imgui_std.h | 46 +- src/imgui/imgui_texture.h | 45 + src/imgui/layer/video_info.cpp | 2 +- src/imgui/renderer/imgui_core.cpp | 25 +- src/imgui/renderer/imgui_impl_vulkan.cpp | 304 ++- src/imgui/renderer/imgui_impl_vulkan.h | 26 +- src/imgui/renderer/texture_manager.cpp | 243 +++ src/imgui/renderer/texture_manager.h | 30 + src/qt_gui/game_info.h | 13 +- src/qt_gui/gui_context_menus.h | 46 +- src/qt_gui/main_window.cpp | 12 +- src/qt_gui/pkg_viewer.cpp | 14 +- src/qt_gui/pkg_viewer.h | 1 - .../renderer_vulkan/vk_scheduler.cpp | 2 + 51 files changed, 4898 insertions(+), 1113 deletions(-) create mode 100644 scripts/file_formats/sfo.hexpat create mode 100644 src/common/cstring.h create mode 100644 src/core/libraries/save_data/dialog/savedatadialog.cpp create mode 100644 src/core/libraries/save_data/dialog/savedatadialog.h create mode 100644 src/core/libraries/save_data/dialog/savedatadialog_ui.cpp create mode 100644 src/core/libraries/save_data/dialog/savedatadialog_ui.h delete mode 100644 src/core/libraries/save_data/error_codes.h create mode 100644 src/core/libraries/save_data/save_backup.cpp create mode 100644 src/core/libraries/save_data/save_backup.h create mode 100644 src/core/libraries/save_data/save_instance.cpp create mode 100644 src/core/libraries/save_data/save_instance.h create mode 100644 src/core/libraries/save_data/save_memory.cpp create mode 100644 src/core/libraries/save_data/save_memory.h delete mode 100644 src/core/libraries/system/savedatadialog.cpp delete mode 100644 src/core/libraries/system/savedatadialog.h create mode 100644 src/imgui/imgui_texture.h create mode 100644 src/imgui/renderer/texture_manager.cpp create mode 100644 src/imgui/renderer/texture_manager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 04cb3ea766f..b334f5940e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,11 +232,18 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/system/msgdialog_ui.cpp src/core/libraries/system/posix.cpp src/core/libraries/system/posix.h - src/core/libraries/save_data/error_codes.h + src/core/libraries/save_data/save_backup.cpp + src/core/libraries/save_data/save_backup.h + src/core/libraries/save_data/save_instance.cpp + src/core/libraries/save_data/save_instance.h + src/core/libraries/save_data/save_memory.cpp + src/core/libraries/save_data/save_memory.h src/core/libraries/save_data/savedata.cpp src/core/libraries/save_data/savedata.h - src/core/libraries/system/savedatadialog.cpp - src/core/libraries/system/savedatadialog.h + src/core/libraries/save_data/dialog/savedatadialog.cpp + src/core/libraries/save_data/dialog/savedatadialog.h + src/core/libraries/save_data/dialog/savedatadialog_ui.cpp + src/core/libraries/save_data/dialog/savedatadialog_ui.h src/core/libraries/system/sysmodule.cpp src/core/libraries/system/sysmodule.h src/core/libraries/system/systemservice.cpp @@ -349,6 +356,7 @@ set(COMMON src/common/logging/backend.cpp src/common/concepts.h src/common/config.cpp src/common/config.h + src/common/cstring.h src/common/debug.h src/common/disassembler.cpp src/common/disassembler.h @@ -607,6 +615,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp set(IMGUI src/imgui/imgui_config.h src/imgui/imgui_layer.h src/imgui/imgui_std.h + src/imgui/imgui_texture.h src/imgui/layer/video_info.cpp src/imgui/layer/video_info.h src/imgui/renderer/imgui_core.cpp @@ -615,6 +624,8 @@ set(IMGUI src/imgui/imgui_config.h src/imgui/renderer/imgui_impl_sdl3.h src/imgui/renderer/imgui_impl_vulkan.cpp src/imgui/renderer/imgui_impl_vulkan.h + src/imgui/renderer/texture_manager.cpp + src/imgui/renderer/texture_manager.h ) set(INPUT src/input/controller.cpp diff --git a/scripts/file_formats/sfo.hexpat b/scripts/file_formats/sfo.hexpat new file mode 100644 index 00000000000..cfc1f87896e --- /dev/null +++ b/scripts/file_formats/sfo.hexpat @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +import std.io; +import std.sys; + +struct Header { + u32 magic; + u32 version; + u32 key_table_offset; + u32 data_table_offset; + u32 index_table_entries; +}; + +struct KeyEntry { + char name[]; +} [[inline]]; + +struct DataEntry { + if (fmt == 0x0404) { + u32 int_value; + } else if(fmt == 0x0004) { + char bin_value[size]; + } else if(fmt == 0x0204) { + char str_value[size]; + } else { + std::warning("unknown fmt type"); + } +} [[inline]]; + +struct IndexEntry { + u16 key_offset; + u16 param_fmt; + u32 param_len; + u32 param_max_len; + u32 data_offset; +}; + +struct Entry { + u64 begin = $; + IndexEntry index; + KeyEntry key @ KeyTableOffset + index.key_offset; + DataEntry data @ DataTableOffset + index.data_offset; + u8 data_empty[index.param_max_len - index.param_len] @ DataTableOffset + index.data_offset + index.param_len; + $ = begin + sizeof(IndexEntry); +}; + +Header header @ 0; +std::assert(header.magic == 0x46535000, "Miss match magic"); +std::assert(header.version == 0x00000101, "Miss match version"); + +Entry list[header.index_table_entries] @ 0x14; \ No newline at end of file diff --git a/src/common/cstring.h b/src/common/cstring.h new file mode 100644 index 00000000000..1b47bdbf031 --- /dev/null +++ b/src/common/cstring.h @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "assert.h" + +namespace Common { + +/** + * @brief A null-terminated string with a fixed maximum length + * This class is not meant to be used as a general-purpose string class + * It is meant to be used as `char[N]` where memory layout is fixed + * @tparam N Maximum length of the string + * @tparam T Type of character + */ +template +class CString { + T data[N]{}; + +public: + class Iterator; + + CString() = default; + + template + explicit CString(const CString& other) + requires(M <= N) + { + std::ranges::copy(other.begin(), other.end(), data); + } + + void FromString(const std::basic_string_view& str) { + size_t p = str.copy(data, N - 1); + data[p] = '\0'; + } + + void Zero() { + std::ranges::fill(data, 0); + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-undefined-compare" + explicit(false) operator std::basic_string_view() const { + if (this == nullptr) { + return {}; + } + return std::basic_string_view{data}; + } + + explicit operator std::basic_string() const { + if (this == nullptr) { + return {}; + } + return std::basic_string{data}; + } + + std::basic_string to_string() const { + if (this == nullptr) { + return {}; + } + return std::basic_string{data}; + } + + std::basic_string_view to_view() const { + if (this == nullptr) { + return {}; + } + return std::basic_string_view{data}; + } +#pragma clang diagnostic pop + + char* begin() { + return data; + } + + const char* begin() const { + return data; + } + + char* end() { + return data + N; + } + + const char* end() const { + return data + N; + } + + T& operator[](size_t idx) { + return data[idx]; + } + + const T& operator[](size_t idx) const { + return data[idx]; + } + + class Iterator { + T* ptr; + T* end; + + public: + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = T*; + using reference = T&; + using iterator_category = std::random_access_iterator_tag; + + Iterator() = default; + explicit Iterator(T* ptr) : ptr(ptr), end(ptr + N) {} + + Iterator& operator++() { + ++ptr; + return *this; + } + + Iterator operator++(int) { + Iterator tmp = *this; + ++ptr; + return tmp; + } + + operator T*() { + ASSERT_MSG(ptr >= end, "CString iterator out of bounds"); + return ptr; + } + }; +}; +static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array +static_assert(std::weakly_incrementable::Iterator>); + +} // namespace Common \ No newline at end of file diff --git a/src/common/io_file.cpp b/src/common/io_file.cpp index fbc37a10c98..c1d9cc59207 100644 --- a/src/common/io_file.cpp +++ b/src/common/io_file.cpp @@ -396,4 +396,18 @@ s64 IOFile::Tell() const { return ftello(file); } +u64 GetDirectorySize(const std::filesystem::path& path) { + if (!fs::exists(path)) { + return 0; + } + + u64 total = 0; + for (const auto& entry : fs::recursive_directory_iterator(path)) { + if (fs::is_regular_file(entry.path())) { + total += fs::file_size(entry.path()); + } + } + return total; +} + } // namespace Common::FS diff --git a/src/common/io_file.h b/src/common/io_file.h index 2c3df3f69c2..177bddbad63 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -219,4 +219,6 @@ class IOFile final { uintptr_t file_mapping = 0; }; +u64 GetDirectorySize(const std::filesystem::path& path); + } // namespace Common::FS diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 29e6aeb4f4f..6d5a254cd28 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -14,12 +14,17 @@ namespace Common { -std::string ToLower(std::string str) { - std::transform(str.begin(), str.end(), str.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); +std::string ToLower(std::string_view input) { + std::string str; + str.resize(input.size()); + std::ranges::transform(input, str.begin(), tolower); return str; } +void ToLowerInPlace(std::string& str) { + std::ranges::transform(str, str.begin(), tolower); +} + std::vector SplitString(const std::string& str, char delimiter) { std::istringstream iss(str); std::vector output(1); diff --git a/src/common/string_util.h b/src/common/string_util.h index 8dae6c75b06..23e82b93c83 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -10,7 +10,9 @@ namespace Common { /// Make a string lowercase -[[nodiscard]] std::string ToLower(std::string str); +[[nodiscard]] std::string ToLower(std::string_view str); + +void ToLowerInPlace(std::string& str); std::vector SplitString(const std::string& str, char delimiter); diff --git a/src/core/file_format/psf.cpp b/src/core/file_format/psf.cpp index 3d076acdceb..1df5d430ee6 100644 --- a/src/core/file_format/psf.cpp +++ b/src/core/file_format/psf.cpp @@ -2,61 +2,275 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include + +#include "common/assert.h" #include "common/io_file.h" +#include "common/logging/log.h" #include "core/file_format/psf.h" -PSF::PSF() = default; - -PSF::~PSF() = default; +static const std::unordered_map psf_known_max_sizes = { + {"ACCOUNT_ID", 8}, {"CATEGORY", 4}, {"DETAIL", 1024}, {"FORMAT", 4}, + {"MAINTITLE", 128}, {"PARAMS", 1024}, {"SAVEDATA_BLOCKS", 8}, {"SAVEDATA_DIRECTORY", 32}, + {"SUBTITLE", 128}, {"TITLE_ID", 12}, +}; +static inline u32 get_max_size(std::string_view key, u32 default_value) { + if (const auto& v = psf_known_max_sizes.find(key); v != psf_known_max_sizes.end()) { + return v->second; + } + return default_value; +} -bool PSF::open(const std::string& filepath, const std::vector& psfBuffer) { - if (!psfBuffer.empty()) { - psf.resize(psfBuffer.size()); - psf = psfBuffer; - } else { - Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } +bool PSF::Open(const std::filesystem::path& filepath) { + if (std::filesystem::exists(filepath)) { + last_write = std::filesystem::last_write_time(filepath); + } - const u64 psfSize = file.GetSize(); - psf.resize(psfSize); - file.Seek(0); - file.Read(psf); - file.Close(); + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + return false; } + const u64 psfSize = file.GetSize(); + std::vector psf(psfSize); + file.Seek(0); + file.Read(psf); + file.Close(); + return Open(psf); +} + +bool PSF::Open(const std::vector& psf_buffer) { + const u8* psf_data = psf_buffer.data(); + + entry_list.clear(); + map_binaries.clear(); + map_strings.clear(); + map_integers.clear(); + // Parse file contents - PSFHeader header; - std::memcpy(&header, psf.data(), sizeof(header)); + PSFHeader header{}; + std::memcpy(&header, psf_data, sizeof(header)); + + if (header.magic != PSF_MAGIC) { + LOG_ERROR(Core, "Invalid PSF magic number"); + return false; + } + if (header.version != PSF_VERSION_1_1 && header.version != PSF_VERSION_1_0) { + LOG_ERROR(Core, "Unsupported PSF version: 0x{:08x}", header.version); + return false; + } + for (u32 i = 0; i < header.index_table_entries; i++) { - PSFEntry entry; - std::memcpy(&entry, &psf[sizeof(PSFHeader) + i * sizeof(PSFEntry)], sizeof(entry)); + PSFRawEntry raw_entry{}; + std::memcpy(&raw_entry, psf_data + sizeof(PSFHeader) + i * sizeof(PSFRawEntry), + sizeof(raw_entry)); - const std::string key = (char*)&psf[header.key_table_offset + entry.key_offset]; - if (entry.param_fmt == PSFEntry::Fmt::TextRaw || - entry.param_fmt == PSFEntry::Fmt::TextNormal) { - map_strings[key] = (char*)&psf[header.data_table_offset + entry.data_offset]; - } - if (entry.param_fmt == PSFEntry::Fmt::Integer) { - u32 value; - std::memcpy(&value, &psf[header.data_table_offset + entry.data_offset], sizeof(value)); - map_integers[key] = value; + Entry& entry = entry_list.emplace_back(); + entry.key = std::string{(char*)(psf_data + header.key_table_offset + raw_entry.key_offset)}; + entry.param_fmt = static_cast(raw_entry.param_fmt.Raw()); + entry.max_len = raw_entry.param_max_len; + + const u8* data = psf_data + header.data_table_offset + raw_entry.data_offset; + + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + std::vector value(raw_entry.param_len); + std::memcpy(value.data(), data, raw_entry.param_len); + map_binaries.emplace(i, std::move(value)); + } break; + case PSFEntryFmt::Text: { + std::string c_str{reinterpret_cast(data)}; + map_strings.emplace(i, std::move(c_str)); + } break; + case PSFEntryFmt::Integer: { + ASSERT_MSG(raw_entry.param_len == sizeof(s32), "PSF integer entry size mismatch"); + s32 integer = *(s32*)data; + map_integers.emplace(i, integer); + } break; + default: + UNREACHABLE_MSG("Unknown PSF entry format"); } } return true; } -std::string PSF::GetString(const std::string& key) { - if (map_strings.find(key) != map_strings.end()) { - return map_strings.at(key); +bool PSF::Encode(const std::filesystem::path& filepath) const { + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write); + if (!file.IsOpen()) { + return false; } - return ""; + + last_write = std::filesystem::file_time_type::clock::now(); + + const auto psf_buffer = Encode(); + return file.Write(psf_buffer) == psf_buffer.size(); +} + +std::vector PSF::Encode() const { + std::vector psf_buffer; + Encode(psf_buffer); + return psf_buffer; } -u32 PSF::GetInteger(const std::string& key) { - if (map_integers.find(key) != map_integers.end()) { - return map_integers.at(key); +void PSF::Encode(std::vector& psf_buffer) const { + psf_buffer.resize(sizeof(PSFHeader) + sizeof(PSFRawEntry) * entry_list.size()); + + { + auto& header = *(PSFHeader*)psf_buffer.data(); + header.magic = PSF_MAGIC; + header.version = PSF_VERSION_1_1; + header.index_table_entries = entry_list.size(); + } + + const size_t key_table_offset = psf_buffer.size(); + ((PSFHeader*)psf_buffer.data())->key_table_offset = key_table_offset; + for (size_t i = 0; i < entry_list.size(); i++) { + auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i]; + const Entry& entry = entry_list[i]; + raw_entry.key_offset = psf_buffer.size() - key_table_offset; + raw_entry.param_fmt.FromRaw(static_cast(entry.param_fmt)); + raw_entry.param_max_len = entry.max_len; + std::ranges::copy(entry.key, std::back_inserter(psf_buffer)); + psf_buffer.push_back(0); // NULL terminator } - return -1; + + const size_t data_table_offset = psf_buffer.size(); + ((PSFHeader*)psf_buffer.data())->data_table_offset = data_table_offset; + for (size_t i = 0; i < entry_list.size(); i++) { + if (psf_buffer.size() % 4 != 0) { + std::ranges::fill_n(std::back_inserter(psf_buffer), 4 - psf_buffer.size() % 4, 0); + } + auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i]; + const Entry& entry = entry_list[i]; + raw_entry.data_offset = psf_buffer.size() - data_table_offset; + + s32 additional_padding = s32(raw_entry.param_max_len); + + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + const auto& value = map_binaries.at(i); + raw_entry.param_len = value.size(); + additional_padding -= s32(raw_entry.param_len); + std::ranges::copy(value, std::back_inserter(psf_buffer)); + } break; + case PSFEntryFmt::Text: { + const auto& value = map_strings.at(i); + raw_entry.param_len = value.size() + 1; + additional_padding -= s32(raw_entry.param_len); + std::ranges::copy(value, std::back_inserter(psf_buffer)); + psf_buffer.push_back(0); // NULL terminator + } break; + case PSFEntryFmt::Integer: { + const auto& value = map_integers.at(i); + raw_entry.param_len = sizeof(s32); + additional_padding -= s32(raw_entry.param_len); + const auto value_bytes = reinterpret_cast(&value); + std::ranges::copy(value_bytes, value_bytes + sizeof(s32), + std::back_inserter(psf_buffer)); + } break; + default: + UNREACHABLE_MSG("Unknown PSF entry format"); + } + ASSERT_MSG(additional_padding >= 0, "PSF entry max size mismatch"); + std::ranges::fill_n(std::back_inserter(psf_buffer), additional_padding, 0); + } +} + +std::optional> PSF::GetBinary(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + ASSERT(it->param_fmt == PSFEntryFmt::Binary); + return std::span{map_binaries.at(index)}; +} + +std::optional PSF::GetString(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + ASSERT(it->param_fmt == PSFEntryFmt::Text); + return std::string_view{map_strings.at(index)}; +} + +std::optional PSF::GetInteger(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + ASSERT(it->param_fmt == PSFEntryFmt::Integer); + return map_integers.at(index); +} + +void PSF::AddBinary(std::string key, std::vector value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + LOG_ERROR(Core, "PSF: Tried to add binary key that already exists: {}", key); + return; + } + if (exist) { + ASSERT_MSG(it->param_fmt == PSFEntryFmt::Binary, "PSF: Change format is not supported"); + it->max_len = get_max_size(key, value.size()); + map_binaries.at(index) = std::move(value); + return; + } + Entry& entry = entry_list.emplace_back(); + entry.max_len = get_max_size(key, value.size()); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Binary; + map_binaries.emplace(entry_list.size() - 1, std::move(value)); +} + +void PSF::AddString(std::string key, std::string value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + LOG_ERROR(Core, "PSF: Tried to add string key that already exists: {}", key); + return; + } + if (exist) { + ASSERT_MSG(it->param_fmt == PSFEntryFmt::Text, "PSF: Change format is not supported"); + it->max_len = get_max_size(key, value.size() + 1); + map_strings.at(index) = std::move(value); + return; + } + Entry& entry = entry_list.emplace_back(); + entry.max_len = get_max_size(key, value.size() + 1); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Text; + map_strings.emplace(entry_list.size() - 1, std::move(value)); +} + +void PSF::AddInteger(std::string key, s32 value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + LOG_ERROR(Core, "PSF: Tried to add integer key that already exists: {}", key); + return; + } + if (exist) { + ASSERT_MSG(it->param_fmt == PSFEntryFmt::Integer, "PSF: Change format is not supported"); + it->max_len = sizeof(s32); + map_integers.at(index) = value; + return; + } + Entry& entry = entry_list.emplace_back(); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Integer; + entry.max_len = sizeof(s32); + map_integers.emplace(entry_list.size() - 1, value); +} + +std::pair::iterator, size_t> PSF::FindEntry(std::string_view key) { + auto entry = + std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; }); + return {entry, std::distance(entry_list.begin(), entry)}; +} + +std::pair::const_iterator, size_t> PSF::FindEntry( + std::string_view key) const { + auto entry = + std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; }); + return {entry, std::distance(entry_list.begin(), entry)}; } diff --git a/src/core/file_format/psf.h b/src/core/file_format/psf.h index 9a162b1d526..d25b79eece7 100644 --- a/src/core/file_format/psf.h +++ b/src/core/file_format/psf.h @@ -3,11 +3,18 @@ #pragma once +#include +#include #include +#include #include #include #include "common/endian.h" +constexpr u32 PSF_MAGIC = 0x00505346; +constexpr u32 PSF_VERSION_1_1 = 0x00000101; +constexpr u32 PSF_VERSION_1_0 = 0x00000100; + struct PSFHeader { u32_be magic; u32_le version; @@ -15,34 +22,72 @@ struct PSFHeader { u32_le data_table_offset; u32_le index_table_entries; }; +static_assert(sizeof(PSFHeader) == 0x14); -struct PSFEntry { - enum Fmt : u16 { - TextRaw = 0x0400, // String in UTF-8 format and not NULL terminated - TextNormal = 0x0402, // String in UTF-8 format and NULL terminated - Integer = 0x0404, // Unsigned 32-bit integer - }; - +struct PSFRawEntry { u16_le key_offset; u16_be param_fmt; u32_le param_len; u32_le param_max_len; u32_le data_offset; }; +static_assert(sizeof(PSFRawEntry) == 0x10); + +enum class PSFEntryFmt : u16 { + Binary = 0x0004, // Binary data + Text = 0x0204, // String in UTF-8 format and NULL terminated + Integer = 0x0404, // Signed 32-bit integer +}; class PSF { + struct Entry { + std::string key; + PSFEntryFmt param_fmt; + u32 max_len; + }; + public: - PSF(); - ~PSF(); + PSF() = default; + ~PSF() = default; - bool open(const std::string& filepath, const std::vector& psfBuffer); + PSF(const PSF& other) = default; + PSF(PSF&& other) noexcept = default; + PSF& operator=(const PSF& other) = default; + PSF& operator=(PSF&& other) noexcept = default; - std::string GetString(const std::string& key); - u32 GetInteger(const std::string& key); + bool Open(const std::filesystem::path& filepath); + bool Open(const std::vector& psf_buffer); - std::unordered_map map_strings; - std::unordered_map map_integers; + [[nodiscard]] std::vector Encode() const; + void Encode(std::vector& buf) const; + bool Encode(const std::filesystem::path& filepath) const; + + std::optional> GetBinary(std::string_view key) const; + std::optional GetString(std::string_view key) const; + std::optional GetInteger(std::string_view key) const; + + void AddBinary(std::string key, std::vector value, bool update = false); + void AddString(std::string key, std::string value, bool update = false); + void AddInteger(std::string key, s32 value, bool update = false); + + [[nodiscard]] std::filesystem::file_time_type GetLastWrite() const { + return last_write; + } + + [[nodiscard]] const std::vector& GetEntries() const { + return entry_list; + } private: - std::vector psf; + mutable std::filesystem::file_time_type last_write; + + std::vector entry_list; + + std::unordered_map> map_binaries; + std::unordered_map map_strings; + std::unordered_map map_integers; + + [[nodiscard]] std::pair::iterator, size_t> FindEntry(std::string_view key); + [[nodiscard]] std::pair::const_iterator, size_t> FindEntry( + std::string_view key) const; }; diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index da631d6377d..3b060dd8327 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -9,9 +9,10 @@ namespace Core::FileSys { constexpr int RESERVED_HANDLES = 3; // First 3 handles are stdin,stdout,stderr -void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder) { +void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder, + bool read_only) { std::scoped_lock lock{m_mutex}; - m_mnt_pairs.emplace_back(host_folder, guest_folder); + m_mnt_pairs.emplace_back(host_folder, guest_folder, read_only); } void MntPoints::Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder) { @@ -26,7 +27,7 @@ void MntPoints::UnmountAll() { m_mnt_pairs.clear(); } -std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) { +std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory, bool* is_read_only) { // Evil games like Turok2 pass double slashes e.g /app0//game.kpf std::string corrected_path(guest_directory); size_t pos = corrected_path.find("//"); @@ -40,6 +41,10 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) { return ""; } + if (is_read_only) { + *is_read_only = mount->read_only; + } + // Nothing to do if getting the mount itself. if (corrected_path == mount->mount) { return mount->host_path; diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index ddd02f5313b..eeaeaf7810b 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -22,16 +22,19 @@ class MntPoints { struct MntPair { std::filesystem::path host_path; std::string mount; // e.g /app0/ + bool read_only; }; explicit MntPoints() = default; ~MntPoints() = default; - void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder); + void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder, + bool read_only = false); void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder); void UnmountAll(); - std::filesystem::path GetHostPath(std::string_view guest_directory); + std::filesystem::path GetHostPath(std::string_view guest_directory, + bool* is_read_only = nullptr); const MntPair* GetMount(const std::string& guest_path) { std::scoped_lock lock{m_mutex}; diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index 125d19684a6..81ce044fa4b 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -90,37 +90,32 @@ int PS4_SYSV_ABI sceAppContentAddcontUnmount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* value) { - if (value == nullptr) +int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* out_value) { + if (out_value == nullptr) return ORBIS_APP_CONTENT_ERROR_PARAMETER; auto* param_sfo = Common::Singleton::Instance(); + std::optional value; switch (paramId) { case ORBIS_APP_CONTENT_APPPARAM_ID_SKU_FLAG: - *value = ORBIS_APP_CONTENT_APPPARAM_SKU_FLAG_FULL; + value = ORBIS_APP_CONTENT_APPPARAM_SKU_FLAG_FULL; break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_1: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_1"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_1"); break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_2: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_2"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_2"); break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_3: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_3"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_3"); break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_4: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_4"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_4"); break; default: - LOG_ERROR(Lib_AppContent, " paramId = {}, value = {} paramId is not valid", paramId, - *value); - return ORBIS_APP_CONTENT_ERROR_PARAMETER; - } - if (*value == -1) { - LOG_ERROR(Lib_AppContent, - " paramId = {}, value = {} value is not valid can't read param.sfo?", paramId, - *value); + LOG_ERROR(Lib_AppContent, " paramId = {} paramId is not valid", paramId); return ORBIS_APP_CONTENT_ERROR_PARAMETER; } + *out_value = value.value_or(0); return ORBIS_OK; } @@ -251,7 +246,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar auto* param_sfo = Common::Singleton::Instance(); const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir); - title_id = param_sfo->GetString("TITLE_ID"); + title_id = *param_sfo->GetString("TITLE_ID"); auto addon_path = addons_dir / title_id; if (std::filesystem::exists(addon_path)) { for (const auto& entry : std::filesystem::directory_iterator(addon_path)) { diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index ae2c6e2beb3..cb8e0aac2ca 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -179,11 +179,16 @@ int PS4_SYSV_ABI sceKernelUnlink(const char* path) { auto* h = Common::Singleton::Instance(); auto* mnt = Common::Singleton::Instance(); - const auto host_path = mnt->GetHostPath(path); + bool ro = false; + const auto host_path = mnt->GetHostPath(path, &ro); if (host_path.empty()) { return SCE_KERNEL_ERROR_EACCES; } + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } + if (std::filesystem::is_directory(host_path)) { return SCE_KERNEL_ERROR_EPERM; } @@ -270,11 +275,18 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) { return SCE_KERNEL_ERROR_EINVAL; } auto* mnt = Common::Singleton::Instance(); - const auto dir_name = mnt->GetHostPath(path); + + bool ro = false; + const auto dir_name = mnt->GetHostPath(path, &ro); + if (std::filesystem::exists(dir_name)) { return SCE_KERNEL_ERROR_EEXIST; } + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } + // CUSA02456: path = /aotl after sceSaveDataMount(mode = 1) if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) { return SCE_KERNEL_ERROR_EIO; @@ -299,7 +311,8 @@ int PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) { int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path); auto* mnt = Common::Singleton::Instance(); - const auto path_name = mnt->GetHostPath(path); + bool ro = false; + const auto path_name = mnt->GetHostPath(path, &ro); std::memset(sb, 0, sizeof(OrbisKernelStat)); const bool is_dir = std::filesystem::is_directory(path_name); const bool is_file = std::filesystem::is_regular_file(path_name); @@ -319,6 +332,10 @@ int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { sb->st_blocks = (sb->st_size + 511) / 512; // TODO incomplete } + if (ro) { + sb->st_mode &= ~0000555u; + } + return ORBIS_OK; } @@ -500,11 +517,18 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) { s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) { auto* mnt = Common::Singleton::Instance(); - const auto src_path = mnt->GetHostPath(from); + bool ro = false; + const auto src_path = mnt->GetHostPath(from, &ro); if (!std::filesystem::exists(src_path)) { return ORBIS_KERNEL_ERROR_ENOENT; } - const auto dst_path = mnt->GetHostPath(to); + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } + const auto dst_path = mnt->GetHostPath(to, &ro); + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } const bool src_is_dir = std::filesystem::is_directory(src_path); const bool dst_is_dir = std::filesystem::is_directory(dst_path); if (src_is_dir && !dst_is_dir) { diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index d56f4dc4167..3cbf21f07b4 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -244,7 +244,7 @@ int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) { auto* param_sfo = Common::Singleton::Instance(); - int version = param_sfo->GetInteger("SYSTEM_VER"); + int version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000); LOG_INFO(Kernel, "returned system version = {:#x}", version); *ver = version; return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL; diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index da41eaf00a7..5b6c17b10e5 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -27,12 +27,12 @@ #include "core/libraries/playgo/playgo.h" #include "core/libraries/random/random.h" #include "core/libraries/rtc/rtc.h" +#include "core/libraries/save_data/dialog/savedatadialog.h" #include "core/libraries/save_data/savedata.h" #include "core/libraries/screenshot/screenshot.h" #include "core/libraries/system/commondialog.h" #include "core/libraries/system/msgdialog.h" #include "core/libraries/system/posix.h" -#include "core/libraries/system/savedatadialog.h" #include "core/libraries/system/sysmodule.h" #include "core/libraries/system/systemservice.h" #include "core/libraries/system/userservice.h" @@ -57,11 +57,11 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Net::RegisterlibSceNet(sym); Libraries::NetCtl::RegisterlibSceNetCtl(sym); Libraries::SaveData::RegisterlibSceSaveData(sym); + Libraries::SaveData::Dialog::RegisterlibSceSaveDataDialog(sym); Libraries::Ssl::RegisterlibSceSsl(sym); Libraries::SysModule::RegisterlibSceSysmodule(sym); Libraries::Posix::Registerlibsceposix(sym); Libraries::AudioIn::RegisterlibSceAudioIn(sym); - Libraries::SaveDataDialog::RegisterlibSceSaveDataDialog(sym); Libraries::NpManager::RegisterlibSceNpManager(sym); Libraries::NpScore::RegisterlibSceNpScore(sym); Libraries::NpTrophy::RegisterlibSceNpTrophy(sym); diff --git a/src/core/libraries/np_trophy/trophy_ui.h b/src/core/libraries/np_trophy/trophy_ui.h index d730aca579f..060d80dec57 100644 --- a/src/core/libraries/np_trophy/trophy_ui.h +++ b/src/core/libraries/np_trophy/trophy_ui.h @@ -31,10 +31,6 @@ class TrophyUI final : public ImGui::Layer { void Finish(); void Draw() override; - - bool ShouldGrabGamepad() override { - return false; - } }; }; // namespace Libraries::NpTrophy \ No newline at end of file diff --git a/src/core/libraries/save_data/dialog/savedatadialog.cpp b/src/core/libraries/save_data/dialog/savedatadialog.cpp new file mode 100644 index 00000000000..a647d80f9ac --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog.cpp @@ -0,0 +1,163 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/libraries/libs.h" +#include "core/libraries/system/commondialog.h" +#include "magic_enum.hpp" +#include "savedatadialog.h" +#include "savedatadialog_ui.h" + +namespace Libraries::SaveData::Dialog { + +using CommonDialog::Error; +using CommonDialog::Result; +using CommonDialog::Status; + +static auto g_status = Status::NONE; +static SaveDialogState g_state{}; +static SaveDialogResult g_result{}; +static SaveDialogUi g_save_dialog_ui; + +Error PS4_SYSV_ABI sceSaveDataDialogClose() { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + g_save_dialog_ui.Finish(ButtonId::INVALID); + g_save_dialog_ui = SaveDialogUi{}; + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result) { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::FINISHED) { + return Error::NOT_FINISHED; + } + if (result == nullptr) { + return Error::ARG_NULL; + } + g_result.CopyTo(*result); + return Error::OK; +} + +Status PS4_SYSV_ABI sceSaveDataDialogGetStatus() { + LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status)); + return g_status; +} + +Error PS4_SYSV_ABI sceSaveDataDialogInitialize() { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (!CommonDialog::g_isInitialized) { + return Error::NOT_SYSTEM_INITIALIZED; + } + if (g_status != Status::NONE) { + return Error::ALREADY_INITIALIZED; + } + if (CommonDialog::g_isUsed) { + return Error::BUSY; + } + g_status = Status::INITIALIZED; + CommonDialog::g_isUsed = true; + + return Error::OK; +} + +s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() { + return 1; +} + +Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param) { + if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) { + LOG_INFO(Lib_SaveDataDialog, "called without initialize"); + return Error::INVALID_STATE; + } + if (param == nullptr) { + LOG_DEBUG(Lib_SaveDataDialog, "called param:(NULL)"); + return Error::ARG_NULL; + } + LOG_DEBUG(Lib_SaveDataDialog, "called param->mode: {}", magic_enum::enum_name(param->mode)); + ASSERT(param->size == sizeof(OrbisSaveDataDialogParam)); + ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam)); + g_result = {}; + g_state = SaveDialogState{*param}; + g_status = Status::RUNNING; + g_save_dialog_ui = SaveDialogUi(&g_state, &g_status, &g_result); + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target, + u32 delta) { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress += delta; + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target, + u32 rate) { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress = rate; + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogTerminate() { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status == Status::RUNNING) { + sceSaveDataDialogClose(); + } + if (g_status == Status::NONE) { + return Error::NOT_INITIALIZED; + } + g_save_dialog_ui = SaveDialogUi{}; + g_status = Status::NONE; + CommonDialog::g_isUsed = false; + return Error::OK; +} + +Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() { + LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status)); + return g_status; +} + +void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogClose); + LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogGetResult); + LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogGetStatus); + LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogInitialize); + LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogIsReadyToDisplay); + LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogOpen); + LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogProgressBarInc); + LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogProgressBarSetValue); + LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogTerminate); + LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogUpdateStatus); +}; + +} // namespace Libraries::SaveData::Dialog diff --git a/src/core/libraries/save_data/dialog/savedatadialog.h b/src/core/libraries/save_data/dialog/savedatadialog.h new file mode 100644 index 00000000000..34afe98a746 --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "core/libraries/system/commondialog.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::SaveData::Dialog { + +struct OrbisSaveDataDialogParam; +struct OrbisSaveDataDialogResult; +enum class OrbisSaveDataDialogProgressBarTarget : u32; + +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogClose(); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result); +CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogGetStatus(); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogInitialize(); +s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay(); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param); +CommonDialog::Error PS4_SYSV_ABI +sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target, u32 delta); +CommonDialog::Error PS4_SYSV_ABI +sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target, u32 rate); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogTerminate(); +CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus(); + +void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::SaveData::Dialog diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp new file mode 100644 index 00000000000..8d8cdff4548 --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp @@ -0,0 +1,802 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "common/singleton.h" +#include "core/file_sys/fs.h" +#include "core/libraries/save_data/save_instance.h" +#include "imgui/imgui_std.h" +#include "savedatadialog_ui.h" + +using namespace ImGui; +using namespace Libraries::CommonDialog; + +constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB + +constexpr auto SAVE_ICON_SIZE = ImVec2{152.0f, 85.0f}; +constexpr auto SAVE_ICON_PADDING = ImVec2{8.0f, 2.0f}; + +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; +constexpr auto FOOTER_HEIGHT = BUTTON_SIZE.y + 15.0f; +static constexpr float PROGRESS_BAR_WIDTH{0.8f}; + +static ::Core::FileSys::MntPoints* g_mnt = + Common::Singleton<::Core::FileSys::MntPoints>::Instance(); + +static std::string SpaceSizeToString(size_t size) { + std::string size_str; + if (size > 1024 * 1024 * 1024) { // > 1GB + size_str = fmt::format("{:.2f} GB", double(size / 1024 / 1024) / 1024.0f); + } else if (size > 1024 * 1024) { // > 1MB + size_str = fmt::format("{:.2f} MB", double(size / 1024) / 1024.0f); + } else if (size > 1024) { // > 1KB + size_str = fmt::format("{:.2f} KB", double(size) / 1024.0f); + } else { + size_str = fmt::format("{} B", size); + } + return size_str; +} + +namespace Libraries::SaveData::Dialog { + +void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const { + result.mode = this->mode; + result.result = this->result; + result.buttonId = this->button_id; + if (result.dirName != nullptr) { + result.dirName->data.FromString(this->dir_name); + } + if (result.param != nullptr && this->param.GetString(SaveParams::MAINTITLE).has_value()) { + result.param->FromSFO(this->param); + } + result.userData = this->user_data; +} + +SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) { + this->mode = param.mode; + this->type = param.dispType; + this->user_data = param.userData; + if (param.optionParam != nullptr) { + this->enable_back = {param.optionParam->back == OptionBack::ENABLE}; + } + + static std::string game_serial{*Common::Singleton::Instance()->GetString("CONTENT_ID"), 7, + 9}; + + const auto item = param.items; + this->user_id = item->userId; + + if (item->titleId == nullptr) { + this->title_id = game_serial; + } else { + this->title_id = item->titleId->data.to_string(); + } + + for (u32 i = 0; i < item->dirNameNum; i++) { + const auto dir_name = item->dirName[i].data.to_view(); + + if (dir_name.empty()) { + continue; + } + + auto dir_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name); + + auto param_sfo_path = dir_path / "sce_sys" / "param.sfo"; + if (!std::filesystem::exists(param_sfo_path)) { + continue; + } + + PSF param_sfo; + param_sfo.Open(param_sfo_path); + + auto last_write = param_sfo.GetLastWrite(); +#ifdef _WIN32 + auto utc_time = std::chrono::file_clock::to_utc(last_write); +#else + auto utc_time = std::chrono::file_clock::to_sys(last_write); +#endif + std::string date_str = fmt::format("{:%d %b, %Y %R}", utc_time); + + size_t size = Common::FS::GetDirectorySize(dir_path); + std::string size_str = SpaceSizeToString(size); + + auto icon_path = dir_path / "sce_sys" / "icon0.png"; + RefCountedTexture icon; + if (std::filesystem::exists(icon_path)) { + icon = RefCountedTexture::DecodePngFile(icon_path); + } + + bool is_corrupted = std::filesystem::exists(dir_path / "sce_sys" / "corrupted"); + + this->save_list.emplace_back(Item{ + .dir_name = std::string{dir_name}, + .icon = icon, + + .title = std::string{*param_sfo.GetString(SaveParams::MAINTITLE)}, + .subtitle = std::string{*param_sfo.GetString(SaveParams::SUBTITLE)}, + .details = std::string{*param_sfo.GetString(SaveParams::DETAIL)}, + .date = date_str, + .size = size_str, + .last_write = param_sfo.GetLastWrite(), + .pfo = param_sfo, + .is_corrupted = is_corrupted, + }); + } + + if (type == DialogType::SAVE) { + RefCountedTexture icon; + std::string title{"New Save"}; + + const auto new_item = item->newItem; + if (new_item != nullptr && new_item->iconBuf && new_item->iconSize) { + auto buf = (u8*)new_item->iconBuf; + icon = RefCountedTexture::DecodePngTexture({buf, buf + new_item->iconSize}); + } else { + const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png"); + if (std::filesystem::exists(src_icon)) { + icon = RefCountedTexture::DecodePngFile(src_icon); + } + } + if (new_item != nullptr && new_item->title != nullptr) { + title = std::string{new_item->title}; + } + + this->new_item = Item{ + .dir_name = "", + .icon = icon, + .title = title, + }; + } + + if (item->focusPos != FocusPos::DIRNAME) { + this->focus_pos = item->focusPos; + } else { + this->focus_pos = item->focusPosDirName->data.to_string(); + } + this->style = item->itemStyle; + + switch (mode) { + case SaveDataDialogMode::USER_MSG: { + this->state = UserState{param}; + } break; + case SaveDataDialogMode::SYSTEM_MSG: + this->state = SystemState{*this, param}; + break; + case SaveDataDialogMode::ERROR_CODE: { + this->state = ErrorCodeState{param}; + } break; + case SaveDataDialogMode::PROGRESS_BAR: { + this->state = ProgressBarState{*this, param}; + } break; + default: + break; + } +} + +SaveDialogState::UserState::UserState(const OrbisSaveDataDialogParam& param) { + auto& user = *param.userMsgParam; + this->type = user.buttonType; + this->msg_type = user.msgType; + this->msg = user.msg != nullptr ? std::string{user.msg} : std::string{}; +} + +SaveDialogState::SystemState::SystemState(const SaveDialogState& state, + const OrbisSaveDataDialogParam& param) { +#define M(save, load, del) \ + if (type == DialogType::SAVE) \ + this->msg = save; \ + else if (type == DialogType::LOAD) \ + this->msg = load; \ + else if (type == DialogType::DELETE) \ + this->msg = del; \ + else \ + UNREACHABLE() + + auto type = param.dispType; + auto& sys = *param.sysMsgParam; + switch (sys.msgType) { + case SystemMessageType::NODATA: { + this->msg = "There is no saved data"; + } break; + case SystemMessageType::CONFIRM: + show_no = true; + M("Do you want to save?", "Do you want to load this saved data?", + "Do you want to delete this saved data?"); + break; + case SystemMessageType::OVERWRITE: + show_no = true; + M("Do you want to overwrite the existing saved data?", "##UNKNOWN##", "##UNKNOWN##"); + break; + case SystemMessageType::NOSPACE: + M(fmt::format( + "There is not enough space to save the data. To continue {} free space is required.", + SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)), + "##UNKNOWN##", "##UNKNOWN##"); + break; + case SystemMessageType::PROGRESS: + hide_ok = true; + show_cancel = state.enable_back.value_or(false); + M("Saving...", "Loading...", "Deleting..."); + break; + case SystemMessageType::FILE_CORRUPTED: + this->msg = "The saved data is corrupted."; + break; + case SystemMessageType::FINISHED: + M("Saved successfully.", "Loading complete.", "Deletion complete."); + break; + case SystemMessageType::NOSPACE_CONTINUABLE: + M(fmt::format("There is not enough space to save the data. {} free space is required.", + SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)), + "##UNKNOWN##", "##UNKNOWN##"); + break; + case SystemMessageType::CORRUPTED_AND_DELETED: { + show_cancel = state.enable_back.value_or(true); + const char* msg1 = "The saved data is corrupted and will be deleted."; + M(msg1, msg1, "##UNKNOWN##"); + } break; + case SystemMessageType::CORRUPTED_AND_CREATED: { + show_cancel = state.enable_back.value_or(true); + const char* msg2 = "The saved data is corrupted. This saved data will be deleted and a new " + "one will be created."; + M(msg2, msg2, "##UNKNOWN##"); + } break; + case SystemMessageType::CORRUPTED_AND_RESTORE: { + show_cancel = state.enable_back.value_or(true); + const char* msg3 = + "The saved data is corrupted. The data that was backed up by the system will be " + "restored."; + M(msg3, msg3, "##UNKNOWN##"); + } break; + case SystemMessageType::TOTAL_SIZE_EXCEEDED: + M("Cannot create more saved data", "##UNKNOWN##", "##UNKNOWN##"); + break; + default: + msg = fmt::format("Unknown message type: {}", magic_enum::enum_name(sys.msgType)); + break; + } + +#undef M +} + +SaveDialogState::ErrorCodeState::ErrorCodeState(const OrbisSaveDataDialogParam& param) { + auto& err = *param.errorCodeParam; + constexpr auto NOT_FOUND = 0x809F0008; + constexpr auto BROKEN = 0x809F000F; + switch (err.errorCode) { + case NOT_FOUND: + this->error_msg = "There is not saved data."; + break; + case BROKEN: + this->error_msg = "The data is corrupted."; + break; + default: + this->error_msg = fmt::format("An error has occurred. ({:X})", err.errorCode); + break; + } +} +SaveDialogState::ProgressBarState::ProgressBarState(const SaveDialogState& state, + const OrbisSaveDataDialogParam& param) { + this->progress = 0; + + auto& bar = *param.progressBarParam; + switch (bar.sysMsgType) { + case ProgressSystemMessageType::INVALID: + this->msg = bar.msg != nullptr ? std::string{bar.msg} : std::string{}; + break; + case ProgressSystemMessageType::PROGRESS: + switch (state.type) { + case DialogType::SAVE: + this->msg = "Saving..."; + break; + case DialogType::LOAD: + this->msg = "Loading..."; + break; + case DialogType::DELETE: + this->msg = "Deleting..."; + break; + } + break; + case ProgressSystemMessageType::RESTORE: + this->msg = "Restoring saved data..."; + break; + } +} + +SaveDialogUi::SaveDialogUi(SaveDialogState* state, Status* status, SaveDialogResult* result) + : state(state), status(status), result(result) { + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } +} + +SaveDialogUi::~SaveDialogUi() { + Finish(ButtonId::INVALID); +} + +SaveDialogUi::SaveDialogUi(SaveDialogUi&& other) noexcept + : Layer(other), state(other.state), status(other.status), result(other.result) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } +} + +SaveDialogUi& SaveDialogUi::operator=(SaveDialogUi other) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + using std::swap; + swap(state, other.state); + swap(status, other.status); + swap(result, other.result); + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } + return *this; +} + +void SaveDialogUi::Finish(ButtonId buttonId, Result r) { + std::unique_lock lock(draw_mutex); + if (result) { + result->mode = this->state->mode; + result->result = r; + result->button_id = buttonId; + result->user_data = this->state->user_data; + if (state && state->mode != SaveDataDialogMode::LIST && !state->save_list.empty()) { + result->dir_name = state->save_list.front().dir_name; + } + } + if (status) { + *status = Status::FINISHED; + } + RemoveLayer(this); +} + +void SaveDialogUi::Draw() { + std::unique_lock lock{draw_mutex}; + + if (status == nullptr || *status != Status::RUNNING || state == nullptr) { + return; + } + + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + + ImVec2 window_size; + + if (state->GetMode() == SaveDataDialogMode::LIST) { + window_size = ImVec2{ + std::min(io.DisplaySize.x - 200.0f, 1100.0f), + std::min(io.DisplaySize.y - 100.0f, 700.0f), + }; + } else { + window_size = ImVec2{ + std::min(io.DisplaySize.x, 500.0f), + std::min(io.DisplaySize.y, 300.0f), + }; + } + + CentralizeWindow(); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } + if (Begin("Save Data Dialog##SaveDataDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + DrawPrettyBackground(); + + Separator(); + // Draw title bigger + SetWindowFontScale(1.7f); + switch (state->type) { + case DialogType::SAVE: + TextUnformatted("Save"); + break; + case DialogType::LOAD: + TextUnformatted("Load"); + break; + case DialogType::DELETE: + TextUnformatted("Delete"); + break; + } + SetWindowFontScale(1.0f); + Separator(); + + BeginGroup(); + switch (state->GetMode()) { + case SaveDataDialogMode::LIST: + DrawList(); + break; + case SaveDataDialogMode::USER_MSG: + DrawUser(); + break; + case SaveDataDialogMode::SYSTEM_MSG: + DrawSystemMessage(); + break; + case SaveDataDialogMode::ERROR_CODE: + DrawErrorCode(); + break; + case SaveDataDialogMode::PROGRESS_BAR: + DrawProgressBar(); + break; + default: + TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "!!!Unknown dialog mode!!!"); + } + EndGroup(); + } + End(); + + first_render = false; + if (*status == Status::FINISHED) { + if (state) { + *state = SaveDialogState{}; + } + state = nullptr; + status = nullptr; + result = nullptr; + } +} + +void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool clickable) { + constexpr auto text_spacing = 1.2f; + + auto& ctx = *GetCurrentContext(); + auto& window = *ctx.CurrentWindow; + + auto content_region_avail = GetContentRegionAvail(); + const auto outer_pos = window.DC.CursorPos; + const auto pos = outer_pos + SAVE_ICON_PADDING; + + const ImVec2 size = {content_region_avail.x - SAVE_ICON_PADDING.x, + SAVE_ICON_SIZE.y + SAVE_ICON_PADDING.y}; + const ImRect bb{outer_pos, outer_pos + size + SAVE_ICON_PADDING}; + + const ImGuiID id = GetID(_id); + + ItemSize(size + ImVec2{0.0f, SAVE_ICON_PADDING.y * 2.0f}); + if (!ItemAdd(bb, id)) { + return; + } + + window.DrawList->AddRectFilled(bb.Min + SAVE_ICON_PADDING, bb.Max - SAVE_ICON_PADDING, + GetColorU32(ImVec4{0.3f})); + + bool hovered = false; + if (clickable) { + bool held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + if (pressed) { + result->dir_name = item.dir_name; + result->param = item.pfo; + Finish(ButtonId::INVALID); + } + RenderNavHighlight(bb, id); + } + + if (item.icon) { + auto texture = item.icon.GetTexture(); + window.DrawList->AddImage(texture.im_id, pos, pos + SAVE_ICON_SIZE); + } else { + // placeholder + window.DrawList->AddRectFilled(pos, pos + SAVE_ICON_SIZE, GetColorU32(ImVec4{0.7f})); + } + + auto pos_x = SAVE_ICON_SIZE.x + 5.0f; + auto pos_y = 2.0f; + + if (!item.title.empty()) { + const char* begin = &item.title.front(); + const char* end = &item.title.back() + 1; + SetWindowFontScale(2.0f); + RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false); + if (item.is_corrupted) { + float width = CalcTextSize(begin, end).x + 10.0f; + PushStyleColor(ImGuiCol_Text, 0xFF0000FF); + RenderText(pos + ImVec2{pos_x + width, pos_y}, "- Corrupted", nullptr, false); + PopStyleColor(); + } + pos_y += ctx.FontSize * text_spacing; + } + + SetWindowFontScale(1.3f); + + if (state->style == ItemStyle::TITLE_SUBTITLE_DATESIZE) { + if (!item.subtitle.empty()) { + const char* begin = &item.subtitle.front(); + const char* end = &item.subtitle.back() + 1; + RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false); + } + pos_y += ctx.FontSize * text_spacing; + } + + { + float width = 0.0f; + if (!item.date.empty()) { + const char* d_begin = &item.date.front(); + const char* d_end = &item.date.back() + 1; + width = CalcTextSize(d_begin, d_end).x + 15.0f; + RenderText(pos + ImVec2{pos_x, pos_y}, d_begin, d_end, false); + } + if (!item.size.empty()) { + const char* s_begin = &item.size.front(); + const char* s_end = &item.size.back() + 1; + RenderText(pos + ImVec2{pos_x + width, pos_y}, s_begin, s_end, false); + } + pos_y += ctx.FontSize * text_spacing; + } + + if (state->style == ItemStyle::TITLE_DATASIZE_SUBTITLE && !item.subtitle.empty()) { + const char* begin = &item.subtitle.front(); + const char* end = &item.subtitle.back() + 1; + RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false); + } + + SetWindowFontScale(1.0f); + + if (hovered) { + window.DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, 0, 2.0f); + } +} + +void SaveDialogUi::DrawList() { + auto availableSize = GetContentRegionAvail(); + + constexpr auto footerHeight = 30.0f; + availableSize.y -= footerHeight + 1.0f; + + BeginChild("##ScrollingRegion", availableSize, ImGuiChildFlags_NavFlattened); + int i = 0; + if (state->new_item.has_value()) { + DrawItem(i++, state->new_item.value()); + } + for (const auto& item : state->save_list) { + DrawItem(i++, item); + } + if (first_render) { // Make the initial focus + if (std::holds_alternative(state->focus_pos)) { + auto pos = std::get(state->focus_pos); + if (pos == FocusPos::LISTHEAD || pos == FocusPos::DATAHEAD) { + SetItemCurrentNavFocus(GetID(0)); + } else if (pos == FocusPos::LISTTAIL || pos == FocusPos::DATATAIL) { + SetItemCurrentNavFocus(GetID(std::max(i - 1, 0))); + } else { // Date + int idx = 0; + int max_idx = 0; + bool is_min = pos == FocusPos::DATAOLDEST; + std::filesystem::file_time_type max_write{}; + if (state->new_item.has_value()) { + idx++; + } + for (const auto& item : state->save_list) { + if (item.last_write > max_write ^ is_min) { + max_write = item.last_write; + max_idx = idx; + } + idx++; + } + SetItemCurrentNavFocus(GetID(max_idx)); + } + } else if (std::holds_alternative(state->focus_pos)) { + auto dir_name = std::get(state->focus_pos); + if (dir_name.empty()) { + SetItemCurrentNavFocus(GetID(0)); + } else { + int idx = 0; + if (state->new_item.has_value()) { + if (dir_name == state->new_item->dir_name) { + SetItemCurrentNavFocus(GetID(idx)); + } + idx++; + } + for (const auto& item : state->save_list) { + if (item.dir_name == dir_name) { + SetItemCurrentNavFocus(GetID(idx)); + break; + } + idx++; + } + } + } + } + EndChild(); + + Separator(); + if (state->enable_back.value_or(true)) { + constexpr auto back = "Back"; + constexpr float pad = 7.0f; + const auto txt_size = CalcTextSize(back); + const auto button_size = ImVec2{ + std::max(txt_size.x, 100.0f) + pad * 2.0f, + footerHeight - pad, + }; + SetCursorPosX(GetContentRegionAvail().x - button_size.x); + if (Button(back, button_size)) { + result->dir_name.clear(); + Finish(ButtonId::INVALID); + } + if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + } +} + +void SaveDialogUi::DrawUser() { + const auto& user_state = state->GetState(); + const auto btn_type = user_state.type; + + const auto ws = GetWindowSize(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } + + auto has_btn = btn_type != ButtonType::NONE; + ImVec2 btn_space; + if (has_btn) { + btn_space = ImVec2{0.0f, FOOTER_HEIGHT}; + } + + const auto& msg = user_state.msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + if (user_state.msg_type == UserMessageType::ERROR) { + PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + // Maybe make the text bold? + } + DrawCenteredText(begin, end, GetContentRegionAvail() - btn_space); + if (user_state.msg_type == UserMessageType::ERROR) { + PopStyleColor(); + } + } + + if (has_btn) { + int count = 1; + if (btn_type == ButtonType::YESNO || btn_type == ButtonType::ONCANCEL) { + ++count; + } + + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count), + ws.y - FOOTER_HEIGHT + 5.0f, + }); + + BeginGroup(); + if (btn_type == ButtonType::YESNO) { + if (Button("Yes", BUTTON_SIZE)) { + Finish(ButtonId::YES); + } + SameLine(); + if (Button("No", BUTTON_SIZE)) { + Finish(ButtonId::NO); + } + if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + } else { + if (Button("OK", BUTTON_SIZE)) { + Finish(ButtonId::OK); + } + if (first_render) { + SetItemCurrentNavFocus(); + } + if (btn_type == ButtonType::ONCANCEL) { + SameLine(); + if (Button("Cancel", BUTTON_SIZE)) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + } + } + EndGroup(); + } +} + +void SaveDialogUi::DrawSystemMessage() { + const auto& sys_state = state->GetState(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } + + const auto ws = GetWindowSize(); + const auto& msg = sys_state.msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT}); + } + int count = 1; + if (sys_state.hide_ok) { + --count; + } + if (sys_state.show_no || sys_state.show_cancel) { + ++count; + } + + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count), + ws.y - FOOTER_HEIGHT + 5.0f, + }); + BeginGroup(); + if (Button(sys_state.show_no ? "Yes" : "OK", BUTTON_SIZE)) { + Finish(ButtonId::YES); + } + SameLine(); + if (sys_state.show_no) { + if (Button("No", BUTTON_SIZE)) { + Finish(ButtonId::NO); + } + } else if (sys_state.show_cancel) { + if (Button("Cancel", BUTTON_SIZE)) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + } + if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + EndGroup(); +} + +void SaveDialogUi::DrawErrorCode() { + const auto& err_state = state->GetState(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } + + const auto ws = GetWindowSize(); + const auto& msg = err_state.error_msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT}); + } + + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f, + ws.y - FOOTER_HEIGHT + 5.0f, + }); + if (Button("OK", BUTTON_SIZE)) { + Finish(ButtonId::OK); + } + if (first_render) { + SetItemCurrentNavFocus(); + } +} + +void SaveDialogUi::DrawProgressBar() { + const auto& bar_state = state->GetState(); + + const auto ws = GetWindowSize(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } + + const auto& msg = bar_state.msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT}); + } + + SetCursorPos({ + ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f), + ws.y - FOOTER_HEIGHT + 5.0f, + }); + + ProgressBar(static_cast(bar_state.progress) / 100.0f, + {PROGRESS_BAR_WIDTH * ws.x, BUTTON_SIZE.y}); +} +}; // namespace Libraries::SaveData::Dialog \ No newline at end of file diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.h b/src/core/libraries/save_data/dialog/savedatadialog_ui.h new file mode 100644 index 00000000000..8b9a68e1360 --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.h @@ -0,0 +1,317 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "core/file_format/psf.h" +#include "core/libraries/save_data/savedata.h" +#include "core/libraries/system/commondialog.h" +#include "imgui/imgui_layer.h" +#include "imgui/imgui_texture.h" + +namespace Libraries::SaveData::Dialog { + +using OrbisUserServiceUserId = s32; + +enum class SaveDataDialogMode : u32 { + INVALID = 0, + LIST = 1, + USER_MSG = 2, + SYSTEM_MSG = 3, + ERROR_CODE = 4, + PROGRESS_BAR = 5, +}; + +enum class DialogType : u32 { + SAVE = 1, + LOAD = 2, + DELETE = 3, +}; + +enum class DialogAnimation : u32 { + ON = 0, + OFF = 1, +}; + +enum class ButtonId : u32 { + INVALID = 0, + OK = 1, + YES = 1, + NO = 2, +}; + +enum class ButtonType : u32 { + OK = 0, + YESNO = 1, + NONE = 2, + ONCANCEL = 3, +}; + +enum class UserMessageType : u32 { + NORMAL = 0, + ERROR = 1, +}; + +enum class FocusPos : u32 { + LISTHEAD = 0, + LISTTAIL = 1, + DATAHEAD = 2, + DATATAIL = 3, + DATALTATEST = 4, + DATAOLDEST = 5, + DIRNAME = 6, +}; + +enum class ItemStyle : u32 { + TITLE_DATASIZE_SUBTITLE = 0, + TITLE_SUBTITLE_DATESIZE = 1, + TITLE_DATESIZE = 2, +}; + +enum class SystemMessageType : u32 { + NODATA = 1, + CONFIRM = 2, + OVERWRITE = 3, + NOSPACE = 4, + PROGRESS = 5, + FILE_CORRUPTED = 6, + FINISHED = 7, + NOSPACE_CONTINUABLE = 8, + CORRUPTED_AND_DELETED = 10, + CORRUPTED_AND_CREATED = 11, + CORRUPTED_AND_RESTORE = 13, + TOTAL_SIZE_EXCEEDED = 14, +}; + +enum class ProgressBarType : u32 { + PERCENTAGE = 0, +}; + +enum class ProgressSystemMessageType : u32 { + INVALID = 0, + PROGRESS = 1, + RESTORE = 2, +}; + +enum class OptionBack : u32 { + ENABLE = 0, + DISABLE = 1, +}; + +enum class OrbisSaveDataDialogProgressBarTarget : u32 { + DEFAULT = 0, +}; + +struct AnimationParam { + DialogAnimation userOK; + DialogAnimation userCancel; + std::array _reserved; +}; + +struct SaveDialogNewItem { + const char* title; + void* iconBuf; + size_t iconSize; + std::array _reserved; +}; + +struct SaveDialogItems { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + u32 dirNameNum; + s32 : 32; + const SaveDialogNewItem* newItem; + FocusPos focusPos; + s32 : 32; + const OrbisSaveDataDirName* focusPosDirName; + ItemStyle itemStyle; + std::array _reserved; +}; + +struct UserMessageParam { + ButtonType buttonType; + UserMessageType msgType; + const char* msg; + std::array _reserved; +}; + +struct SystemMessageParam { + SystemMessageType msgType; + s32 : 32; + u64 value; + std::array _reserved; +}; + +struct ErrorCodeParam { + u32 errorCode; + std::array _reserved; +}; + +struct ProgressBarParam { + ProgressBarType barType; + s32 : 32; + const char* msg; + ProgressSystemMessageType sysMsgType; + std::array _reserved; +}; + +struct OptionParam { + OptionBack back; + std::array _reserved; +}; + +struct OrbisSaveDataDialogParam { + CommonDialog::BaseParam baseParam; + s32 size; + SaveDataDialogMode mode; + DialogType dispType; + s32 : 32; + AnimationParam* animParam; + SaveDialogItems* items; + UserMessageParam* userMsgParam; + SystemMessageParam* sysMsgParam; + ErrorCodeParam* errorCodeParam; + ProgressBarParam* progressBarParam; + void* userData; + OptionParam* optionParam; + std::array _reserved; +}; + +struct OrbisSaveDataDialogResult { + SaveDataDialogMode mode{}; + CommonDialog::Result result{}; + ButtonId buttonId{}; + s32 : 32; + OrbisSaveDataDirName* dirName; + OrbisSaveDataParam* param; + void* userData; + std::array _reserved; +}; + +struct SaveDialogResult { + SaveDataDialogMode mode{}; + CommonDialog::Result result{CommonDialog::Result::OK}; + ButtonId button_id{ButtonId::INVALID}; + std::string dir_name{}; + PSF param{}; + void* user_data{}; + + void CopyTo(OrbisSaveDataDialogResult& result) const; +}; + +class SaveDialogState { + friend class SaveDialogUi; + +public: + struct UserState { + ButtonType type{}; + UserMessageType msg_type{}; + std::string msg{}; + + UserState(const OrbisSaveDataDialogParam& param); + }; + struct SystemState { + std::string msg{}; + bool hide_ok{}; + bool show_no{}; // Yes instead of OK + bool show_cancel{}; + + SystemState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param); + }; + struct ErrorCodeState { + std::string error_msg{}; + + ErrorCodeState(const OrbisSaveDataDialogParam& param); + }; + struct ProgressBarState { + std::string msg{}; + u32 progress{}; + + ProgressBarState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param); + }; + + struct Item { + std::string dir_name{}; + ImGui::RefCountedTexture icon{}; + + std::string title{}; + std::string subtitle{}; + std::string details{}; + std::string date{}; + std::string size{}; + + std::filesystem::file_time_type last_write{}; + PSF pfo{}; + bool is_corrupted{}; + }; + +private: + SaveDataDialogMode mode{}; + DialogType type{}; + void* user_data{}; + std::optional enable_back{}; + + OrbisUserServiceUserId user_id{}; + std::string title_id{}; + std::vector save_list{}; + std::variant focus_pos{std::monostate{}}; + ItemStyle style{}; + + std::optional new_item{}; + + std::variant state{ + std::monostate{}}; + +public: + explicit SaveDialogState(const OrbisSaveDataDialogParam& param); + + SaveDialogState() = default; + + [[nodiscard]] SaveDataDialogMode GetMode() const { + return mode; + } + + template + [[nodiscard]] T& GetState() { + return std::get(state); + } +}; + +class SaveDialogUi final : public ImGui::Layer { + bool first_render{false}; + SaveDialogState* state{}; + CommonDialog::Status* status{}; + SaveDialogResult* result{}; + + std::recursive_mutex draw_mutex{}; + +public: + explicit SaveDialogUi(SaveDialogState* state = nullptr, CommonDialog::Status* status = nullptr, + SaveDialogResult* result = nullptr); + + ~SaveDialogUi() override; + SaveDialogUi(const SaveDialogUi& other) = delete; + SaveDialogUi(SaveDialogUi&& other) noexcept; + SaveDialogUi& operator=(SaveDialogUi other); + + void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK); + + void Draw() override; + +private: + void DrawItem(int id, const SaveDialogState::Item& item, bool clickable = true); + + void DrawList(); + void DrawUser(); + void DrawSystemMessage(); + void DrawErrorCode(); + void DrawProgressBar(); +}; + +}; // namespace Libraries::SaveData::Dialog \ No newline at end of file diff --git a/src/core/libraries/save_data/error_codes.h b/src/core/libraries/save_data/error_codes.h deleted file mode 100644 index a4a1b7a5a98..00000000000 --- a/src/core/libraries/save_data/error_codes.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -constexpr int ORBIS_SAVE_DATA_ERROR_PARAMETER = 0x809F0000; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_INITIALIZED = 0x809F0001; -constexpr int ORBIS_SAVE_DATA_ERROR_OUT_OF_MEMORY = 0x809F0002; -constexpr int ORBIS_SAVE_DATA_ERROR_BUSY = 0x809F0003; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED = 0x809F0004; -constexpr int ORBIS_SAVE_DATA_ERROR_NO_PERMISSION = 0x809F0005; -constexpr int ORBIS_SAVE_DATA_ERROR_FINGERPRINT_MISMATCH = 0x809F0006; -constexpr int ORBIS_SAVE_DATA_ERROR_EXISTS = 0x809F0007; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_FOUND = 0x809F0008; -constexpr int ORBIS_SAVE_DATA_ERROR_NO_SPACE_FS = 0x809F000A; -constexpr int ORBIS_SAVE_DATA_ERROR_INTERNAL = 0x809F000B; -constexpr int ORBIS_SAVE_DATA_ERROR_MOUNT_FULL = 0x809F000C; -constexpr int ORBIS_SAVE_DATA_ERROR_BAD_MOUNTED = 0x809F000D; -constexpr int ORBIS_SAVE_DATA_ERROR_FILE_NOT_FOUND = 0x809F000E; -constexpr int ORBIS_SAVE_DATA_ERROR_BROKEN = 0x809F000F; -constexpr int ORBIS_SAVE_DATA_ERROR_INVALID_LOGIN_USER = 0x809F0011; -constexpr int ORBIS_SAVE_DATA_ERROR_MEMORY_NOT_READY = 0x809F0012; -constexpr int ORBIS_SAVE_DATA_ERROR_BACKUP_BUSY = 0x809F0013; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_REGIST_CALLBACK = 0x809F0015; -constexpr int ORBIS_SAVE_DATA_ERROR_BUSY_FOR_SAVING = 0x809F0016; -constexpr int ORBIS_SAVE_DATA_ERROR_LIMITATION_OVER = 0x809F0017; -constexpr int ORBIS_SAVE_DATA_ERROR_EVENT_BUSY = 0x809F0018; -constexpr int ORBIS_SAVE_DATA_ERROR_PARAMSFO_TRANSFER_TITLE_ID_NOT_FOUND = 0x809F0019; diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp new file mode 100644 index 00000000000..93af373a861 --- /dev/null +++ b/src/core/libraries/save_data/save_backup.cpp @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include + +#include "save_backup.h" +#include "save_instance.h" + +#include "common/logging/log.h" +#include "common/logging/log_entry.h" +#include "common/polyfill_thread.h" +#include "common/thread.h" + +constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save +constexpr std::string_view backup_dir = "sce_backup"; // backup folder +constexpr std::string_view backup_dir_tmp = "sce_backup_tmp"; // in-progress backup folder + +namespace fs = std::filesystem; + +namespace Libraries::SaveData::Backup { + +static std::jthread g_backup_thread; +static std::counting_semaphore g_backup_thread_semaphore{0}; + +static std::mutex g_backup_queue_mutex; +static std::deque g_backup_queue; +static std::deque g_result_queue; + +static std::atomic_int g_backup_progress = 0; +static std::atomic g_backup_status = WorkerStatus::NotStarted; + +static void backup(const std::filesystem::path& dir_name) { + if (!fs::exists(dir_name)) { + return; + } + std::vector backup_files; + for (const auto& entry : fs::directory_iterator(dir_name)) { + const auto filename = entry.path().filename(); + if (filename != backup_dir && filename != backup_dir_tmp) { + backup_files.push_back(entry.path()); + } + } + const auto backup_dir = dir_name / ::backup_dir; + const auto backup_dir_tmp = dir_name / ::backup_dir_tmp; + + g_backup_progress = 0; + + int total_count = static_cast(backup_files.size()); + int current_count = 0; + + fs::remove_all(backup_dir_tmp); + fs::create_directory(backup_dir_tmp); + for (const auto& file : backup_files) { + fs::copy(file, backup_dir_tmp / file.filename(), fs::copy_options::recursive); + current_count++; + g_backup_progress = current_count * 100 / total_count; + } + bool has_existing = fs::exists(backup_dir); + if (has_existing) { + fs::rename(backup_dir, dir_name / "sce_backup_old"); + } + fs::rename(backup_dir_tmp, backup_dir); + if (has_existing) { + fs::remove_all(dir_name / "sce_backup_old"); + } +} + +static void BackupThreadBody() { + Common::SetCurrentThreadName("SaveData_BackupThread"); + while (true) { + g_backup_status = WorkerStatus::Waiting; + g_backup_thread_semaphore.acquire(); + BackupRequest req; + { + std::scoped_lock lk{g_backup_queue_mutex}; + req = g_backup_queue.front(); + } + if (req.save_path.empty()) { + break; + } + g_backup_status = WorkerStatus::Running; + LOG_INFO(Lib_SaveData, "Backing up the following directory: {}", req.save_path.string()); + backup(req.save_path); + LOG_DEBUG(Lib_SaveData, "Backing up the following directory: {} finished", + req.save_path.string()); + { + std::scoped_lock lk{g_backup_queue_mutex}; + g_backup_queue.pop_front(); + g_result_queue.push_back(std::move(req)); + if (g_result_queue.size() > 20) { + g_result_queue.pop_front(); + } + } + } + g_backup_status = WorkerStatus::NotStarted; +} + +void StartThread() { + if (g_backup_status != WorkerStatus::NotStarted) { + return; + } + LOG_DEBUG(Lib_SaveData, "Starting backup thread"); + g_backup_thread = std::jthread{BackupThreadBody}; + g_backup_status = WorkerStatus::Waiting; +} + +void StopThread() { + if (g_backup_status == WorkerStatus::NotStarted || g_backup_status == WorkerStatus::Stopping) { + return; + } + LOG_DEBUG(Lib_SaveData, "Stopping backup thread"); + { + std::scoped_lock lk{g_backup_queue_mutex}; + g_backup_queue.emplace_back(BackupRequest{}); + } + g_backup_thread_semaphore.release(); + g_backup_status = WorkerStatus::Stopping; +} + +bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id, + std::string_view dir_name, OrbisSaveDataEventType origin) { + auto save_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name); + + if (g_backup_status != WorkerStatus::Waiting && g_backup_status != WorkerStatus::Running) { + LOG_ERROR(Lib_SaveData, "Called backup while status is {}. Backup request to {} ignored", + magic_enum::enum_name(g_backup_status.load()), save_path.string()); + return false; + } + { + std::scoped_lock lk{g_backup_queue_mutex}; + g_backup_queue.push_back(BackupRequest{ + .user_id = user_id, + .title_id = std::string{title_id}, + .dir_name = std::string{dir_name}, + .origin = origin, + .save_path = std::move(save_path), + }); + } + g_backup_thread_semaphore.release(); + return true; +} + +bool Restore(const std::filesystem::path& save_path) { + LOG_INFO(Lib_SaveData, "Restoring backup for {}", save_path.string()); + if (!fs::exists(save_path) || !fs::exists(save_path / backup_dir)) { + return false; + } + for (const auto& entry : fs::directory_iterator(save_path)) { + const auto filename = entry.path().filename(); + if (filename != backup_dir) { + fs::remove_all(entry.path()); + } + } + + for (const auto& entry : fs::directory_iterator(save_path / backup_dir)) { + const auto filename = entry.path().filename(); + fs::copy(entry.path(), save_path / filename, fs::copy_options::recursive); + } + + return true; +} + +WorkerStatus GetWorkerStatus() { + return g_backup_status; +} + +bool IsBackupExecutingFor(const std::filesystem::path& save_path) { + std::scoped_lock lk{g_backup_queue_mutex}; + return std::ranges::find(g_backup_queue, save_path, + [](const auto& v) { return v.save_path; }) != g_backup_queue.end(); +} + +std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path) { + return save_path / backup_dir; +} + +std::optional PopLastEvent() { + std::scoped_lock lk{g_backup_queue_mutex}; + if (g_result_queue.empty()) { + return std::nullopt; + } + auto req = std::move(g_result_queue.front()); + g_result_queue.pop_front(); + return req; +} + +void PushBackupEvent(BackupRequest&& req) { + std::scoped_lock lk{g_backup_queue_mutex}; + g_result_queue.push_back(std::move(req)); + if (g_result_queue.size() > 20) { + g_result_queue.pop_front(); + } +} + +float GetProgress() { + return static_cast(g_backup_progress) / 100.0f; +} + +void ClearProgress() { + g_backup_progress = 0; +} + +} // namespace Libraries::SaveData::Backup \ No newline at end of file diff --git a/src/core/libraries/save_data/save_backup.h b/src/core/libraries/save_data/save_backup.h new file mode 100644 index 00000000000..f0aef369662 --- /dev/null +++ b/src/core/libraries/save_data/save_backup.h @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/types.h" + +namespace Libraries::SaveData { + +using OrbisUserServiceUserId = s32; + +namespace Backup { + +enum class WorkerStatus { + NotStarted, + Waiting, + Running, + Stopping, +}; + +enum class OrbisSaveDataEventType : u32 { + UMOUNT_BACKUP = 1, + BACKUP = 2, + SAVE_DATA_MEMORY_SYNC = 3, +}; + +struct BackupRequest { + OrbisUserServiceUserId user_id{}; + std::string title_id{}; + std::string dir_name{}; + OrbisSaveDataEventType origin{}; + + std::filesystem::path save_path{}; +}; + +// No problem calling this function if the backup thread is already running +void StartThread(); + +void StopThread(); + +bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id, + std::string_view dir_name, OrbisSaveDataEventType origin); + +bool Restore(const std::filesystem::path& save_path); + +WorkerStatus GetWorkerStatus(); + +bool IsBackupExecutingFor(const std::filesystem::path& save_path); + +std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path); + +std::optional PopLastEvent(); + +void PushBackupEvent(BackupRequest&& req); + +float GetProgress(); + +void ClearProgress(); + +} // namespace Backup + +} // namespace Libraries::SaveData diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp new file mode 100644 index 00000000000..2624ca363a2 --- /dev/null +++ b/src/core/libraries/save_data/save_instance.cpp @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include + +#include "common/assert.h" +#include "common/config.h" +#include "common/path_util.h" +#include "common/singleton.h" +#include "core/file_sys/fs.h" +#include "save_instance.h" + +constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB +constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save +constexpr std::string_view max_block_file_name = "max_block.txt"; + +static Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance(); + +namespace fs = std::filesystem; + +// clang-format off +static const std::unordered_map default_title = { + {"ja_JP", "セーブデータ"}, + {"en", "Saved Data"}, + {"fr", "Données sauvegardées"}, + {"es_ES", "Datos guardados"}, + {"de", "Gespeicherte Daten"}, + {"it", "Dati salvati"}, + {"nl", "Opgeslagen data"}, + {"pt_PT", "Dados guardados"}, + {"ru", "Сохраненные данные"}, + {"ko_KR", "저장 데이터"}, + {"zh_CN", "保存数据"}, + {"fi", "Tallennetut tiedot"}, + {"sv_SE", "Sparade data"}, + {"da_DK", "Gemte data"}, + {"no_NO", "Lagrede data"}, + {"pl_PL", "Zapisane dane"}, + {"pt_BR", "Dados salvos"}, + {"tr_TR", "Kayıtlı Veriler"}, +}; +// clang-format on + +namespace Libraries::SaveData { + +std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial) { + return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) / + game_serial; +} + +std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial, + std::string_view dir_name) { + return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) / + game_serial / dir_name; +} + +int SaveInstance::GetMaxBlocks(const std::filesystem::path& save_path) { + Common::FS::IOFile max_blocks_file{save_path / sce_sys / max_block_file_name, + Common::FS::FileAccessMode::Read}; + int max_blocks = 0; + if (max_blocks_file.IsOpen()) { + max_blocks = std::atoi(max_blocks_file.ReadString(16).c_str()); + } + if (max_blocks <= 0) { + max_blocks = OrbisSaveDataBlocksMax; + } + + return max_blocks; +} + +std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) { + return dir_path / sce_sys / "param.sfo"; +} + +void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, + std::string game_serial) { + std::string locale = Config::getEmulatorLanguage(); + if (!default_title.contains(locale)) { + locale = "en"; + } + +#define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__) + // TODO Link with user service + P(Binary, SaveParams::ACCOUNT_ID, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + P(String, SaveParams::MAINTITLE, default_title.at(locale)); + P(String, SaveParams::SUBTITLE, ""); + P(String, SaveParams::DETAIL, ""); + P(String, SaveParams::SAVEDATA_DIRECTORY, std::move(dir_name)); + P(Integer, SaveParams::SAVEDATA_LIST_PARAM, 0); + P(String, SaveParams::TITLE_ID, std::move(game_serial)); +#undef P +} + +SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial, + std::string_view _dir_name, int max_blocks) + : slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)), + dir_name(_dir_name), max_blocks(max_blocks) { + ASSERT(slot_num >= 0 && slot_num < 16); + + save_path = MakeDirSavePath(user_id, game_serial, dir_name); + + const auto sce_sys_path = save_path / sce_sys; + param_sfo_path = sce_sys_path / "param.sfo"; + corrupt_file_path = sce_sys_path / "corrupted"; + + mount_point = "/savedata" + std::to_string(slot_num); + + this->exists = fs::exists(param_sfo_path); + this->mounted = g_mnt->GetMount(mount_point) != nullptr; +} + +SaveInstance::~SaveInstance() { + if (mounted) { + Umount(); + } +} +SaveInstance::SaveInstance(SaveInstance&& other) noexcept { + if (this == &other) + return; + *this = std::move(other); +} + +SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept { + if (this == &other) + return *this; + slot_num = other.slot_num; + user_id = other.user_id; + game_serial = std::move(other.game_serial); + dir_name = std::move(other.dir_name); + save_path = std::move(other.save_path); + param_sfo_path = std::move(other.param_sfo_path); + corrupt_file_path = std::move(other.corrupt_file_path); + corrupt_file = std::move(other.corrupt_file); + param_sfo = std::move(other.param_sfo); + mount_point = std::move(other.mount_point); + max_blocks = other.max_blocks; + exists = other.exists; + mounted = other.mounted; + read_only = other.read_only; + + other.mounted = false; + + return *this; +} + +void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt) { + if (mounted) { + UNREACHABLE_MSG("Save instance is already mounted"); + } + this->exists = fs::exists(param_sfo_path); // check again just in case + if (!exists) { + CreateFiles(); + if (copy_icon) { + const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png"); + if (fs::exists(src_icon)) { + fs::copy_file(src_icon, GetIconPath()); + } + } + exists = true; + } else { + if (!ignore_corrupt && fs::exists(corrupt_file_path)) { + throw std::filesystem::filesystem_error( + "Corrupted save data", corrupt_file_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + if (!param_sfo.Open(param_sfo_path)) { + throw std::filesystem::filesystem_error( + "Failed to read param.sfo", param_sfo_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + } + + if (!ignore_corrupt && !read_only) { + int err = corrupt_file.Open(corrupt_file_path, Common::FS::FileAccessMode::Write); + if (err != 0) { + throw std::filesystem::filesystem_error( + "Failed to open corrupted file", corrupt_file_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + } + + max_blocks = GetMaxBlocks(save_path); + + g_mnt->Mount(save_path, mount_point, read_only); + mounted = true; + this->read_only = read_only; +} + +void SaveInstance::Umount() { + if (!mounted) { + UNREACHABLE_MSG("Save instance is not mounted"); + return; + } + mounted = false; + const bool ok = param_sfo.Encode(param_sfo_path); + if (!ok) { + throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, + std::make_error_code(std::errc::permission_denied)); + } + param_sfo = PSF(); + + corrupt_file.Close(); + fs::remove(corrupt_file_path); + g_mnt->Unmount(save_path, mount_point); +} + +void SaveInstance::CreateFiles() { + const auto sce_sys_dir = save_path / sce_sys; + fs::create_directories(sce_sys_dir); + + SetupDefaultParamSFO(param_sfo, dir_name, game_serial); + + const bool ok = param_sfo.Encode(param_sfo_path); + if (!ok) { + throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, + std::make_error_code(std::errc::permission_denied)); + } + + Common::FS::IOFile max_block{sce_sys_dir / max_block_file_name, + Common::FS::FileAccessMode::Write}; + max_block.WriteString(std::to_string(max_blocks == 0 ? OrbisSaveDataBlocksMax : max_blocks)); +} + +} // namespace Libraries::SaveData \ No newline at end of file diff --git a/src/core/libraries/save_data/save_instance.h b/src/core/libraries/save_data/save_instance.h new file mode 100644 index 00000000000..f07011047ce --- /dev/null +++ b/src/core/libraries/save_data/save_instance.h @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/io_file.h" +#include "core/file_format/psf.h" + +namespace Core::FileSys { +class MntPoints; +} + +namespace Libraries::SaveData { + +// Used constexpr to easily use as string +namespace SaveParams { +constexpr std::string_view ACCOUNT_ID = "ACCOUNT_ID"; +constexpr std::string_view ATTRIBUTE = "ATTRIBUTE"; +constexpr std::string_view CATEGORY = "CATEGORY"; +constexpr std::string_view DETAIL = "DETAIL"; +constexpr std::string_view FORMAT = "FORMAT"; +constexpr std::string_view MAINTITLE = "MAINTITLE"; +constexpr std::string_view PARAMS = "PARAMS"; +constexpr std::string_view SAVEDATA_BLOCKS = "SAVEDATA_BLOCKS"; +constexpr std::string_view SAVEDATA_DIRECTORY = "SAVEDATA_DIRECTORY"; +constexpr std::string_view SAVEDATA_LIST_PARAM = "SAVEDATA_LIST_PARAM"; +constexpr std::string_view SUBTITLE = "SUBTITLE"; +constexpr std::string_view TITLE_ID = "TITLE_ID"; +} // namespace SaveParams + +using OrbisUserServiceUserId = s32; + +class SaveInstance { + int slot_num{}; + int user_id{}; + std::string game_serial; + std::string dir_name; + + std::filesystem::path save_path; + std::filesystem::path param_sfo_path; + std::filesystem::path corrupt_file_path; + + Common::FS::IOFile corrupt_file; + + PSF param_sfo; + std::string mount_point; + + int max_blocks{}; + bool exists{}; + bool mounted{}; + bool read_only{}; + +public: + // Location of all save data for a title + static std::filesystem::path MakeTitleSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial); + + // Location of a specific save data directory + static std::filesystem::path MakeDirSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial, + std::string_view dir_name); + + static int GetMaxBlocks(const std::filesystem::path& save_path); + + // Get param.sfo path from a dir_path generated by MakeDirSavePath + static std::filesystem::path GetParamSFOPath(const std::filesystem::path& dir_path); + + static void SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial); + + explicit SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string game_serial, + std::string_view dir_name, int max_blocks = 0); + + ~SaveInstance(); + + SaveInstance(const SaveInstance& other) = delete; + SaveInstance(SaveInstance&& other) noexcept; + + SaveInstance& operator=(const SaveInstance& other) = delete; + SaveInstance& operator=(SaveInstance&& other) noexcept; + + void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false); + + void Umount(); + + [[nodiscard]] std::filesystem::path GetIconPath() const noexcept { + return save_path / "sce_sys" / "icon0.png"; + } + + [[nodiscard]] bool Exists() const noexcept { + return exists; + } + + [[nodiscard]] OrbisUserServiceUserId GetUserId() const noexcept { + return user_id; + } + + [[nodiscard]] std::string_view GetTitleId() const noexcept { + return game_serial; + } + + [[nodiscard]] const std::string& GetDirName() const noexcept { + return dir_name; + } + + [[nodiscard]] const std::filesystem::path& GetSavePath() const noexcept { + return save_path; + } + + [[nodiscard]] const PSF& GetParamSFO() const noexcept { + return param_sfo; + } + + [[nodiscard]] PSF& GetParamSFO() noexcept { + return param_sfo; + } + + [[nodiscard]] const std::string& GetMountPoint() const noexcept { + return mount_point; + } + + [[nodiscard]] int GetMaxBlocks() const noexcept { + return max_blocks; + } + + [[nodiscard]] bool Mounted() const noexcept { + return mounted; + } + + [[nodiscard]] bool IsReadOnly() const noexcept { + return read_only; + } + + void CreateFiles(); +}; + +} // namespace Libraries::SaveData diff --git a/src/core/libraries/save_data/save_memory.cpp b/src/core/libraries/save_data/save_memory.cpp new file mode 100644 index 00000000000..6a685ee3916 --- /dev/null +++ b/src/core/libraries/save_data/save_memory.cpp @@ -0,0 +1,287 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "save_memory.h" + +#include +#include +#include +#include +#include + +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/polyfill_thread.h" +#include "common/singleton.h" +#include "common/thread.h" +#include "core/file_sys/fs.h" +#include "save_instance.h" + +using Common::FS::IOFile; +namespace fs = std::filesystem; + +constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save +constexpr std::string_view DirnameSaveDataMemory = "sce_sdmemory"; +constexpr std::string_view FilenameSaveDataMemory = "memory.dat"; + +namespace Libraries::SaveData::SaveMemory { + +static Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance(); + +static OrbisUserServiceUserId g_user_id{}; +static std::string g_game_serial{}; +static std::filesystem::path g_save_path{}; +static std::filesystem::path g_param_sfo_path{}; +static PSF g_param_sfo; + +static bool g_save_memory_initialized = false; +static std::mutex g_saving_memory_mutex; +static std::vector g_save_memory; + +static std::filesystem::path g_icon_path; +static std::vector g_icon_memory; + +static std::condition_variable g_trigger_save_memory; +static std::atomic_bool g_saving_memory = false; +static std::atomic_bool g_save_event = false; +static std::jthread g_save_memory_thread; + +static std::atomic_bool g_memory_dirty = false; +static std::atomic_bool g_param_dirty = false; +static std::atomic_bool g_icon_dirty = false; + +static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& path) { + const auto& dir = path.parent_path(); + const auto& name = path.filename(); + const auto tmp_path = dir / (name.string() + ".tmp"); + + IOFile file(tmp_path, Common::FS::FileAccessMode::Write); + file.WriteRaw(buf, count); + file.Close(); + + fs::remove(path); + fs::rename(tmp_path, path); +} + +[[noreturn]] void SaveThreadLoop() { + Common::SetCurrentThreadName("SaveData_SaveDataMemoryThread"); + std::mutex mtx; + while (true) { + { + std::unique_lock lk{mtx}; + g_trigger_save_memory.wait(lk); + } + // Save the memory + g_saving_memory = true; + std::scoped_lock lk{g_saving_memory_mutex}; + try { + LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", g_save_path.string()); + + if (g_memory_dirty) { + g_memory_dirty = false; + SaveFileSafe(g_save_memory.data(), g_save_memory.size(), + g_save_path / FilenameSaveDataMemory); + } + if (g_param_dirty) { + g_param_dirty = false; + static std::vector buf; + g_param_sfo.Encode(buf); + SaveFileSafe(buf.data(), buf.size(), g_param_sfo_path); + } + if (g_icon_dirty) { + g_icon_dirty = false; + SaveFileSafe(g_icon_memory.data(), g_icon_memory.size(), g_icon_path); + } + + if (g_save_event) { + Backup::PushBackupEvent(Backup::BackupRequest{ + .user_id = g_user_id, + .title_id = g_game_serial, + .dir_name = std::string{DirnameSaveDataMemory}, + .origin = Backup::OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC, + .save_path = g_save_path, + }); + g_save_event = false; + } + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to save save data memory: {}", e.what()); + MsgDialog::ShowMsgDialog(MsgDialog::MsgDialogState{ + MsgDialog::MsgDialogState::UserState{ + .type = MsgDialog::ButtonType::OK, + .msg = fmt::format("Failed to save save data memory.\nCode: <{}>\n{}", + e.code().message(), e.what()), + }, + }); + } + g_saving_memory = false; + } +} + +void SetDirectories(OrbisUserServiceUserId user_id, std::string _game_serial) { + g_user_id = user_id; + g_game_serial = std::move(_game_serial); + g_save_path = SaveInstance::MakeDirSavePath(user_id, g_game_serial, DirnameSaveDataMemory); + g_param_sfo_path = SaveInstance::GetParamSFOPath(g_save_path); + g_param_sfo = PSF(); + g_icon_path = g_save_path / sce_sys / "icon0.png"; +} + +const std::filesystem::path& GetSavePath() { + return g_save_path; +} + +size_t CreateSaveMemory(size_t memory_size) { + size_t existed_size = 0; + + static std::once_flag init_save_thread_flag; + std::call_once(init_save_thread_flag, + [] { g_save_memory_thread = std::jthread{SaveThreadLoop}; }); + + g_save_memory.resize(memory_size); + SaveInstance::SetupDefaultParamSFO(g_param_sfo, std::string{DirnameSaveDataMemory}, + g_game_serial); + + g_save_memory_initialized = true; + + if (!fs::exists(g_param_sfo_path)) { + // Create save memory + fs::create_directories(g_save_path / sce_sys); + + IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Write}; + bool ok = memory_file.SetSize(memory_size); + if (!ok) { + LOG_ERROR(Lib_SaveData, "Failed to set memory size"); + throw std::filesystem::filesystem_error( + "Failed to set save memory size", g_save_path / FilenameSaveDataMemory, + std::make_error_code(std::errc::no_space_on_device)); + } + memory_file.Close(); + } else { + // Load save memory + + bool ok = g_param_sfo.Open(g_param_sfo_path); + if (!ok) { + LOG_ERROR(Lib_SaveData, "Failed to open SFO at {}", g_param_sfo_path.string()); + throw std::filesystem::filesystem_error( + "failed to open SFO", g_param_sfo_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + + IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read}; + if (!memory_file.IsOpen()) { + LOG_ERROR(Lib_SaveData, "Failed to open save memory"); + throw std::filesystem::filesystem_error( + "failed to open save memory", g_save_path / FilenameSaveDataMemory, + std::make_error_code(std::errc::permission_denied)); + } + size_t save_size = memory_file.GetSize(); + existed_size = save_size; + memory_file.Seek(0); + memory_file.ReadRaw(g_save_memory.data(), std::min(save_size, memory_size)); + memory_file.Close(); + } + + return existed_size; +} + +void SetIcon(void* buf, size_t buf_size) { + if (buf == nullptr) { + const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png"); + if (fs::exists(src_icon)) { + fs::copy_file(src_icon, g_icon_path); + } + IOFile file(g_icon_path, Common::FS::FileAccessMode::Read); + size_t size = file.GetSize(); + file.Seek(0); + g_icon_memory.resize(size); + file.ReadRaw(g_icon_memory.data(), size); + file.Close(); + } else { + g_icon_memory.resize(buf_size); + std::memcpy(g_icon_memory.data(), buf, buf_size); + IOFile file(g_icon_path, Common::FS::FileAccessMode::Append); + file.Seek(0); + file.WriteRaw(g_icon_memory.data(), buf_size); + file.Close(); + } +} + +void WriteIcon(void* buf, size_t buf_size) { + if (buf_size != g_icon_memory.size()) { + g_icon_memory.resize(buf_size); + } + std::memcpy(g_icon_memory.data(), buf, buf_size); + g_icon_dirty = true; +} + +bool IsSaveMemoryInitialized() { + return g_save_memory_initialized; +} + +PSF& GetParamSFO() { + return g_param_sfo; +} + +std::span GetIcon() { + return {g_icon_memory}; +} + +void SaveSFO(bool sync) { + if (!sync) { + g_param_dirty = true; + return; + } + const bool ok = g_param_sfo.Encode(g_param_sfo_path); + if (!ok) { + LOG_ERROR(Lib_SaveData, "Failed to encode param.sfo"); + throw std::filesystem::filesystem_error("Failed to write param.sfo", g_param_sfo_path, + std::make_error_code(std::errc::permission_denied)); + } +} +bool IsSaving() { + return g_saving_memory; +} + +bool TriggerSaveWithoutEvent() { + if (g_saving_memory) { + return false; + } + g_trigger_save_memory.notify_one(); + return true; +} + +bool TriggerSave() { + if (g_saving_memory) { + return false; + } + g_save_event = true; + g_trigger_save_memory.notify_one(); + return true; +} + +void ReadMemory(void* buf, size_t buf_size, int64_t offset) { + std::scoped_lock lk{g_saving_memory_mutex}; + if (offset > g_save_memory.size()) { + UNREACHABLE_MSG("ReadMemory out of bounds"); + } + if (offset + buf_size > g_save_memory.size()) { + UNREACHABLE_MSG("ReadMemory out of bounds"); + } + std::memcpy(buf, g_save_memory.data() + offset, buf_size); +} + +void WriteMemory(void* buf, size_t buf_size, int64_t offset) { + std::scoped_lock lk{g_saving_memory_mutex}; + if (offset > g_save_memory.size()) { + UNREACHABLE_MSG("WriteMemory out of bounds"); + } + if (offset + buf_size > g_save_memory.size()) { + UNREACHABLE_MSG("WriteMemory out of bounds"); + } + std::memcpy(g_save_memory.data() + offset, buf, buf_size); + g_memory_dirty = true; +} + +} // namespace Libraries::SaveData::SaveMemory \ No newline at end of file diff --git a/src/core/libraries/save_data/save_memory.h b/src/core/libraries/save_data/save_memory.h new file mode 100644 index 00000000000..04eeaa65260 --- /dev/null +++ b/src/core/libraries/save_data/save_memory.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "save_backup.h" + +class PSF; + +namespace Libraries::SaveData { +using OrbisUserServiceUserId = s32; +} + +namespace Libraries::SaveData::SaveMemory { + +void SetDirectories(OrbisUserServiceUserId user_id, std::string game_serial); + +[[nodiscard]] const std::filesystem::path& GetSavePath(); + +// returns the size of the existed save memory +size_t CreateSaveMemory(size_t memory_size); + +// Initialize the icon. Set buf to null to read the standard icon. +void SetIcon(void* buf, size_t buf_size); + +// Update the icon +void WriteIcon(void* buf, size_t buf_size); + +[[nodiscard]] bool IsSaveMemoryInitialized(); + +[[nodiscard]] PSF& GetParamSFO(); + +[[nodiscard]] std::span GetIcon(); + +// Save now or wait for the background thread to save +void SaveSFO(bool sync = false); + +[[nodiscard]] bool IsSaving(); + +bool TriggerSaveWithoutEvent(); + +bool TriggerSave(); + +void ReadMemory(void* buf, size_t buf_size, int64_t offset); + +void WriteMemory(void* buf, size_t buf_size, int64_t offset); + +} // namespace Libraries::SaveData::SaveMemory \ No newline at end of file diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 10a89f66943..839ec335b50 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -4,30 +4,528 @@ #include #include +#include +#include + #include "common/assert.h" +#include "common/cstring.h" +#include "common/enum.h" #include "common/logging/log.h" #include "common/path_util.h" #include "common/singleton.h" +#include "common/string_util.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/save_data/savedata.h" -#include "error_codes.h" +#include "core/libraries/system/msgdialog.h" +#include "save_backup.h" +#include "save_instance.h" +#include "save_memory.h" + +namespace fs = std::filesystem; +namespace chrono = std::chrono; + +using Common::CString; namespace Libraries::SaveData { -bool is_rw_mode = false; -static constexpr std::string_view g_mount_point = "/savedata0"; // temp mount point (todo) -std::string game_serial; + +enum class Error : u32 { + OK = 0, + PARAMETER = 0x809F0000, + NOT_INITIALIZED = 0x809F0001, + OUT_OF_MEMORY = 0x809F0002, + BUSY = 0x809F0003, + NOT_MOUNTED = 0x809F0004, + EXISTS = 0x809F0007, + NOT_FOUND = 0x809F0008, + NO_SPACE_FS = 0x809F000A, + INTERNAL = 0x809F000B, + MOUNT_FULL = 0x809F000C, + BAD_MOUNTED = 0x809F000D, + BROKEN = 0x809F000F, + INVALID_LOGIN_USER = 0x809F0011, + MEMORY_NOT_READY = 0x809F0012, + BACKUP_BUSY = 0x809F0013, + BUSY_FOR_SAVING = 0x809F0016, +}; + +enum class OrbisSaveDataSaveDataMemoryOption : u32 { + NONE = 0, + SET_PARAM = 1 << 0, + DOUBLE_BUFFER = 1 << 1, + UNLOCK_LIMITATIONS = 1 << 2, +}; + +using OrbisUserServiceUserId = s32; +using OrbisSaveDataBlocks = u64; + +constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB +constexpr u32 OrbisSaveDataBlocksMin2 = 96; // 3MiB +constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB + +// Maximum size for a mount point "/savedataN", where N is a number +constexpr size_t OrbisSaveDataMountPointDataMaxsize = 16; + +constexpr size_t OrbisSaveDataFingerprintDataSize = 65; // Maximum fingerprint size + +enum class OrbisSaveDataMountMode : u32 { + RDONLY = 1 << 0, + RDWR = 1 << 1, + CREATE = 1 << 2, + DESTRUCT_OFF = 1 << 3, + COPY_ICON = 1 << 4, + CREATE2 = 1 << 5, +}; +DECLARE_ENUM_FLAG_OPERATORS(OrbisSaveDataMountMode); + +enum class OrbisSaveDataMountStatus : u32 { + NOTHING = 0, + CREATED = 1, +}; + +enum class OrbisSaveDataParamType : u32 { + ALL = 0, + TITLE = 1, + SUB_TITLE = 2, + DETAIL = 3, + USER_PARAM = 4, + MTIME = 5, +}; + +enum class OrbisSaveDataSortKey : u32 { + DIRNAME = 0, + USER_PARAM = 1, + BLOCKS = 2, + MTIME = 3, + FREE_BLOCKS = 5, +}; + +enum class OrbisSaveDataSortOrder : u32 { + ASCENT = 0, + DESCENT = 1, +}; + +struct OrbisSaveDataFingerprint { + CString data; + std::array _pad; +}; + +struct OrbisSaveDataBackup { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + const OrbisSaveDataFingerprint* param; + std::array _reserved; +}; + +struct OrbisSaveDataCheckBackupData { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + OrbisSaveDataParam* param; + OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataDelete { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + u32 _unused; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataIcon { + void* buf; + size_t bufSize; + size_t dataSize; + std::array _reserved; + + Error LoadIcon(const std::filesystem::path& icon_path) { + try { + const Common::FS::IOFile file(icon_path, Common::FS::FileAccessMode::Read); + dataSize = file.GetSize(); + file.Seek(0); + file.ReadRaw(buf, std::min(bufSize, dataSize)); + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what()); + return Error::INTERNAL; + } + return Error::OK; + } +}; + +struct OrbisSaveDataMemoryData { + void* buf; + size_t bufSize; + s64 offset; + u8 _reserved[40]; +}; + +struct OrbisSaveDataMemoryGet2 { + OrbisUserServiceUserId userId; + std::array _pad; + OrbisSaveDataMemoryData* data; + OrbisSaveDataParam* param; + OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySet2 { + OrbisUserServiceUserId userId; + std::array _pad; + const OrbisSaveDataMemoryData* data; + const OrbisSaveDataParam* param; + const OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySetup2 { + OrbisSaveDataSaveDataMemoryOption option; + OrbisUserServiceUserId userId; + size_t memorySize; + size_t iconMemorySize; + const OrbisSaveDataParam* initParam; + const OrbisSaveDataIcon* initIcon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySetupResult { + size_t existedMemorySize; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySync { + OrbisUserServiceUserId userId; + std::array _reserved; +}; + +struct OrbisSaveDataMount2 { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataDirName* dirName; + OrbisSaveDataBlocks blocks; + OrbisSaveDataMountMode mountMode; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataMount { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + const OrbisSaveDataFingerprint* fingerprint; + OrbisSaveDataBlocks blocks; + OrbisSaveDataMountMode mountMode; + std::array _reserved; +}; + +struct OrbisSaveDataMountInfo { + OrbisSaveDataBlocks blocks; + OrbisSaveDataBlocks freeBlocks; + std::array _reserved; +}; + +struct OrbisSaveDataMountPoint { + CString data; +}; + +struct OrbisSaveDataMountResult { + OrbisSaveDataMountPoint mount_point; + OrbisSaveDataBlocks required_blocks; + u32 _unused; + OrbisSaveDataMountStatus mount_status; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataRestoreBackupData { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + const OrbisSaveDataFingerprint* fingerprint; + u32 _unused; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataDirNameSearchCond { + OrbisUserServiceUserId userId; + int : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + OrbisSaveDataSortKey key; + OrbisSaveDataSortOrder order; + std::array _reserved; +}; + +struct OrbisSaveDataSearchInfo { + u64 blocks; + u64 freeBlocks; + std::array _reserved; +}; + +struct OrbisSaveDataDirNameSearchResult { + u32 hitNum; + int : 32; + OrbisSaveDataDirName* dirNames; + u32 dirNamesNum; + u32 setNum; + OrbisSaveDataParam* params; + OrbisSaveDataSearchInfo* infos; + std::array _reserved; + int : 32; +}; + +struct OrbisSaveDataEventParam { // dummy structure + OrbisSaveDataEventParam() = delete; +}; + +using OrbisSaveDataEventType = Backup::OrbisSaveDataEventType; + +struct OrbisSaveDataEvent { + OrbisSaveDataEventType type; + s32 errorCode; + OrbisUserServiceUserId userId; + std::array _pad; + OrbisSaveDataTitleId titleId; + OrbisSaveDataDirName dirName; + std::array _reserved; +}; + +static bool g_initialized = false; +static std::string g_game_serial; +static std::array, 16> g_mount_slots; + +static void initialize() { + g_initialized = true; + static auto* param_sfo = Common::Singleton::Instance(); + g_game_serial = std::string(*param_sfo->GetString("CONTENT_ID"), 7, 9); +} + +// game_00other | game*other + +static bool match(std::string_view str, std::string_view pattern) { + auto str_it = str.begin(); + auto pat_it = pattern.begin(); + while (str_it != str.end() && pat_it != pattern.end()) { + if (*pat_it == '%') { // 0 or more wildcard + for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) { + if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) { + return true; + } + } + return false; + } + if (*pat_it == '_') { // 1 character wildcard + ++str_it; + ++pat_it; + } else if (*pat_it != *str_it) { + return false; + } + ++str_it; + ++pat_it; + } + return str_it == str.end() && pat_it == pattern.end(); +} + +static Error saveDataMount(const OrbisSaveDataMount2* mount_info, + OrbisSaveDataMountResult* mount_result) { + + if (mount_info->userId < 0) { + return Error::INVALID_LOGIN_USER; + } + if (mount_info->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called without dirName"); + return Error::PARAMETER; + } + + // check backup status + { + const auto save_path = SaveInstance::MakeDirSavePath(mount_info->userId, g_game_serial, + mount_info->dirName->data); + if (Backup::IsBackupExecutingFor(save_path)) { + return Error::BACKUP_BUSY; + } + } + + auto mount_mode = mount_info->mountMode; + const bool is_ro = True(mount_mode & OrbisSaveDataMountMode::RDONLY); + + const bool create = True(mount_mode & OrbisSaveDataMountMode::CREATE); + const bool create_if_not_exist = True(mount_mode & OrbisSaveDataMountMode::CREATE2); + ASSERT(!create || !create_if_not_exist); // Can't have both + + const bool copy_icon = True(mount_mode & OrbisSaveDataMountMode::COPY_ICON); + const bool ignore_corrupt = True(mount_mode & OrbisSaveDataMountMode::DESTRUCT_OFF); + + const std::string_view dir_name{mount_info->dirName->data}; + + // find available mount point + int slot_num = -1; + for (size_t i = 0; i < g_mount_slots.size(); i++) { + const auto& instance = g_mount_slots[i]; + if (instance.has_value()) { + const auto& slot_name = instance->GetDirName(); + if (slot_name == dir_name) { + return Error::BUSY; + } + } else { + slot_num = static_cast(i); + break; + } + } + if (slot_num == -1) { + return Error::MOUNT_FULL; + } + + SaveInstance save_instance{slot_num, mount_info->userId, g_game_serial, dir_name, + (int)mount_info->blocks}; + + if (save_instance.Mounted()) { + UNREACHABLE_MSG("Save instance should not be mounted"); + } + + if (!create && !create_if_not_exist && !save_instance.Exists()) { + return Error::NOT_FOUND; + } + if (create && save_instance.Exists()) { + return Error::EXISTS; + } + + bool to_be_created = !save_instance.Exists(); + + if (to_be_created) { // Check size + + if (mount_info->blocks < OrbisSaveDataBlocksMin2 || + mount_info->blocks > OrbisSaveDataBlocksMax) { + LOG_INFO(Lib_SaveData, "called with invalid block size"); + } + + const auto root_save = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir); + fs::create_directories(root_save); + const auto available = fs::space(root_save).available; + + auto requested_size = mount_info->blocks * OrbisSaveDataBlockSize; + if (requested_size > available) { + mount_result->required_blocks = (requested_size - available) / OrbisSaveDataBlockSize; + return Error::NO_SPACE_FS; + } + } + + try { + save_instance.SetupAndMount(is_ro, copy_icon, ignore_corrupt); + } catch (const fs::filesystem_error& e) { + if (e.code() == std::errc::illegal_byte_sequence) { + LOG_ERROR(Lib_SaveData, "Corrupted save data"); + return Error::BROKEN; + } + if (e.code() == std::errc::no_space_on_device) { + return Error::NO_SPACE_FS; + } + LOG_ERROR(Lib_SaveData, "Failed to mount save data: {}", e.what()); + return Error::INTERNAL; + } + + mount_result->mount_point.data.FromString(save_instance.GetMountPoint()); + + mount_result->mount_status = create_if_not_exist && to_be_created + ? OrbisSaveDataMountStatus::CREATED + : OrbisSaveDataMountStatus::NOTHING; + + g_mount_slots[slot_num].emplace(std::move(save_instance)); + + return Error::OK; +} + +static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup = false) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (mountPoint == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "Umount mountPoint:{}", mountPoint->data.to_view()); + const std::string_view mount_point_str{mountPoint->data}; + for (auto& instance : g_mount_slots) { + if (instance.has_value()) { + const auto& slot_name = instance->GetMountPoint(); + if (slot_name == mount_point_str) { + if (call_backup) { + Backup::StartThread(); + Backup::NewRequest(instance->GetUserId(), instance->GetTitleId(), + instance->GetDirName(), + OrbisSaveDataEventType::UMOUNT_BACKUP); + } + // TODO: check if is busy + instance->Umount(); + instance.reset(); + return Error::OK; + } + } + } + return Error::NOT_FOUND; +} + +void OrbisSaveDataParam::FromSFO(const PSF& sfo) { + memset(this, 0, sizeof(OrbisSaveDataParam)); + title.FromString(*sfo.GetString(SaveParams::MAINTITLE)); + subTitle.FromString(*sfo.GetString(SaveParams::SUBTITLE)); + detail.FromString(*sfo.GetString(SaveParams::DETAIL)); + userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0); + const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch(); + mtime = chrono::duration_cast(time_since_epoch).count(); +} + +void OrbisSaveDataParam::ToSFO(PSF& sfo) const { + sfo.AddString(std::string{SaveParams::MAINTITLE}, std::string{title}, true); + sfo.AddString(std::string{SaveParams::SUBTITLE}, std::string{subTitle}, true); + sfo.AddString(std::string{SaveParams::DETAIL}, std::string{detail}, true); + sfo.AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, static_cast(userParam), true); +} int PS4_SYSV_ABI sceSaveDataAbort() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataBackup() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (backup == nullptr || backup->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + + const std::string_view dir_name{backup->dirName->data}; + LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name); + + std::string_view title{backup->titleId != nullptr ? std::string_view{backup->titleId->data} + : std::string_view{g_game_serial}}; + + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetUserId() == backup->userId && + instance->GetTitleId() == title && instance->GetDirName() == dir_name) { + return Error::BUSY; + } + } + + Backup::StartThread(); + Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP); + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataBindPsnAccount() { @@ -50,15 +548,54 @@ int PS4_SYSV_ABI sceSaveDataChangeInternal() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) { - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(check->dirName->data); - if (!std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; +Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (check == nullptr || check->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; } - LOG_INFO(Lib_SaveData, "called = {}", mount_dir.string()); - return ORBIS_OK; + const std::string_view title{check->titleId != nullptr ? std::string_view{check->titleId->data} + : std::string_view{g_game_serial}}; + + const auto save_path = + SaveInstance::MakeDirSavePath(check->userId, title, check->dirName->data); + + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetSavePath() == save_path) { + return Error::BUSY; + } + } + + if (Backup::IsBackupExecutingFor(save_path)) { + return Error::BACKUP_BUSY; + } + + const auto backup_path = Backup::MakeBackupPath(save_path); + if (!fs::exists(backup_path)) { + return Error::NOT_FOUND; + } + + if (check->param != nullptr) { + PSF sfo; + if (!sfo.Open(backup_path / "sce_sys" / "param.sfo")) { + LOG_ERROR(Lib_SaveData, "Failed to read SFO at {}", backup_path.string()); + return Error::INTERNAL; + } + check->param->FromSFO(sfo); + } + + if (check->icon != nullptr) { + const auto icon_path = backup_path / "sce_sys" / "icon0.png"; + if (fs::exists(icon_path) && check->icon->LoadIcon(icon_path) != Error::OK) { + return Error::INTERNAL; + } + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg() { @@ -96,9 +633,14 @@ int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataClearProgress() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataClearProgress() { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + LOG_DEBUG(Lib_SaveData, "called"); + Backup::ClearProgress(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataCopy5() { @@ -146,15 +688,35 @@ int PS4_SYSV_ABI sceSaveDataDebugTarget() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(1) / game_serial / std::string(del->dirName->data); - LOG_INFO(Lib_SaveData, "called: dirname = {}, mount_dir = {}", (char*)del->dirName->data, - mount_dir.string()); - if (std::filesystem::exists(mount_dir) && std::filesystem::is_directory(mount_dir)) { - std::filesystem::remove_all(mount_dir); +Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; } - return ORBIS_OK; + if (del == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + const std::string_view dirName{del->dirName->data}; + LOG_DEBUG(Lib_SaveData, "called dirName: {}", dirName); + if (dirName.empty()) { + return Error::PARAMETER; + } + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetDirName() == dirName) { + return Error::BUSY; + } + } + const auto save_path = SaveInstance::MakeDirSavePath(del->userId, g_game_serial, dirName); + try { + if (fs::exists(save_path)) { + fs::remove_all(save_path); + } + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to delete save data: {}", e.what()); + return Error::INTERNAL; + } + return Error::OK; } int PS4_SYSV_ABI sceSaveDataDelete5() { @@ -177,89 +739,115 @@ int PS4_SYSV_ABI sceSaveDataDeleteUser() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, - OrbisSaveDataDirNameSearchResult* result) { - if (cond == nullptr || result == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - LOG_INFO(Lib_SaveData, "Number of directories = {}", result->dirNamesNum); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(cond->userId) / game_serial; - if (!mount_dir.empty() && std::filesystem::exists(mount_dir)) { - int maxDirNum = result->dirNamesNum; // Games set a maximum of directories to search for - int i = 0; - - if (cond->dirName == nullptr || std::string_view(cond->dirName->data).empty()) { - // Look for all dirs if no dir is provided. - for (const auto& entry : std::filesystem::directory_iterator(mount_dir)) { - if (i >= maxDirNum) - break; - - if (std::filesystem::is_directory(entry.path()) && - entry.path().filename().string() != "sdmemory") { - // sceSaveDataDirNameSearch does not search for dataMemory1/2 dirs. - // copy dir name to be used by sceSaveDataMount in read mode. - strncpy(result->dirNames[i].data, entry.path().filename().string().c_str(), 32); - i++; - result->hitNum = i; - result->dirNamesNum = i; - result->setNum = i; - } - } - } else { - // Game checks for a specific directory. - LOG_INFO(Lib_SaveData, "dirName = {}", cond->dirName->data); - - // Games can pass '%' as a wildcard - // e.g. `SAVELIST%` searches for all folders with names starting with `SAVELIST` - std::string baseName(cond->dirName->data); - u64 wildcardPos = baseName.find('%'); - if (wildcardPos != std::string::npos) { - baseName = baseName.substr(0, wildcardPos); - } - - for (const auto& entry : std::filesystem::directory_iterator(mount_dir)) { - if (i >= maxDirNum) - break; +Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, + OrbisSaveDataDirNameSearchResult* result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (cond == nullptr || result == nullptr || cond->key > OrbisSaveDataSortKey::FREE_BLOCKS || + cond->order > OrbisSaveDataSortOrder::DESCENT) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + const std::string_view title_id{cond->titleId == nullptr + ? std::string_view{g_game_serial} + : std::string_view{cond->titleId->data}}; + const auto save_path = SaveInstance::MakeTitleSavePath(cond->userId, title_id); - if (std::filesystem::is_directory(entry.path())) { - std::string dirName = entry.path().filename().string(); + if (!fs::exists(save_path)) { + result->hitNum = 0; + result->setNum = 0; + return Error::OK; + } - if (wildcardPos != std::string::npos) { - if (dirName.compare(0, baseName.size(), baseName) != 0) { - continue; - } - } else if (wildcardPos == std::string::npos && dirName != cond->dirName->data) { - continue; - } + std::vector dir_list; - strncpy(result->dirNames[i].data, cond->dirName->data, 32); + for (const auto& path : fs::directory_iterator{save_path}) { + auto dir_name = path.path().filename().string(); + // skip non-directories, sce_* and directories without param.sfo + if (fs::is_directory(path) && !dir_name.starts_with("sce_") && + fs::exists(SaveInstance::GetParamSFOPath(path))) { + dir_list.push_back(dir_name); + } + } + if (cond->dirName != nullptr) { + // Filter names + const auto pat = Common::ToLower(std::string_view{cond->dirName->data}); + std::erase_if(dir_list, [&](const std::string& dir_name) { + return !match(Common::ToLower(dir_name), pat); + }); + } - i++; - result->hitNum = i; - result->dirNamesNum = i; - result->setNum = i; - } - } + std::unordered_map map_dir_sfo; + std::unordered_map map_max_blocks; + std::unordered_map map_free_size; + + for (const auto& dir_name : dir_list) { + const auto dir_path = SaveInstance::MakeDirSavePath(cond->userId, title_id, dir_name); + const auto sfo_path = SaveInstance::GetParamSFOPath(dir_path); + PSF sfo; + if (!sfo.Open(sfo_path)) { + LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", sfo_path.string()); + ASSERT_MSG(false, "Failed to read SFO"); } + map_dir_sfo.emplace(dir_name, std::move(sfo)); + + size_t size = Common::FS::GetDirectorySize(dir_path); + size_t total = SaveInstance::GetMaxBlocks(dir_path); + map_free_size.emplace(dir_name, total - size / OrbisSaveDataBlockSize); + map_max_blocks.emplace(dir_name, total); + } + +#define PROJ(x) [&](const auto& v) { return x; } + switch (cond->key) { + case OrbisSaveDataSortKey::DIRNAME: + std::ranges::stable_sort(dir_list); + break; + case OrbisSaveDataSortKey::USER_PARAM: + std::ranges::stable_sort( + dir_list, {}, + PROJ(map_dir_sfo.at(v).GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0))); + break; + case OrbisSaveDataSortKey::BLOCKS: + std::ranges::stable_sort(dir_list, {}, PROJ(map_max_blocks.at(v))); + break; + case OrbisSaveDataSortKey::FREE_BLOCKS: + std::ranges::stable_sort(dir_list, {}, PROJ(map_free_size.at(v))); + break; + case OrbisSaveDataSortKey::MTIME: + std::ranges::stable_sort(dir_list, {}, PROJ(map_dir_sfo.at(v).GetLastWrite())); + break; + } +#undef PROJ + + if (cond->order == OrbisSaveDataSortOrder::DESCENT) { + std::ranges::reverse(dir_list); + } + + result->hitNum = dir_list.size(); + size_t max_count = std::min(static_cast(result->dirNamesNum), dir_list.size()); + result->setNum = max_count; + + for (size_t i = 0; i < max_count; i++) { + auto& name_data = result->dirNames[i].data; + name_data.FromString(dir_list[i]); if (result->params != nullptr) { - Common::FS::IOFile file(mount_dir / cond->dirName->data / "param.txt", - Common::FS::FileAccessMode::Read); - if (file.IsOpen()) { - file.ReadRaw((void*)result->params, sizeof(OrbisSaveDataParam)); - file.Close(); - } + auto& sfo = map_dir_sfo.at(dir_list[i]); + auto& param_data = result->params[i]; + param_data.FromSFO(sfo); + } + + if (result->infos != nullptr) { + auto& info = result->infos[i]; + info.blocks = map_max_blocks.at(dir_list[i]); + info.freeBlocks = map_free_size.at(dir_list[i]); } - } else { - result->hitNum = 0; - result->dirNamesNum = 0; - result->setNum = 0; - } - if (result->infos != nullptr) { - result->infos->blocks = ORBIS_SAVE_DATA_BLOCK_SIZE; - result->infos->freeBlocks = ORBIS_SAVE_DATA_BLOCK_SIZE; } - return ORBIS_OK; + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal() { @@ -322,15 +910,30 @@ int PS4_SYSV_ABI sceSaveDataGetEventInfo() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam, - OrbisSaveDataEvent* event) { - // eventParam can be 0/null. - if (event == nullptr) - return ORBIS_SAVE_DATA_ERROR_NOT_INITIALIZED; +Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam*, + OrbisSaveDataEvent* event) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (event == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_TRACE(Lib_SaveData, "called"); - LOG_INFO(Lib_SaveData, "called: Todo."); - event->userId = 1; - return ORBIS_OK; + auto last_event = Backup::PopLastEvent(); + if (!last_event.has_value()) { + return Error::NOT_FOUND; + } + + event->type = last_event->origin; + event->errorCode = 0; + event->userId = last_event->user_id; + event->titleId.data.FromString(last_event->title_id); + event->dirName.data.FromString(last_event->dir_name); + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetFormat() { @@ -343,65 +946,119 @@ int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataMountInfo* info) { - LOG_INFO(Lib_SaveData, "called"); - info->blocks = ORBIS_SAVE_DATA_BLOCKS_MAX; - info->freeBlocks = ORBIS_SAVE_DATA_BLOCKS_MAX; - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataMountInfo* info) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (mountPoint == nullptr || info == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + const u32 blocks = instance->GetMaxBlocks(); + const u64 size = Common::FS::GetDirectorySize(instance->GetSavePath()); + info->blocks = blocks; + info->freeBlocks = blocks - size / OrbisSaveDataBlockSize; + return Error::OK; + } + } + return Error::NOT_MOUNTED; } -int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataParamType paramType, void* paramBuf, - const size_t paramBufSize, size_t* gotSize) { - - if (mountPoint == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Read); - OrbisSaveDataParam params; - file.Read(params); - - LOG_INFO(Lib_SaveData, "called"); +Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, void* paramBuf, + size_t paramBufSize, size_t* gotSize) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (paramType > OrbisSaveDataParamType::MTIME || paramBuf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType)); + const PSF* param_sfo = nullptr; + + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + param_sfo = &instance->GetParamSFO(); + break; + } + } + if (param_sfo == nullptr) { + return Error::NOT_MOUNTED; + } switch (paramType) { - case ORBIS_SAVE_DATA_PARAM_TYPE_ALL: { - memcpy(paramBuf, ¶ms, sizeof(OrbisSaveDataParam)); - *gotSize = sizeof(OrbisSaveDataParam); - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_TITLE: { - std::memcpy(paramBuf, ¶ms.title, ORBIS_SAVE_DATA_TITLE_MAXSIZE); - *gotSize = ORBIS_SAVE_DATA_TITLE_MAXSIZE; - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE: { - std::memcpy(paramBuf, ¶ms.subTitle, ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE); - *gotSize = ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE; - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL: { - std::memcpy(paramBuf, ¶ms.detail, ORBIS_SAVE_DATA_DETAIL_MAXSIZE); - *gotSize = ORBIS_SAVE_DATA_DETAIL_MAXSIZE; - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM: { - std::memcpy(paramBuf, ¶ms.userParam, sizeof(u32)); - *gotSize = sizeof(u32); + case OrbisSaveDataParamType::ALL: { + const auto param = static_cast(paramBuf); + ASSERT(paramBufSize == sizeof(OrbisSaveDataParam)); + param->FromSFO(*param_sfo); + if (gotSize != nullptr) { + *gotSize = sizeof(OrbisSaveDataParam); + } + break; + } + case OrbisSaveDataParamType::TITLE: + case OrbisSaveDataParamType::SUB_TITLE: + case OrbisSaveDataParamType::DETAIL: { + const auto param = static_cast(paramBuf); + std::string_view key; + if (paramType == OrbisSaveDataParamType::TITLE) { + key = SaveParams::MAINTITLE; + } else if (paramType == OrbisSaveDataParamType::SUB_TITLE) { + key = SaveParams::SUBTITLE; + } else if (paramType == OrbisSaveDataParamType::DETAIL) { + key = SaveParams::DETAIL; + } else { + UNREACHABLE(); + } + const size_t s = param_sfo->GetString(key)->copy(param, paramBufSize - 1); + param[s] = '\0'; // null terminate + if (gotSize != nullptr) { + *gotSize = s + 1; + } } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_MTIME: { - std::memcpy(paramBuf, ¶ms.mtime, sizeof(time_t)); - *gotSize = sizeof(time_t); + case OrbisSaveDataParamType::USER_PARAM: { + const auto param = static_cast(paramBuf); + *param = param_sfo->GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0); + if (gotSize != nullptr) { + *gotSize = sizeof(u32); + } } break; - default: { - UNREACHABLE_MSG("Unknown Param = {}", paramType); + case OrbisSaveDataParamType::MTIME: { + const auto param = static_cast(paramBuf); + const auto last_write = param_sfo->GetLastWrite().time_since_epoch(); + *param = chrono::duration_cast(last_write).count(); + if (gotSize != nullptr) { + *gotSize = sizeof(time_t); + } } break; + default: + UNREACHABLE(); } - return ORBIS_OK; + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataGetProgress() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (progress == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + *progress = Backup::GetProgress(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() { @@ -409,44 +1066,56 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize, - const int64_t offset) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "sdmemory/save_mem1.sav"; - - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - file.Seek(offset); - size_t nbytes = file.ReadRaw(buf, bufSize); - LOG_INFO(Lib_SaveData, "called: bufSize = {}, offset = {}", bufSize, offset, nbytes); - - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId userId, void* buf, + const size_t bufSize, const int64_t offset) { + LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataGetSaveDataMemory2"); + OrbisSaveDataMemoryData data{}; + data.buf = buf; + data.bufSize = bufSize; + data.offset = offset; + OrbisSaveDataMemoryGet2 param{}; + param.userId = userId; + param.data = &data; + param.param = nullptr; + param.icon = nullptr; + return sceSaveDataGetSaveDataMemory2(¶m); } -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(getParam->userId) / game_serial / "sdmemory"; - if (getParam == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - if (getParam->data != nullptr) { - Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - file.Seek(getParam->data->offset); - file.ReadRaw(getParam->data->buf, getParam->data->bufSize); - LOG_INFO(Lib_SaveData, "called: bufSize = {}, offset = {}", getParam->data->bufSize, - getParam->data->offset); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; } - - if (getParam->param != nullptr) { - Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Read); - file.ReadRaw(getParam->param, sizeof(OrbisSaveDataParam)); + if (getParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + if (SaveMemory::IsSaving()) { + LOG_TRACE(Lib_SaveData, "called while saving"); + return Error::BUSY_FOR_SAVING; + } + LOG_DEBUG(Lib_SaveData, "called"); + auto data = getParam->data; + if (data != nullptr) { + SaveMemory::ReadMemory(data->buf, data->bufSize, data->offset); + } + auto param = getParam->param; + if (param != nullptr) { + param->FromSFO(SaveMemory::GetParamSFO()); + } + auto icon = getParam->icon; + if (icon != nullptr) { + auto icon_mem = SaveMemory::GetIcon(); + size_t total = std::min(icon->bufSize, icon_mem.size()); + std::memcpy(icon->buf, icon_mem.data(), total); + icon->dataSize = total; } - return ORBIS_OK; + return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir() { @@ -474,25 +1143,22 @@ int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataInitialize() { - LOG_INFO(Lib_SaveData, "called"); - static auto* param_sfo = Common::Singleton::Instance(); - game_serial = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataInitialize(void*) { + LOG_DEBUG(Lib_SaveData, "called"); + initialize(); + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataInitialize2() { - LOG_INFO(Lib_SaveData, "called"); - static auto* param_sfo = Common::Singleton::Instance(); - game_serial = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataInitialize2(void*) { + LOG_DEBUG(Lib_SaveData, "called"); + initialize(); + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataInitialize3() { - LOG_INFO(Lib_SaveData, "called"); - static auto* param_sfo = Common::Singleton::Instance(); - game_serial = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataInitialize3(void*) { + LOG_DEBUG(Lib_SaveData, "called"); + initialize(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataInitializeForCdlg() { @@ -510,101 +1176,69 @@ int PS4_SYSV_ABI sceSaveDataIsMounted() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataIcon* icon) { - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - LOG_INFO(Lib_SaveData, "called: dir = {}", mount_dir.string()); - - if (icon != nullptr) { - Common::FS::IOFile file(mount_dir / "save_data.png", Common::FS::FileAccessMode::Read); - icon->bufSize = file.GetSize(); - file.ReadRaw(icon->buf, icon->bufSize); - } - return ORBIS_OK; -} - -s32 saveDataMount(u32 user_id, char* dir_name, u32 mount_mode, - OrbisSaveDataMountResult* mount_result) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(user_id) / game_serial / dir_name; - auto* mnt = Common::Singleton::Instance(); - switch (mount_mode) { - case ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY: - case ORBIS_SAVE_DATA_MOUNT_MODE_RDWR: - case ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: - case ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: { - is_rw_mode = (mount_mode == ORBIS_SAVE_DATA_MOUNT_MODE_RDWR) ? true : false; - if (!std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; - } - mount_result->mount_status = 0; - g_mount_point.copy(mount_result->mount_point.data, 16); - mnt->Mount(mount_dir, mount_result->mount_point.data); - } break; - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF | - ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: { - if (std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_EXISTS; - } - if (std::filesystem::create_directories(mount_dir)) { - g_mount_point.copy(mount_result->mount_point.data, 16); - mnt->Mount(mount_dir, mount_result->mount_point.data); - mount_result->mount_status = 1; - } - } break; - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: { - if (!std::filesystem::exists(mount_dir)) { - std::filesystem::create_directories(mount_dir); +Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataIcon* icon) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + std::filesystem::path path; + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + path = instance->GetIconPath(); + break; } - g_mount_point.copy(mount_result->mount_point.data, 16); - mnt->Mount(mount_dir, mount_result->mount_point.data); - mount_result->mount_status = 1; - } break; - default: - UNREACHABLE_MSG("Unknown mount mode = {}", mount_mode); } - mount_result->required_blocks = 0; + if (path.empty()) { + return Error::NOT_MOUNTED; + } + if (!fs::exists(path)) { + return Error::NOT_FOUND; + } - return ORBIS_OK; + return icon->LoadIcon(path); } -s32 PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, - OrbisSaveDataMountResult* mount_result) { - if (mount == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; +Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, + OrbisSaveDataMountResult* mount_result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; } - LOG_INFO(Lib_SaveData, "called: dirName = {}, mode = {}, blocks = {}", mount->dir_name->data, - mount->mount_mode, mount->blocks); - return saveDataMount(mount->user_id, (char*)mount->dir_name->data, mount->mount_mode, - mount_result); -} - -s32 PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, - OrbisSaveDataMountResult* mount_result) { - if (mount == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; + if (mount == nullptr && mount->dirName != nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called dirName: {}, mode: {:0b}, blocks: {}", + mount->dirName->data.to_view(), (int)mount->mountMode, mount->blocks); + + OrbisSaveDataMount2 mount_info{}; + mount_info.userId = mount->userId; + mount_info.dirName = mount->dirName; + mount_info.mountMode = mount->mountMode; + mount_info.blocks = mount->blocks; + return saveDataMount(&mount_info, mount_result); +} + +Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, + OrbisSaveDataMountResult* mount_result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; } - LOG_INFO(Lib_SaveData, "called: dirName = {}, mode = {}, blocks = {}", mount->dir_name->data, - mount->mount_mode, mount->blocks); - return saveDataMount(mount->user_id, (char*)mount->dir_name->data, mount->mount_mode, - mount_result); + if (mount == nullptr && mount->dirName != nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called dirName: {}, mode: {:0b}, blocks: {}", + mount->dirName->data.to_view(), (int)mount->mountMode, mount->blocks); + return saveDataMount(mount, mount_result); } int PS4_SYSV_ABI sceSaveDataMount5() { @@ -637,9 +1271,44 @@ int PS4_SYSV_ABI sceSaveDataRegisterEventCallback() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataRestoreBackupData() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (restore == nullptr || restore->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + + const std::string_view dir_name{restore->dirName->data}; + LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name); + + std::string_view title{restore->titleId != nullptr ? std::string_view{restore->titleId->data} + : std::string_view{g_game_serial}}; + + const auto save_path = SaveInstance::MakeDirSavePath(restore->userId, title, dir_name); + + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetSavePath() == save_path) { + return Error::BUSY; + } + } + if (Backup::IsBackupExecutingFor(save_path)) { + return Error::BACKUP_BUSY; + } + + const auto backup_path = Backup::MakeBackupPath(save_path); + if (!fs::exists(backup_path)) { + return Error::NOT_FOUND; + } + + const bool ok = Backup::Restore(save_path); + if (!ok) { + return Error::INTERNAL; + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg() { @@ -652,17 +1321,41 @@ int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataIcon* icon) { - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - LOG_INFO(Lib_SaveData, "called = {}", mount_dir.string()); +Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, + const OrbisSaveDataIcon* icon) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + std::filesystem::path path; + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + if (instance->IsReadOnly()) { + return Error::BAD_MOUNTED; + } + path = instance->GetIconPath(); + break; + } + } + if (path.empty()) { + return Error::NOT_MOUNTED; + } - if (icon != nullptr) { - Common::FS::IOFile file(mount_dir / "save_data.png", Common::FS::FileAccessMode::Write); - file.WriteRaw(icon->buf, icon->bufSize); + try { + const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Write); + file.WriteRaw(icon->buf, std::min(icon->bufSize, icon->dataSize)); + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what()); + return Error::INTERNAL; } - return ORBIS_OK; + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting() { @@ -675,50 +1368,59 @@ int PS4_SYSV_ABI sceSaveDataSetEventInfo() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataParamType paramType, const void* paramBuf, - size_t paramBufSize) { - if (paramBuf == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data) / "param.txt"; - OrbisSaveDataParam params; - if (std::filesystem::exists(mount_dir)) { - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read); - file.ReadRaw(¶ms, sizeof(OrbisSaveDataParam)); +Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, const void* paramBuf, + size_t paramBufSize) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (paramType > OrbisSaveDataParamType::USER_PARAM || mountPoint == nullptr || + paramBuf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType)); + PSF* param_sfo = nullptr; + const std::string_view mount_point_str{mountPoint->data}; + for (auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + param_sfo = &instance->GetParamSFO(); + break; + } + } + if (param_sfo == nullptr) { + return Error::NOT_MOUNTED; } - - LOG_INFO(Lib_SaveData, "called"); switch (paramType) { - case ORBIS_SAVE_DATA_PARAM_TYPE_ALL: { - memcpy(¶ms, paramBuf, sizeof(OrbisSaveDataParam)); - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_TITLE: { - strncpy(params.title, static_cast(paramBuf), paramBufSize); + case OrbisSaveDataParamType::ALL: { + const auto param = static_cast(paramBuf); + ASSERT(paramBufSize == sizeof(OrbisSaveDataParam)); + param->ToSFO(*param_sfo); + return Error::OK; } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE: { - strncpy(params.subTitle, static_cast(paramBuf), paramBufSize); + case OrbisSaveDataParamType::TITLE: { + const auto value = static_cast(paramBuf); + param_sfo->AddString(std::string{SaveParams::MAINTITLE}, {value}, true); } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL: { - strncpy(params.detail, static_cast(paramBuf), paramBufSize); + case OrbisSaveDataParamType::SUB_TITLE: { + const auto value = static_cast(paramBuf); + param_sfo->AddString(std::string{SaveParams::SUBTITLE}, {value}, true); } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM: { - params.userParam = *(static_cast(paramBuf)); + case OrbisSaveDataParamType::DETAIL: { + const auto value = static_cast(paramBuf); + param_sfo->AddString(std::string{SaveParams::DETAIL}, {value}, true); } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_MTIME: { - params.mtime = *(static_cast(paramBuf)); + case OrbisSaveDataParamType::USER_PARAM: { + const auto value = static_cast(paramBuf); + param_sfo->AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, *value, true); } break; - default: { - UNREACHABLE_MSG("Unknown Param = {}", paramType); - } + default: + UNREACHABLE(); } - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write); - file.WriteRaw(¶ms, sizeof(OrbisSaveDataParam)); - - return ORBIS_OK; + return Error::OK; } int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() { @@ -726,99 +1428,114 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, - const size_t bufSize, const int64_t offset) { - LOG_INFO(Lib_SaveData, "called"); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "sdmemory"; - - Common::FS::IOFile file(mount_dir / "save_mem1.sav", Common::FS::FileAccessMode::Write); - if (!file.IsOpen()) - return -1; - file.Seek(offset); - file.WriteRaw(buf, bufSize); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset) { + LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataSetSaveDataMemory2"); + OrbisSaveDataMemoryData data{}; + data.buf = buf; + data.bufSize = bufSize; + data.offset = offset; + OrbisSaveDataMemorySet2 setParam{}; + setParam.userId = userId; + setParam.data = &data; + return sceSaveDataSetSaveDataMemory2(&setParam); } -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) { - LOG_INFO(Lib_SaveData, "called: dataNum = {}, slotId= {}", setParam->dataNum, setParam->slotId); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(setParam->userId) / game_serial / "sdmemory"; - if (setParam->data != nullptr) { - Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Write); - if (!file.IsOpen()) - return -1; - file.Seek(setParam->data->offset); - file.WriteRaw(setParam->data->buf, setParam->data->bufSize); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; } - - if (setParam->param != nullptr) { - Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Write); - file.WriteRaw((void*)setParam->param, sizeof(OrbisSaveDataParam)); + if (setParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; } - - if (setParam->icon != nullptr) { - Common::FS::IOFile file(mount_dir / "save_icon.png", Common::FS::FileAccessMode::Write); - file.WriteRaw(setParam->icon->buf, setParam->icon->bufSize); + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + if (SaveMemory::IsSaving()) { + LOG_TRACE(Lib_SaveData, "called while saving"); + return Error::BUSY_FOR_SAVING; + } + LOG_DEBUG(Lib_SaveData, "called"); + auto data = setParam->data; + if (data != nullptr) { + SaveMemory::WriteMemory(data->buf, data->bufSize, data->offset); + } + auto param = setParam->param; + if (param != nullptr) { + param->ToSFO(SaveMemory::GetParamSFO()); + SaveMemory::SaveSFO(); + } + auto icon = setParam->icon; + if (icon != nullptr) { + SaveMemory::WriteIcon(icon->buf, icon->bufSize); } - return ORBIS_OK; + SaveMemory::TriggerSaveWithoutEvent(); + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize, - OrbisSaveDataParam* param) { - - LOG_INFO(Lib_SaveData, "called:userId = {}, memorySize = {}", userId, memorySize); +int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize, + OrbisSaveDataParam* param*/) { + LOG_ERROR(Lib_SaveData, "(STUBBED) called"); + return ORBIS_OK; +} - if (param == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, + OrbisSaveDataMemorySetupResult* result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; } + if (setupParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "sdmemory"; + SaveMemory::SetDirectories(setupParam->userId, g_game_serial); - if (!std::filesystem::exists(mount_dir)) { - std::filesystem::create_directories(mount_dir); + const auto& save_path = SaveMemory::GetSavePath(); + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetSavePath() == save_path) { + return Error::BUSY; + } } - // NOTE: Reminder that games can pass params: - // memset(param, 0, sizeof(param_t)); - // strncpy(param->title, "Beach Buggy Racing", 127); + try { + size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize); + if (existed_size == 0) { // Just created + if (setupParam->initParam != nullptr) { + auto& sfo = SaveMemory::GetParamSFO(); + setupParam->initParam->ToSFO(sfo); + } + SaveMemory::SaveSFO(); - std::vector buf(memorySize); - Common::FS::IOFile::WriteBytes(mount_dir / "save_mem1.sav", buf); - return ORBIS_OK; -} + auto init_icon = setupParam->initIcon; + if (init_icon != nullptr) { + SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize); + } else { + SaveMemory::SetIcon(nullptr, 0); + } + } + if (result != nullptr) { + result->existedMemorySize = existed_size; + } + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to create/load save memory: {}", e.what()); -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, - OrbisSaveDataMemorySetupResult* result) { - if (setupParam == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - } - LOG_INFO(Lib_SaveData, "called"); - // if (setupParam->option == 1) { // check this later. - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(setupParam->userId) / game_serial / "sdmemory"; - if (std::filesystem::exists(mount_dir) && - std::filesystem::exists(mount_dir / "save_mem2.sav")) { - Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) - return -1; - // Bunny - CUSA07988 has a null result, having null result is checked and valid. - if (result != nullptr) - result->existedMemorySize = file.GetSize(); // Assign the saved data size. - // do not return ORBIS_SAVE_DATA_ERROR_EXISTS, as it will not trigger - // sceSaveDataGetSaveDataMemory2. - } else { - std::filesystem::create_directories(mount_dir); - std::vector buf(setupParam->memorySize); // check if > 0x1000000 (16.77mb) or x2? - Common::FS::IOFile::WriteBytes(mount_dir / "save_mem2.sav", buf); - std::vector paramBuf(sizeof(OrbisSaveDataParam)); - Common::FS::IOFile::WriteBytes(mount_dir / "param.txt", paramBuf); - std::vector iconBuf(setupParam->iconMemorySize); - Common::FS::IOFile::WriteBytes(mount_dir / "save_icon.png", iconBuf); + const MsgDialog::MsgDialogState dialog{MsgDialog::MsgDialogState::UserState{ + .type = MsgDialog::ButtonType::OK, + .msg = "Failed to create or load save memory:\n" + std::string{e.what()}, + }}; + MsgDialog::ShowMsgDialog(dialog); + + return Error::INTERNAL; } - return ORBIS_OK; + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataShutdownStart() { @@ -836,14 +1553,40 @@ int PS4_SYSV_ABI sceSaveDataSyncCloudList() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) { - LOG_ERROR(Lib_SaveData, "(STUBBED) called: option = {}", syncParam->option); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (syncParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + LOG_DEBUG(Lib_SaveData, "called"); + bool ok = SaveMemory::TriggerSave(); + if (!ok) { + return Error::BUSY_FOR_SAVING; + } + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataTerminate() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataTerminate() { + LOG_DEBUG(Lib_SaveData, "called"); + if (!g_initialized) { + return Error::NOT_INITIALIZED; + } + for (const auto& instance : g_mount_slots) { + if (instance.has_value()) { + return Error::BUSY; + } + } + g_initialized = false; + Backup::StopThread(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataTransferringMount() { @@ -851,19 +1594,9 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { - LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data); - if (std::string_view(mountPoint->data).empty()) { - return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; - } - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(1) / game_serial / mountPoint->data; - auto* mnt = Common::Singleton::Instance(); - const auto& guest_path = mnt->GetHostPath(mountPoint->data); - if (guest_path.empty()) - return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; - mnt->Unmount(mount_dir, mountPoint->data); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { + LOG_DEBUG(Lib_SaveData, "called"); + return Umount(mountPoint); } int PS4_SYSV_ABI sceSaveDataUmountSys() { @@ -871,37 +1604,9 @@ int PS4_SYSV_ABI sceSaveDataUmountSys() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) { - LOG_INFO(Lib_SaveData, "called mount = {}, is_rw_mode = {}", std::string(mountPoint->data), - is_rw_mode); - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - if (!std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; - } - // leave disabled for now. and just unmount. - - /* if (is_rw_mode) { // backup is done only when mount mode is ReadWrite. - auto backup_path = mount_dir; - std::string save_data_dir = (mount_dir.string() + "_backup"); - backup_path.replace_filename(save_data_dir); - - std::filesystem::create_directories(backup_path); - - for (const auto& entry : std::filesystem::recursive_directory_iterator(mount_dir)) { - const auto& path = entry.path(); - if (std::filesystem::is_regular_file(path)) { - std::filesystem::copy(path, save_data_dir, - std::filesystem::copy_options::overwrite_existing); - } - } - }*/ - const auto& guest_path = mnt->GetHostPath(mountPoint->data); - if (guest_path.empty()) - return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; - - mnt->Unmount(mount_dir, mountPoint->data); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) { + LOG_DEBUG(Lib_SaveData, "called"); + return Umount(mountPoint, true); } int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback() { diff --git a/src/core/libraries/save_data/savedata.h b/src/core/libraries/save_data/savedata.h index 9b3cf900fcf..5e6a8ad4c02 100644 --- a/src/core/libraries/save_data/savedata.h +++ b/src/core/libraries/save_data/savedata.h @@ -3,259 +3,81 @@ #pragma once +#include "common/cstring.h" #include "common/types.h" namespace Core::Loader { class SymbolsResolver; } +class PSF; + namespace Libraries::SaveData { -constexpr int ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE = - 32; // Maximum size for a save data directory name -constexpr int ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE = 16; // Maximum size for a mount point name +constexpr size_t OrbisSaveDataTitleMaxsize = 128; // Maximum title name size +constexpr size_t OrbisSaveDataSubtitleMaxsize = 128; // Maximum subtitle name size +constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size -struct OrbisSaveDataDirName { - char data[ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE]; -}; +enum class Error : u32; +enum class OrbisSaveDataParamType : u32; -struct OrbisSaveDataMount2 { - s32 user_id; - s32 unk1; - const OrbisSaveDataDirName* dir_name; - u64 blocks; - u32 mount_mode; - u8 reserved[32]; - s32 unk2; -}; +using OrbisUserServiceUserId = s32; -struct OrbisSaveDataMountPoint { - char data[ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE]; -}; - -struct OrbisSaveDataMountResult { - OrbisSaveDataMountPoint mount_point; - u64 required_blocks; - u32 unused; - u32 mount_status; - u8 reserved[28]; - s32 unk1; -}; +// Maximum size for a title ID (4 uppercase letters + 5 digits) +constexpr int OrbisSaveDataTitleIdDataSize = 10; +// Maximum save directory name size +constexpr int OrbisSaveDataDirnameDataMaxsize = 32; -constexpr int ORBIS_SAVE_DATA_TITLE_ID_DATA_SIZE = 10; struct OrbisSaveDataTitleId { - char data[ORBIS_SAVE_DATA_TITLE_ID_DATA_SIZE]; - char padding[6]; + Common::CString data; + std::array _pad; }; -constexpr int ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE = 65; -struct OrbisSaveDataFingerprint { - char data[ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE]; - char padding[15]; -}; - -struct OrbisSaveDataMount { - s32 user_id; - s32 pad; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dir_name; - const OrbisSaveDataFingerprint* fingerprint; - u64 blocks; - u32 mount_mode; - u8 reserved[32]; +struct OrbisSaveDataDirName { + Common::CString data; }; -typedef u32 OrbisSaveDataParamType; - -constexpr int ORBIS_SAVE_DATA_TITLE_MAXSIZE = 128; -constexpr int ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE = 128; -constexpr int ORBIS_SAVE_DATA_DETAIL_MAXSIZE = 1024; struct OrbisSaveDataParam { - char title[ORBIS_SAVE_DATA_TITLE_MAXSIZE]; - char subTitle[ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE]; - char detail[ORBIS_SAVE_DATA_DETAIL_MAXSIZE]; + Common::CString title; + Common::CString subTitle; + Common::CString detail; u32 userParam; int : 32; time_t mtime; - u8 reserved[32]; -}; - -struct OrbisSaveDataIcon { - void* buf; - size_t bufSize; - size_t dataSize; - u8 reserved[32]; -}; - -typedef u32 OrbisSaveDataSaveDataMemoryOption; -#define ORBIS_SAVE_DATA_MEMORY_OPTION_NONE (0x00000000) -#define ORBIS_SAVE_DATA_MEMORY_OPTION_SET_PARAM (0x00000001 << 0) -#define ORBIS_SAVE_DATA_MEMORY_OPTION_DOUBLE_BUFFER (0x00000001 << 1) - -struct OrbisSaveDataMemorySetup2 { - OrbisSaveDataSaveDataMemoryOption option; - s32 userId; - size_t memorySize; - size_t iconMemorySize; - const OrbisSaveDataParam* initParam; - const OrbisSaveDataIcon* initIcon; - u32 slotId; - u8 reserved[20]; -}; - -struct OrbisSaveDataMemorySetupResult { - size_t existedMemorySize; - u8 reserved[16]; -}; - -typedef u32 OrbisSaveDataEventType; -#define SCE_SAVE_DATA_EVENT_TYPE_INVALID (0) -#define SCE_SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP_END (1) -#define SCE_SAVE_DATA_EVENT_TYPE_BACKUP_END (2) -#define SCE_SAVE_DATA_EVENT_TYPE_SAVE_DATA_MEMORY_SYNC_END (3) - -struct OrbisSaveDataEvent { - OrbisSaveDataEventType type; - s32 errorCode; - s32 userId; - u8 padding[4]; - OrbisSaveDataTitleId titleId; - OrbisSaveDataDirName dirName; - u8 reserved[40]; -}; - -struct OrbisSaveDataMemoryData { - void* buf; - size_t bufSize; - off_t offset; - u8 reserved[40]; -}; - -struct OrbisSaveDataMemoryGet2 { - s32 userId; - u8 padding[4]; - OrbisSaveDataMemoryData* data; - OrbisSaveDataParam* param; - OrbisSaveDataIcon* icon; - u32 slotId; - u8 reserved[28]; -}; - -struct OrbisSaveDataMemorySet2 { - s32 userId; - u8 padding[4]; - const OrbisSaveDataMemoryData* data; - const OrbisSaveDataParam* param; - const OrbisSaveDataIcon* icon; - u32 dataNum; - u8 slotId; - u8 reserved[24]; -}; - -struct OrbisSaveDataCheckBackupData { - s32 userId; - int : 32; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dirName; - OrbisSaveDataParam* param; - OrbisSaveDataIcon* icon; - u8 reserved[32]; -}; - -struct OrbisSaveDataMountInfo { - u64 blocks; - u64 freeBlocks; - u8 reserved[32]; -}; - -#define ORBIS_SAVE_DATA_BLOCK_SIZE (32768) -#define ORBIS_SAVE_DATA_BLOCKS_MIN2 (96) -#define ORBIS_SAVE_DATA_BLOCKS_MAX (32768) - -// savedataMount2 mountModes (ORed values) -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY = 1; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_RDWR = 2; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_CREATE = 4; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF = 8; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON = 16; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 = 32; -typedef struct _OrbisSaveDataEventParam OrbisSaveDataEventParam; - -typedef u32 OrbisSaveDataSortKey; -#define ORBIS_SAVE_DATA_SORT_KEY_DIRNAME (0) -#define ORBIS_SAVE_DATA_SORT_KEY_USER_PARAM (1) -#define ORBIS_SAVE_DATA_SORT_KEY_BLOCKS (2) -#define ORBIS_SAVE_DATA_SORT_KEY_MTIME (3) -#define ORBIS_SAVE_DATA_SORT_KEY_FREE_BLOCKS (5) - -typedef u32 OrbisSaveDataSortOrder; -#define ORBIS_SAVE_DATA_SORT_ORDER_ASCENT (0) -#define ORBIS_SAVE_DATA_SORT_ORDER_DESCENT (1) - -struct OrbisSaveDataDirNameSearchCond { - s32 userId; - int : 32; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dirName; - OrbisSaveDataSortKey key; - OrbisSaveDataSortOrder order; - u8 reserved[32]; -}; - -struct OrbisSaveDataSearchInfo { - u64 blocks; - u64 freeBlocks; - u8 reserved[32]; -}; - -struct OrbisSaveDataDirNameSearchResult { - u32 hitNum; - int : 32; - OrbisSaveDataDirName* dirNames; - u32 dirNamesNum; - u32 setNum; - OrbisSaveDataParam* params; - OrbisSaveDataSearchInfo* infos; - u8 reserved[12]; - int : 32; -}; - -struct OrbisSaveDataDelete { - s32 userId; - int : 32; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dirName; - u32 unused; - u8 reserved[32]; - int : 32; -}; - -typedef u32 OrbisSaveDataMemorySyncOption; - -#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_NONE (0x00000000) -#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_BLOCKING (0x00000001 << 0) - -struct OrbisSaveDataMemorySync { - s32 userId; - u32 slotId; - OrbisSaveDataMemorySyncOption option; - u8 reserved[28]; -}; - -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_ALL = 0; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_TITLE = 1; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE = 2; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL = 3; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM = 4; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_MTIME = 5; + std::array _reserved; + + void FromSFO(const PSF& sfo); + + void ToSFO(PSF& sfo) const; +}; + +struct OrbisSaveDataBackup; +struct OrbisSaveDataCheckBackupData; +struct OrbisSaveDataDelete; +struct OrbisSaveDataDirNameSearchCond; +struct OrbisSaveDataDirNameSearchResult; +struct OrbisSaveDataEvent; +struct OrbisSaveDataEventParam; +struct OrbisSaveDataIcon; +struct OrbisSaveDataMemoryGet2; +struct OrbisSaveDataMemorySet2; +struct OrbisSaveDataMemorySetup2; +struct OrbisSaveDataMemorySetupResult; +struct OrbisSaveDataMemorySync; +struct OrbisSaveDataMount2; +struct OrbisSaveDataMount; +struct OrbisSaveDataMountInfo; +struct OrbisSaveDataMountPoint; +struct OrbisSaveDataMountResult; +struct OrbisSaveDataRestoreBackupData; int PS4_SYSV_ABI sceSaveDataAbort(); -int PS4_SYSV_ABI sceSaveDataBackup(); +Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup); int PS4_SYSV_ABI sceSaveDataBindPsnAccount(); int PS4_SYSV_ABI sceSaveDataBindPsnAccountForSystemBackup(); int PS4_SYSV_ABI sceSaveDataChangeDatabase(); int PS4_SYSV_ABI sceSaveDataChangeInternal(); -int PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check); +Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check); int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg(); int PS4_SYSV_ABI sceSaveDataCheckBackupDataInternal(); int PS4_SYSV_ABI sceSaveDataCheckCloudData(); @@ -263,7 +85,7 @@ int PS4_SYSV_ABI sceSaveDataCheckIpmiIfSize(); int PS4_SYSV_ABI sceSaveDataCheckSaveDataBroken(); int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersion(); int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest(); -int PS4_SYSV_ABI sceSaveDataClearProgress(); +Error PS4_SYSV_ABI sceSaveDataClearProgress(); int PS4_SYSV_ABI sceSaveDataCopy5(); int PS4_SYSV_ABI sceSaveDataCreateUploadData(); int PS4_SYSV_ABI sceSaveDataDebug(); @@ -273,13 +95,13 @@ int PS4_SYSV_ABI sceSaveDataDebugCreateSaveDataRoot(); int PS4_SYSV_ABI sceSaveDataDebugGetThreadId(); int PS4_SYSV_ABI sceSaveDataDebugRemoveSaveDataRoot(); int PS4_SYSV_ABI sceSaveDataDebugTarget(); -int PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del); +Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del); int PS4_SYSV_ABI sceSaveDataDelete5(); int PS4_SYSV_ABI sceSaveDataDeleteAllUser(); int PS4_SYSV_ABI sceSaveDataDeleteCloudData(); int PS4_SYSV_ABI sceSaveDataDeleteUser(); -int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, - OrbisSaveDataDirNameSearchResult* result); +Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, + OrbisSaveDataDirNameSearchResult* result); int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal(); int PS4_SYSV_ABI sceSaveDataDownload(); int PS4_SYSV_ABI sceSaveDataGetAllSize(); @@ -292,70 +114,70 @@ int PS4_SYSV_ABI sceSaveDataGetClientThreadPriority(); int PS4_SYSV_ABI sceSaveDataGetCloudQuotaInfo(); int PS4_SYSV_ABI sceSaveDataGetDataBaseFilePath(); int PS4_SYSV_ABI sceSaveDataGetEventInfo(); -int PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam, - OrbisSaveDataEvent* event); +Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam, + OrbisSaveDataEvent* event); int PS4_SYSV_ABI sceSaveDataGetFormat(); int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount(); -int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataMountInfo* info); -int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataParamType paramType, void* paramBuf, - const size_t paramBufSize, size_t* gotSize); -int PS4_SYSV_ABI sceSaveDataGetProgress(); +Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataMountInfo* info); +Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, void* paramBuf, + size_t paramBufSize, size_t* gotSize); +Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress); int PS4_SYSV_ABI sceSaveDataGetSaveDataCount(); -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize, - const int64_t offset); -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir(); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath(); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootUsbPath(); int PS4_SYSV_ABI sceSaveDataGetSavePoint(); int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount(); -int PS4_SYSV_ABI sceSaveDataInitialize(); -int PS4_SYSV_ABI sceSaveDataInitialize2(); -int PS4_SYSV_ABI sceSaveDataInitialize3(); +Error PS4_SYSV_ABI sceSaveDataInitialize(void*); +Error PS4_SYSV_ABI sceSaveDataInitialize2(void*); +Error PS4_SYSV_ABI sceSaveDataInitialize3(void*); int PS4_SYSV_ABI sceSaveDataInitializeForCdlg(); int PS4_SYSV_ABI sceSaveDataIsDeletingUsbDb(); int PS4_SYSV_ABI sceSaveDataIsMounted(); -int PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataIcon* icon); -int PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, - OrbisSaveDataMountResult* mount_result); -s32 PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, - OrbisSaveDataMountResult* mount_result); +Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataIcon* icon); +Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, + OrbisSaveDataMountResult* mount_result); +Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, + OrbisSaveDataMountResult* mount_result); int PS4_SYSV_ABI sceSaveDataMount5(); int PS4_SYSV_ABI sceSaveDataMountInternal(); int PS4_SYSV_ABI sceSaveDataMountSys(); int PS4_SYSV_ABI sceSaveDataPromote5(); int PS4_SYSV_ABI sceSaveDataRebuildDatabase(); int PS4_SYSV_ABI sceSaveDataRegisterEventCallback(); -int PS4_SYSV_ABI sceSaveDataRestoreBackupData(); +Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore); int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg(); int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory(); -int PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataIcon* icon); +Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, + const OrbisSaveDataIcon* icon); int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting(); int PS4_SYSV_ABI sceSaveDataSetEventInfo(); -int PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataParamType paramType, const void* paramBuf, - size_t paramBufSize); +Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, const void* paramBuf, + size_t paramBufSize); int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser(); -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, - const size_t bufSize, const int64_t offset); -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam); -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize, - OrbisSaveDataParam* param); -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, - OrbisSaveDataMemorySetupResult* result); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam); +int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize, + OrbisSaveDataParam* param*/); +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, + OrbisSaveDataMemorySetupResult* result); int PS4_SYSV_ABI sceSaveDataShutdownStart(); int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus(); int PS4_SYSV_ABI sceSaveDataSyncCloudList(); -int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam); -int PS4_SYSV_ABI sceSaveDataTerminate(); +Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam); +Error PS4_SYSV_ABI sceSaveDataTerminate(); int PS4_SYSV_ABI sceSaveDataTransferringMount(); -int PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint); +Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint); int PS4_SYSV_ABI sceSaveDataUmountSys(); -int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint); +Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint); int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback(); int PS4_SYSV_ABI sceSaveDataUpload(); int PS4_SYSV_ABI Func_02E4C4D201716422(); diff --git a/src/core/libraries/system/msgdialog.cpp b/src/core/libraries/system/msgdialog.cpp index 94c122d9bb9..7d924e4ad8c 100644 --- a/src/core/libraries/system/msgdialog.cpp +++ b/src/core/libraries/system/msgdialog.cpp @@ -39,11 +39,6 @@ Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result) { if (result == nullptr) { return Error::ARG_NULL; } - for (const auto v : result->reserved) { - if (v != 0) { - return Error::PARAM_INVALID; - } - } *result = g_result; return Error::OK; } diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp index 63b3390c910..15d6f4dbd50 100644 --- a/src/core/libraries/system/msgdialog_ui.cpp +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include #include "common/assert.h" #include "imgui/imgui_std.h" @@ -31,18 +33,6 @@ struct { }; static_assert(std::size(user_button_texts) == static_cast(ButtonType::TWO_BUTTONS) + 1); -static void DrawCenteredText(const char* text) { - const auto ws = GetWindowSize(); - const auto text_size = CalcTextSize(text, nullptr, false, ws.x - 40.0f); - PushTextWrapPos(ws.x - 30.0f); - SetCursorPos({ - (ws.x - text_size.x) / 2.0f, - (ws.y - text_size.y) / 2.0f - 50.0f, - }); - Text("%s", text); - PopTextWrapPos(); -} - MsgDialogState::MsgDialogState(const OrbisParam& param) { this->mode = param.mode; switch (mode) { @@ -81,11 +71,29 @@ MsgDialogState::MsgDialogState(const OrbisParam& param) { } } +MsgDialogState::MsgDialogState(UserState mode) { + this->mode = MsgDialogMode::USER_MSG; + this->state = mode; +} + +MsgDialogState::MsgDialogState(ProgressState mode) { + this->mode = MsgDialogMode::PROGRESS_BAR; + this->state = mode; +} + +MsgDialogState::MsgDialogState(SystemState mode) { + this->mode = MsgDialogMode::SYSTEM_MSG; + this->state = mode; +} + void MsgDialogUi::DrawUser() { const auto& [button_type, msg, btn_param1, btn_param2] = state->GetState(); const auto ws = GetWindowSize(); - DrawCenteredText(msg.c_str()); + if (!msg.empty()) { + DrawCenteredText(&msg.front(), &msg.back() + 1, + GetContentRegionAvail() - ImVec2{0.0f, 15.0f + BUTTON_SIZE.y}); + } ASSERT(button_type <= ButtonType::TWO_BUTTONS); auto [count, text1, text2] = user_button_texts[static_cast(button_type)]; if (count == 0xFF) { // TWO_BUTTONS -> User defined message @@ -115,7 +123,7 @@ void MsgDialogUi::DrawUser() { break; } } - if (first_render && !focus_first) { + if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && !focus_first) { SetItemCurrentNavFocus(); } PopID(); @@ -125,7 +133,7 @@ void MsgDialogUi::DrawUser() { if (Button(text1, BUTTON_SIZE)) { Finish(ButtonId::BUTTON1); } - if (first_render && focus_first) { + if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && focus_first) { SetItemCurrentNavFocus(); } PopID(); @@ -249,11 +257,13 @@ void MsgDialogUi::Draw() { CentralizeWindow(); SetNextWindowSize(window_size); - SetNextWindowFocus(); SetNextWindowCollapsed(false); + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } KeepNavHighlight(); - // Hack to allow every dialog to have a unique window - if (Begin("Message Dialog##MessageDialog", nullptr, ImGuiWindowFlags_NoSavedSettings)) { + if (Begin("Message Dialog##MessageDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { switch (state->GetMode()) { case MsgDialogMode::USER_MSG: DrawUser(); @@ -269,4 +279,16 @@ void MsgDialogUi::Draw() { End(); first_render = false; -} \ No newline at end of file +} + +DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState state, bool block) { + DialogResult result{}; + Status status = Status::RUNNING; + MsgDialogUi dialog(&state, &status, &result); + if (block) { + while (status == Status::RUNNING) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + return result; +} diff --git a/src/core/libraries/system/msgdialog_ui.h b/src/core/libraries/system/msgdialog_ui.h index 845abdc43f8..d24ec067c51 100644 --- a/src/core/libraries/system/msgdialog_ui.h +++ b/src/core/libraries/system/msgdialog_ui.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "common/fixed_value.h" @@ -129,6 +130,11 @@ class MsgDialogState { public: explicit MsgDialogState(const OrbisParam& param); + + explicit MsgDialogState(UserState mode); + explicit MsgDialogState(ProgressState mode); + explicit MsgDialogState(SystemState mode); + MsgDialogState() = default; [[nodiscard]] OrbisUserServiceUserId GetUserId() const { @@ -165,13 +171,11 @@ class MsgDialogUi final : public ImGui::Layer { void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK); - void SetProgressBarValue(u32 value, bool increment); - void Draw() override; - - bool ShouldGrabGamepad() override { - return true; - } }; +// Utility function to show a message dialog +// !!! This function can block !!! +DialogResult ShowMsgDialog(MsgDialogState state, bool block = true); + }; // namespace Libraries::MsgDialog \ No newline at end of file diff --git a/src/core/libraries/system/savedatadialog.cpp b/src/core/libraries/system/savedatadialog.cpp deleted file mode 100644 index 5aad480d034..00000000000 --- a/src/core/libraries/system/savedatadialog.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/logging/log.h" -#include "core/libraries/error_codes.h" -#include "core/libraries/libs.h" -#include "core/libraries/system/savedatadialog.h" - -namespace Libraries::SaveDataDialog { - -int PS4_SYSV_ABI sceSaveDataDialogClose() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogGetResult() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogGetStatus() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogInitialize() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return 1; -} - -int PS4_SYSV_ABI sceSaveDataDialogOpen() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogTerminate() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return 3; // SCE_COMMON_DIALOG_STATUS_FINISHED -} - -void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogClose); - LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogGetResult); - LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogGetStatus); - LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogInitialize); - LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogIsReadyToDisplay); - LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogOpen); - LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogProgressBarInc); - LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogProgressBarSetValue); - LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogTerminate); - LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogUpdateStatus); -}; - -} // namespace Libraries::SaveDataDialog diff --git a/src/core/libraries/system/savedatadialog.h b/src/core/libraries/system/savedatadialog.h deleted file mode 100644 index e8fe7c75f72..00000000000 --- a/src/core/libraries/system/savedatadialog.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" - -namespace Core::Loader { -class SymbolsResolver; -} - -namespace Libraries::SaveDataDialog { - -int PS4_SYSV_ABI sceSaveDataDialogClose(); -int PS4_SYSV_ABI sceSaveDataDialogGetResult(); -int PS4_SYSV_ABI sceSaveDataDialogGetStatus(); -int PS4_SYSV_ABI sceSaveDataDialogInitialize(); -int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay(); -int PS4_SYSV_ABI sceSaveDataDialogOpen(); -int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc(); -int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue(); -int PS4_SYSV_ABI sceSaveDataDialogTerminate(); -int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus(); - -void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::SaveDataDialog diff --git a/src/emulator.cpp b/src/emulator.cpp index bf2d4588a5c..581d0da83db 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -10,6 +10,7 @@ #ifdef ENABLE_QT_GUI #include "common/memory_patcher.h" #endif +#include "common/assert.h" #include "common/ntapi.h" #include "common/path_util.h" #include "common/polyfill_thread.h" @@ -99,8 +100,9 @@ void Emulator::Run(const std::filesystem::path& file) { for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) { if (entry.path().filename() == "param.sfo") { auto* param_sfo = Common::Singleton::Instance(); - param_sfo->open(sce_sys_folder.string() + "/param.sfo", {}); - id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); + const bool success = param_sfo->Open(sce_sys_folder / "param.sfo"); + ASSERT_MSG(success, "Failed to open param.sfo"); + id = std::string(*param_sfo->GetString("CONTENT_ID"), 7, 9); Libraries::NpTrophy::game_serial = id; const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles"; @@ -113,10 +115,10 @@ void Emulator::Run(const std::filesystem::path& file) { #ifdef ENABLE_QT_GUI MemoryPatcher::g_game_serial = id; #endif - title = param_sfo->GetString("TITLE"); + title = *param_sfo->GetString("TITLE"); LOG_INFO(Loader, "Game id: {} Title: {}", id, title); - u32 fw_version = param_sfo->GetInteger("SYSTEM_VER"); - app_version = param_sfo->GetString("APP_VER"); + u32 fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000); + app_version = *param_sfo->GetString("APP_VER"); LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version); } else if (entry.path().filename() == "playgo-chunk.dat") { auto* playgo = Common::Singleton::Instance(); diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h index 4602382edbf..2094d56bc9a 100644 --- a/src/imgui/imgui_config.h +++ b/src/imgui/imgui_config.h @@ -26,4 +26,7 @@ extern void assert_fail_debug_msg(const char* msg); #define IMGUI_DEFINE_MATH_OPERATORS #define IM_VEC2_CLASS_EXTRA \ - constexpr ImVec2(float _v) : x(_v), y(_v) {} \ No newline at end of file + constexpr ImVec2(float _v) : x(_v), y(_v) {} + +#define IM_VEC4_CLASS_EXTRA \ + constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {} \ No newline at end of file diff --git a/src/imgui/imgui_layer.h b/src/imgui/imgui_layer.h index a2ec7fd24d2..a6c7e2a4a79 100644 --- a/src/imgui/imgui_layer.h +++ b/src/imgui/imgui_layer.h @@ -12,10 +12,6 @@ class Layer { static void RemoveLayer(Layer* layer); virtual void Draw() = 0; - - virtual bool ShouldGrabGamepad() { - return false; - } }; } // namespace ImGui \ No newline at end of file diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h index 6d97cc11bc6..ec1e2f79d75 100644 --- a/src/imgui/imgui_std.h +++ b/src/imgui/imgui_std.h @@ -3,12 +3,25 @@ #pragma once +#include #include #include "imgui_internal.h" +#define IM_COL32_GRAY(x) IM_COL32(x, x, x, 0xFF) + namespace ImGui { +namespace Easing { + +inline float FastInFastOutCubic(float x) { + constexpr float c4 = 1.587401f; // 4^(1/3) + constexpr float c05 = 0.7937f; // 0.5^(1/3) + return std::pow(c4 * x - c05, 3.0f) + 0.5f; +} + +} // namespace Easing + inline void CentralizeWindow() { const auto display_size = GetIO().DisplaySize; SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f}); @@ -18,10 +31,39 @@ inline void KeepNavHighlight() { GetCurrentContext()->NavDisableHighlight = false; } -inline void SetItemCurrentNavFocus() { +inline void SetItemCurrentNavFocus(const ImGuiID id = -1) { const auto ctx = GetCurrentContext(); - SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow); + SetFocusID(id == -1 ? ctx->LastItemData.ID : id, ctx->CurrentWindow); ctx->NavInitResult.Clear(); + ctx->NavDisableHighlight = false; +} + +inline void DrawPrettyBackground() { + const double time = GetTime() / 1.5f; + const float x = ((float)std::cos(time) + 1.0f) / 2.0f; + const float d = Easing::FastInFastOutCubic(x); + u8 top_left = ImLerp(0x13, 0x05, d); + u8 top_right = ImLerp(0x00, 0x07, d); + u8 bottom_right = ImLerp(0x03, 0x27, d); + u8 bottom_left = ImLerp(0x05, 0x00, d); + + auto& window = *GetCurrentWindowRead(); + auto inner_pos = window.DC.CursorPos - window.WindowPadding; + auto inner_size = GetContentRegionAvail() + window.WindowPadding * 2.0f; + GetWindowDrawList()->AddRectFilledMultiColor( + inner_pos, inner_pos + inner_size, IM_COL32_GRAY(top_left), IM_COL32_GRAY(top_right), + IM_COL32_GRAY(bottom_right), IM_COL32_GRAY(bottom_left)); +} + +static void DrawCenteredText(const char* text, const char* text_end = nullptr, + ImVec2 content = GetContentRegionAvail()) { + auto pos = GetCursorPos(); + const auto text_size = CalcTextSize(text, text_end, false, content.x - 40.0f); + PushTextWrapPos(content.x); + SetCursorPos(pos + (content - text_size) / 2.0f); + TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); + PopTextWrapPos(); + SetCursorPos(pos + content); } } // namespace ImGui diff --git a/src/imgui/imgui_texture.h b/src/imgui/imgui_texture.h new file mode 100644 index 00000000000..1a38066d019 --- /dev/null +++ b/src/imgui/imgui_texture.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace ImGui { + +namespace Core::TextureManager { +struct Inner; +} // namespace Core::TextureManager + +class RefCountedTexture { + Core::TextureManager::Inner* inner; + + explicit RefCountedTexture(Core::TextureManager::Inner* inner); + +public: + struct Image { + ImTextureID im_id; + u32 width; + u32 height; + }; + + static RefCountedTexture DecodePngTexture(std::vector data); + + static RefCountedTexture DecodePngFile(std::filesystem::path path); + + RefCountedTexture(); + + RefCountedTexture(const RefCountedTexture& other); + RefCountedTexture(RefCountedTexture&& other) noexcept; + RefCountedTexture& operator=(const RefCountedTexture& other); + RefCountedTexture& operator=(RefCountedTexture&& other) noexcept; + + virtual ~RefCountedTexture(); + + [[nodiscard]] Image GetTexture() const; + + explicit(false) operator bool() const; +}; + +}; // namespace ImGui \ No newline at end of file diff --git a/src/imgui/layer/video_info.cpp b/src/imgui/layer/video_info.cpp index 2a60926faea..bf30f87015a 100644 --- a/src/imgui/layer/video_info.cpp +++ b/src/imgui/layer/video_info.cpp @@ -10,7 +10,7 @@ void ImGui::Layers::VideoInfo::Draw() { m_show = IsKeyPressed(ImGuiKey_F10, false) ^ m_show; if (m_show) { - if (Begin("Video Info")) { + if (Begin("Video Info", 0, ImGuiWindowFlags_NoNav)) { Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); } End(); diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 1c6313972e5..d52536f68f1 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -9,7 +9,9 @@ #include "imgui_core.h" #include "imgui_impl_sdl3.h" #include "imgui_impl_vulkan.h" +#include "imgui_internal.h" #include "sdl_window.h" +#include "texture_manager.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" static void CheckVkResult(const vk::Result err) { @@ -68,6 +70,8 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w .check_vk_result_fn = &CheckVkResult, }; Vulkan::Init(vk_info); + + TextureManager::StartWorker(); } void OnResize() { @@ -77,6 +81,8 @@ void OnResize() { void Shutdown(const vk::Device& device) { device.waitIdle(); + TextureManager::StopWorker(); + const ImGuiIO& io = GetIO(); const auto ini_filename = (void*)io.IniFilename; const auto log_filename = (void*)io.LogFilename; @@ -92,24 +98,19 @@ void Shutdown(const vk::Device& device) { bool ProcessEvent(SDL_Event* event) { Sdl::ProcessEvent(event); switch (event->type) { + // Don't block release/up events case SDL_EVENT_MOUSE_MOTION: case SDL_EVENT_MOUSE_WHEEL: case SDL_EVENT_MOUSE_BUTTON_DOWN: - case SDL_EVENT_MOUSE_BUTTON_UP: return GetIO().WantCaptureMouse; case SDL_EVENT_TEXT_INPUT: case SDL_EVENT_KEY_DOWN: - case SDL_EVENT_KEY_UP: return GetIO().WantCaptureKeyboard; case SDL_EVENT_GAMEPAD_BUTTON_DOWN: - case SDL_EVENT_GAMEPAD_BUTTON_UP: case SDL_EVENT_GAMEPAD_AXIS_MOTION: - case SDL_EVENT_GAMEPAD_ADDED: - case SDL_EVENT_GAMEPAD_REMOVED: case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: - case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: - return (GetIO().BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + return GetIO().NavActive; default: return false; } @@ -130,21 +131,11 @@ void NewFrame() { } } - Vulkan::NewFrame(); Sdl::NewFrame(); ImGui::NewFrame(); - bool capture_gamepad = false; for (auto* layer : layers) { layer->Draw(); - if (layer->ShouldGrabGamepad()) { - capture_gamepad = true; - } - } - if (capture_gamepad) { - GetIO().BackendFlags |= ImGuiBackendFlags_HasGamepad; - } else { - GetIO().BackendFlags &= ~ImGuiBackendFlags_HasGamepad; } } diff --git a/src/imgui/renderer/imgui_impl_vulkan.cpp b/src/imgui/renderer/imgui_impl_vulkan.cpp index 2c1c135f7ce..cf8c5ea4eea 100644 --- a/src/imgui/renderer/imgui_impl_vulkan.cpp +++ b/src/imgui/renderer/imgui_impl_vulkan.cpp @@ -4,6 +4,8 @@ // Based on imgui_impl_vulkan.cpp from Dear ImGui repository #include +#include + #include #include "imgui_impl_vulkan.h" @@ -47,13 +49,15 @@ struct VkData { vk::ShaderModule shader_module_vert{}; vk::ShaderModule shader_module_frag{}; + std::mutex command_pool_mutex; + vk::CommandPool command_pool{}; + vk::Sampler simple_sampler{}; + // Font data - vk::Sampler font_sampler{}; vk::DeviceMemory font_memory{}; vk::Image font_image{}; vk::ImageView font_view{}; vk::DescriptorSet font_descriptor_set{}; - vk::CommandPool font_command_pool{}; vk::CommandBuffer font_command_buffer{}; // Render buffers @@ -222,12 +226,53 @@ static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize return (size + alignment - 1) & ~(alignment - 1); } +void UploadTextureData::Upload() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + vk::SubmitInfo submit_info{ + .commandBufferCount = 1, + .pCommandBuffers = &command_buffer, + }; + CheckVkErr(v.queue.submit({submit_info})); + CheckVkErr(v.queue.waitIdle()); + + v.device.destroyBuffer(upload_buffer, v.allocator); + v.device.freeMemory(upload_buffer_memory, v.allocator); + { + std::unique_lock lk(bd->command_pool_mutex); + v.device.freeCommandBuffers(bd->command_pool, {command_buffer}); + } + upload_buffer = VK_NULL_HANDLE; + upload_buffer_memory = VK_NULL_HANDLE; +} + +void UploadTextureData::Destroy() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + CheckVkErr(v.device.waitIdle()); + RemoveTexture(descriptor_set); + descriptor_set = VK_NULL_HANDLE; + + v.device.destroyImageView(image_view, v.allocator); + image_view = VK_NULL_HANDLE; + v.device.destroyImage(image, v.allocator); + image = VK_NULL_HANDLE; + v.device.freeMemory(image_memory, v.allocator); + image_memory = VK_NULL_HANDLE; +} + // Register a texture -vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, - vk::ImageLayout image_layout) { +vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, + vk::Sampler sampler) { VkData* bd = GetBackendData(); const InitInfo& v = bd->init_info; + if (sampler == VK_NULL_HANDLE) { + sampler = bd->simple_sampler; + } + // Create Descriptor Set: vk::DescriptorSet descriptor_set; { @@ -262,6 +307,166 @@ vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, } return descriptor_set; } +UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height, + size_t size) { + ImGuiIO& io = GetIO(); + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + UploadTextureData info{}; + { + std::unique_lock lk(bd->command_pool_mutex); + info.command_buffer = + CheckVkResult(v.device.allocateCommandBuffers(vk::CommandBufferAllocateInfo{ + .commandPool = bd->command_pool, + .commandBufferCount = 1, + })) + .front(); + CheckVkErr(info.command_buffer.begin(vk::CommandBufferBeginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, + })); + } + + // Create Image + { + vk::ImageCreateInfo image_info{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent{ + .width = width, + .height = height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = vk::ImageTiling::eOptimal, + .usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined, + }; + info.image = CheckVkResult(v.device.createImage(image_info, v.allocator)); + auto req = v.device.getImageMemoryRequirements(info.image); + vk::MemoryAllocateInfo alloc_info{ + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits), + }; + info.image_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindImageMemory(info.image, info.image_memory, 0)); + } + + // Create Image View + { + vk::ImageViewCreateInfo view_info{ + .image = info.image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }; + info.image_view = CheckVkResult(v.device.createImageView(view_info, v.allocator)); + } + + // Create descriptor set (ImTextureID) + info.descriptor_set = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal); + + // Create Upload Buffer + { + vk::BufferCreateInfo buffer_info{ + .size = size, + .usage = vk::BufferUsageFlagBits::eTransferSrc, + .sharingMode = vk::SharingMode::eExclusive, + }; + info.upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator)); + auto req = v.device.getBufferMemoryRequirements(info.upload_buffer); + auto alignemtn = IM_MAX(bd->buffer_memory_alignment, req.alignment); + vk::MemoryAllocateInfo alloc_info{ + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), + }; + info.upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindBufferMemory(info.upload_buffer, info.upload_buffer_memory, 0)); + } + + // Upload to Buffer + { + char* map = (char*)CheckVkResult(v.device.mapMemory(info.upload_buffer_memory, 0, size)); + memcpy(map, data, size); + vk::MappedMemoryRange range[1]{ + { + .memory = info.upload_buffer_memory, + .size = size, + }, + }; + CheckVkErr(v.device.flushMappedMemoryRanges(range)); + v.device.unmapMemory(info.upload_buffer_memory); + } + + // Copy to Image + { + vk::ImageMemoryBarrier copy_barrier[1]{ + { + .sType = vk::StructureType::eImageMemoryBarrier, + .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eTransferDstOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = info.image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost, + vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, + {copy_barrier}); + + vk::BufferImageCopy region{ + .imageSubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .layerCount = 1, + }, + .imageExtent{ + .width = width, + .height = height, + .depth = 1, + }, + }; + info.command_buffer.copyBufferToImage(info.upload_buffer, info.image, + vk::ImageLayout::eTransferDstOptimal, {region}); + + vk::ImageMemoryBarrier use_barrier[1]{{ + .sType = vk::StructureType::eImageMemoryBarrier, + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eShaderRead, + .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = info.image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }}; + info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, + {use_barrier}); + } + + CheckVkErr(info.command_buffer.end()); + + return info; +} void RemoveTexture(vk::DescriptorSet descriptor_set) { VkData* bd = GetBackendData(); @@ -517,27 +722,20 @@ static bool CreateFontsTexture() { DestroyFontsTexture(); } - // Create command pool/buffer - if (bd->font_command_pool == VK_NULL_HANDLE) { - vk::CommandPoolCreateInfo info{ - .sType = vk::StructureType::eCommandPoolCreateInfo, - .flags = vk::CommandPoolCreateFlags{}, - .queueFamilyIndex = v.queue_family, - }; - bd->font_command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator)); - } + // Create command buffer if (bd->font_command_buffer == VK_NULL_HANDLE) { vk::CommandBufferAllocateInfo info{ .sType = vk::StructureType::eCommandBufferAllocateInfo, - .commandPool = bd->font_command_pool, + .commandPool = bd->command_pool, .commandBufferCount = 1, }; + std::unique_lock lk(bd->command_pool_mutex); bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front(); } // Start command buffer { - CheckVkErr(v.device.resetCommandPool(bd->font_command_pool, vk::CommandPoolResetFlags{})); + CheckVkErr(bd->font_command_buffer.reset()); vk::CommandBufferBeginInfo begin_info{}; begin_info.sType = vk::StructureType::eCommandBufferBeginInfo; begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit; @@ -597,8 +795,7 @@ static bool CreateFontsTexture() { } // Create the Descriptor Set: - bd->font_descriptor_set = - AddTexture(bd->font_sampler, bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); + bd->font_descriptor_set = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); // Create the Upload Buffer: vk::DeviceMemory upload_buffer_memory{}; @@ -956,25 +1153,6 @@ bool CreateDeviceObjects() { bd->descriptor_pool = CheckVkResult(v.device.createDescriptorPool(pool_info)); } - if (!bd->font_sampler) { - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= - // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow - // point/nearest sampling. - vk::SamplerCreateInfo info{ - .sType = vk::StructureType::eSamplerCreateInfo, - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .maxAnisotropy = 1.0f, - .minLod = -1000, - .maxLod = 1000, - }; - bd->font_sampler = CheckVkResult(v.device.createSampler(info, v.allocator)); - } - if (!bd->descriptor_set_layout) { vk::DescriptorSetLayoutBinding binding[1]{ { @@ -1016,6 +1194,35 @@ bool CreateDeviceObjects() { CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass); + if (bd->command_pool == VK_NULL_HANDLE) { + vk::CommandPoolCreateInfo info{ + .sType = vk::StructureType::eCommandPoolCreateInfo, + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = v.queue_family, + }; + std::unique_lock lk(bd->command_pool_mutex); + bd->command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator)); + } + + if (!bd->simple_sampler) { + // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= + // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow + // point/nearest sampling. + vk::SamplerCreateInfo info{ + .sType = vk::StructureType::eSamplerCreateInfo, + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .maxAnisotropy = 1.0f, + .minLod = -1000, + .maxLod = 1000, + }; + bd->simple_sampler = CheckVkResult(v.device.createSampler(info, v.allocator)); + } + return true; } @@ -1026,12 +1233,14 @@ void ImGuiImplVulkanDestroyDeviceObjects() { DestroyFontsTexture(); if (bd->font_command_buffer) { - v.device.freeCommandBuffers(bd->font_command_pool, {bd->font_command_buffer}); + std::unique_lock lk(bd->command_pool_mutex); + v.device.freeCommandBuffers(bd->command_pool, {bd->font_command_buffer}); bd->font_command_buffer = VK_NULL_HANDLE; } - if (bd->font_command_pool) { - v.device.destroyCommandPool(bd->font_command_pool, v.allocator); - bd->font_command_pool = VK_NULL_HANDLE; + if (bd->command_pool) { + std::unique_lock lk(bd->command_pool_mutex); + v.device.destroyCommandPool(bd->command_pool, v.allocator); + bd->command_pool = VK_NULL_HANDLE; } if (bd->shader_module_vert) { v.device.destroyShaderModule(bd->shader_module_vert, v.allocator); @@ -1041,9 +1250,9 @@ void ImGuiImplVulkanDestroyDeviceObjects() { v.device.destroyShaderModule(bd->shader_module_frag, v.allocator); bd->shader_module_frag = VK_NULL_HANDLE; } - if (bd->font_sampler) { - v.device.destroySampler(bd->font_sampler, v.allocator); - bd->font_sampler = VK_NULL_HANDLE; + if (bd->simple_sampler) { + v.device.destroySampler(bd->simple_sampler, v.allocator); + bd->simple_sampler = VK_NULL_HANDLE; } if (bd->descriptor_set_layout) { v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator); @@ -1095,13 +1304,4 @@ void Shutdown() { IM_DELETE(bd); } -void NewFrame() { - VkData* bd = GetBackendData(); - IM_ASSERT(bd != nullptr && - "Context or backend not initialized! Did you call ImGuiImplVulkanInit()?"); - - if (!bd->font_descriptor_set) - CreateFontsTexture(); -} - } // namespace ImGui::Vulkan diff --git a/src/imgui/renderer/imgui_impl_vulkan.h b/src/imgui/renderer/imgui_impl_vulkan.h index e68b8723f59..ca76fda6d9b 100644 --- a/src/imgui/renderer/imgui_impl_vulkan.h +++ b/src/imgui/renderer/imgui_impl_vulkan.h @@ -6,6 +6,7 @@ #pragma once #define VULKAN_HPP_NO_EXCEPTIONS +#include "common/types.h" #include "video_core/renderer_vulkan/vk_common.h" struct ImDrawData; @@ -29,14 +30,33 @@ struct InitInfo { void (*check_vk_result_fn)(vk::Result err); }; -vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, - vk::ImageLayout image_layout); +// Prepare all resources needed for uploading textures +// Caller should clean up the returned data. +struct UploadTextureData { + vk::Image image; + vk::ImageView image_view; + vk::DescriptorSet descriptor_set; + vk::DeviceMemory image_memory; + + vk::CommandBuffer command_buffer; // Submit to the queue + vk::Buffer upload_buffer; + vk::DeviceMemory upload_buffer_memory; + + void Upload(); + + void Destroy(); +}; + +vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, + vk::Sampler sampler = VK_NULL_HANDLE); + +UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height, + size_t size); void RemoveTexture(vk::DescriptorSet descriptor_set); bool Init(InitInfo info); void Shutdown(); -void NewFrame(); void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, vk::Pipeline pipeline = VK_NULL_HANDLE); diff --git a/src/imgui/renderer/texture_manager.cpp b/src/imgui/renderer/texture_manager.cpp new file mode 100644 index 00000000000..ba4a05d015c --- /dev/null +++ b/src/imgui/renderer/texture_manager.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include + +#include "common/assert.h" +#include "common/io_file.h" +#include "common/polyfill_thread.h" +#include "imgui_impl_vulkan.h" +#include "texture_manager.h" + +namespace ImGui { + +namespace Core::TextureManager { +struct Inner { + std::atomic_int count = 0; + ImTextureID texture_id = nullptr; + u32 width = 0; + u32 height = 0; + + Vulkan::UploadTextureData upload_data; + + ~Inner(); +}; +} // namespace Core::TextureManager + +using namespace Core::TextureManager; + +RefCountedTexture::RefCountedTexture(Inner* inner) : inner(inner) { + ++inner->count; +} + +RefCountedTexture RefCountedTexture::DecodePngTexture(std::vector data) { + const auto core = new Inner; + Core::TextureManager::DecodePngTexture(std::move(data), core); + return RefCountedTexture(core); +} + +RefCountedTexture RefCountedTexture::DecodePngFile(std::filesystem::path path) { + const auto core = new Inner; + Core::TextureManager::DecodePngFile(std::move(path), core); + return RefCountedTexture(core); +} + +RefCountedTexture::RefCountedTexture() : inner(nullptr) {} + +RefCountedTexture::RefCountedTexture(const RefCountedTexture& other) : inner(other.inner) { + if (inner != nullptr) { + ++inner->count; + } +} + +RefCountedTexture::RefCountedTexture(RefCountedTexture&& other) noexcept : inner(other.inner) { + other.inner = nullptr; +} + +RefCountedTexture& RefCountedTexture::operator=(const RefCountedTexture& other) { + if (this == &other) + return *this; + inner = other.inner; + if (inner != nullptr) { + ++inner->count; + } + return *this; +} + +RefCountedTexture& RefCountedTexture::operator=(RefCountedTexture&& other) noexcept { + if (this == &other) + return *this; + std::swap(inner, other.inner); + return *this; +} + +RefCountedTexture::~RefCountedTexture() { + if (inner != nullptr) { + if (inner->count.fetch_sub(1) == 1) { + delete inner; + } + } +} +RefCountedTexture::Image RefCountedTexture::GetTexture() const { + if (inner == nullptr) { + return {}; + } + return Image{ + .im_id = inner->texture_id, + .width = inner->width, + .height = inner->height, + }; +} +RefCountedTexture::operator bool() const { + return inner != nullptr && inner->texture_id != nullptr; +} + +struct Job { + Inner* core; + std::vector data; + std::filesystem::path path; +}; + +struct UploadJob { + Inner* core = nullptr; + Vulkan::UploadTextureData data; + int tick = 0; // Used to skip the first frame when destroying to await the current frame to draw +}; + +static bool g_is_worker_running = false; +static std::jthread g_worker_thread; +static std::condition_variable g_worker_cv; + +static std::mutex g_job_list_mtx; +static std::deque g_job_list; + +static std::mutex g_upload_mtx; +static std::deque g_upload_list; + +namespace Core::TextureManager { + +Inner::~Inner() { + if (upload_data.descriptor_set != nullptr) { + std::unique_lock lk{g_upload_mtx}; + g_upload_list.emplace_back(UploadJob{ + .data = this->upload_data, + .tick = 2, + }); + } +} + +void WorkerLoop() { + std::mutex mtx; + while (g_is_worker_running) { + std::unique_lock lk{mtx}; + g_worker_cv.wait(lk); + if (!g_is_worker_running) { + break; + } + while (true) { + g_job_list_mtx.lock(); + if (g_job_list.empty()) { + g_job_list_mtx.unlock(); + break; + } + auto [core, png_raw, path] = std::move(g_job_list.front()); + g_job_list.pop_front(); + g_job_list_mtx.unlock(); + + if (!path.empty()) { // Decode PNG from file + Common::FS::IOFile file(path, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + LOG_ERROR(ImGui, "Failed to open PNG file: {}", path.string()); + continue; + } + png_raw.resize(file.GetSize()); + file.Seek(0); + file.ReadRaw(png_raw.data(), png_raw.size()); + file.Close(); + } + + int width, height; + const stbi_uc* pixels = + stbi_load_from_memory(png_raw.data(), png_raw.size(), &width, &height, nullptr, 4); + + auto texture = Vulkan::UploadTexture(pixels, vk::Format::eR8G8B8A8Unorm, width, height, + width * height * 4 * sizeof(stbi_uc)); + + core->upload_data = texture; + core->width = width; + core->height = height; + + std::unique_lock upload_lk{g_upload_mtx}; + g_upload_list.emplace_back(UploadJob{ + .core = core, + }); + } + } +} + +void StartWorker() { + ASSERT(!g_is_worker_running); + g_worker_thread = std::jthread(WorkerLoop); + g_is_worker_running = true; +} + +void StopWorker() { + ASSERT(g_is_worker_running); + g_is_worker_running = false; + g_worker_cv.notify_one(); +} + +void DecodePngTexture(std::vector data, Inner* core) { + ++core->count; + Job job{ + .core = core, + .data = std::move(data), + }; + std::unique_lock lk{g_job_list_mtx}; + g_job_list.push_back(std::move(job)); + g_worker_cv.notify_one(); +} + +void DecodePngFile(std::filesystem::path path, Inner* core) { + ++core->count; + Job job{ + .core = core, + .path = std::move(path), + }; + std::unique_lock lk{g_job_list_mtx}; + g_job_list.push_back(std::move(job)); + g_worker_cv.notify_one(); +} + +void Submit() { + UploadJob upload; + { + std::unique_lock lk{g_upload_mtx}; + if (g_upload_list.empty()) { + return; + } + // Upload one texture at a time to avoid slow down + upload = g_upload_list.front(); + g_upload_list.pop_front(); + if (upload.tick > 0) { + --upload.tick; + g_upload_list.emplace_back(upload); + return; + } + } + if (upload.core != nullptr) { + upload.core->upload_data.Upload(); + upload.core->texture_id = upload.core->upload_data.descriptor_set; + if (upload.core->count.fetch_sub(1) == 1) { + delete upload.core; + } + } else { + upload.data.Destroy(); + } +} +} // namespace Core::TextureManager + +} // namespace ImGui \ No newline at end of file diff --git a/src/imgui/renderer/texture_manager.h b/src/imgui/renderer/texture_manager.h new file mode 100644 index 00000000000..4fa7b99241c --- /dev/null +++ b/src/imgui/renderer/texture_manager.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/types.h" +#include "imgui/imgui_texture.h" + +namespace vk { +class CommandBuffer; +} + +namespace ImGui::Core::TextureManager { + +struct Inner; + +void StartWorker(); + +void StopWorker(); + +void DecodePngTexture(std::vector data, Inner* core); + +void DecodePngFile(std::filesystem::path path, Inner* core); + +void Submit(); + +}; // namespace ImGui::Core::TextureManager \ No newline at end of file diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index 6032e1c3aa7..2d08bc08501 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -27,20 +27,21 @@ class GameInfoClass : public QObject { game.path = filePath; PSF psf; - if (psf.open(game.path + "/sce_sys/param.sfo", {})) { + if (psf.Open(std::filesystem::path(game.path) / "sce_sys" / "param.sfo")) { game.icon_path = game.path + "/sce_sys/icon0.png"; QString iconpath = QString::fromStdString(game.icon_path); game.icon = QImage(iconpath); game.pic_path = game.path + "/sce_sys/pic1.png"; - game.name = psf.GetString("TITLE"); - game.serial = psf.GetString("TITLE_ID"); - game.region = GameListUtils::GetRegion(psf.GetString("CONTENT_ID").at(0)).toStdString(); - u32 fw_int = psf.GetInteger("SYSTEM_VER"); + game.name = *psf.GetString("TITLE"); + game.serial = *psf.GetString("TITLE_ID"); + game.region = + GameListUtils::GetRegion(psf.GetString("CONTENT_ID")->at(0)).toStdString(); + u32 fw_int = *psf.GetInteger("SYSTEM_VER"); QString fw = QString::number(fw_int, 16); QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') : fw.left(3).insert(1, '.'); game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString(); - game.version = psf.GetString("APP_VER"); + game.version = *psf.GetString("APP_VER"); } return game; } diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index fb1994bb0f8..bd3961dd9b2 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -80,8 +80,8 @@ class GuiContextMenus : public QObject { if (selected == &openSfoViewer) { PSF psf; - if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo", {})) { - int rows = psf.map_strings.size() + psf.map_integers.size(); + if (psf.Open(std::filesystem::path(m_games[itemID].path) / "sce_sys" / "param.sfo")) { + int rows = psf.GetEntries().size(); QTableWidget* tableWidget = new QTableWidget(rows, 2); tableWidget->setAttribute(Qt::WA_DeleteOnClose); connect(widget->parent(), &QWidget::destroyed, tableWidget, @@ -90,23 +90,33 @@ class GuiContextMenus : public QObject { tableWidget->verticalHeader()->setVisible(false); // Hide vertical header int row = 0; - for (const auto& pair : psf.map_strings) { + for (const auto& entry : psf.GetEntries()) { QTableWidgetItem* keyItem = - new QTableWidgetItem(QString::fromStdString(pair.first)); - QTableWidgetItem* valueItem = - new QTableWidgetItem(QString::fromStdString(pair.second)); - - tableWidget->setItem(row, 0, keyItem); - tableWidget->setItem(row, 1, valueItem); - keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable); - valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable); - row++; - } - for (const auto& pair : psf.map_integers) { - QTableWidgetItem* keyItem = - new QTableWidgetItem(QString::fromStdString(pair.first)); - QTableWidgetItem* valueItem = new QTableWidgetItem( - QString("0x").append(QString::number(pair.second, 16))); + new QTableWidgetItem(QString::fromStdString(entry.key)); + QTableWidgetItem* valueItem; + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + + const auto bin = *psf.GetBinary(entry.key); + std::string text; + text.reserve(bin.size() * 2); + for (const auto& c : bin) { + static constexpr char hex[] = "0123456789ABCDEF"; + text.push_back(hex[c >> 4 & 0xF]); + text.push_back(hex[c & 0xF]); + } + valueItem = new QTableWidgetItem(QString::fromStdString(text)); + } break; + case PSFEntryFmt::Text: { + auto text = *psf.GetString(entry.key); + valueItem = new QTableWidgetItem(QString::fromStdString(std::string{text})); + } break; + case PSFEntryFmt::Integer: { + auto integer = *psf.GetInteger(entry.key); + valueItem = + new QTableWidgetItem(QString("0x") + QString::number(integer, 16)); + } break; + } tableWidget->setItem(row, 0, keyItem); tableWidget->setItem(row, 1, valueItem); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 1945db7f978..cb0129c8e2e 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -636,9 +636,9 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int QMessageBox msgBox; msgBox.setWindowTitle(tr("PKG Extraction")); - psf.open("", pkg.sfo); + psf.Open(pkg.sfo); - std::string content_id = psf.GetString("CONTENT_ID"); + std::string content_id{*psf.GetString("CONTENT_ID")}; std::string entitlement_label = Common::SplitString(content_id, '-')[2]; auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) / @@ -647,9 +647,11 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int auto category = psf.GetString("CATEGORY"); if (pkgType.contains("PATCH")) { - QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER")); - psf.open(extract_path.string() + "/sce_sys/param.sfo", {}); - QString game_app_version = QString::fromStdString(psf.GetString("APP_VER")); + QString pkg_app_version = + QString::fromStdString(std::string{*psf.GetString("APP_VER")}); + psf.Open(extract_path / "sce_sys" / "param.sfo"); + QString game_app_version = + QString::fromStdString(std::string{*psf.GetString("APP_VER")}); double appD = game_app_version.toDouble(); double pkgD = pkg_app_version.toDouble(); if (pkgD == appD) { diff --git a/src/qt_gui/pkg_viewer.cpp b/src/qt_gui/pkg_viewer.cpp index 49005c720aa..d41d37dbe5d 100644 --- a/src/qt_gui/pkg_viewer.cpp +++ b/src/qt_gui/pkg_viewer.cpp @@ -109,12 +109,12 @@ void PKGViewer::ProcessPKGInfo() { path = std::filesystem::path(m_pkg_list[i].toStdWString()); #endif package.Open(path); - psf.open("", package.sfo); - QString title_name = QString::fromStdString(psf.GetString("TITLE")); - QString title_id = QString::fromStdString(psf.GetString("TITLE_ID")); - QString app_type = game_list_util.GetAppType(psf.GetInteger("APP_TYPE")); - QString app_version = QString::fromStdString(psf.GetString("APP_VER")); - QString title_category = QString::fromStdString(psf.GetString("CATEGORY")); + psf.Open(package.sfo); + QString title_name = QString::fromStdString(std::string{*psf.GetString("TITLE")}); + QString title_id = QString::fromStdString(std::string{*psf.GetString("TITLE_ID")}); + QString app_type = game_list_util.GetAppType(*psf.GetInteger("APP_TYPE")); + QString app_version = QString::fromStdString(std::string{*psf.GetString("APP_VER")}); + QString title_category = QString::fromStdString(std::string{*psf.GetString("CATEGORY")}); QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size); pkg_content_flag = package.GetPkgHeader().pkg_content_flags; QString flagss = ""; @@ -126,7 +126,7 @@ void PKGViewer::ProcessPKGInfo() { } } - u32 fw_int = psf.GetInteger("SYSTEM_VER"); + u32 fw_int = *psf.GetInteger("SYSTEM_VER"); QString fw = QString::number(fw_int, 16); QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') : fw.left(3).insert(1, '.'); diff --git a/src/qt_gui/pkg_viewer.h b/src/qt_gui/pkg_viewer.h index 9598328a079..265a03b92d7 100644 --- a/src/qt_gui/pkg_viewer.h +++ b/src/qt_gui/pkg_viewer.h @@ -33,7 +33,6 @@ class PKGViewer : public QMainWindow { PKGHeader pkgheader; PKGEntry entry; PSFHeader header; - PSFEntry psfentry; char pkgTitleID[9]; std::vector pkg; u64 pkgSize = 0; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 9ff332aefe4..b99dfdbb467 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -4,6 +4,7 @@ #include #include "common/assert.h" #include "common/debug.h" +#include "imgui/renderer/texture_manager.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -190,6 +191,7 @@ void Scheduler::SubmitExecution(SubmitInfo& info) { }; try { + ImGui::Core::TextureManager::Submit(); instance.GetGraphicsQueue().submit(submit_info, info.fence); } catch (vk::DeviceLostError& err) { UNREACHABLE_MSG("Device lost during submit: {}", err.what()); From 0c63267b1cbec30059cadd0b91b2c17a953d0fea Mon Sep 17 00:00:00 2001 From: SleepingSnakezzz <71992016+SleepingSnakezzz@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:38:42 +0200 Subject: [PATCH 11/11] Update Discord invite link.md (#994) I missed one last time :P --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2127a579156..1be14c4fa28 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ SPDX-License-Identifier: GPL-2.0-or-later

- +