#include "stdafx.h" #include "Emu/VFS.h" #include "TRP.h" #include "Crypto/sha1.h" #include "util/StrUtil.h" LOG_CHANNEL(trp_log, "Trophy"); TRPLoader::TRPLoader(const fs::file& f) : trp_f(f) { } bool TRPLoader::Install(std::string_view dest, bool /*show*/) { if (!trp_f) { fs::g_tls_error = fs::error::noent; return false; } fs::g_tls_error = {}; const std::string local_path = vfs::get(dest); const std::string temp = fmt::format(u8"%s.$temp$%u", local_path, utils::get_unique_tsc()); if (!fs::create_dir(temp)) { trp_log.error("Failed to create temp dir: '%s' (error=%s)", temp, fs::g_tls_error); return false; } // Save TROPUSR.DAT if (!fs::copy_file(local_path + "/TROPUSR.DAT", temp + "/TROPUSR.DAT", false)) { trp_log.error("Failed to copy TROPUSR.DAT from '%s' to '%s' (error=%s)", local_path, temp, fs::g_tls_error); } std::vector buffer(65536); bool success = true; for (const TRPEntry& entry : m_entries) { trp_f.seek(entry.offset); if (!trp_f.read(buffer, entry.size)) { trp_log.error("Failed to read TRPEntry at: offset=0x%x, size=0x%x", entry.offset, entry.size); continue; // ??? } // Create the file in the temporary directory const std::string filename = temp + '/' + vfs::escape(entry.name); success = fs::write_file(filename, fs::create + fs::excl, buffer); if (!success) { trp_log.error("Failed to write file '%s' (error=%s)", filename, fs::g_tls_error); break; } } if (success) { success = fs::remove_all(local_path, true, true); if (success) { // Atomically create trophy data (overwrite existing data) success = fs::rename(temp, local_path, false); if (!success) { trp_log.error("Failed to move directory '%s' to '%s' (error=%s)", temp, local_path, fs::g_tls_error); } } else { trp_log.error("Failed to remove directory '%s' (error=%s)", local_path, fs::g_tls_error); } } if (!success) { // Remove temporary directory manually on failure (removed automatically on success) auto old_error = fs::g_tls_error; fs::remove_all(temp); fs::g_tls_error = old_error; } return success; } bool TRPLoader::LoadHeader(bool show) { if (!trp_f) { return false; } trp_f.seek(0); if (!trp_f.read(m_header)) { return false; } if (m_header.trp_magic != 0xDCA24D00) { return false; } if (show) { trp_log.notice("TRP version: 0x%x", m_header.trp_version); } if (m_header.trp_version >= 2) { unsigned char hash[20]; std::vector file_contents; trp_f.seek(0); if (!trp_f.read(file_contents, m_header.trp_file_size)) { trp_log.notice("Failed verifying checksum"); } else { memset(&(reinterpret_cast(file_contents.data()))->sha1, 0, 20); sha1(reinterpret_cast(file_contents.data()), m_header.trp_file_size, hash); if (memcmp(hash, m_header.sha1, 20) != 0) { trp_log.error("Invalid checksum of TROPHY.TRP file"); return false; } } trp_f.seek(sizeof(m_header)); } m_entries.clear(); if (!trp_f.read(m_entries, m_header.trp_files_count)) { return false; } if (show) { for (const auto& entry : m_entries) { trp_log.notice("TRP entry #%u: %s", &entry - m_entries.data(), entry.name); } } return true; } u64 TRPLoader::GetRequiredSpace() const { const u64 file_size = m_header.trp_file_size; const u64 file_element_size = u64{1} * m_header.trp_files_count * m_header.trp_element_size; return file_size - sizeof(m_header) - file_element_size; } bool TRPLoader::ContainsEntry(std::string_view filename) { if (filename.size() >= sizeof(TRPEntry::name)) { return false; } for (const TRPEntry& entry : m_entries) { if (entry.name == filename) { return true; } } return false; } void TRPLoader::RemoveEntry(std::string_view filename) { if (filename.size() >= sizeof(TRPEntry::name)) { return; } std::vector::iterator i = m_entries.begin(); while (i != m_entries.end()) { if (i->name == filename) { i = m_entries.erase(i); } else { i++; } } } void TRPLoader::RenameEntry(std::string_view oldname, std::string_view newname) { if (oldname.size() >= sizeof(TRPEntry::name) || newname.size() >= sizeof(TRPEntry::name)) { return; } for (TRPEntry& entry : m_entries) { if (entry.name == oldname) { strcpy_trunc(entry.name, newname); } } }