mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-12-06 07:12:28 +01:00
626 lines
15 KiB
C++
626 lines
15 KiB
C++
#include "stdafx.h"
|
||
#include "system_utils.hpp"
|
||
#include "system_config.h"
|
||
#include "vfs_config.h"
|
||
#include "Emu/Io/pad_config.h"
|
||
#include "Emu/System.h"
|
||
#include "Emu/VFS.h"
|
||
#include "util/sysinfo.hpp"
|
||
#include "Utilities/File.h"
|
||
#include "Utilities/Thread.h"
|
||
#include "Crypto/unpkg.h"
|
||
#include "Crypto/unself.h"
|
||
#include "Crypto/unedat.h"
|
||
|
||
#include <charconv>
|
||
#include <thread>
|
||
|
||
LOG_CHANNEL(sys_log, "SYS");
|
||
|
||
namespace rpcs3::utils
|
||
{
|
||
u32 get_max_threads()
|
||
{
|
||
const u32 max_threads = static_cast<u32>(g_cfg.core.llvm_threads);
|
||
const u32 hw_threads = ::utils::get_thread_count();
|
||
const u32 thread_count = max_threads > 0 ? std::min(max_threads, hw_threads) : hw_threads;
|
||
return thread_count;
|
||
}
|
||
|
||
void configure_logs(bool force_enable)
|
||
{
|
||
static bool was_silenced = false;
|
||
|
||
const bool silenced = g_cfg.misc.silence_all_logs.get() && !force_enable;
|
||
|
||
if (silenced)
|
||
{
|
||
if (!was_silenced)
|
||
{
|
||
sys_log.always()("Disabling logging! Do not create issues on GitHub or on the forums while logging is disabled.");
|
||
}
|
||
|
||
logs::silence();
|
||
}
|
||
else
|
||
{
|
||
logs::reset();
|
||
logs::set_channel_levels(g_cfg.log.get_map());
|
||
|
||
if (was_silenced)
|
||
{
|
||
sys_log.success("Logging enabled");
|
||
}
|
||
}
|
||
|
||
was_silenced = silenced;
|
||
}
|
||
|
||
u32 check_user(const std::string& user)
|
||
{
|
||
u32 id = 0;
|
||
|
||
if (user.size() == 8)
|
||
{
|
||
std::from_chars(&user.front(), &user.back() + 1, id);
|
||
}
|
||
|
||
return id;
|
||
}
|
||
|
||
bool install_pkg(const std::string& path)
|
||
{
|
||
sys_log.success("Installing package: %s", path);
|
||
|
||
int int_progress = 0;
|
||
|
||
std::deque<package_reader> reader;
|
||
reader.emplace_back(path);
|
||
|
||
// Run PKG unpacking asynchronously
|
||
named_thread worker("PKG Installer", [&]
|
||
{
|
||
std::deque<std::string> bootables;
|
||
const package_install_result result = package_reader::extract_data(reader, bootables);
|
||
return result.error == package_install_result::error_type::no_error;
|
||
});
|
||
|
||
// Wait for the completion
|
||
while (std::this_thread::sleep_for(5ms), worker <= thread_state::aborting)
|
||
{
|
||
// TODO: update unified progress dialog
|
||
const int pval = reader[0].get_progress(100);
|
||
|
||
if (pval > int_progress)
|
||
{
|
||
int_progress = pval;
|
||
sys_log.success("... %u%%", int_progress);
|
||
}
|
||
}
|
||
|
||
return worker();
|
||
}
|
||
|
||
std::vector<std::pair<std::string, u64>> get_vfs_disk_usage()
|
||
{
|
||
std::vector<std::pair<std::string, u64>> disk_usage;
|
||
|
||
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_hdd0_dir(), 1); data_size != umax)
|
||
{
|
||
disk_usage.push_back({"dev_hdd0", data_size});
|
||
}
|
||
|
||
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_hdd1_dir(), 1); data_size != umax)
|
||
{
|
||
disk_usage.push_back({"dev_hdd1", data_size});
|
||
}
|
||
|
||
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_flash_dir(), 1); data_size != umax)
|
||
{
|
||
disk_usage.push_back({"dev_flash", data_size});
|
||
}
|
||
|
||
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_flash2_dir(), 1); data_size != umax)
|
||
{
|
||
disk_usage.push_back({"dev_flash2", data_size});
|
||
}
|
||
|
||
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_flash3_dir(), 1); data_size != umax)
|
||
{
|
||
disk_usage.push_back({"dev_flash3", data_size});
|
||
}
|
||
|
||
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_bdvd_dir(), 1); data_size != umax)
|
||
{
|
||
disk_usage.push_back({"dev_bdvd", data_size});
|
||
}
|
||
|
||
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_games_dir(), 1); data_size != umax)
|
||
{
|
||
disk_usage.push_back({"games", data_size});
|
||
}
|
||
|
||
return disk_usage;
|
||
}
|
||
|
||
std::string get_emu_dir()
|
||
{
|
||
const std::string& emu_dir_ = g_cfg_vfs.emulator_dir;
|
||
return emu_dir_.empty() ? fs::get_config_dir() : emu_dir_;
|
||
}
|
||
|
||
std::string get_hdd0_dir()
|
||
{
|
||
return g_cfg_vfs.get(g_cfg_vfs.dev_hdd0, get_emu_dir());
|
||
}
|
||
|
||
std::string get_hdd1_dir()
|
||
{
|
||
return g_cfg_vfs.get(g_cfg_vfs.dev_hdd1, get_emu_dir());
|
||
}
|
||
|
||
std::string get_flash_dir()
|
||
{
|
||
return g_cfg_vfs.get(g_cfg_vfs.dev_flash, get_emu_dir());
|
||
}
|
||
|
||
std::string get_flash2_dir()
|
||
{
|
||
return g_cfg_vfs.get(g_cfg_vfs.dev_flash2, get_emu_dir());
|
||
}
|
||
|
||
std::string get_flash3_dir()
|
||
{
|
||
return g_cfg_vfs.get(g_cfg_vfs.dev_flash3, get_emu_dir());
|
||
}
|
||
|
||
std::string get_bdvd_dir()
|
||
{
|
||
return g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, get_emu_dir());
|
||
}
|
||
|
||
std::string get_games_dir()
|
||
{
|
||
return g_cfg_vfs.get(g_cfg_vfs.games_dir, get_emu_dir());
|
||
}
|
||
|
||
std::string get_hdd0_game_dir()
|
||
{
|
||
return get_hdd0_dir() + "game/";
|
||
}
|
||
|
||
std::string get_hdd0_locks_dir()
|
||
{
|
||
return get_hdd0_game_dir() + "$locks/";
|
||
}
|
||
|
||
std::string get_hdd1_cache_dir()
|
||
{
|
||
return get_hdd1_dir() + "caches/";
|
||
}
|
||
|
||
std::string get_games_shortcuts_dir()
|
||
{
|
||
return get_games_dir() + "shortcuts/";
|
||
}
|
||
|
||
u64 get_cache_disk_usage()
|
||
{
|
||
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_cache_dir(), 1); data_size != umax)
|
||
{
|
||
return data_size;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
std::string get_cache_dir()
|
||
{
|
||
return fs::get_cache_dir() + "cache/";
|
||
}
|
||
|
||
std::string get_cache_dir(std::string_view module_path)
|
||
{
|
||
std::string cache_dir = get_cache_dir();
|
||
|
||
const std::string dev_flash = g_cfg_vfs.get_dev_flash();
|
||
const bool in_dev_flash = Emu.IsPathInsideDir(module_path, dev_flash);
|
||
|
||
if (in_dev_flash && !Emu.IsPathInsideDir(module_path, dev_flash + "sys/external/"))
|
||
{
|
||
// Add prefix for vsh
|
||
cache_dir += "vsh/";
|
||
}
|
||
else if (!in_dev_flash && !Emu.GetTitleID().empty() && Emu.GetCat() != "1P")
|
||
{
|
||
// Add prefix for anything except dev_flash files, standalone elfs or PS1 classics
|
||
cache_dir += Emu.GetTitleID();
|
||
cache_dir += '/';
|
||
}
|
||
|
||
return cache_dir;
|
||
}
|
||
|
||
std::string get_data_dir()
|
||
{
|
||
return fs::get_config_dir() + "data/";
|
||
}
|
||
|
||
std::string get_icons_dir()
|
||
{
|
||
return fs::get_config_dir() + "Icons/game_icons/";
|
||
}
|
||
|
||
std::string get_savestates_dir()
|
||
{
|
||
return fs::get_config_dir() + "savestates/";
|
||
}
|
||
|
||
std::string get_captures_dir()
|
||
{
|
||
return fs::get_config_dir() + "captures/";
|
||
}
|
||
|
||
std::string get_recordings_dir()
|
||
{
|
||
return fs::get_config_dir() + "recordings/";
|
||
}
|
||
|
||
std::string get_screenshots_dir()
|
||
{
|
||
return fs::get_config_dir() + "screenshots/";
|
||
}
|
||
|
||
std::string get_cache_dir_by_serial(const std::string& serial)
|
||
{
|
||
return get_cache_dir() + (serial == "vsh.self" ? "vsh" : serial);
|
||
}
|
||
|
||
std::string get_data_dir(const std::string& serial)
|
||
{
|
||
return get_data_dir() + serial;
|
||
}
|
||
|
||
std::string get_icons_dir(const std::string& serial)
|
||
{
|
||
return get_icons_dir() + serial;
|
||
}
|
||
|
||
std::string get_savestates_dir(const std::string& serial)
|
||
{
|
||
return get_savestates_dir() + serial;
|
||
}
|
||
|
||
std::string get_recordings_dir(const std::string& serial)
|
||
{
|
||
return get_recordings_dir() + serial;
|
||
}
|
||
|
||
std::string get_screenshots_dir(const std::string& serial)
|
||
{
|
||
return get_screenshots_dir() + serial;
|
||
}
|
||
|
||
std::string get_existing_dir(const std::string& dir)
|
||
{
|
||
return fs::is_dir(dir) ? dir : "";
|
||
}
|
||
|
||
std::set<std::string> get_dir_list(const std::string& base_dir, const std::string& serial)
|
||
{
|
||
std::set<std::string> dir_list;
|
||
|
||
for (const auto& entry : fs::dir(base_dir))
|
||
{
|
||
// Check for sub folder starting with serial (e.g. BCES01118_BCES01118)
|
||
if (entry.is_directory && entry.name.starts_with(serial))
|
||
{
|
||
dir_list.insert(base_dir + entry.name);
|
||
}
|
||
}
|
||
|
||
return dir_list;
|
||
}
|
||
|
||
std::set<std::string> get_file_list(const std::string& base_dir, const std::string& serial)
|
||
{
|
||
std::set<std::string> file_list;
|
||
|
||
for (const auto& entry : fs::dir(base_dir))
|
||
{
|
||
// Check for files starting with serial (e.g. BCES01118_BCES01118)
|
||
if (!entry.is_directory && entry.name.starts_with(serial))
|
||
{
|
||
file_list.insert(base_dir + entry.name);
|
||
}
|
||
}
|
||
|
||
return file_list;
|
||
}
|
||
|
||
std::string get_rap_file_path(const std::string_view& rap)
|
||
{
|
||
const std::string home_dir = get_hdd0_dir() + "home";
|
||
|
||
std::string rap_path;
|
||
|
||
for (auto&& entry : fs::dir(home_dir))
|
||
{
|
||
if (entry.is_directory && check_user(entry.name))
|
||
{
|
||
rap_path = fmt::format("%s/%s/exdata/%s.rap", home_dir, entry.name, rap);
|
||
if (fs::is_file(rap_path))
|
||
{
|
||
return rap_path;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Return a sample path tested for logging purposes
|
||
return rap_path;
|
||
}
|
||
|
||
std::string get_c00_unlock_edat_path(const std::string_view& content_id)
|
||
{
|
||
const std::string home_dir = get_hdd0_dir() + "home";
|
||
|
||
std::string edat_path;
|
||
|
||
for (auto&& entry : fs::dir(home_dir))
|
||
{
|
||
if (entry.is_directory && check_user(entry.name))
|
||
{
|
||
edat_path = fmt::format("%s/%s/exdata/%s.edat", home_dir, entry.name, content_id);
|
||
if (fs::is_file(edat_path))
|
||
{
|
||
return edat_path;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Return a sample path tested for logging purposes
|
||
return edat_path;
|
||
}
|
||
|
||
bool verify_c00_unlock_edat(const std::string_view& content_id, bool fast)
|
||
{
|
||
const std::string edat_path = rpcs3::utils::get_c00_unlock_edat_path(content_id);
|
||
|
||
// Check if user has unlock EDAT installed
|
||
fs::file enc_file(edat_path);
|
||
|
||
if (!enc_file)
|
||
{
|
||
sys_log.notice("verify_c00_unlock_edat(): '%s' not found", edat_path);
|
||
return false;
|
||
}
|
||
|
||
// Use simple check for GUI
|
||
if (fast)
|
||
return true;
|
||
|
||
u128 k_licensee = get_default_self_klic();
|
||
NPD_HEADER npd;
|
||
|
||
if (!VerifyEDATHeaderWithKLicense(enc_file, edat_path, reinterpret_cast<u8*>(&k_licensee), &npd))
|
||
{
|
||
sys_log.error("verify_c00_unlock_edat(): Failed to verify npd file '%s'", edat_path);
|
||
return false;
|
||
}
|
||
|
||
std::string edat_content_id = npd.content_id;
|
||
|
||
if (edat_content_id != content_id)
|
||
{
|
||
sys_log.error("verify_c00_unlock_edat(): Content ID mismatch in npd header of '%s'", edat_path);
|
||
return false;
|
||
}
|
||
|
||
// Decrypt EDAT and verify its contents
|
||
fs::file dec_file = DecryptEDAT(enc_file, edat_path, 8, reinterpret_cast<u8*>(&k_licensee));
|
||
if (!dec_file)
|
||
{
|
||
sys_log.error("verify_c00_unlock_edat(): Failed to decrypt '%s'", edat_path);
|
||
return false;
|
||
}
|
||
|
||
u32 magic{};
|
||
dec_file.read<u32>(magic);
|
||
if (magic != "GOMA"_u32)
|
||
{
|
||
sys_log.error("verify_c00_unlock_edat(): Bad header magic in unlock EDAT '%s'", edat_path);
|
||
return false;
|
||
}
|
||
|
||
// Read null-terminated string
|
||
dec_file.seek(0x10);
|
||
dec_file.read(edat_content_id, 0x30);
|
||
edat_content_id.resize(std::min<usz>(0x30, edat_content_id.find_first_of('\0')));
|
||
if (edat_content_id != content_id)
|
||
{
|
||
sys_log.error("verify_c00_unlock_edat(): Content ID mismatch in unlock EDAT '%s'", edat_path);
|
||
return false;
|
||
}
|
||
|
||
// Game has been purchased and EDAT is verified
|
||
return true;
|
||
}
|
||
|
||
std::string get_sfo_dir_from_game_path(const std::string& game_path, const std::string& title_id)
|
||
{
|
||
if (fs::is_file(game_path + "/PS3_DISC.SFB"))
|
||
{
|
||
// This is a disc game.
|
||
if (!title_id.empty())
|
||
{
|
||
for (auto&& entry : fs::dir{game_path})
|
||
{
|
||
if (entry.name == "." || entry.name == "..")
|
||
{
|
||
continue;
|
||
}
|
||
|
||
const std::string sfo_path = game_path + "/" + entry.name + "/PARAM.SFO";
|
||
|
||
if (entry.is_directory && fs::is_file(sfo_path))
|
||
{
|
||
const auto psf = psf::load_object(sfo_path);
|
||
const auto serial = psf::get_string(psf, "TITLE_ID");
|
||
if (serial == title_id)
|
||
{
|
||
return game_path + "/" + entry.name;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return game_path + "/PS3_GAME";
|
||
}
|
||
|
||
const auto psf = psf::load_object(game_path + "/PARAM.SFO");
|
||
|
||
const auto category = psf::get_string(psf, "CATEGORY");
|
||
const auto content_id = psf::get_string(psf, "CONTENT_ID");
|
||
|
||
if (category == "HG" && !content_id.empty())
|
||
{
|
||
// This is a trial game. Check if the user has EDAT file to unlock it.
|
||
const auto c00_title_id = psf::get_string(psf, "TITLE_ID");
|
||
|
||
if (fs::is_file(game_path + "/C00/PARAM.SFO") && verify_c00_unlock_edat(content_id, true))
|
||
{
|
||
// Load full game data.
|
||
sys_log.notice("Found EDAT file %s.edat for trial game %s", content_id, c00_title_id);
|
||
return game_path + "/C00";
|
||
}
|
||
}
|
||
|
||
return game_path;
|
||
}
|
||
|
||
std::string get_custom_config_dir()
|
||
{
|
||
return fs::get_config_dir(true) + "custom_configs/";
|
||
}
|
||
|
||
std::string get_custom_config_path(const std::string& identifier)
|
||
{
|
||
if (identifier.empty())
|
||
{
|
||
return {};
|
||
}
|
||
|
||
return get_custom_config_dir() + "config_" + identifier + ".yml";
|
||
}
|
||
|
||
std::string get_input_config_root()
|
||
{
|
||
return fs::get_config_dir(true) + "input_configs/";
|
||
}
|
||
|
||
std::string get_input_config_dir(const std::string& title_id)
|
||
{
|
||
return get_input_config_root() + (title_id.empty() ? "global" : title_id) + "/";
|
||
}
|
||
|
||
std::string get_custom_input_config_path(const std::string& title_id)
|
||
{
|
||
if (title_id.empty()) return "";
|
||
return get_input_config_dir(title_id) + g_cfg_input_configs.default_config + ".yml";
|
||
}
|
||
|
||
std::string get_game_content_path(game_content_type type)
|
||
{
|
||
const std::string locale_suffix = fmt::format("_%02d", static_cast<s32>(g_cfg.sys.language.get()));
|
||
const std::string disc_dir = vfs::get("/dev_bdvd/PS3_GAME");
|
||
std::string hdd0_dir = Emu.GetSfoDir(false);
|
||
|
||
if (hdd0_dir == disc_dir)
|
||
{
|
||
hdd0_dir.clear(); // No hdd0 dir
|
||
}
|
||
|
||
const bool check_disc = !disc_dir.empty();
|
||
const bool check_hdd0 = !hdd0_dir.empty() && !check_disc;
|
||
|
||
const auto find_content = [&](const std::string& name, const std::string& extension) -> std::string
|
||
{
|
||
// Check localized content first
|
||
for (bool localized : { true, false })
|
||
{
|
||
const std::string filename = fmt::format("/%s%s.%s", name, localized ? locale_suffix : std::string(), extension);
|
||
|
||
// Check content on hdd0 first
|
||
if (check_hdd0)
|
||
{
|
||
if (std::string path = hdd0_dir + filename; fs::is_file(path))
|
||
{
|
||
return path;
|
||
}
|
||
}
|
||
|
||
// Check content on disc
|
||
if (check_disc)
|
||
{
|
||
if (std::string path = disc_dir + filename; fs::is_file(path))
|
||
{
|
||
return path;
|
||
}
|
||
}
|
||
}
|
||
|
||
return {};
|
||
};
|
||
|
||
switch (type)
|
||
{
|
||
case game_content_type::content_icon:
|
||
{
|
||
return find_content("ICON0", "PNG");
|
||
}
|
||
case game_content_type::content_video:
|
||
{
|
||
return find_content("ICON1", "PAM");
|
||
}
|
||
case game_content_type::content_sound:
|
||
{
|
||
return find_content("SND0", "AT3");
|
||
}
|
||
case game_content_type::overlay_picture:
|
||
{
|
||
const bool high_res = g_cfg.video.aspect_ratio == video_aspect::_16_9;
|
||
return find_content(high_res ? "PIC0" : "PIC2", "PNG");
|
||
}
|
||
case game_content_type::background_picture:
|
||
case game_content_type::background_picture_2:
|
||
{
|
||
// Try to find a custom background first
|
||
if (std::string path = fs::get_config_dir() + "/Icons/game_icons/" + Emu.GetTitleID() + "/PIC1.PNG"; fs::is_file(path))
|
||
{
|
||
return path;
|
||
}
|
||
|
||
// Look for proper background
|
||
return find_content(type == game_content_type::background_picture ? "PIC1" : "PIC3", "PNG");
|
||
}
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
bool version_is_bigger(std::string_view v0, std::string_view v1, std::string_view serial, bool is_fw)
|
||
{
|
||
std::add_pointer_t<char> ev0, ev1;
|
||
const double ver0 = std::strtod(v0.data(), &ev0);
|
||
const double ver1 = std::strtod(v1.data(), &ev1);
|
||
|
||
if (v0.data() + v0.size() == ev0 && v1.data() + v1.size() == ev1)
|
||
{
|
||
return ver0 > ver1;
|
||
}
|
||
|
||
sys_log.error("Failed to compare the %s numbers for title ID %s: '%s'-'%s'", is_fw ? "firmware version" : "version", serial, v0, v1);
|
||
return false;
|
||
}
|
||
}
|