#include "stdafx.h" #include "util/types.hpp" #include "util/logs.hpp" #include "util/v128.hpp" #include "util/simd.hpp" #include "Utilities/File.h" #include "Utilities/StrFmt.h" #include "system_config.h" #include "savestate_utils.hpp" #include "System.h" #include #include LOG_CHANNEL(sys_log, "SYS"); struct serial_ver_t { std::string ver_name; bool used = false; u16 current_version = 0; std::set compatible_versions; }; static std::array s_serial_versions; #define SERIALIZATION_VER(name, identifier, ...) \ \ const bool s_##name##_serialization_fill = []() { auto& e = ::s_serial_versions[identifier]; if (e.compatible_versions.empty()) { e.compatible_versions = {__VA_ARGS__}; e.ver_name = #name; } return true; }();\ \ extern void using_##name##_serialization()\ {\ ensure(Emu.IsStopped());\ ::s_serial_versions[identifier].used = true;\ }\ \ extern s32 get_##name##_serialization_version()\ {\ return ::s_serial_versions[identifier].current_version;\ } SERIALIZATION_VER(global_version, 0, 21) // For stuff not listed here SERIALIZATION_VER(ppu, 1, 1, 2/*PPU sleep order*/, 3/*PPU FNID and module*/) SERIALIZATION_VER(spu, 2, 1) SERIALIZATION_VER(lv2_sync, 3, 1) SERIALIZATION_VER(lv2_vm, 4, 1) SERIALIZATION_VER(lv2_net, 5, 1, 2/*TCP Feign conection loss*/) SERIALIZATION_VER(lv2_fs, 6, 1, 2/*NPDRM key saving*/) SERIALIZATION_VER(lv2_prx_overlay, 7, 1) SERIALIZATION_VER(lv2_memory, 8, 1) SERIALIZATION_VER(lv2_config, 9, 1) namespace rsx { SERIALIZATION_VER(rsx, 10, 1, 2/*Pending flip*/, 3/*avconf scan_mode*/) } namespace np { SERIALIZATION_VER(sceNp, 11, 1) } #ifdef _MSC_VER // Compiler bug, lambda function body does seem to inherit used namespace atleast for function declaration SERIALIZATION_VER(rsx, 10) SERIALIZATION_VER(sceNp, 11) #endif SERIALIZATION_VER(cellVdec, 12, 1) SERIALIZATION_VER(cellAudio, 13, 1) SERIALIZATION_VER(cellCamera, 14, 1, 2/*gem_camera_shared*/) SERIALIZATION_VER(cellGem, 15, 1, 2/*calibration_status_flags*/, 3/*video_conversion*/) SERIALIZATION_VER(sceNpTrophy, 16, 1) SERIALIZATION_VER(cellMusic, 17, 1) SERIALIZATION_VER(cellVoice, 18, 1) SERIALIZATION_VER(cellGcm, 19, 1) SERIALIZATION_VER(sysPrxForUser, 20, 1) SERIALIZATION_VER(cellSaveData, 21, 1) SERIALIZATION_VER(cellAudioOut, 22, 1) SERIALIZATION_VER(sys_io, 23, 2) // Misc versions for HLE/LLE not included so main version would not invalidated SERIALIZATION_VER(LLE, 24, 1) SERIALIZATION_VER(HLE, 25, 1) SERIALIZATION_VER(cellSysutil, 26, 1, 2/*AVC2 Muting,Volume*/) SERIALIZATION_VER(cellDmuxPamf, 27, 1) // For future modules SERIALIZATION_VER(cellReserved1, 27, 1) SERIALIZATION_VER(cellReserved2, 28, 1) SERIALIZATION_VER(cellReserved3, 29, 1) SERIALIZATION_VER(cellReserved4, 30, 1) SERIALIZATION_VER(cellReserved6, 31, 1) SERIALIZATION_VER(cellReserved7, 32, 1) SERIALIZATION_VER(cellReserved8, 33, 1) template <> void fmt_class_string>::format(std::string& out, u64 arg) { bool is_first = true; const auto& serials = get_object(arg); out += "{ "; for (auto& entry : serials) { if (entry.current_version) { if (!is_first) { out += ", "; } is_first = false; fmt::append(out, "%s=%d", entry.ver_name, entry.current_version); } } out += " }"; } std::vector get_savestate_versioning_data(fs::file&& file, std::string_view filepath) { if (!file) { return {}; } file.seek(0); utils::serial ar; ar.set_reading_state({}, true); if (filepath.ends_with(".zst")) { ar.m_file_handler = make_compressed_zstd_serialization_file_handler(std::move(file)); } else if (filepath.ends_with(".gz")) { ar.m_file_handler = make_compressed_serialization_file_handler(std::move(file)); } else { ar.m_file_handler = make_uncompressed_serialization_file_handler(std::move(file)); } if (u64 r = 0; ar.try_read(r) != 0 || r != "RPCS3SAV"_u64) { return {}; } ar.pos = 10; u64 offs = ar.try_read().second; const usz fsize = ar.get_size(offs); if (!offs || fsize <= offs) { return {}; } ar.seek_pos(offs); ar.breathe(true); std::vector ver_data = ar.pop>(); return ver_data; } std::shared_ptr make_savestate_reader(const std::string& path) { std::shared_ptr ar; fs::file save{path, fs::isfile + fs::read}; if (!save) { return ar; } if (path.ends_with(".SAVESTAT") && save.size() >= 8 && save.read() == "RPCS3SAV"_u64) { ar = std::make_shared(); ar->set_reading_state(); ar->m_file_handler = make_uncompressed_serialization_file_handler(std::move(save)); } else if (path.ends_with(".zst")) { ar = std::make_shared(); ar->set_reading_state(); ar->m_file_handler = make_compressed_zstd_serialization_file_handler(std::move(save)); if (ar->try_read().second != "RPCS3SAV"_u64) { ar.reset(); } else { ar->pos = 0; } } else if (path.ends_with(".gz")) { ar = std::make_shared(); ar->set_reading_state(); ar->m_file_handler = make_compressed_serialization_file_handler(std::move(save)); if (ar->try_read().second != "RPCS3SAV"_u64) { ar.reset(); } else { ar->pos = 0; } } return ar; } bool is_savestate_version_compatible(const std::vector& data, bool is_boot_check) { if (data.empty()) { return false; } bool ok = true; if (is_boot_check) { for (auto& entry : s_serial_versions) { // Version 0 means that the entire constructor using the version should be skipped entry.current_version = 0; } } auto& channel = (is_boot_check ? sys_log.error : sys_log.trace); for (const auto& entry : data) { if (entry.type >= s_serial_versions.size()) { channel("Savestate version identifier is unknown! (category=%u, version=%u)", entry.type, entry.version); ok = false; // Log all mismatches } else if (!s_serial_versions[entry.type].compatible_versions.count(entry.version)) { channel("Savestate version is not supported. (category=%u, version=%u)", entry.type, entry.version); ok = false; } else if (is_boot_check) { s_serial_versions[entry.type].current_version = entry.version; } } if (is_boot_check) { if (ok) { sys_log.success("Savestate versions: %s", s_serial_versions); } else { for (auto& entry : s_serial_versions) { entry.current_version = 0; } } } return ok; } std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 rel_id, u64 aggregate_file_size) { const std::string title = std::string{title_id.empty() ? boot_path.substr(boot_path.find_last_of(fs::delim) + 1) : title_id}; // Internal functionality ATM constexpr s64 abs_id = 0; if (aggregate_file_size == umax && rel_id == -1) { // Return directory return fs::get_config_dir() + "savestates/" + title + "/"; } if (rel_id >= 0) { std::string dir_path = fs::get_config_dir() + "savestates/" + title + "/"; fs::dir dir_view{dir_path}; std::map> save_files; for (auto&& dir_entry : dir_view) { if (dir_entry.is_directory || dir_entry.size <= 1024) { continue; } const std::string& entry = dir_entry.name; if (!title_id.empty() && !entry.starts_with(title + "_")) { // Check prefix only for certified applications // Because ELF file names can be long and unhelpful continue; } if (entry.ends_with(".SAVESTAT.zst") || entry.ends_with(".SAVESTAT.gz") || entry.ends_with(".SAVESTAT")) { if (usz dot_idx = entry.rfind(".SAVESTAT"); dot_idx && dot_idx != umax) { if (usz uc_pos = entry.rfind("_", dot_idx - 1); uc_pos != umax && uc_pos + 1 < dot_idx) { if (std::all_of(entry.begin() + uc_pos + 1, entry.begin() + dot_idx, [](char c) { return c >= '0' && c <= '9'; })) { save_files.emplace(entry, dir_entry.size); } } } } } std::string rel_path; std::string size_based_path; auto prepare_return_value = [](std::string& dir_path, std::string& filename) -> std::string&& { dir_path.append(filename); filename = std::move(dir_path); return std::move(filename); }; if (rel_id > 0) { if (static_cast(rel_id - 1) < save_files.size()) { rel_path = std::next(save_files.begin(), rel_id - 1)->first; } } if (aggregate_file_size != umax) { usz size_sum = 0; for (auto&& [path, size] : save_files) { if (size_sum >= aggregate_file_size) { size_based_path = path; break; } size_sum += size; } } if (!rel_path.empty() || !size_based_path.empty()) { if (rel_path > size_based_path) { return prepare_return_value(dir_path, rel_path); } else { return prepare_return_value(dir_path, size_based_path); } } if (rel_id > 0 || aggregate_file_size != umax) { return {}; } // Increment number in string in reverse // Return index of new character if appended a character, umax otherwise auto increment_string = [](std::string& out, usz pos) -> usz { while (pos != umax && out[pos] == '9') { out[pos] = '0'; pos--; } if (pos == umax || (out[pos] < '0' || out[pos] > '9')) { out.insert(out.begin() + (pos + 1), '1'); return pos + 1; } out[pos]++; return umax; }; if (!save_files.empty()) { std::string last_entry = save_files.begin()->first; // Increment entry ID if (usz inc_pos = increment_string(last_entry, last_entry.rfind(".SAVESTAT") - 1); inc_pos != umax) { // Increment entry suffix - ID has become wider in length (keeps the string in alphbetic ordering) ensure(inc_pos >= 2); ensure(last_entry[inc_pos - 2]++ != 'z'); } return prepare_return_value(dir_path, last_entry); } // Fallback - create new file } const std::string save_id = fmt::format("%d", abs_id); // Make sure that savestate file with higher IDs are placed at the bottom of "by name" file ordering in directory view by adding a single character prefix which tells the ID length // While not needing to keep a 59 chars long suffix at all times for this purpose const char prefix = ::at32("0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"sv, save_id.size()); std::string path = fs::get_config_dir() + "/savestates/" + title + "/" + title + '_' + prefix + '_' + save_id + ".SAVESTAT"; if (std::string path_compressed = path + ".zst"; fs::is_file(path_compressed)) { return path_compressed; } if (std::string path_compressed = path + ".gz"; fs::is_file(path_compressed)) { return path_compressed; } return path; } bool is_savestate_compatible(fs::file&& file, std::string_view filepath) { return is_savestate_version_compatible(get_savestate_versioning_data(std::move(file), filepath), false); } bool is_savestate_compatible(const std::string& filepath) { if (fs::file file{filepath, fs::isfile + fs::read}) { return is_savestate_compatible(std::move(file), filepath); } return false; } std::vector read_used_savestate_versions() { std::vector used_serial; used_serial.reserve(s_serial_versions.size()); for (serial_ver_t& ver : s_serial_versions) { if (std::exchange(ver.used, false)) { used_serial.push_back(version_entry{static_cast(&ver - s_serial_versions.data()), *ver.compatible_versions.rbegin()}); } ver.current_version = 0; } return used_serial; } bool boot_current_game_savestate(bool testing, u32 index) { if (index == 0 || index > g_cfg.savestate.max_files.get()) { return false; } if (!g_cfg.savestate.suspend_emu && !Emu.GetTitleID().empty() && (Emu.IsRunning() || Emu.GetStatus() == system_state::paused)) { const std::string savestate_path = get_savestate_file(Emu.GetTitleID(), Emu.GetBoot(), index); const bool result = fs::is_file(savestate_path); if (testing) { sys_log.trace("boot_current_game_savestate(true, %d) returned %s.", index, result); return result; } if (result) { sys_log.success("Booting the most recent savestate \'%s\' using the Reload shortcut.", savestate_path); // Make sure we keep the game window opened Emu.SetContinuousMode(true); Emu.GracefulShutdown(false, false, false, true); if (game_boot_result error = Emu.BootGame(savestate_path, "", true); error != game_boot_result::no_errors) { sys_log.error("Failed to boot savestate \'%s\' using the Reload shortcut. (error: %s)", savestate_path, error); } else { return true; } } sys_log.error("No compatible savestate file found in \'%s\''", get_savestate_file(Emu.GetTitleID(), Emu.GetBoot(), -1)); } return false; } void clean_savestates(std::string_view title_id, std::string_view boot_path, usz max_files, usz max_files_size) { ensure(max_files && max_files != umax); bool logged_limits = false; while (true) { const std::string to_remove = get_savestate_file(title_id, boot_path, max_files + 1, max_files_size == 0 ? u64{umax} : max_files_size); if (to_remove.empty()) { break; } if (!fs::remove_file(to_remove)) { sys_log.error("Failed to remove savestate file at '%s'! (error: %s)", to_remove, fs::g_tls_error); break; } else { if (!logged_limits) { sys_log.success("Maximum save state files set: %d.\nMaximum save state disk space set: %d (MiB).\nRemoved old savestate file at '%s'.\n" , max_files, max_files_size, to_remove); logged_limits = true; } else { sys_log.success("Removed old savestate file at '%s'.", to_remove); } } } } bool load_and_check_reserved(utils::serial& ar, usz size) { u8 bytes[4096]; ensure(size < std::size(bytes)); std::memset(&bytes[size & (0 - sizeof(v128))], 0, sizeof(v128)); const usz old_pos = ar.pos; ar(std::span(bytes, size)); // Check if all are 0 for (usz i = 0; i < size; i += sizeof(v128)) { if (v128::loadu(&bytes[i]) != v128{}) { return false; } } return old_pos + size == ar.pos; } namespace stx { extern u16 serial_breathe_and_tag(utils::serial& ar, std::string_view name, bool tag_bit) { thread_local std::string_view s_tls_object_name = "none"; thread_local u64 s_tls_call_count = 1, s_tls_current_pos = 0; if (s_tls_current_pos >= ar.pos) { // Reset, probably a new utils::serial object s_tls_call_count = 1; s_tls_object_name = "none"sv; } s_tls_current_pos = ar.pos; constexpr u16 data_mask = 0x7fff; if (ar.m_file_handler && ar.m_file_handler->is_null()) { return (tag_bit ? data_mask + 1 : 0); } u16 tag = static_cast((static_cast(ar.pos / 2) & data_mask) | (tag_bit ? data_mask + 1 : 0)); u16 saved = tag; ar(saved); sys_log.trace("serial_breathe_and_tag(%u): %s, object: '%s', next-object: '%s', expected/tag: 0x%x == 0x%x", s_tls_call_count, ar, s_tls_object_name, name, tag, saved); if ((saved ^ tag) & data_mask) { ensure(!ar.is_writing()); fmt::throw_exception("serial_breathe_and_tag(%u): %s\nobject: '%s', next-object: '%s', expected/tag: 0x%x != 0x%x,", s_tls_call_count, ar, s_tls_object_name, name, tag, saved); } s_tls_object_name = name; s_tls_call_count++; ar.breathe(); return saved; } } // MSVC bug workaround, see above similar case extern u16 serial_breathe_and_tag(utils::serial& ar, std::string_view name, bool tag_bit) { return ::stx::serial_breathe_and_tag(ar, name, tag_bit); } [[noreturn]] void hle_locks_t::lock() { // Unreachable fmt::throw_exception("Unreachable"); } bool hle_locks_t::try_lock() { while (true) { auto [old, success] = lock_val.fetch_op([](s64& value) { if (value >= 0) { value++; return true; } return false; }); if (success) { return true; } if (old == finalized) { break; } lock_val.wait(old); } return false; } void hle_locks_t::unlock() { lock_val--; } bool hle_locks_t::try_finalize(std::function test) { if (!test()) { return false; } if (!lock_val.compare_and_swap_test(0, waiting_for_evaluation)) { return false; } if (!test()) { // Failed ensure(lock_val.compare_and_swap_test(waiting_for_evaluation, 0)); return false; } ensure(lock_val.compare_and_swap_test(waiting_for_evaluation, finalized)); // Sanity check when debugging (the result is not expected to change after finalization) //ensure(test()); lock_val.notify_all(); return true; }