mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 02:32:36 +01:00
Atomic PARAM.SFO writes
This commit is contained in:
parent
0878db4e17
commit
932f31e37b
@ -18,6 +18,11 @@ using namespace std::literals::string_literals;
|
||||
#include <cwchar>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
u64 get_unique_tsc();
|
||||
}
|
||||
|
||||
static std::unique_ptr<wchar_t[]> to_wchar(const std::string& source)
|
||||
{
|
||||
// String size + null terminator
|
||||
@ -1915,6 +1920,55 @@ fs::file fs::make_gather(std::vector<fs::file> files)
|
||||
return result;
|
||||
}
|
||||
|
||||
fs::pending_file::pending_file(const std::string& path)
|
||||
{
|
||||
do
|
||||
{
|
||||
m_path = fmt::format(u8"%s/$%s.%s.tmp", get_parent_dir(path), std::string_view(path).substr(path.find_last_of(fs::delim) + 1), fmt::base57(utils::get_unique_tsc()));
|
||||
|
||||
if (file.open(m_path, fs::create + fs::write + fs::read + fs::excl))
|
||||
{
|
||||
m_dest = path;
|
||||
break;
|
||||
}
|
||||
|
||||
m_path.clear();
|
||||
}
|
||||
while (fs::g_tls_error == fs::error::exist); // Only retry if failed due to existing file
|
||||
}
|
||||
|
||||
fs::pending_file::~pending_file()
|
||||
{
|
||||
file.close();
|
||||
|
||||
if (!m_path.empty())
|
||||
{
|
||||
fs::remove_file(m_path);
|
||||
}
|
||||
}
|
||||
|
||||
bool fs::pending_file::commit(bool overwrite)
|
||||
{
|
||||
if (!file || m_path.empty())
|
||||
{
|
||||
fs::g_tls_error = fs::error::noent;
|
||||
return false;
|
||||
}
|
||||
|
||||
// The temporary file's contents must be on disk before rename
|
||||
file.sync();
|
||||
file.close();
|
||||
|
||||
if (fs::rename(m_path, m_dest, overwrite))
|
||||
{
|
||||
// Disable the destructor
|
||||
m_path.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<fs::seek_mode>::format(std::string& out, u64 arg)
|
||||
{
|
||||
|
@ -602,6 +602,22 @@ namespace fs
|
||||
// Get common cache directory
|
||||
const std::string& get_cache_dir();
|
||||
|
||||
// Unique pending file creation destined to be renamed to the destination file
|
||||
struct pending_file
|
||||
{
|
||||
fs::file file;
|
||||
|
||||
// This is meant to modify files atomically, overwriting is likely
|
||||
bool commit(bool overwrite = true);
|
||||
|
||||
pending_file(const std::string& path);
|
||||
~pending_file();
|
||||
|
||||
private:
|
||||
std::string m_path; // Pending file path
|
||||
std::string m_dest; // Destination file path
|
||||
};
|
||||
|
||||
// Get real path for comparisons (TODO: investigate std::filesystem::path::compare implementation)
|
||||
std::string escape_path(std::string_view path);
|
||||
|
||||
|
@ -666,7 +666,9 @@ error_code cellGameContentPermit(vm::ptr<char[CELL_GAME_PATH_MAX]> contentInfoPa
|
||||
if (!perm->temp.empty())
|
||||
{
|
||||
// Create PARAM.SFO
|
||||
psf::save_object(fs::file(perm->temp + "/PARAM.SFO", fs::rewrite), perm->sfo);
|
||||
fs::pending_file temp(perm->temp + "/PARAM.SFO");
|
||||
temp.file.write(psf::save_object(perm->sfo));
|
||||
ensure(temp.commit());
|
||||
|
||||
// Make temporary directory persistent (atomically)
|
||||
if (vfs::host::rename(perm->temp, vfs::get(dir), &g_mp_sys_dev_hdd0, false))
|
||||
@ -684,7 +686,9 @@ error_code cellGameContentPermit(vm::ptr<char[CELL_GAME_PATH_MAX]> contentInfoPa
|
||||
else if (perm->can_create)
|
||||
{
|
||||
// Update PARAM.SFO
|
||||
psf::save_object(fs::file(vfs::get(dir + "/PARAM.SFO"), fs::rewrite), perm->sfo);
|
||||
fs::pending_file temp(vfs::get(dir + "/PARAM.SFO"));
|
||||
temp.file.write(psf::save_object(perm->sfo));
|
||||
ensure(temp.commit());
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
@ -806,7 +810,9 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr<char>
|
||||
psf::assign(sfo, fmt::format("TITLE_%02d", i), psf::string(CELL_GAME_SYSP_TITLE_SIZE, setParam->titleLang[i]));
|
||||
}
|
||||
|
||||
psf::save_object(fs::file(vfs::get(dir + "/PARAM.SFO"), fs::rewrite), sfo);
|
||||
fs::pending_file temp(vfs::get(dir + "/PARAM.SFO"));
|
||||
temp.file.write(psf::save_object(sfo));
|
||||
ensure(temp.commit());
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
|
@ -1306,7 +1306,12 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
|
||||
|
||||
if (!entry.is_directory)
|
||||
{
|
||||
if (entry.name == "PARAM.SFO" || entry.name == "PARAM.PFD")
|
||||
if (entry.name == "."sv)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.name == "PARAM.SFO"sv || entry.name == "PARAM.PFD"sv)
|
||||
{
|
||||
continue; // system files are not included in the file list
|
||||
}
|
||||
@ -1899,7 +1904,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
|
||||
// Write all files in temporary directory
|
||||
auto& fsfo = all_files["PARAM.SFO"];
|
||||
fsfo = fs::make_stream<std::vector<uchar>>();
|
||||
psf::save_object(fsfo, psf);
|
||||
fsfo.write(psf::save_object(psf));
|
||||
|
||||
for (auto&& pair : all_files)
|
||||
{
|
||||
|
@ -768,6 +768,13 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u32> fd)
|
||||
// Preprocess entries
|
||||
data.back().name = vfs::unescape(data.back().name);
|
||||
|
||||
if (!data.back().is_directory && data.back().name == "."sv)
|
||||
{
|
||||
// Files hidden from emulation
|
||||
data.resize(data.size() - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add additional entries for split file candidates (while ends with .66600)
|
||||
while (data.back().name.ends_with(".66600"))
|
||||
{
|
||||
|
@ -1299,8 +1299,10 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
|
||||
games[m_title_id] = bdvd_dir;
|
||||
YAML::Emitter out;
|
||||
out << games;
|
||||
fs::file(fs::get_config_dir() + "/games.yml.tmp", fs::rewrite).write(out.c_str(), out.size());
|
||||
fs::rename(fs::get_config_dir() + "/games.yml.tmp", fs::get_config_dir() + "/games.yml", true);
|
||||
|
||||
fs::pending_file temp(fs::get_config_dir() + "/games.yml");
|
||||
temp.file.write(out.c_str(), out.size());
|
||||
temp.commit();
|
||||
}
|
||||
else if (m_cat == "1P" && from_hdd0_game)
|
||||
{
|
||||
|
@ -113,6 +113,8 @@ namespace psf
|
||||
return result;
|
||||
}
|
||||
|
||||
stream.seek(0);
|
||||
|
||||
// Get header
|
||||
header_t header;
|
||||
ensure(stream.read(header));
|
||||
@ -186,8 +188,10 @@ namespace psf
|
||||
return result;
|
||||
}
|
||||
|
||||
void save_object(const fs::file& stream, const psf::registry& psf)
|
||||
std::vector<u8> save_object(const psf::registry& psf, std::vector<u8>&& init)
|
||||
{
|
||||
fs::file stream = fs::make_stream<std::vector<u8>>(std::move(init));
|
||||
|
||||
std::vector<def_table_t> indices; indices.reserve(psf.size());
|
||||
|
||||
// Generate indices and calculate key table length
|
||||
@ -264,6 +268,8 @@ namespace psf
|
||||
fmt::throw_exception("Invalid entry format (key='%s', fmt=0x%x)", entry.first, fmt);
|
||||
}
|
||||
}
|
||||
|
||||
return std::move(static_cast<fs::container_stream<std::vector<u8>>*>(stream.release().get())->obj);
|
||||
}
|
||||
|
||||
std::string_view get_string(const registry& psf, const std::string& key, std::string_view def)
|
||||
|
@ -53,7 +53,7 @@ namespace psf
|
||||
registry load_object(const fs::file&);
|
||||
|
||||
// Convert PSF registry to SFO binary format
|
||||
void save_object(const fs::file&, const registry&);
|
||||
std::vector<u8> save_object(const registry&, std::vector<u8>&& init = std::vector<u8>{});
|
||||
|
||||
// Get string value or default value
|
||||
std::string_view get_string(const registry& psf, const std::string& key, std::string_view def = ""sv);
|
||||
|
@ -190,8 +190,9 @@ void emu_settings::SaveSettings()
|
||||
}
|
||||
|
||||
// Save config atomically
|
||||
fs::file(config_name + ".tmp", fs::rewrite).write(out.c_str(), out.size());
|
||||
fs::rename(config_name + ".tmp", config_name, true);
|
||||
fs::pending_file temp(config_name);
|
||||
temp.file.write(out.c_str(), out.size());
|
||||
temp.commit();
|
||||
|
||||
// Check if the running config/title is the same as the edited config/title.
|
||||
if (config_name == g_cfg.name || m_title_id == Emu.GetTitleID())
|
||||
|
Loading…
Reference in New Issue
Block a user