From c0e97b4e962196b5d937cfe423b31b14a1f9ed90 Mon Sep 17 00:00:00 2001 From: Eladash Date: Fri, 9 Jun 2023 21:23:37 +0300 Subject: [PATCH] Qt: Improve PS3 Binaries Decryption tool --- rpcs3/Crypto/decrypt_binaries.cpp | 106 ++++++++++++++++-------------- rpcs3/Crypto/decrypt_binaries.h | 25 ++++++- rpcs3/Emu/Cell/Modules/sceNp.cpp | 2 +- rpcs3/main.cpp | 38 +++++++---- rpcs3/rpcs3qt/main_window.cpp | 62 +++++++++++------ 5 files changed, 152 insertions(+), 81 deletions(-) diff --git a/rpcs3/Crypto/decrypt_binaries.cpp b/rpcs3/Crypto/decrypt_binaries.cpp index 49ff13c36a..36c26f0ac3 100644 --- a/rpcs3/Crypto/decrypt_binaries.cpp +++ b/rpcs3/Crypto/decrypt_binaries.cpp @@ -11,34 +11,66 @@ LOG_CHANNEL(dec_log, "DECRYPT"); -void decrypt_sprx_libraries(std::vector modules, std::function input_cb) +usz decrypt_binaries_t::decrypt(std::string klic_input) { - if (modules.empty()) + if (m_index >= m_modules.size()) { std::cout << "No paths specified" << std::endl; // For CLI - return; + m_index = umax; + return umax; } - dec_log.notice("Decrypting binaries..."); - std::cout << "Decrypting binaries..." << std::endl; // For CLI - - // Always start with no KLIC - std::vector klics{u128{}}; - - if (const auto keys = g_fxo->try_get()) + if (m_klics.empty()) { - // Second klic: get it from a running game - if (const u128 klic = keys->last_key()) + dec_log.notice("Decrypting binaries..."); + std::cout << "Decrypting binaries..." << std::endl; // For CLI + + // Always start with no KLIC + m_klics.emplace_back(u128{}); + + if (const auto keys = g_fxo->try_get()) { - klics.emplace_back(klic); + // Second klic: get it from a running game + if (const u128 klic = keys->last_key()) + { + m_klics.emplace_back(klic); + } + } + + // Try to use the key that has been for the current running ELF + m_klics.insert(m_klics.end(), Emu.klic.begin(), Emu.klic.end()); + } + + if (std::string_view text = std::string_view{klic_input}.substr(klic_input.find_first_of('x') + 1); text.size() == 32) + { + // Allowed to fail (would simply repeat the operation if fails again) + u64 lo = 0; + u64 hi = 0; + bool success = false; + + if (auto res = std::from_chars(text.data() + 0, text.data() + 16, lo, 16); res.ec == std::errc() && res.ptr == text.data() + 16) + { + if (res = std::from_chars(text.data() + 16, text.data() + 32, hi, 16); res.ec == std::errc() && res.ptr == text.data() + 32) + { + success = true; + } + } + + if (success) + { + lo = std::bit_cast>(lo); + hi = std::bit_cast>(hi); + + if (u128 input_key = ((u128{hi} << 64) | lo)) + { + m_klics.emplace_back(input_key); + } } } - // Try to use the key that has been for the current running ELF - klics.insert(klics.end(), Emu.klic.begin(), Emu.klic.end()); - - for (const std::string& _module : modules) + while (m_index < m_modules.size()) { + const std::string& _module = m_modules[m_index]; const std::string old_path = _module; fs::file elf_file; @@ -50,7 +82,7 @@ void decrypt_sprx_libraries(std::vector modules, std::function modules, std::function(&klics[key_it]) : nullptr); + elf_file = decrypt_self(std::move(elf_file), key_it != 0 ? reinterpret_cast(&m_klics[key_it]) : nullptr); if (!elf_file) { @@ -75,7 +107,7 @@ void decrypt_sprx_libraries(std::vector modules, std::function(&klics[key_it]), true); + elf_file = DecryptEDAT(elf_file, old_path, key_it != 0 ? 8 : 1, reinterpret_cast(&m_klics[key_it]), true); if (!elf_file) { @@ -111,11 +143,13 @@ void decrypt_sprx_libraries(std::vector modules, std::function %s", old_path, new_path); std::cout << "Decrypted " << old_path << " -> " << new_path << std::endl; // For CLI + m_index++; } else { dec_log.error("Failed to create %s", new_path); std::cout << "Failed to create " << new_path << std::endl; // For CLI + m_index = umax; } break; @@ -124,42 +158,16 @@ void decrypt_sprx_libraries(std::vector modules, std::function lo = std::bit_cast>(lo_); - be_t hi = std::bit_cast>(hi_); - - klic = (u128{+hi} << 64) | +lo; - - // Retry with specified KLIC - key_it -= +std::exchange(tried, true); // Rewind on second and above attempt - dec_log.notice("KLIC entered for %s: %s", filename, klic); - continue; - } - - dec_log.notice("User has cancelled entering KLIC."); + return m_index; } dec_log.error("Failed to decrypt \"%s\".", old_path); std::cout << "Failed to decrypt \"" << old_path << "\"." << std::endl; // For CLI - break; + return m_index; } } dec_log.notice("Finished decrypting all binaries."); std::cout << "Finished decrypting all binaries." << std::endl; // For CLI + return m_index; } diff --git a/rpcs3/Crypto/decrypt_binaries.h b/rpcs3/Crypto/decrypt_binaries.h index f27e58146f..d9887b3d5f 100644 --- a/rpcs3/Crypto/decrypt_binaries.h +++ b/rpcs3/Crypto/decrypt_binaries.h @@ -1,3 +1,26 @@ #pragma once -void decrypt_sprx_libraries(std::vector modules, std::function input_cb); +class decrypt_binaries_t +{ + std::vector m_klics; + std::vector m_modules; + usz m_index = 0; + +public: + decrypt_binaries_t(std::vector modules) noexcept + : m_modules(std::move(modules)) + { + } + + usz decrypt(std::string klic_input = {}); + + bool done() const + { + return m_index >= m_klics.size(); + } + + const std::string& operator[](usz index) const + { + return ::at32(m_modules, index); + } +}; diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index 7466869709..da84bb05a5 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -468,7 +468,7 @@ error_code npDrmIsAvailable(vm::cptr k_licensee_addr, vm::cptr drm_pat if (k_licensee_addr) { std::memcpy(&k_licensee, k_licensee_addr.get_ptr(), sizeof(k_licensee)); - sceNp.notice("npDrmIsAvailable(): KLicense key %s", std::bit_cast>(k_licensee)); + sceNp.notice("npDrmIsAvailable(): KLicense key or KLIC=%s", std::bit_cast>(k_licensee)); } if (Emu.GetFakeCat() == "PE") diff --git a/rpcs3/main.cpp b/rpcs3/main.cpp index ba699b07ef..bcfd82f6c1 100644 --- a/rpcs3/main.cpp +++ b/rpcs3/main.cpp @@ -1103,29 +1103,45 @@ int main(int argc, char** argv) std::cout << "Not a file: " << mod.toStdString() << std::endl; return 1; } + vec_modules.push_back(fi.absoluteFilePath().toStdString()); } - const auto input_cb = [](std::string old_path, std::string path, bool tried) -> std::string - { - const std::string hint = fmt::format("Hint: KLIC (KLicense key) is a 16-byte long string. (32 hexadecimal characters)" - "\nAnd is logged with some sceNpDrm* functions when the game/application which owns \"%0\" is running.", path); + Emu.Init(); - if (tried) + std::shared_ptr decrypter = std::make_shared(std::move(vec_modules)); + + usz mod_index = decrypter->decrypt(); + usz repeat_count = mod_index == 0 ? 1 : 0; + + while (!decrypter->done()) + { + const std::string& path = (*decrypter)[mod_index]; + const std::string filename = path.substr(path.find_last_of(fs::delim) + 1); + + const std::string hint = fmt::format("Hint: KLIC (KLicense key) is a 16-byte long string. (32 hexadecimal characters)" + "\nAnd is logged with some sceNpDrm* functions when the game/application which owns \"%0\" is running.", filename); + + if (repeat_count >= 2) { - std::cout << "Failed to decrypt " << old_path << " with specfied KLIC, retrying.\n" << hint << std::endl; + std::cout << "Failed to decrypt " << path << " with specfied KLIC, retrying.\n" << hint << std::endl; } - std::cout << "Enter KLIC of " << path << "\nHexadecimal only, 32 characters:" << std::endl; + std::cout << "Enter KLIC of " << filename << "\nHexadecimal only, 32 characters:" << std::endl; std::string input; std::cin >> input; - return input; - }; + if (input.empty()) + { + break; + } + + const usz new_index = decrypter->decrypt(input); + repeat_count = new_index == mod_index ? repeat_count + 1 : 0; + mod_index = new_index; + } - Emu.Init(); - decrypt_sprx_libraries(vec_modules, input_cb); Emu.Quit(true); return 0; } diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index b2bc6091a0..3862522829 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -1584,40 +1584,64 @@ void main_window::DecryptSPRXLibraries() vec_modules.push_back(mod.toStdString()); } - const auto input_cb = [this](std::string old_path, std::string path, bool tried) -> std::string - { - const QString hint = tr("Hint: KLIC (KLicense key) is a 16-byte long string. (32 hexadecimal characters)" - "\nAnd is logged with some sceNpDrm* functions when the game/application which owns \"%0\" is running.").arg(qstr(path)); + auto iterate = std::make_shared>(); + const auto decrypter = std::make_shared(std::move(vec_modules)); - if (tried) + *iterate = [this, iterate, decrypter](usz mod_index, usz repeat_count) + { + const std::string& path = (*decrypter)[mod_index]; + const std::string filename = path.substr(path.find_last_of(fs::delim) + 1); + + const QString hint = tr("Hint: KLIC (KLicense key) is a 16-byte long string. (32 hexadecimal characters, can be prefixed with \"KLIC=0x\" from the log message)" + "\nAnd is logged with some sceNpDrm* functions when the game/application which owns \"%0\" is running.").arg(qstr(filename)); + + if (repeat_count >= 2) { - gui_log.error("Failed to decrypt %s with specfied KLIC, retrying.\n%s", old_path, sstr(hint)); + gui_log.error("Failed to decrypt %s with specified KLIC, retrying.\n%s", path, sstr(hint)); } - input_dialog dlg(32, "", tr("Enter KLIC of %0").arg(qstr(path)), - tried ? tr("Decryption failed with provided KLIC.\n%0").arg(hint) : tr("Hexadecimal only."), "00000000000000000000000000000000", this); + input_dialog* dlg = new input_dialog(39, "", tr("Enter KLIC of %0").arg(qstr(filename)), + repeat_count >= 2 ? tr("Decryption failed with provided KLIC.\n%0").arg(hint) : tr("Hexadecimal value."), "KLIC=0x00000000000000000000000000000000", this); QFont mono = QFontDatabase::systemFont(QFontDatabase::FixedFont); mono.setPointSize(8); - dlg.set_input_font(mono, true, '0'); - dlg.set_clear_button_enabled(false); - dlg.set_button_enabled(QDialogButtonBox::StandardButton::Ok, false); - dlg.set_validator(new QRegularExpressionValidator(QRegularExpression("^[a-fA-F0-9]*$"))); // HEX only + dlg->set_input_font(mono, true, '0'); + dlg->set_clear_button_enabled(false); + dlg->set_button_enabled(QDialogButtonBox::StandardButton::Ok, false); + dlg->set_validator(new QRegularExpressionValidator(QRegularExpression("^((((((K?L)?I)?C)?=)?0)?x)?[a-fA-F0-9]{0,32}$"))); // HEX only (with additional KLIC=0x prefix for convenience) + dlg->setAttribute(Qt::WA_DeleteOnClose); - connect(&dlg, &input_dialog::text_changed, &dlg, [&dlg](const QString& text) + connect(dlg, &input_dialog::text_changed, dlg, [dlg](const QString& text) { - dlg.set_button_enabled(QDialogButtonBox::StandardButton::Ok, text.size() == 32); + dlg->set_button_enabled(QDialogButtonBox::StandardButton::Ok, text.size() - (text.indexOf('x') + 1) == 32); }); - if (dlg.exec() == QDialog::Accepted) + connect(dlg, &QDialog::accepted, this, [this, iterate, dlg, mod_index, decrypter, repeat_count]() { - return sstr(dlg.get_input_text()); - } + std::string text = sstr(dlg->get_input_text()); - return {}; + if (usz new_index = decrypter->decrypt(std::move(text)); !decrypter->done()) + { + QTimer::singleShot(0, [iterate, mod_index, repeat_count, new_index]() + { + // Increase repeat count if "stuck" on the same file + (*iterate)(new_index, new_index == mod_index ? repeat_count + 1 : 0); + }); + } + }); + + connect(dlg, &QDialog::rejected, this, []() + { + gui_log.notice("User has cancelled entering KLIC."); + }); + + dlg->show(); }; - decrypt_sprx_libraries(vec_modules, input_cb); + if (usz new_index = decrypter->decrypt(); !decrypter->done()) + { + (*iterate)(new_index, new_index == 0 ? 1 : 0); + } } /** Needed so that when a backup occurs of window state in gui_settings, the state is current.