diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 44b2ac18e8..4f0094d23d 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -502,7 +502,7 @@ std::string_view fs::get_parent_dir_view(std::string_view path, u32 parent_level return result; } -bool fs::stat(const std::string& path, stat_t& info) +bool fs::get_stat(const std::string& path, stat_t& info) { // Ensure consistent information on failure info = {}; @@ -618,13 +618,13 @@ bool fs::stat(const std::string& path, stat_t& info) bool fs::exists(const std::string& path) { fs::stat_t info{}; - return fs::stat(path, info); + return fs::get_stat(path, info); } bool fs::is_file(const std::string& path) { fs::stat_t info{}; - if (!fs::stat(path, info)) + if (!fs::get_stat(path, info)) { return false; } @@ -641,7 +641,7 @@ bool fs::is_file(const std::string& path) bool fs::is_dir(const std::string& path) { fs::stat_t info{}; - if (!fs::stat(path, info)) + if (!fs::get_stat(path, info)) { return false; } diff --git a/Utilities/File.h b/Utilities/File.h index a8c1de8773..5832e0f594 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -186,7 +186,7 @@ namespace fs } // Get file information - bool stat(const std::string& path, stat_t& info); + bool get_stat(const std::string& path, stat_t& info); // Check whether a file or a directory exists (not recommended, use is_file() or is_dir() instead) bool exists(const std::string& path); diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp index ba25cf5368..a7ea0a6c94 100644 --- a/Utilities/bin_patch.cpp +++ b/Utilities/bin_patch.cpp @@ -726,7 +726,7 @@ bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie break; default: { - const u32 offset = get_yaml_node_value(addr_node, error_message); + get_yaml_node_value(addr_node, error_message); if (!error_message.empty()) { error_message = fmt::format("Skipping patch data entry: [ %s, 0x%.8x, %s ] (key: %s, location: %s) Invalid patch offset '%s' (not a valid u32 or overflow)", diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index dd26620ab1..031176a948 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -682,6 +682,7 @@ static atomic_t s_dummy_atomic = 0; bool cpu_thread::check_state() noexcept { bool cpu_sleep_called = false; + bool cpu_memory_checked = false; bool cpu_can_stop = true; bool escape{}, retval{}; @@ -770,7 +771,7 @@ bool cpu_thread::check_state() noexcept if (!is_stopped(flags) && flags.none_of(cpu_flag::ret)) { // Check pause flags which hold thread inside check_state (ignore suspend/debug flags on cpu_flag::temp) - if (flags & (cpu_flag::pause + cpu_flag::memory) || (cpu_can_stop && flags & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause + cpu_flag::suspend + cpu_flag::yield + cpu_flag::preempt))) + if (flags & cpu_flag::pause || (!cpu_memory_checked && flags & cpu_flag::memory) || (cpu_can_stop && flags & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause + cpu_flag::suspend + cpu_flag::yield + cpu_flag::preempt))) { if (!(flags & cpu_flag::wait)) { @@ -789,12 +790,18 @@ bool cpu_thread::check_state() noexcept return store; } + if (flags & (cpu_flag::wait + cpu_flag::memory)) + { + flags -= (cpu_flag::wait + cpu_flag::memory); + store = true; + } + if (s_tls_thread_slot == umax) { - if (cpu_flag::wait - state) + if (cpu_flag::wait - this->state.load()) { // Force wait flag (must be set during ownership of s_cpu_lock), this makes the atomic op fail as a side effect - state += cpu_flag::wait; + this->state += cpu_flag::wait; store = true; } @@ -802,12 +809,6 @@ bool cpu_thread::check_state() noexcept cpu_counter::add(this); } - if (flags & cpu_flag::wait) - { - flags -= cpu_flag::wait; - store = true; - } - retval = false; } else @@ -856,6 +857,14 @@ bool cpu_thread::check_state() noexcept if (escape) { + if (vm::g_range_lock_bits[1] && vm::g_tls_locked && *vm::g_tls_locked == this) + { + state += cpu_flag::wait + cpu_flag::memory; + cpu_sleep_called = false; + cpu_memory_checked = false; + continue; + } + if (cpu_can_stop && state0 & cpu_flag::pending) { // Execute pending work @@ -866,6 +875,7 @@ bool cpu_thread::check_state() noexcept // Work could have changed flags // Reset internal flags as if check_state() has just been called cpu_sleep_called = false; + cpu_memory_checked = false; continue; } } @@ -883,6 +893,7 @@ bool cpu_thread::check_state() noexcept { cpu_sleep(); cpu_sleep_called = true; + cpu_memory_checked = false; if (s_tls_thread_slot != umax) { @@ -907,6 +918,7 @@ bool cpu_thread::check_state() noexcept if (state0 & cpu_flag::memory) { vm::passive_lock(*this); + cpu_memory_checked = true; continue; } diff --git a/rpcs3/Emu/Cell/MFC.cpp b/rpcs3/Emu/Cell/MFC.cpp index eb195a7470..1af9f4aa1a 100644 --- a/rpcs3/Emu/Cell/MFC.cpp +++ b/rpcs3/Emu/Cell/MFC.cpp @@ -70,5 +70,5 @@ void fmt_class_string::format(std::string& out, u64 arg) const u8 tag = cmd.tag; - fmt::append(out, "%s #%02u 0x%05x:0x%08llx 0x%x%s", cmd.cmd, tag & 0x7f, cmd.lsa, u64{cmd.eah} << 32 | cmd.eal, cmd.size, (tag & 0x80) ? " (stalled)" : ""); + fmt::append(out, "%-8s #%02u 0x%05x:0x%08llx 0x%x%s", cmd.cmd, tag & 0x7f, cmd.lsa, u64{cmd.eah} << 32 | cmd.eal, cmd.size, (tag & 0x80) ? " (stalled)" : ""); } diff --git a/rpcs3/Emu/Cell/Modules/cellPhotoImport.cpp b/rpcs3/Emu/Cell/Modules/cellPhotoImport.cpp index c8ff17e10e..5ea954afbc 100644 --- a/rpcs3/Emu/Cell/Modules/cellPhotoImport.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPhotoImport.cpp @@ -155,7 +155,7 @@ error_code select_photo(std::string dst_dir) { fs::stat_t f_info{}; - if (!fs::stat(info.path, f_info) || f_info.is_directory) + if (!fs::get_stat(info.path, f_info) || f_info.is_directory) { cellPhotoImportUtil.error("Path does not belong to a valid file: '%s'", info.path); result = CELL_PHOTO_IMPORT_ERROR_ACCESS_ERROR; // TODO: is this correct ? diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index 474696f000..648c94b225 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -1380,6 +1380,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v bool recreated = false; lv2_sleep(ppu, 250); + ppu.state += cpu_flag::wait; // Check if RPCS3_BLIST section exist in PARAM.SFO // This section contains the list of files in the save ordered as they would be in BSD filesystem @@ -1397,7 +1398,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v } fs::stat_t dir_info{}; - if (!fs::stat(dir_path, dir_info)) + if (!fs::get_stat(dir_path, dir_info)) { // funcStat is called even if the directory doesn't exist. } @@ -1523,6 +1524,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v // Stat Callback funcStat(ppu, result, statGet, statSet); + ppu.state += cpu_flag::wait; if (const s32 res = result->result; res != CELL_SAVEDATA_CBRESULT_OK_NEXT) { @@ -1689,6 +1691,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v std::memset(result.get_ptr(), 0, ::offset32(&CellSaveDataCBResult::userdata)); funcFile(ppu, result, fileGet, fileSet); + ppu.state += cpu_flag::wait; if (const s32 res = result->result; res != CELL_SAVEDATA_CBRESULT_OK_NEXT) { @@ -2099,7 +2102,7 @@ static NEVER_INLINE error_code savedata_get_list_item(vm::cptr dirName, vm if (dir) { fs::stat_t dir_info{}; - if (!fs::stat(save_path, dir_info)) + if (!fs::get_stat(save_path, dir_info)) { return CELL_SAVEDATA_ERROR_INTERNAL; } @@ -2406,8 +2409,10 @@ error_code cellSaveDataFixedExport(ppu_thread& ppu, vm::cptr dirName, u32 return CELL_OK; } -error_code cellSaveDataGetListItem(vm::cptr dirName, vm::ptr dir, vm::ptr sysFileParam, vm::ptr bind, vm::ptr sizeKB) +error_code cellSaveDataGetListItem(ppu_thread& ppu, vm::cptr dirName, vm::ptr dir, vm::ptr sysFileParam, vm::ptr bind, vm::ptr sizeKB) { + ppu.state += cpu_flag::wait; + cellSaveData.warning("cellSaveDataGetListItem(dirName=%s, dir=*0x%x, sysFileParam=*0x%x, bind=*0x%x, sizeKB=*0x%x)", dirName, dir, sysFileParam, bind, sizeKB); return savedata_get_list_item(dirName, dir, sysFileParam, bind, sizeKB, 0); diff --git a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp index 497b8cd32b..8852df6955 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp @@ -244,6 +244,15 @@ void fmt_class_string::format(std::string& out, u64 fmt::append(out, "%s", sign.data); } +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + const auto& id = get_object(arg); + + const u8 term = id.data[9]; + fmt::append(out, "{ data='%s', term='%s' (0x%x), num=%d, dummy=%d }", id.data, std::isprint(term) ? fmt::format("%c", term) : "", term, id.num, id.dummy); +} + // Helpers static error_code NpTrophyGetTrophyInfo(const trophy_context_t* ctxt, s32 trophyId, SceNpTrophyDetails* details, SceNpTrophyData* data); @@ -442,27 +451,48 @@ error_code sceNpTrophyCreateContext(vm::ptr context, vm::cptrnum > 99) + const s32 comm_num = commId->num; + if (comm_num > 99) { return SCE_NP_TROPHY_ERROR_INVALID_NP_COMM_ID; } // NOTE: commId->term is unused in our code (at least until someone finds out if we need to account for it) - // generate trophy context name, limited to 9 characters - std::string_view name_sv(commId->data, 9); + // Generate trophy context name, limited to 9 characters + // Read once for thread-safety reasons + std::string name_str(commId->data, 9); // resize the name if it was shorter than expected - if (const auto pos = name_sv.find_first_of('\0'); pos != std::string_view::npos) + if (const auto pos = name_str.find_first_of('\0'); pos != std::string_view::npos) { - name_sv = name_sv.substr(0, pos); + name_str = name_str.substr(0, pos); } - sceNpTrophy.warning("sceNpTrophyCreateContext(): data='%s' term=0x%x (0x%x) num=%d", name_sv, commId->data[9], commId->data[9], commId->num); + const SceNpCommunicationSignature commSign_data = *commSign; + + if (read_from_ptr>(commSign_data.data, 0) != NP_TROPHY_COMM_SIGN_MAGIC) + { + return SCE_NP_TROPHY_ERROR_INVALID_NP_COMM_ID; + } + + if (std::basic_string_view(&commSign_data.data[6], 6).find_first_not_of('\0') != umax) + { + // 6 padding bytes - must be 0 + return SCE_NP_TROPHY_ERROR_INVALID_NP_COMM_ID; + } + + if (read_from_ptr>(commSign_data.data, 4) != 0x100) + { + // Signifies version (1.00), although only one constant is allowed + return SCE_NP_TROPHY_ERROR_INVALID_NP_COMM_ID; + } // append the commId number as "_xx" - std::string name = fmt::format("%s_%02d", name_sv, commId->num); + std::string name = fmt::format("%s_%02d", name_str, comm_num); // create trophy context const auto ctxt = idm::make_ptr(); @@ -546,12 +576,28 @@ error_code sceNpTrophyRegisterContext(ppu_thread& ppu, u32 context, u32 handle, } }; - // TODO: Callbacks - // From RE-ing a game's state machine, it seems the possible order is one of the following: - // * Install (Not installed) - Setup - Progress * ? - Finalize - Complete - Installed - // * Reinstall (Corrupted) - Setup - Progress * ? - Finalize - Complete - Installed - // * Update (Required update) - Setup - Progress * ? - Finalize - Complete - Installed - // * Installed + // open trophy pack file + std::string trp_path = vfs::get(Emu.GetDir() + "TROPDIR/" + ctxt->trp_name + "/TROPHY.TRP"); + fs::file stream(trp_path); + + if (!stream && Emu.GetCat() == "GD") + { + sceNpTrophy.warning("sceNpTrophyRegisterContext failed to open trophy file from boot path: '%s' (%s)", trp_path, fs::g_tls_error); + trp_path = vfs::get("/dev_bdvd/PS3_GAME/TROPDIR/" + ctxt->trp_name + "/TROPHY.TRP"); + stream.open(trp_path); + } + + // check if exists and opened + if (!stream) + { + const std::string msg = fmt::format("Failed to open trophy file: '%s' (%s)", trp_path, fs::g_tls_error); + return {SCE_NP_TROPHY_ERROR_CONF_DOES_NOT_EXIST, msg}; + } + + // TODO: + // SCE_NP_TROPHY_STATUS_DATA_CORRUPT -> reinstall + // SCE_NP_TROPHY_STATUS_REQUIRES_UPDATE -> reinstall (for example if a patch has updates for the trophy data) + // SCE_NP_TROPHY_STATUS_CHANGES_DETECTED -> reinstall (only possible in dev mode) const std::string trophyPath = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name; const s32 trp_status = fs::is_dir(vfs::get(trophyPath)) ? SCE_NP_TROPHY_STATUS_INSTALLED : SCE_NP_TROPHY_STATUS_NOT_INSTALLED; @@ -560,6 +606,7 @@ error_code sceNpTrophyRegisterContext(ppu_thread& ppu, u32 context, u32 handle, sceNpTrophy.notice("sceNpTrophyRegisterContext(): Callback is being called (trp_status=%u)", trp_status); + // "Ask permission" to install the trophy data. // The callback is called once and then if it returns >= 0 the cb is called through events(coming from vsh) that are passed to the CB through cellSysutilCheckCallback if (statusCb(ppu, context, trp_status, 0, 0, arg) < 0) { @@ -599,24 +646,6 @@ error_code sceNpTrophyRegisterContext(ppu_thread& ppu, u32 context, u32 handle, return SCE_NP_TROPHY_ERROR_UNKNOWN_HANDLE; } - // open trophy pack file - std::string trp_path = vfs::get(Emu.GetDir() + "TROPDIR/" + ctxt->trp_name + "/TROPHY.TRP"); - fs::file stream(trp_path); - - if (!stream && Emu.GetCat() == "GD") - { - sceNpTrophy.warning("sceNpTrophyRegisterContext failed to open trophy file from boot path: '%s' (%s)", trp_path, fs::g_tls_error); - trp_path = vfs::get("/dev_bdvd/PS3_GAME/TROPDIR/" + ctxt->trp_name + "/TROPHY.TRP"); - stream.open(trp_path); - } - - // check if exists and opened - if (!stream) - { - const std::string msg = fmt::format("Failed to open trophy file: '%s' (%s)", trp_path, fs::g_tls_error); - return {SCE_NP_TROPHY_ERROR_CONF_DOES_NOT_EXIST, msg}; - } - TRPLoader trp(stream); if (!trp.LoadHeader()) { @@ -696,6 +725,7 @@ error_code sceNpTrophyRegisterContext(ppu_thread& ppu, u32 context, u32 handle, { sysutil_register_cb([statusCb, status, context, completed, arg, wkptr](ppu_thread& cb_ppu) -> s32 { + // TODO: it is possible that we need to check the return value here as well. statusCb(cb_ppu, context, status.first, completed, status.second, arg); const auto queued = wkptr.lock(); diff --git a/rpcs3/Emu/Cell/Modules/sceNpTrophy.h b/rpcs3/Emu/Cell/Modules/sceNpTrophy.h index 0f3cc729cb..565f7d0b5d 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpTrophy.h +++ b/rpcs3/Emu/Cell/Modules/sceNpTrophy.h @@ -151,6 +151,11 @@ enum SCE_NP_TROPHY_STATUS_CHANGES_DETECTED = 9, }; +enum : u32 +{ + NP_TROPHY_COMM_SIGN_MAGIC = 0xB9DDE13B, +}; + using SceNpTrophyStatusCallback = s32(u32 context, u32 status, s32 completed, s32 total, vm::ptr arg); // Forward declare this function since trophyunlock needs it diff --git a/rpcs3/Emu/Cell/SPURecompiler.cpp b/rpcs3/Emu/Cell/SPURecompiler.cpp index 211805cc4d..6df6380485 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.cpp +++ b/rpcs3/Emu/Cell/SPURecompiler.cpp @@ -3847,16 +3847,24 @@ void spu_recompiler_base::dump(const spu_program& result, std::string& out) fmt::append(hash, "%s", fmt::base57(output)); } - fmt::append(out, "========== SPU BLOCK 0x%05x (size %u, %s) ==========\n", result.entry_point, result.data.size(), hash); + fmt::append(out, "========== SPU BLOCK 0x%05x (size %u, %s) ==========\n\n", result.entry_point, result.data.size(), hash); for (auto& bb : m_bbs) { for (u32 pos = bb.first, end = bb.first + bb.second.size * 4; pos < end; pos += 4) { dis_asm.disasm(pos); - fmt::append(out, ">%s\n", dis_asm.last_opcode); + + if (!dis_asm.last_opcode.ends_with('\n')) + { + dis_asm.last_opcode += '\n'; + } + + fmt::append(out, ">%s", dis_asm.last_opcode); } + out += '\n'; + if (m_block_info[bb.first / 4]) { fmt::append(out, "A: [0x%05x] %s\n", bb.first, m_entry_info[bb.first / 4] ? (m_ret_info[bb.first / 4] ? "Chunk" : "Entry") : "Block"); @@ -5823,7 +5831,7 @@ public: if (g_cfg.core.spu_debug) { out.flush(); - fs::file(m_spurt->get_cache_path() + "spu-ir.log", fs::write + fs::append).write(log); + fs::write_file(m_spurt->get_cache_path() + "spu-ir.log", fs::create + fs::write + fs::append, log); } #if defined(__APPLE__) @@ -6241,7 +6249,7 @@ public: if (g_cfg.core.spu_debug) { - fs::file(m_spurt->get_cache_path() + "spu-ir.log", fs::write + fs::append).write(log); + fs::write_file(m_spurt->get_cache_path() + "spu-ir.log", fs::create + fs::write + fs::append, log); } fmt::throw_exception("Compilation failed"); @@ -6276,7 +6284,7 @@ public: if (g_cfg.core.spu_debug) { out.flush(); - fs::file(m_spurt->get_cache_path() + "spu-ir.log", fs::write + fs::append).write(log); + fs::write_file(m_spurt->get_cache_path() + "spu-ir.log", fs::create + fs::write + fs::append, log); } return spu_runtime::g_interpreter; @@ -11243,7 +11251,7 @@ struct spu_fast : public spu_recompiler_base { std::string log; this->dump(func, log); - fs::file(m_spurt->get_cache_path() + "spu.log", fs::write + fs::append).write(log); + fs::write_file(m_spurt->get_cache_path() + "spu.log", fs::create + fs::write + fs::append, log); } // Allocate executable area with necessary size diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 805751a8de..7a36b46565 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -326,6 +326,29 @@ extern thread_local u64 g_tls_fault_spu; const spu_decoder s_spu_itype; +namespace vm +{ + extern atomic_t g_range_lock_set[64]; + + // Defined here for performance reasons + writer_lock::~writer_lock() noexcept + { + if (range_lock) + { + if (!*range_lock) + { + return; + } + + g_range_lock_bits[1] &= ~(1ull << (range_lock - g_range_lock_set)); + range_lock->release(0); + return; + } + + g_range_lock_bits[1].release(0); + } +} + namespace spu { namespace scheduler @@ -3548,19 +3571,18 @@ bool spu_thread::do_putllc(const spu_mfc_cmd& args) { // Full lock (heavyweight) // TODO: vm::check_addr - vm::writer_lock lock(addr); + vm::writer_lock lock(addr, range_lock); if (cmp_rdata(rdata, super_data)) { mov_rdata(super_data, to_write); - res += 64; return true; } - res -= 64; return false; }(); + res += success ? 64 : 0 - 64; return success; }()) { @@ -3695,7 +3717,8 @@ void do_cell_atomic_128_store(u32 addr, const void* to_write) vm::_ref>(addr) += 0; // Hard lock - vm::writer_lock lock(addr); + auto spu = cpu ? cpu->try_get() : nullptr; + vm::writer_lock lock(addr, spu ? spu->range_lock : nullptr); mov_rdata(sdata, *static_cast(to_write)); vm::reservation_acquire(addr) += 32; } @@ -4461,7 +4484,7 @@ bool spu_thread::reservation_check(u32 addr, const decltype(rdata)& data) const // Set range_lock first optimistically range_lock->store(u64{128} << 32 | addr); - u64 lock_val = vm::g_range_lock; + u64 lock_val = *std::prev(std::end(vm::g_range_lock_set)); u64 old_lock = 0; while (lock_val != old_lock) @@ -4516,7 +4539,7 @@ bool spu_thread::reservation_check(u32 addr, const decltype(rdata)& data) const break; } - old_lock = std::exchange(lock_val, vm::g_range_lock); + old_lock = std::exchange(lock_val, *std::prev(std::end(vm::g_range_lock_set))); } if (!range_lock->load()) [[unlikely]] diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index f079bf4c13..bfcf0c09ec 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -1488,7 +1488,7 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr fs::stat_t info{}; - if (!fs::stat(local_path, info)) + if (!fs::get_stat(local_path, info)) { switch (auto error = fs::g_tls_error) { @@ -1499,7 +1499,7 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr for (u32 i = 66601; i <= 66699; i++) { - if (fs::stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) + if (fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) { total_size += info.size; } @@ -1510,7 +1510,7 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr } // Use attributes from the first fragment (consistently with sys_fs_open+fstat) - if (fs::stat(local_path + ".66600", info) && !info.is_directory) + if (fs::get_stat(local_path + ".66600", info) && !info.is_directory) { // Success info.size += total_size; @@ -2064,27 +2064,18 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr _arg, u32 { const auto arg = vm::static_ptr_cast(_arg); - std::string_view device{arg->device.get_ptr(), arg->device_size}; - - // Trim trailing '\0' - if (const auto trim_pos = device.find('\0'); trim_pos != umax) - device.remove_suffix(device.size() - trim_pos); - - if (device != "CELL_FS_IOS:ATA_HDD"sv) - { - arg->out_code = CELL_ENOTSUP; - return {CELL_ENOTSUP, device}; - } - - const auto model = g_cfg.sys.hdd_model.to_string(); - const auto serial = g_cfg.sys.hdd_serial.to_string(); - - strcpy_trunc(std::span(arg->model.get_ptr(), arg->model_size), model); - strcpy_trunc(std::span(arg->serial.get_ptr(), arg->serial_size), serial); - arg->out_code = CELL_OK; - sys_fs.trace("sys_fs_fcntl(0xc0000007): found device \"%s\" (model=\"%s\", serial=\"%s\")", device, model, serial); + if (const auto size = arg->model_size; size > 0) + strcpy_trunc(std::span(arg->model.get_ptr(), size), + fmt::format("%-*s", size - 1, g_cfg.sys.hdd_model.to_string())); // Example: "TOSHIBA MK3265GSX H " + + if (const auto size = arg->serial_size; size > 0) + strcpy_trunc(std::span(arg->serial.get_ptr(), size), + fmt::format("%*s", size - 1, g_cfg.sys.hdd_serial.to_string())); // Example: " 0A1B2C3D4" + else + return CELL_EFAULT; // CELL_EFAULT is returned only when arg->serial_size == 0 + return CELL_OK; } @@ -2190,39 +2181,37 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr _arg, u32 break; } - std::string_view vpath{arg->name.get_ptr(), arg->name_size}; + std::string_view vpath{arg->path.get_ptr(), arg->path_size}; + + if (vpath.size() == 0) + return CELL_ENOMEM; // Trim trailing '\0' if (const auto trim_pos = vpath.find('\0'); trim_pos != umax) vpath.remove_suffix(vpath.size() - trim_pos); - if (vfs::get(vpath).empty()) + arg->out_code = CELL_ENOTMOUNTED; // arg->out_code is set to CELL_ENOTMOUNTED on real hardware when the device doesn't exist or when the device isn't USB + + if (!vfs::get(vpath).empty()) { - arg->out_code = CELL_ENOTMOUNTED; - return {CELL_ENOTMOUNTED, vpath}; + if (const auto& mp = g_fxo->get().lookup(vpath, true); mp == &g_mp_sys_dev_usb) + { + const cfg::device_info device = g_cfg_vfs.get_device(g_cfg_vfs.dev_usb, fmt::format("%s%s", mp->root, mp.device.substr(mp->device.size()))); + const auto usb_ids = device.get_usb_ids(); + std::tie(arg->vendorID, arg->productID) = usb_ids; + + if (with_serial) + { + const auto arg_c000001c = vm::static_ptr_cast(_arg); + const std::u16string serial = utf8_to_utf16(device.serial); // Serial needs to be encoded to utf-16 BE + std::copy_n(serial.begin(), std::min(serial.size(), sizeof(arg_c000001c->serial) / sizeof(u16)), arg_c000001c->serial); + } + + arg->out_code = CELL_OK; + sys_fs.trace("sys_fs_fcntl(0x%08x): found device \"%s\" (vid=0x%04x, pid=0x%04x, serial=\"%s\")", op, mp.device, usb_ids.first, usb_ids.second, device.serial); + } } - const auto& mp = g_fxo->get().lookup(vpath, true); - - if (mp != &g_mp_sys_dev_usb) - { - arg->out_code = CELL_ENOTSUP; - return {CELL_ENOTSUP, vpath}; - } - - const cfg::device_info device = g_cfg_vfs.get_device(g_cfg_vfs.dev_usb, fmt::format("%s%s", mp->root, mp.device.substr(mp->device.size()))); - std::tie(arg->vendorID, arg->productID) = device.get_usb_ids(); - - if (with_serial) - { - const auto arg_c000001c = vm::static_ptr_cast(_arg); - const std::u16string serial = utf8_to_utf16(device.serial); // Serial needs to be encoded to utf-16 BE - std::copy_n(serial.begin(), std::min(serial.size(), sizeof(arg_c000001c->serial) / sizeof(u16)), arg_c000001c->serial); - } - - arg->out_code = CELL_OK; - - sys_fs.trace("sys_fs_fcntl(0x%08x): found device \"%s\" (vid=0x%04x, pid=0x%04x, serial=\"%s\")", op, mp.device, arg->vendorID, arg->productID, device.serial); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.h b/rpcs3/Emu/Cell/lv2/sys_fs.h index 0f5c9a3be3..79ee4636bd 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.h +++ b/rpcs3/Emu/Cell/lv2/sys_fs.h @@ -529,8 +529,8 @@ CHECK_SIZE(lv2_file_c0000006, 0x20); // sys_fs_fcntl: cellFsArcadeHddSerialNumber struct lv2_file_c0000007 : lv2_file_op { - be_t out_code; - vm::bcptr device; + be_t out_code; // set to 0 + vm::bcptr device; // CELL_FS_IOS:ATA_HDD be_t device_size; // 0x14 vm::bptr model; be_t model_size; // 0x29 @@ -562,8 +562,8 @@ struct lv2_file_c0000015 : lv2_file_op be_t size; // 0x20 be_t _x4; // 0x10 be_t _x8; // 0x18 - offset of out_code - be_t name_size; - vm::bcptr name; + be_t path_size; + vm::bcptr path; be_t _x14; // be_t vendorID; be_t productID; @@ -590,8 +590,8 @@ struct lv2_file_c000001c : lv2_file_op be_t size; // 0x60 be_t _x4; // 0x10 be_t _x8; // 0x18 - offset of out_code - be_t name_size; - vm::bcptr name; + be_t path_size; + vm::bcptr path; be_t unk1; be_t vendorID; be_t productID; diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp index 35661a22cd..1440a32883 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp @@ -79,15 +79,7 @@ constexpr u32 c_max_ppu_name_size = 28; void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode) { ppu.state += cpu_flag::wait; - - // Need to wait until the current writer finish - if (ppu.state & cpu_flag::memory) - { - while (vm::g_range_lock) - { - busy_wait(200); - } - } + u64 writer_mask = 0; sys_ppu_thread.trace("_sys_ppu_thread_exit(errorcode=0x%llx)", errorcode); @@ -126,6 +118,9 @@ void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode) old_ppu = g_fxo->get().clean(std::move(idm::find_unlocked>(ppu.id)->second)); } + // Get writers mask (wait for all current writers to quit) + writer_mask = vm::g_range_lock_bits[1]; + // Unqueue lv2_obj::sleep(ppu); notify.cleanup(); @@ -154,6 +149,15 @@ void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode) // It is detached from IDM now so join must be done explicitly now *static_cast*>(old_ppu.get()) = thread_state::finished; } + + // Need to wait until the current writers finish + if (ppu.state & cpu_flag::memory) + { + for (; writer_mask; writer_mask &= vm::g_range_lock_bits[1]) + { + busy_wait(200); + } + } } s32 sys_ppu_thread_yield(ppu_thread& ppu) diff --git a/rpcs3/Emu/Cell/lv2/sys_tty.cpp b/rpcs3/Emu/Cell/lv2/sys_tty.cpp index 3a93172746..3df7c72f29 100644 --- a/rpcs3/Emu/Cell/lv2/sys_tty.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_tty.cpp @@ -92,6 +92,8 @@ std::string dump_useful_thread_info(); error_code sys_tty_write([[maybe_unused]] ppu_thread& ppu, s32 ch, vm::cptr buf, u32 len, vm::ptr pwritelen) { + ppu.state += cpu_flag::wait; + sys_tty.notice("sys_tty_write(ch=%d, buf=*0x%x, len=%d, pwritelen=*0x%x)", ch, buf, len, pwritelen); std::string msg; diff --git a/rpcs3/Emu/Io/usio.cpp b/rpcs3/Emu/Io/usio.cpp index 0da11170fc..a5b4348937 100644 --- a/rpcs3/Emu/Io/usio.cpp +++ b/rpcs3/Emu/Io/usio.cpp @@ -334,7 +334,7 @@ void usb_device_usio::translate_input_tekken() } break; case usio_btn::down: - if ( pressed) + if (pressed) { digital_input |= 0x100000ULL << shift; if (player == 0) diff --git a/rpcs3/Emu/Memory/vm.cpp b/rpcs3/Emu/Memory/vm.cpp index a7d4647434..0c2c719957 100644 --- a/rpcs3/Emu/Memory/vm.cpp +++ b/rpcs3/Emu/Memory/vm.cpp @@ -70,14 +70,16 @@ namespace vm // Memory mutex acknowledgement thread_local atomic_t* g_tls_locked = nullptr; - // "Unique locked" range lock, as opposed to "shared" range locks from set - atomic_t g_range_lock = 0; - // Memory mutex: passive locks std::array, g_cfg.core.ppu_threads.max> g_locks{}; // Range lock slot allocation bits - atomic_t g_range_lock_bits{}; + atomic_t g_range_lock_bits[2]{}; + + auto& get_range_lock_bits(bool is_exclusive_range) + { + return g_range_lock_bits[+is_exclusive_range]; + } // Memory range lock slots (sparse atomics) atomic_t g_range_lock_set[64]{}; @@ -138,9 +140,10 @@ namespace vm atomic_t* alloc_range_lock() { - const auto [bits, ok] = g_range_lock_bits.fetch_op([](u64& bits) + const auto [bits, ok] = get_range_lock_bits(false).fetch_op([](u64& bits) { - if (~bits) [[likely]] + // MSB is reserved for locking with memory setting changes + if ((~(bits | (bits + 1))) << 1) [[likely]] { bits |= bits + 1; return true; @@ -157,6 +160,9 @@ namespace vm return &g_range_lock_set[std::countr_one(bits)]; } + template + static u64 for_all_range_locks(u64 input, F func); + void range_lock_internal(atomic_t* range_lock, u32 begin, u32 size) { perf_meter<"RHW_LOCK"_u64> perf0(0); @@ -168,32 +174,44 @@ namespace vm range_lock->store(to_store); } - for (u64 i = 0;; i++) + for (u64 i = 0, to_clear = umax;; i++) { - const u64 lock_val = g_range_lock.load(); const u64 is_share = g_shmem[begin >> 16].load(); + to_clear &= get_range_lock_bits(true); - u64 lock_addr = static_cast(lock_val); // -> u64 - u32 lock_size = static_cast(lock_val << range_bits >> (range_bits + 32)); - - u64 addr = begin; - - if ((lock_val & range_full_mask) == range_locked) [[likely]] + const u64 busy = for_all_range_locks(to_clear, [&](u64 addr_exec, u32 size_exec) { - lock_size = 128; + u64 addr = begin; - if (is_share) + if ((size_exec & (range_full_mask >> 32)) == (range_locked >> 32)) [[likely]] { - addr = static_cast(addr) | is_share; - lock_addr = lock_val; + size_exec = 128; + + if (is_share) + { + addr = static_cast(addr) | is_share; + } } - } - if (addr + size <= lock_addr || addr >= lock_addr + lock_size) [[likely]] + size_exec = (size_exec << range_bits) >> range_bits; + + // TODO (currently not possible): handle 2 64K pages (inverse range), or more pages + if (u64 is_shared = g_shmem[addr_exec >> 16]) [[unlikely]] + { + addr_exec = static_cast(addr_exec) | is_shared; + } + + if (addr <= addr_exec + size_exec - 1 && addr_exec <= addr + size - 1) [[unlikely]] + { + return 1; + } + + return 0; + }); + + if (!busy) [[likely]] { - const u64 new_lock_val = g_range_lock.load(); - - if (vm::check_addr(begin, vm::page_readable, size) && (!new_lock_val || new_lock_val == lock_val)) [[likely]] + if (vm::check_addr(begin, vm::page_readable, size)) [[likely]] { break; } @@ -265,7 +283,7 @@ namespace vm // Use ptr difference to determine location const auto diff = range_lock - g_range_lock_set; - g_range_lock_bits &= ~(1ull << diff); + g_range_lock_bits[0] &= ~(1ull << diff); } template @@ -295,13 +313,13 @@ namespace vm return result; } - static void _lock_main_range_lock(u64 flags, u32 addr, u32 size) + static atomic_t* _lock_main_range_lock(u64 flags, u32 addr, u32 size) { // Shouldn't really happen if (size == 0) { vm_log.warning("Tried to lock empty range (flags=0x%x, addr=0x%x)", flags >> 32, addr); - return; + return {}; } // Limit to <512 MiB at once; make sure if it operates on big amount of data, it's page-aligned @@ -311,7 +329,8 @@ namespace vm } // Block or signal new range locks - g_range_lock = addr | u64{size} << 32 | flags; + auto range_lock = &*std::prev(std::end(vm::g_range_lock_set)); + *range_lock = addr | u64{size} << 32 | flags; utils::prefetch_read(g_range_lock_set + 0); utils::prefetch_read(g_range_lock_set + 2); @@ -319,7 +338,7 @@ namespace vm const auto range = utils::address_range::start_length(addr, size); - u64 to_clear = g_range_lock_bits.load(); + u64 to_clear = get_range_lock_bits(false).load(); while (to_clear) { @@ -340,22 +359,21 @@ namespace vm utils::pause(); } + + return range_lock; } void passive_lock(cpu_thread& cpu) { + ensure(cpu.state & cpu_flag::wait); + bool ok = true; if (!g_tls_locked || *g_tls_locked != &cpu) [[unlikely]] { _register_lock(&cpu); - if (cpu.state & cpu_flag::memory) [[likely]] - { - cpu.state -= cpu_flag::memory; - } - - if (!g_range_lock) + if (!get_range_lock_bits(true)) { return; } @@ -367,19 +385,18 @@ namespace vm { for (u64 i = 0;; i++) { + if (cpu.is_paused()) + { + // Assume called from cpu_thread::check_state(), it can handle the pause flags better + return; + } + if (i < 100) busy_wait(200); else std::this_thread::yield(); - if (g_range_lock) - { - continue; - } - - cpu.state -= cpu_flag::memory; - - if (!g_range_lock) [[likely]] + if (!get_range_lock_bits(true)) [[likely]] { return; } @@ -427,18 +444,22 @@ namespace vm } } - writer_lock::writer_lock() - : writer_lock(0, 1) + writer_lock::writer_lock() noexcept + : writer_lock(0, nullptr, 1) { } - writer_lock::writer_lock(u32 const addr, u32 const size, u64 const flags) + writer_lock::writer_lock(u32 const addr, atomic_t* range_lock, u32 const size, u64 const flags) noexcept + : range_lock(range_lock) { - auto cpu = get_current_cpu_thread(); + cpu_thread* cpu{}; - if (cpu) + if (g_tls_locked) { - if (!g_tls_locked || *g_tls_locked != cpu || cpu->state & cpu_flag::wait) + cpu = get_current_cpu_thread(); + AUDIT(cpu); + + if (*g_tls_locked != cpu || cpu->state & cpu_flag::wait) { cpu = nullptr; } @@ -448,18 +469,51 @@ namespace vm } } + bool to_prepare_memory = addr >= 0x10000; + for (u64 i = 0;; i++) { - if (g_range_lock || !g_range_lock.compare_and_swap_test(0, addr | u64{size} << 32 | flags)) + auto& bits = get_range_lock_bits(true); + + if (!range_lock || addr < 0x10000) { - if (i < 100) - busy_wait(200); - else - std::this_thread::yield(); + if (!bits && bits.compare_and_swap_test(0, u64{umax})) + { + break; + } } else { - break; + range_lock->release(addr | u64{size} << 32 | flags); + + const auto diff = range_lock - g_range_lock_set; + + if (bits != umax && !bits.bit_test_set(diff)) + { + break; + } + + range_lock->release(0); + } + + if (i < 100) + { + if (to_prepare_memory) + { + // We have some spare time, prepare cache lines (todo: reservation tests here) + utils::prefetch_write(vm::get_super_ptr(addr)); + utils::prefetch_write(vm::get_super_ptr(addr) + 64); + to_prepare_memory = false; + } + + busy_wait(200); + } + else + { + std::this_thread::yield(); + + // Thread may have been switched or the cache clue has been undermined, cache needs to be prapred again + to_prepare_memory = true; } } @@ -469,7 +523,7 @@ namespace vm for (auto lock = g_locks.cbegin(), end = lock + g_cfg.core.ppu_threads; lock != end; lock++) { - if (auto ptr = +*lock; ptr && !(ptr->state & cpu_flag::memory)) + if (auto ptr = +*lock; ptr && ptr->state.none_of(cpu_flag::wait + cpu_flag::memory)) { ptr->state.test_and_set(cpu_flag::memory); } @@ -487,13 +541,13 @@ namespace vm utils::prefetch_read(g_range_lock_set + 2); utils::prefetch_read(g_range_lock_set + 4); - u64 to_clear = g_range_lock_bits.load(); + u64 to_clear = get_range_lock_bits(false); u64 point = addr1 / 128; while (true) { - to_clear = for_all_range_locks(to_clear, [&](u64 addr2, u32 size2) + to_clear = for_all_range_locks(to_clear & ~get_range_lock_bits(true), [&](u64 addr2, u32 size2) { // Split and check every 64K page separately for (u64 hi = addr2 >> 16, max = (addr2 + size2 - 1) >> 16; hi <= max; hi++) @@ -523,6 +577,13 @@ namespace vm break; } + if (to_prepare_memory) + { + utils::prefetch_write(vm::get_super_ptr(addr)); + utils::prefetch_write(vm::get_super_ptr(addr) + 64); + to_prepare_memory = false; + } + utils::pause(); } @@ -532,6 +593,13 @@ namespace vm { while (!(ptr->state & cpu_flag::wait)) { + if (to_prepare_memory) + { + utils::prefetch_write(vm::get_super_ptr(addr)); + utils::prefetch_write(vm::get_super_ptr(addr) + 64); + to_prepare_memory = false; + } + utils::pause(); } } @@ -544,11 +612,6 @@ namespace vm } } - writer_lock::~writer_lock() - { - g_range_lock = 0; - } - u64 reservation_lock_internal(u32 addr, atomic_t& res) { for (u64 i = 0;; i++) @@ -672,7 +735,7 @@ namespace vm const bool is_noop = bflags & page_size_4k && utils::c_page_size > 4096; // Lock range being mapped - _lock_main_range_lock(range_allocation, addr, size); + auto range_lock = _lock_main_range_lock(range_allocation, addr, size); if (shm && shm->flags() != 0 && shm->info++) { @@ -788,6 +851,8 @@ namespace vm fmt::throw_exception("Concurrent access (addr=0x%x, size=0x%x, flags=0x%x, current_addr=0x%x)", addr, size, flags, i * 4096); } } + + range_lock->release(0); } bool page_protect(u32 addr, u32 size, u8 flags_test, u8 flags_set, u8 flags_clear) @@ -845,7 +910,7 @@ namespace vm safe_bits |= range_writable; // Protect range locks from observing changes in memory protection - _lock_main_range_lock(safe_bits, start * 4096, page_size); + auto range_lock = _lock_main_range_lock(safe_bits, start * 4096, page_size); for (u32 j = start; j < i; j++) { @@ -857,6 +922,8 @@ namespace vm const auto protection = start_value & page_writable ? utils::protection::rw : (start_value & page_readable ? utils::protection::ro : utils::protection::no); utils::memory_protect(g_base_addr + start * 4096, page_size, protection); } + + range_lock->release(0); } start_value = new_val; @@ -904,7 +971,7 @@ namespace vm } // Protect range locks from actual memory protection changes - _lock_main_range_lock(range_allocation, addr, size); + auto range_lock = _lock_main_range_lock(range_allocation, addr, size); if (shm && shm->flags() != 0 && g_shmem[addr >> 16]) { @@ -965,6 +1032,7 @@ namespace vm } } + range_lock->release(0); return size; } @@ -1966,11 +2034,13 @@ namespace vm { auto* range_lock = alloc_range_lock(); // Released at the end of function - range_lock->store(begin | (u64{size} << 32)); + auto mem_lock = &*std::prev(std::end(vm::g_range_lock_set)); while (true) { - const u64 lock_val = g_range_lock.load(); + range_lock->store(begin | (u64{size} << 32)); + + const u64 lock_val = mem_lock->load(); const u64 is_share = g_shmem[begin >> 16].load(); u64 lock_addr = static_cast(lock_val); // -> u64 @@ -1993,7 +2063,7 @@ namespace vm { if (vm::check_addr(begin, is_write ? page_writable : page_readable, size)) [[likely]] { - const u64 new_lock_val = g_range_lock.load(); + const u64 new_lock_val = mem_lock->load(); if (!new_lock_val || new_lock_val == lock_val) [[likely]] { @@ -2026,8 +2096,6 @@ namespace vm range_lock->release(0); busy_wait(200); - - range_lock->store(begin | (u64{size} << 32)); } const bool result = try_access_internal(begin, ptr, size, is_write); @@ -2071,7 +2139,7 @@ namespace vm std::memset(g_reservations, 0, sizeof(g_reservations)); std::memset(g_shmem, 0, sizeof(g_shmem)); std::memset(g_range_lock_set, 0, sizeof(g_range_lock_set)); - g_range_lock_bits = 0; + std::memset(g_range_lock_bits, 0, sizeof(g_range_lock_bits)); #ifdef _WIN32 utils::memory_release(g_hook_addr, 0x800000000); @@ -2104,7 +2172,7 @@ namespace vm #endif std::memset(g_range_lock_set, 0, sizeof(g_range_lock_set)); - g_range_lock_bits = 0; + std::memset(g_range_lock_bits, 0, sizeof(g_range_lock_bits)); } void save(utils::serial& ar) @@ -2209,8 +2277,6 @@ namespace vm loc = std::make_shared(ar, shared); } } - - g_range_lock = 0; } u32 get_shm_addr(const std::shared_ptr& shared) diff --git a/rpcs3/Emu/Memory/vm_locking.h b/rpcs3/Emu/Memory/vm_locking.h index 9923464434..253af406db 100644 --- a/rpcs3/Emu/Memory/vm_locking.h +++ b/rpcs3/Emu/Memory/vm_locking.h @@ -27,7 +27,7 @@ namespace vm range_bits = 3, }; - extern atomic_t g_range_lock; + extern atomic_t g_range_lock_bits[2]; extern atomic_t g_shmem[]; @@ -61,7 +61,7 @@ namespace vm __asm__(""); // Tiny barrier #endif - if (!g_range_lock) + if (!g_range_lock_bits[1]) [[likely]] { return; } @@ -82,10 +82,12 @@ namespace vm struct writer_lock final { + atomic_t* range_lock; + writer_lock(const writer_lock&) = delete; writer_lock& operator=(const writer_lock&) = delete; - writer_lock(); - writer_lock(u32 addr, u32 size = 0, u64 flags = range_locked); - ~writer_lock(); + writer_lock() noexcept; + writer_lock(u32 addr, atomic_t* range_lock = nullptr, u32 size = 128, u64 flags = range_locked) noexcept; + ~writer_lock() noexcept; }; } // namespace vm diff --git a/rpcs3/Emu/RSX/rsx_cache.h b/rpcs3/Emu/RSX/rsx_cache.h index f2cde4ec50..843789d442 100644 --- a/rpcs3/Emu/RSX/rsx_cache.h +++ b/rpcs3/Emu/RSX/rsx_cache.h @@ -296,12 +296,12 @@ namespace rsx // Writeback to cache either if file does not exist or it is invalid (unexpected size) // Note: fs::write_file is not atomic, if the process is terminated in the middle an empty file is created - if (fs::stat_t s{}; !fs::stat(fp_name, s) || s.size != fp.ucode_length) + if (fs::stat_t s{}; !fs::get_stat(fp_name, s) || s.size != fp.ucode_length) { fs::write_file(fp_name, fs::rewrite, fp.get_data(), fp.ucode_length); } - if (fs::stat_t s{}; !fs::stat(vp_name, s) || s.size != vp.data.size() * sizeof(u32)) + if (fs::stat_t s{}; !fs::get_stat(vp_name, s) || s.size != vp.data.size() * sizeof(u32)) { fs::write_file(vp_name, fs::rewrite, vp.data); } diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 1ff8e22e1b..9eff4d8162 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -277,7 +277,7 @@ void Emulator::Init() const std::string cfg_path = fs::get_config_dir() + "/config.yml"; // Save new global config if it doesn't exist or is empty - if (fs::stat_t info{}; !fs::stat(cfg_path, info) || info.size == 0) + if (fs::stat_t info{}; !fs::get_stat(cfg_path, info) || info.size == 0) { Emulator::SaveSettings(g_cfg_defaults, {}); } @@ -2125,6 +2125,8 @@ void Emulator::Run(bool start_playtime) m_pause_start_time = 0; m_pause_amend_time = 0; + m_tty_file_init_pos = g_tty ? g_tty.pos() : usz{umax}; + rpcs3::utils::configure_logs(); m_state = system_state::starting; @@ -2897,6 +2899,8 @@ void Emulator::Kill(bool allow_autoexit, bool savestate) // Final termination from main thread (move the last ownership of join thread in order to destroy it) CallFromMainThread([join_thread = std::move(join_thread), allow_autoexit, this]() mutable { + const std::string cache_path = rpcs3::cache::get_ppu_cache(); + cpu_thread::cleanup(); initialize_timebased_time(0, true); @@ -2957,6 +2961,64 @@ void Emulator::Kill(bool allow_autoexit, bool savestate) m_state = system_state::stopped; GetCallbacks().on_stop(); + if (g_tty && sys_log.notice) + { + // Write merged TTY output after emulation has been safely stopped + + if (usz attempted_read_size = utils::sub_saturate(g_tty.pos(), m_tty_file_init_pos)) + { + if (fs::file tty_read_fd{fs::get_cache_dir() + "TTY.log"}) + { + // Enfore an arbitrary limit for now to avoid OOM in case the guest code has bombarded TTY + // 16MB, this should be enough + constexpr usz c_max_tty_spill_size = 0x10'0000; + + std::string tty_buffer(std::min(attempted_read_size, c_max_tty_spill_size), '\0'); + tty_buffer.resize(tty_read_fd.read_at(m_tty_file_init_pos, tty_buffer.data(), tty_buffer.size())); + tty_read_fd.close(); + + if (!tty_buffer.empty()) + { + // Mark start and end very clearly with RPCS3 put in it + sys_log.notice("\nAccumulated RPCS3 TTY:\n\n\n%s\n\n\nEnd RPCS3 TTY Section.\n", tty_buffer); + } + } + } + } + + if (g_cfg.core.spu_debug && sys_log.notice) + { + if (fs::file spu_log{cache_path + "/spu.log"}) + { + // 96MB limit, this may be a lot but this only has an effect when enabling the debug option + constexpr usz c_max_tty_spill_size = 0x60'0000; + + std::string log_buffer(std::min(spu_log.size(), c_max_tty_spill_size), '\0'); + log_buffer.resize(spu_log.read(log_buffer.data(), log_buffer.size())); + spu_log.close(); + + if (!log_buffer.empty()) + { + usz to_remove = 0; + usz part_ctr = 1; + + for (std::string_view not_logged = log_buffer; !not_logged.empty(); part_ctr++, not_logged.remove_prefix(to_remove)) + { + std::string_view to_log = not_logged; + to_log = to_log.substr(0, 0x8'0000); + to_log = to_log.substr(0, utils::add_saturate(to_log.rfind("\n========== SPU BLOCK"sv), 1)); + to_remove = to_log.size(); + + // Cannot log it all at once due to technical reasons, split it to 8MB at maximum of whole functions + // Assume the block prefix exists because it is created by RPCS3 (or log it in an ugly manner if it does not exist) + sys_log.notice("Logging spu.log part %u:\n\n%s\n", part_ctr, to_log); + } + + sys_log.notice("End spu.log"); + } + } + } + // Always Enable display sleep, not only if it was prevented. enable_display_sleep(); diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 4714a8e472..15753b3631 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -139,6 +139,8 @@ class Emulator final bool m_state_inspection_savestate = false; + usz m_tty_file_init_pos = umax; + std::vector>> m_pause_msgs_refs; std::vector> deferred_deserialization; diff --git a/rpcs3/Loader/TAR.cpp b/rpcs3/Loader/TAR.cpp index 4c90aee996..6eb48f973d 100644 --- a/rpcs3/Loader/TAR.cpp +++ b/rpcs3/Loader/TAR.cpp @@ -263,7 +263,7 @@ std::vector tar_object::save_directory(const std::string& src_dir, std::vect const std::string& target_path = full_path.empty() ? src_dir : full_path; fs::stat_t stat{}; - if (!fs::stat(target_path, stat)) + if (!fs::get_stat(target_path, stat)) { return std::move(init); } diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index fa7ea4108e..71e897a54b 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -2384,7 +2384,7 @@ void main_window::CreateConnects() fs::stat_t raw_stat{}; fs::stat_t archived_stat{}; - if ((!fs::stat(raw_file_path, raw_stat) || raw_stat.is_directory) || (!fs::stat(archived_path, archived_stat) || archived_stat.is_directory) || (raw_stat.size == 0 && archived_stat.size == 0)) + if ((!fs::get_stat(raw_file_path, raw_stat) || raw_stat.is_directory) || (!fs::get_stat(archived_path, archived_stat) || archived_stat.is_directory) || (raw_stat.size == 0 && archived_stat.size == 0)) { QMessageBox::warning(this, tr("Failed to locate log"), tr("Failed to locate log files.\nMake sure that RPCS3.log and RPCS3.log.gz are writable and can be created without permission issues.")); return;