diff --git a/rpcs3/Crypto/utils.cpp b/rpcs3/Crypto/utils.cpp index 461c459b96..19e0b3d2ab 100644 --- a/rpcs3/Crypto/utils.cpp +++ b/rpcs3/Crypto/utils.cpp @@ -5,6 +5,7 @@ #include "utils.h" #include "aes.h" #include "sha1.h" +#include "sha256.h" #include "key_vault.h" #include #include @@ -138,6 +139,27 @@ char* extract_file_name(const char* file_path, char real_file_name[CRYPTO_MAX_PA return real_file_name; } +std::string sha256_get_hash(const char* data, usz size, bool lower_case) +{ + u8 res_hash[32]; + mbedtls_sha256_context ctx; + mbedtls_sha256_init(&ctx); + mbedtls_sha256_starts_ret(&ctx, 0); + mbedtls_sha256_update_ret(&ctx, reinterpret_cast(data), size); + mbedtls_sha256_finish_ret(&ctx, res_hash); + + std::string res_hash_string("0000000000000000000000000000000000000000000000000000000000000000"); + + for (usz index = 0; index < 32; index++) + { + const auto pal = lower_case ? "0123456789abcdef" : "0123456789ABCDEF"; + res_hash_string[index * 2] = pal[res_hash[index] >> 4]; + res_hash_string[(index * 2) + 1] = pal[res_hash[index] & 15]; + } + + return res_hash_string; +} + void mbedtls_zeroize(void *v, size_t n) { static void *(*const volatile unop_memset)(void *, int, size_t) = &memset; diff --git a/rpcs3/Crypto/utils.h b/rpcs3/Crypto/utils.h index 610e095cab..156ba2437b 100644 --- a/rpcs3/Crypto/utils.h +++ b/rpcs3/Crypto/utils.h @@ -40,6 +40,8 @@ inline u64 swap64(u64 i) char* extract_file_name(const char* file_path, char real_file_name[CRYPTO_MAX_PATH]); +std::string sha256_get_hash(const char* data, usz size, bool lower_case); + // Hex string conversion auxiliary functions. u64 hex_to_u64(const char* hex_str); void hex_to_bytes(unsigned char *data, const char *hex_str, unsigned int str_length); diff --git a/rpcs3/Emu/Cell/Modules/cellGame.cpp b/rpcs3/Emu/Cell/Modules/cellGame.cpp index 0fb63e6132..1fbe7d4210 100644 --- a/rpcs3/Emu/Cell/Modules/cellGame.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGame.cpp @@ -17,6 +17,7 @@ #include "Utilities/StrUtil.h" #include "util/init_mutex.hpp" #include "util/asm.hpp" +#include "Crypto/utils.h" #include @@ -1394,18 +1395,144 @@ error_code cellGameThemeInstall(vm::cptr usrdirPath, vm::cptr fileNa return CELL_GAME_ERROR_PARAM; } + const std::string src_path = vfs::get(fmt::format("%s/%s", usrdirPath, fileName)); + + // Use hash to get a hopefully unique filename + std::string hash; + + if (fs::file theme = fs::file(src_path)) + { + u32 magic{}; + + if (src_path.ends_with(".p3t") || !theme.read(magic) || magic != "P3TF"_u32) + { + return CELL_GAME_ERROR_INVALID_THEME_FILE; + } + + hash = sha256_get_hash(theme.to_string().c_str(), theme.size(), true); + } + else + { + return CELL_GAME_ERROR_NOTFOUND; + } + + const std::string dst_path = vfs::get(fmt::format("/dev_hdd0/theme/%s_%s.p3t", Emu.GetTitleID(), hash)); // TODO: this is renamed with some other scheme + + if (fs::is_file(dst_path)) + { + cellGame.notice("cellGameThemeInstall: theme already installed: '%s'", dst_path); + } + else + { + cellGame.notice("cellGameThemeInstall: copying theme from '%s' to '%s'", src_path, dst_path); + + if (!fs::copy_file(src_path, dst_path, false)) // TODO: new file is write protected + { + cellGame.error("cellGameThemeInstall: failed to copy theme from '%s' to '%s' (error=%s)", src_path, dst_path, fs::g_tls_error); + return CELL_GAME_ERROR_ACCESS_ERROR; + } + } + + if (false && !fs::remove_file(src_path)) // TODO: disabled for now + { + cellGame.error("cellGameThemeInstall: failed to remove source theme from '%s' (error=%s)", src_path, fs::g_tls_error); + } + + if (option == CELL_GAME_THEME_OPTION_APPLY) + { + // TODO: apply new theme + } + return CELL_OK; } -error_code cellGameThemeInstallFromBuffer(u32 fileSize, u32 bufSize, vm::ptr buf, vm::ptr func, u32 option) +error_code cellGameThemeInstallFromBuffer(ppu_thread& ppu, u32 fileSize, u32 bufSize, vm::ptr buf, vm::ptr func, u32 option) { cellGame.todo("cellGameThemeInstallFromBuffer(fileSize=%d, bufSize=%d, buf=*0x%x, func=*0x%x, option=0x%x)", fileSize, bufSize, buf, func, option); - if (!buf || !fileSize || (fileSize > bufSize && !func) || bufSize <= 4095 || option > CELL_GAME_THEME_OPTION_APPLY) + if (!buf || !fileSize || (fileSize > bufSize && !func) || bufSize < CELL_GAME_THEMEINSTALL_BUFSIZE_MIN || option > CELL_GAME_THEME_OPTION_APPLY) { return CELL_GAME_ERROR_PARAM; } + const std::string hash = sha256_get_hash(reinterpret_cast(buf.get_ptr()), fileSize, true); + const std::string dst_path = vfs::get(fmt::format("/dev_hdd0/theme/%s_%s.p3t", Emu.GetTitleID(), hash)); // TODO: this is renamed with some scheme + + if (fs::file theme = fs::file(dst_path, fs::write_new + fs::isfile)) // TODO: new file is write protected + { + const u32 magic = *reinterpret_cast(buf.get_ptr()); + + if (magic != "P3TF"_u32) + { + return CELL_GAME_ERROR_INVALID_THEME_FILE; + } + + if (func && bufSize < fileSize) + { + cellGame.notice("cellGameThemeInstallFromBuffer: writing theme with func callback to '%s'", dst_path); + + for (u32 file_offset = 0; file_offset < fileSize;) + { + const u32 read_size = std::min(bufSize, fileSize - file_offset); + cellGame.notice("cellGameThemeInstallFromBuffer: writing %d bytes at pos %d", read_size, file_offset); + + if (theme.write(reinterpret_cast(buf.get_ptr()) + file_offset, read_size) != read_size) + { + cellGame.error("cellGameThemeInstallFromBuffer: failed to write to destination file '%s' (error=%s)", dst_path, fs::g_tls_error); + + if (fs::g_tls_error == fs::error::nospace) + { + return CELL_GAME_ERROR_NOSPACE; + } + + return CELL_GAME_ERROR_ACCESS_ERROR; + } + + file_offset += read_size; + + // Report status with callback + cellGame.notice("cellGameThemeInstallFromBuffer: func(fileOffset=%d, readSize=%d, buf=0x%x)", file_offset, read_size, buf); + const s32 result = func(ppu, file_offset, read_size, buf); + + if (result == CELL_GAME_RET_CANCEL) // same as CELL_GAME_CBRESULT_CANCEL + { + cellGame.notice("cellGameThemeInstallFromBuffer: theme installation was cancelled"); + return not_an_error(CELL_GAME_RET_CANCEL); + } + } + } + else + { + cellGame.notice("cellGameThemeInstallFromBuffer: writing theme to '%s'", dst_path); + + if (theme.write(buf.get_ptr(), fileSize) != fileSize) + { + cellGame.error("cellGameThemeInstallFromBuffer: failed to write to destination file '%s' (error=%s)", dst_path, fs::g_tls_error); + + if (fs::g_tls_error == fs::error::nospace) + { + return CELL_GAME_ERROR_NOSPACE; + } + + return CELL_GAME_ERROR_ACCESS_ERROR; + } + } + } + else if (fs::g_tls_error == fs::error::exist) // Do not overwrite files, but continue. + { + cellGame.notice("cellGameThemeInstallFromBuffer: theme already installed: '%s'", dst_path); + } + else + { + cellGame.error("cellGameThemeInstallFromBuffer: failed to open destination file '%s' (error=%s)", dst_path, fs::g_tls_error); + return CELL_GAME_ERROR_ACCESS_ERROR; + } + + if (option == CELL_GAME_THEME_OPTION_APPLY) + { + // TODO: apply new theme + } + return CELL_OK; } diff --git a/rpcs3/Emu/Cell/Modules/cellGame.h b/rpcs3/Emu/Cell/Modules/cellGame.h index f186c3a290..7140f1e8d9 100644 --- a/rpcs3/Emu/Cell/Modules/cellGame.h +++ b/rpcs3/Emu/Cell/Modules/cellGame.h @@ -315,7 +315,7 @@ using CellHddGameSystemFileParam = CellGameDataSystemFileParam; using CellHddGameCBResult = CellGameDataCBResult; typedef void(CellHddGameStatCallback)(vm::ptr cbResult, vm::ptr get, vm::ptr set); -typedef void(CellGameThemeInstallCallback)(u32 fileOffset, u32 readSize, vm::ptr buf); +typedef s32(CellGameThemeInstallCallback)(u32 fileOffset, u32 readSize, vm::ptr buf); typedef void(CellGameDiscEjectCallback)(); typedef void(CellGameDiscInsertCallback)(u32 discType, vm::ptr titleId); typedef void(CellDiscGameDiscEjectCallback)(); diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 61453025a1..deff7dc3e4 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -601,7 +601,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo } std::string path; - const std::string local_path = vfs::get(vpath, nullptr, &path); + std::string local_path = vfs::get(vpath, nullptr, &path); const auto mp = lv2_fs_object::get_mp(vpath); diff --git a/rpcs3/rpcs3qt/downloader.cpp b/rpcs3/rpcs3qt/downloader.cpp index b349365c9c..1b1015cd75 100644 --- a/rpcs3/rpcs3qt/downloader.cpp +++ b/rpcs3/rpcs3qt/downloader.cpp @@ -5,7 +5,6 @@ #include "curl_handle.h" #include "progress_dialog.h" -#include "Crypto/sha256.h" #include "util/logs.hpp" LOG_CHANNEL(network_log, "NET"); @@ -156,27 +155,6 @@ progress_dialog* downloader::get_progress_dialog() const return m_progress_dialog; } -std::string downloader::get_hash(const char* data, usz size, bool lower_case) -{ - u8 res_hash[32]; - mbedtls_sha256_context ctx; - mbedtls_sha256_init(&ctx); - mbedtls_sha256_starts_ret(&ctx, 0); - mbedtls_sha256_update_ret(&ctx, reinterpret_cast(data), size); - mbedtls_sha256_finish_ret(&ctx, res_hash); - - std::string res_hash_string("0000000000000000000000000000000000000000000000000000000000000000"); - - for (usz index = 0; index < 32; index++) - { - const auto pal = lower_case ? "0123456789abcdef" : "0123456789ABCDEF"; - res_hash_string[index * 2] = pal[res_hash[index] >> 4]; - res_hash_string[(index * 2) + 1] = pal[res_hash[index] & 15]; - } - - return res_hash_string; -} - usz downloader::update_buffer(char* data, usz size) { if (m_curl_abort) diff --git a/rpcs3/rpcs3qt/downloader.h b/rpcs3/rpcs3qt/downloader.h index f5b9452457..8d0b34e68c 100644 --- a/rpcs3/rpcs3qt/downloader.h +++ b/rpcs3/rpcs3qt/downloader.h @@ -29,8 +29,6 @@ public: progress_dialog* get_progress_dialog() const; - static std::string get_hash(const char* data, usz size, bool lower_case); - private Q_SLOTS: void handle_buffer_update(int size, int max) const; diff --git a/rpcs3/rpcs3qt/patch_manager_dialog.cpp b/rpcs3/rpcs3qt/patch_manager_dialog.cpp index 103790ec3f..0df5f57428 100644 --- a/rpcs3/rpcs3qt/patch_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/patch_manager_dialog.cpp @@ -19,6 +19,7 @@ #include "qt_utils.h" #include "Utilities/File.h" #include "util/logs.hpp" +#include "Crypto/utils.h" LOG_CHANNEL(patch_log, "PAT"); @@ -904,7 +905,7 @@ void patch_manager_dialog::download_update(bool automatic, bool auto_accept) { if (const fs::file patch_file{path}) { - const std::string hash = downloader::get_hash(patch_file.to_string().c_str(), patch_file.size(), true); + const std::string hash = sha256_get_hash(patch_file.to_string().c_str(), patch_file.size(), true); url += "&sha256=" + hash; } else @@ -1006,7 +1007,7 @@ bool patch_manager_dialog::handle_json(const QByteArray& data) const std::string content = patch.toString().toStdString(); - if (hash_obj.toString().toStdString() != downloader::get_hash(content.c_str(), content.size(), true)) + if (hash_obj.toString().toStdString() != sha256_get_hash(content.c_str(), content.size(), true)) { patch_log.error("JSON content does not match the provided checksum"); return false; diff --git a/rpcs3/rpcs3qt/update_manager.cpp b/rpcs3/rpcs3qt/update_manager.cpp index 76731c1b2a..ad986a18f6 100644 --- a/rpcs3/rpcs3qt/update_manager.cpp +++ b/rpcs3/rpcs3qt/update_manager.cpp @@ -7,6 +7,7 @@ #include "Utilities/File.h" #include "Emu/System.h" #include "Emu/system_utils.hpp" +#include "Crypto/utils.h" #include "util/logs.hpp" #include @@ -372,7 +373,7 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept) return false; } - if (const std::string res_hash_string = downloader::get_hash(data.data(), data.size(), false); + if (const std::string res_hash_string = sha256_get_hash(data.data(), data.size(), false); m_expected_hash != res_hash_string) { update_log.error("Hash mismatch: %s expected: %s", res_hash_string, m_expected_hash);