mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-05-12 16:19:44 -06:00
Merge 40dbeaf2a2 into 021f16f775
This commit is contained in:
commit
8f119ef0f6
@ -128,7 +128,7 @@ target_sources(rpcs3_emu PRIVATE
|
||||
../Loader/TAR.cpp
|
||||
../Loader/ISO.cpp
|
||||
../Loader/iso_cache.cpp
|
||||
../Loader/iso_validation.cpp
|
||||
../Loader/content_validation.cpp
|
||||
../Loader/TROPUSR.cpp
|
||||
../Loader/TRP.cpp
|
||||
)
|
||||
|
||||
@ -256,6 +256,36 @@ namespace rpcs3::utils
|
||||
return get_data_dir() + "redump/";
|
||||
}
|
||||
|
||||
std::string get_psn_content_db_path()
|
||||
{
|
||||
return fs::get_config_dir(true) + "psn_content.dat";
|
||||
}
|
||||
|
||||
std::string get_psn_content_db_download_url()
|
||||
{
|
||||
return "https://api.rpcs3.net/psn_content/?api=v1";
|
||||
}
|
||||
|
||||
std::string get_psn_dlc_db_path()
|
||||
{
|
||||
return fs::get_config_dir(true) + "psn_dlc.dat";
|
||||
}
|
||||
|
||||
std::string get_psn_dlc_db_download_url()
|
||||
{
|
||||
return "https://api.rpcs3.net/psn_dlc/?api=v1";
|
||||
}
|
||||
|
||||
std::string get_psn_update_db_path()
|
||||
{
|
||||
return fs::get_config_dir(true) + "psn_update.dat";
|
||||
}
|
||||
|
||||
std::string get_psn_update_db_download_url()
|
||||
{
|
||||
return "https://api.rpcs3.net/psn_update/?api=v1";
|
||||
}
|
||||
|
||||
std::string get_data_dir()
|
||||
{
|
||||
return fs::get_config_dir() + "data/";
|
||||
|
||||
@ -49,6 +49,12 @@ namespace rpcs3::utils
|
||||
std::string get_redump_db_path();
|
||||
std::string get_redump_db_download_url();
|
||||
std::string get_redump_key_dir();
|
||||
std::string get_psn_content_db_path();
|
||||
std::string get_psn_content_db_download_url();
|
||||
std::string get_psn_dlc_db_path();
|
||||
std::string get_psn_dlc_db_download_url();
|
||||
std::string get_psn_update_db_path();
|
||||
std::string get_psn_update_db_download_url();
|
||||
|
||||
std::string get_data_dir();
|
||||
std::string get_icons_dir();
|
||||
|
||||
187
rpcs3/Loader/content_validation.cpp
Normal file
187
rpcs3/Loader/content_validation.cpp
Normal file
@ -0,0 +1,187 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "content_validation.h"
|
||||
#include "ISO.h"
|
||||
|
||||
#include "Emu/system_utils.hpp"
|
||||
#include "Utilities/File.h"
|
||||
#include "Utilities/rXml.h"
|
||||
#include "Crypto/md5.h"
|
||||
#include "Crypto/utils.h"
|
||||
|
||||
LOG_CHANNEL(sys_log, "VALIDATION");
|
||||
|
||||
content_integrity_status content_validation::check_integrity(content_file_type file_type, const std::string& hash, std::string* game_name)
|
||||
{
|
||||
//
|
||||
// Check for Redump db
|
||||
//
|
||||
|
||||
std::string db_path;
|
||||
|
||||
switch (file_type)
|
||||
{
|
||||
case content_file_type::ISO:
|
||||
db_path = rpcs3::utils::get_redump_db_path();
|
||||
break;
|
||||
case content_file_type::PSN_CONTENT:
|
||||
db_path = rpcs3::utils::get_psn_content_db_path();
|
||||
break;
|
||||
case content_file_type::PSN_DLC:
|
||||
db_path = rpcs3::utils::get_psn_dlc_db_path();
|
||||
break;
|
||||
case content_file_type::PSN_UPDATE:
|
||||
db_path = rpcs3::utils::get_psn_update_db_path();
|
||||
break;
|
||||
}
|
||||
|
||||
fs::file db_file(db_path);
|
||||
|
||||
// If no db file exists
|
||||
if (!db_file)
|
||||
{
|
||||
// An empty hash is used to simply test the presence (without any logging) of the Redump db
|
||||
if (!hash.empty())
|
||||
{
|
||||
sys_log.error("check_integrity: Failed to open file: %s", db_path);
|
||||
}
|
||||
|
||||
return content_integrity_status::ERROR_OPENING_DB;
|
||||
}
|
||||
|
||||
if (hash.empty())
|
||||
{
|
||||
return content_integrity_status::NO_MATCH;
|
||||
}
|
||||
|
||||
rXmlDocument db;
|
||||
|
||||
if (!db.Read(db_file.to_string()))
|
||||
{
|
||||
sys_log.error("check_integrity: Failed to process file: %s", db_path);
|
||||
return content_integrity_status::ERROR_PARSING_DB;
|
||||
}
|
||||
|
||||
// Close the file and work with the data loaded into the "db" document
|
||||
db_file.close();
|
||||
|
||||
std::shared_ptr<rXmlNode> db_base = db.GetRoot();
|
||||
|
||||
if (!db_base)
|
||||
{
|
||||
sys_log.error("check_integrity: Failed to get 'root' node on file: %s", db_path);
|
||||
return content_integrity_status::ERROR_PARSING_DB;
|
||||
}
|
||||
|
||||
if (db_base = db_base->GetChild(std::string_view("datafile")); !db_base)
|
||||
{
|
||||
sys_log.error("check_integrity: Failed to get 'datafile' node on file: %s", db_path);
|
||||
return content_integrity_status::ERROR_PARSING_DB;
|
||||
}
|
||||
|
||||
//
|
||||
// Check for a match on Redump db
|
||||
//
|
||||
|
||||
for (auto node = db_base->GetChildren(); node; node = node->GetNext())
|
||||
{
|
||||
if (node->GetName() == "game")
|
||||
{
|
||||
for (auto child = node->GetChildren(); child; child = child->GetNext())
|
||||
{
|
||||
// If a match is found, fill in "game_desc" (if requested) and return FOUND_MATCH
|
||||
if (child->GetName() == "rom" && hash == child->GetAttribute(std::string_view("md5")))
|
||||
{
|
||||
if (game_name)
|
||||
{
|
||||
*game_name = node->GetAttribute(std::string_view("name"));
|
||||
}
|
||||
|
||||
return content_integrity_status::FOUND_MATCH;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No match found
|
||||
return content_integrity_status::NO_MATCH;
|
||||
}
|
||||
|
||||
bool content_validation::init_hash(const std::string& path)
|
||||
{
|
||||
std::string new_path = path;
|
||||
|
||||
fs::get_optical_raw_device(path, &new_path);
|
||||
|
||||
iso_file file(new_path);
|
||||
|
||||
// If no file exists
|
||||
if (!file)
|
||||
{
|
||||
sys_log.error("init_hash: Failed to open file: %s", new_path);
|
||||
m_status = content_hash_status::ABORTED;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_path = new_path;
|
||||
m_name = new_path.find_last_of(fs::delim) != umax ? new_path.substr(new_path.find_last_of(fs::delim) + 1) : new_path;
|
||||
m_size = file.size();
|
||||
m_bytes_read = 0;
|
||||
m_status = content_hash_status::INITIALIZED;
|
||||
return true;
|
||||
}
|
||||
|
||||
content_hash_status content_validation::calculate_hash(std::string& hash)
|
||||
{
|
||||
if (m_status != content_hash_status::INITIALIZED)
|
||||
{
|
||||
sys_log.error("calculate_hash: MD5 hash calculation already performed: %s", m_path);
|
||||
m_status = content_hash_status::ABORTED;
|
||||
return m_status;
|
||||
}
|
||||
|
||||
iso_file file(m_path);
|
||||
|
||||
// If no file exists
|
||||
if (!file)
|
||||
{
|
||||
sys_log.error("calculate_hash: Failed to open file: %s", m_path);
|
||||
m_status = content_hash_status::ABORTED;
|
||||
return m_status;
|
||||
}
|
||||
|
||||
constexpr u64 block_size = 4096;
|
||||
std::array<u8, block_size> buf;
|
||||
u64 bytes_read;
|
||||
mbedtls_md5_context md5_ctx;
|
||||
unsigned char md5_hash[16];
|
||||
|
||||
mbedtls_md5_starts_ret(&md5_ctx);
|
||||
|
||||
do
|
||||
{
|
||||
bytes_read = file.read(buf.data(), block_size);
|
||||
mbedtls_md5_update_ret(&md5_ctx, buf.data(), bytes_read);
|
||||
|
||||
m_bytes_read += bytes_read;
|
||||
} while (bytes_read == block_size && m_status != content_hash_status::ABORTED);
|
||||
|
||||
if (m_status == content_hash_status::ABORTED)
|
||||
{
|
||||
sys_log.warning("calculate_hash: MD5 hash calculation aborted by user: %s", m_path);
|
||||
return m_status;
|
||||
}
|
||||
|
||||
if (mbedtls_md5_finish_ret(&md5_ctx, md5_hash) != 0)
|
||||
{
|
||||
sys_log.error("calculate_hash: Failed to calculate MD5 hash on file: %s", m_path);
|
||||
m_status = content_hash_status::ABORTED;
|
||||
return m_status;
|
||||
}
|
||||
|
||||
// Convert the MD5 hash to hex string
|
||||
bytes_to_hex(hash, md5_hash, 16);
|
||||
|
||||
m_status = content_hash_status::COMPLETED;
|
||||
return m_status;
|
||||
}
|
||||
57
rpcs3/Loader/content_validation.h
Normal file
57
rpcs3/Loader/content_validation.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
|
||||
// Enum identifying the content file type
|
||||
enum class content_file_type
|
||||
{
|
||||
ISO,
|
||||
PSN_CONTENT,
|
||||
PSN_DLC,
|
||||
PSN_UPDATE
|
||||
};
|
||||
|
||||
// Enum returned by calculating hash
|
||||
enum class content_hash_status
|
||||
{
|
||||
INITIALIZED,
|
||||
COMPLETED,
|
||||
ABORTED
|
||||
};
|
||||
|
||||
// Enum returned by checking integrity
|
||||
enum class content_integrity_status
|
||||
{
|
||||
NO_MATCH,
|
||||
FOUND_MATCH,
|
||||
ERROR_OPENING_DB,
|
||||
ERROR_PARSING_DB
|
||||
};
|
||||
|
||||
// Content validation class
|
||||
class content_validation
|
||||
{
|
||||
private:
|
||||
std::string m_path;
|
||||
std::string m_name;
|
||||
u64 m_size = 0;
|
||||
u64 m_bytes_read = 0;
|
||||
u16 m_count = 0; // Set only by set_count()
|
||||
content_hash_status m_status = content_hash_status::INITIALIZED;
|
||||
|
||||
public:
|
||||
static content_integrity_status check_integrity(content_file_type file_type, const std::string& hash, std::string* game_name = nullptr);
|
||||
|
||||
const std::string& get_path() const { return m_path; }
|
||||
const std::string& get_name() const { return m_name; }
|
||||
u64 get_size() const { return m_size; }
|
||||
u64 get_bytes_read() const { return m_bytes_read; }
|
||||
u16 get_count() const { return m_count; }
|
||||
content_hash_status get_status() const { return m_status; }
|
||||
|
||||
void set_count(u16 count) { m_count = count; }
|
||||
void abort_hash() { m_status = content_hash_status::ABORTED; }
|
||||
|
||||
bool init_hash(const std::string& path);
|
||||
content_hash_status calculate_hash(std::string& hash);
|
||||
};
|
||||
@ -1,162 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "iso_validation.h"
|
||||
#include "ISO.h"
|
||||
|
||||
#include "Emu/system_utils.hpp"
|
||||
#include "Utilities/File.h"
|
||||
#include "Utilities/rXml.h"
|
||||
#include "Crypto/md5.h"
|
||||
#include "Crypto/utils.h"
|
||||
|
||||
LOG_CHANNEL(iso_log, "ISO");
|
||||
|
||||
iso_integrity_status iso_file_validation::check_integrity(const std::string& hash, std::string* game_name)
|
||||
{
|
||||
//
|
||||
// Check for Redump db
|
||||
//
|
||||
|
||||
const std::string db_path = rpcs3::utils::get_redump_db_path();
|
||||
fs::file db_file(db_path);
|
||||
|
||||
// If no db file exists
|
||||
if (!db_file)
|
||||
{
|
||||
// An empty hash is used to simply test the presence (without any logging) of the Redump db
|
||||
if (!hash.empty())
|
||||
{
|
||||
iso_log.error("check_integrity: Failed to open file: %s", db_path);
|
||||
}
|
||||
|
||||
return iso_integrity_status::ERROR_OPENING_DB;
|
||||
}
|
||||
|
||||
if (hash.empty())
|
||||
{
|
||||
return iso_integrity_status::NO_MATCH;
|
||||
}
|
||||
|
||||
rXmlDocument db;
|
||||
|
||||
if (!db.Read(db_file.to_string()))
|
||||
{
|
||||
iso_log.error("check_integrity: Failed to process file: %s", db_path);
|
||||
return iso_integrity_status::ERROR_PARSING_DB;
|
||||
}
|
||||
|
||||
std::shared_ptr<rXmlNode> db_base = db.GetRoot();
|
||||
|
||||
if (!db_base)
|
||||
{
|
||||
iso_log.error("check_integrity: Failed to get 'root' node on file: %s", db_path);
|
||||
return iso_integrity_status::ERROR_PARSING_DB;
|
||||
}
|
||||
|
||||
if (db_base = db_base->GetChild(std::string_view("datafile")); !db_base)
|
||||
{
|
||||
iso_log.error("check_integrity: Failed to get 'datafile' node on file: %s", db_path);
|
||||
return iso_integrity_status::ERROR_PARSING_DB;
|
||||
}
|
||||
|
||||
//
|
||||
// Check for a match on Redump db
|
||||
//
|
||||
|
||||
for (auto node = db_base->GetChildren(); node; node = node->GetNext())
|
||||
{
|
||||
if (node->GetName() == "game")
|
||||
{
|
||||
if (const auto child = node->GetChild(std::string_view("rom")))
|
||||
{
|
||||
// If a match is found, fill in "game_desc" (if requested) and return FOUND_MATCH
|
||||
if (hash == child->GetAttribute(std::string_view("md5")))
|
||||
{
|
||||
if (game_name)
|
||||
{
|
||||
*game_name = node->GetAttribute(std::string_view("name"));
|
||||
}
|
||||
|
||||
return iso_integrity_status::FOUND_MATCH;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No match found
|
||||
return iso_integrity_status::NO_MATCH;
|
||||
}
|
||||
|
||||
bool iso_file_validation::init_hash(const std::string& path)
|
||||
{
|
||||
fs::file iso_file(path);
|
||||
|
||||
// If no ISO file exists
|
||||
if (!iso_file)
|
||||
{
|
||||
iso_log.error("init_hash: Failed to open file: %s", path);
|
||||
m_status = iso_hash_status::ABORTED;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_path = path;
|
||||
m_size = iso_file.size();
|
||||
m_bytes_read = 0;
|
||||
m_status = iso_hash_status::INITIALIZED;
|
||||
return true;
|
||||
}
|
||||
|
||||
iso_hash_status iso_file_validation::calculate_hash(std::string& hash)
|
||||
{
|
||||
if (m_status != iso_hash_status::INITIALIZED)
|
||||
{
|
||||
iso_log.error("calculate_hash: MD5 hash calculation already performed: %s", m_path);
|
||||
m_status = iso_hash_status::ABORTED;
|
||||
return m_status;
|
||||
}
|
||||
|
||||
fs::file iso_file(m_path);
|
||||
|
||||
// If no ISO file exists
|
||||
if (!iso_file)
|
||||
{
|
||||
iso_log.error("calculate_hash: Failed to open file: %s", m_path);
|
||||
m_status = iso_hash_status::ABORTED;
|
||||
return m_status;
|
||||
}
|
||||
|
||||
constexpr u64 block_size = ISO_SECTOR_SIZE * 2;
|
||||
std::array<u8, block_size> buf;
|
||||
u64 bytes_read;
|
||||
mbedtls_md5_context md5_ctx;
|
||||
unsigned char md5_hash[16];
|
||||
|
||||
mbedtls_md5_starts_ret(&md5_ctx);
|
||||
|
||||
do
|
||||
{
|
||||
bytes_read = iso_file.read(buf.data(), block_size);
|
||||
mbedtls_md5_update_ret(&md5_ctx, buf.data(), bytes_read);
|
||||
|
||||
m_bytes_read += bytes_read;
|
||||
} while (bytes_read == block_size && m_status != iso_hash_status::ABORTED);
|
||||
|
||||
if (m_status == iso_hash_status::ABORTED)
|
||||
{
|
||||
iso_log.warning("calculate_hash: MD5 hash calculation aborted by user: %s", m_path);
|
||||
return m_status;
|
||||
}
|
||||
|
||||
if (mbedtls_md5_finish_ret(&md5_ctx, md5_hash) != 0)
|
||||
{
|
||||
iso_log.error("calculate_hash: Failed to calculate MD5 hash on file: %s", m_path);
|
||||
m_status = iso_hash_status::ABORTED;
|
||||
return m_status;
|
||||
}
|
||||
|
||||
// Convert the MD5 hash to hex string
|
||||
bytes_to_hex(hash, md5_hash, 16);
|
||||
|
||||
m_status = iso_hash_status::COMPLETED;
|
||||
return m_status;
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
|
||||
// Enum returned by calculating hash
|
||||
enum class iso_hash_status
|
||||
{
|
||||
INITIALIZED,
|
||||
COMPLETED,
|
||||
ABORTED
|
||||
};
|
||||
|
||||
// Enum returned by checking integrity
|
||||
enum class iso_integrity_status
|
||||
{
|
||||
NO_MATCH,
|
||||
FOUND_MATCH,
|
||||
ERROR_OPENING_DB,
|
||||
ERROR_PARSING_DB
|
||||
};
|
||||
|
||||
// ISO file validation class
|
||||
class iso_file_validation
|
||||
{
|
||||
private:
|
||||
std::string m_path;
|
||||
u64 m_size = 0;
|
||||
u64 m_bytes_read = 0;
|
||||
iso_hash_status m_status = iso_hash_status::INITIALIZED;
|
||||
|
||||
public:
|
||||
static iso_integrity_status check_integrity(const std::string& hash, std::string* game_name = nullptr);
|
||||
|
||||
const std::string& get_path() const { return m_path; }
|
||||
u64 get_size() const { return m_size; }
|
||||
u64 get_bytes_read() const { return m_bytes_read; }
|
||||
iso_hash_status get_status() const { return m_status; }
|
||||
void abort_hash() { m_status = iso_hash_status::ABORTED; }
|
||||
|
||||
bool init_hash(const std::string& path);
|
||||
iso_hash_status calculate_hash(std::string& hash);
|
||||
};
|
||||
@ -205,7 +205,7 @@
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\vfs_config.cpp" />
|
||||
<ClCompile Include="Loader\disc.cpp" />
|
||||
<ClCompile Include="Loader\iso_validation.cpp" />
|
||||
<ClCompile Include="Loader\content_validation.cpp" />
|
||||
<ClCompile Include="util\emu_utils.cpp" />
|
||||
<ClCompile Include="util\serialization_ext.cpp">
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
@ -780,7 +780,7 @@
|
||||
<ClInclude Include="Emu\system_config_types.h" />
|
||||
<ClInclude Include="Emu\vfs_config.h" />
|
||||
<ClInclude Include="Loader\disc.h" />
|
||||
<ClInclude Include="Loader\iso_validation.h" />
|
||||
<ClInclude Include="Loader\content_validation.h" />
|
||||
<ClInclude Include="Loader\mself.hpp" />
|
||||
<ClInclude Include="util\atomic.hpp" />
|
||||
<ClInclude Include="util\bless.hpp" />
|
||||
@ -1063,6 +1063,7 @@
|
||||
<ClInclude Include="Loader\PUP.h" />
|
||||
<ClInclude Include="Loader\TAR.h" />
|
||||
<ClInclude Include="Loader\ISO.h" />
|
||||
<ClInclude Include="Loader\iso_cache.h" />
|
||||
<ClInclude Include="Loader\TROPUSR.h" />
|
||||
<ClInclude Include="Loader\TRP.h" />
|
||||
<ClInclude Include="rpcs3_version.h" />
|
||||
|
||||
@ -1414,6 +1414,9 @@
|
||||
<ClCompile Include="Loader\ISO.cpp">
|
||||
<Filter>Loader</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Loader\iso_cache.cpp">
|
||||
<Filter>Loader</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\RSX\Overlays\overlay_audio.cpp">
|
||||
<Filter>Emu\GPU\RSX\Overlays</Filter>
|
||||
</ClCompile>
|
||||
@ -1432,7 +1435,7 @@
|
||||
<ClCompile Include="Emu\RSX\Overlays\overlay_select.cpp">
|
||||
<Filter>Emu\GPU\RSX\Overlays</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Loader\iso_validation.cpp">
|
||||
<ClCompile Include="Loader\content_validation.cpp">
|
||||
<Filter>Loader</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Utilities\stereo_config.cpp">
|
||||
@ -2857,6 +2860,9 @@
|
||||
<ClInclude Include="Loader\ISO.h">
|
||||
<Filter>Loader</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Loader\iso_cache.h">
|
||||
<Filter>Loader</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\Overlays\overlay_audio.h">
|
||||
<Filter>Emu\GPU\RSX\Overlays</Filter>
|
||||
</ClInclude>
|
||||
@ -2881,7 +2887,7 @@
|
||||
<ClInclude Include="Emu\RSX\Common\aligned_malloc.hpp">
|
||||
<Filter>Emu\GPU\RSX\Common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Loader\iso_validation.h">
|
||||
<ClInclude Include="Loader\content_validation.h">
|
||||
<Filter>Loader</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Utilities\stereo_config.h">
|
||||
|
||||
@ -341,7 +341,7 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_ipc_settings_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_iso_integrity.cpp">
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_content_integrity.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_kamen_rider_dialog.cpp">
|
||||
@ -653,7 +653,7 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_ipc_settings_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_iso_integrity.cpp">
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_content_integrity.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_kamen_rider_dialog.cpp">
|
||||
@ -929,7 +929,7 @@
|
||||
<ClCompile Include="rpcs3qt\config_database.cpp" />
|
||||
<ClCompile Include="rpcs3qt\game_compatibility.cpp" />
|
||||
<ClCompile Include="rpcs3qt\game_list_grid.cpp" />
|
||||
<ClCompile Include="rpcs3qt\iso_integrity.cpp" />
|
||||
<ClCompile Include="rpcs3qt\content_integrity.cpp" />
|
||||
<ClCompile Include="rpcs3qt\progress_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\qt_utils.cpp" />
|
||||
<ClCompile Include="rpcs3qt\syntax_highlighter.cpp" />
|
||||
@ -1924,13 +1924,13 @@
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\iso_integrity.h">
|
||||
<CustomBuild Include="rpcs3qt\content_integrity.h">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing iso_integrity.h...</Message>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing content_integrity.h...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing iso_integrity.h...</Message>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing content_integrity.h...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
|
||||
</CustomBuild>
|
||||
|
||||
@ -1302,13 +1302,13 @@
|
||||
<ClCompile Include="rpcs3qt\emu_settings_type.cpp">
|
||||
<Filter>Gui\settings</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_iso_integrity.cpp">
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_content_integrity.cpp">
|
||||
<Filter>Generated Files\Debug</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_iso_integrity.cpp">
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_content_integrity.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\iso_integrity.cpp">
|
||||
<ClCompile Include="rpcs3qt\content_integrity.cpp">
|
||||
<Filter>Gui\game list</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\anaglyph_settings_dialog.cpp">
|
||||
@ -2083,7 +2083,7 @@
|
||||
</Text>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="rpcs3qt\iso_integrity.h">
|
||||
<CustomBuild Include="rpcs3qt\content_integrity.h">
|
||||
<Filter>Gui\game list</Filter>
|
||||
</CustomBuild>
|
||||
</ItemGroup>
|
||||
|
||||
@ -13,6 +13,7 @@ add_library(rpcs3_ui STATIC
|
||||
config_adapter.cpp
|
||||
config_checker.cpp
|
||||
config_database.cpp
|
||||
content_integrity.cpp
|
||||
curl_handle.cpp
|
||||
custom_dialog.cpp
|
||||
custom_table_widget_item.cpp
|
||||
@ -51,7 +52,6 @@ add_library(rpcs3_ui STATIC
|
||||
input_dialog.cpp
|
||||
instruction_editor_dialog.cpp
|
||||
ipc_settings_dialog.cpp
|
||||
iso_integrity.cpp
|
||||
kamen_rider_dialog.cpp
|
||||
kernel_explorer.cpp
|
||||
localized.cpp
|
||||
|
||||
134
rpcs3/rpcs3qt/content_integrity.cpp
Normal file
134
rpcs3/rpcs3qt/content_integrity.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
#include "content_integrity.h"
|
||||
#include "gui_settings.h"
|
||||
|
||||
#include "Emu/system_utils.hpp"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
LOG_CHANNEL(sys_log, "INTEGRITY");
|
||||
|
||||
content_integrity::content_integrity(QWidget* parent, content_file_type file_type)
|
||||
: QObject(parent), m_file_type(file_type)
|
||||
{
|
||||
switch (m_file_type)
|
||||
{
|
||||
case content_file_type::ISO:
|
||||
m_file_path = QString::fromStdString(rpcs3::utils::get_redump_db_path());
|
||||
m_url = rpcs3::utils::get_redump_db_download_url();
|
||||
m_data_prefix = "redump";
|
||||
break;
|
||||
case content_file_type::PSN_CONTENT:
|
||||
m_file_path = QString::fromStdString(rpcs3::utils::get_psn_content_db_path());
|
||||
m_url = rpcs3::utils::get_psn_content_db_download_url();
|
||||
m_data_prefix = "psn_content";
|
||||
break;
|
||||
case content_file_type::PSN_DLC:
|
||||
m_file_path = QString::fromStdString(rpcs3::utils::get_psn_dlc_db_path());
|
||||
m_url = rpcs3::utils::get_psn_dlc_db_download_url();
|
||||
m_data_prefix = "psn_dlc";
|
||||
break;
|
||||
case content_file_type::PSN_UPDATE:
|
||||
m_file_path = QString::fromStdString(rpcs3::utils::get_psn_update_db_path());
|
||||
m_url = rpcs3::utils::get_psn_update_db_download_url();
|
||||
m_data_prefix = "psn_update";
|
||||
break;
|
||||
}
|
||||
|
||||
m_downloader = new downloader(parent);
|
||||
|
||||
connect(m_downloader, &downloader::signal_download_finished, this, &content_integrity::handle_download_finished);
|
||||
connect(m_downloader, &downloader::signal_download_canceled, this, &content_integrity::handle_download_canceled);
|
||||
connect(m_downloader, &downloader::signal_download_error, this, &content_integrity::handle_download_error);
|
||||
}
|
||||
|
||||
void content_integrity::download()
|
||||
{
|
||||
sys_log.notice("Starting database download from: %s", m_url);
|
||||
|
||||
m_downloader->start(m_url, true, true, true, tr("Downloading database"));
|
||||
}
|
||||
|
||||
void content_integrity::handle_download_finished(const QByteArray& content)
|
||||
{
|
||||
sys_log.notice("Database download finished");
|
||||
|
||||
// Write database to file
|
||||
if (QByteArray data = read_json(content, true); !data.isEmpty())
|
||||
{
|
||||
QFile file(m_file_path);
|
||||
|
||||
if (file.exists())
|
||||
{
|
||||
sys_log.notice("Database file found: %s", m_file_path);
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
sys_log.error("Failed to write database to file: %s", m_file_path);
|
||||
return;
|
||||
}
|
||||
|
||||
file.write(data);
|
||||
file.close();
|
||||
|
||||
sys_log.success("Database written to file: %s", m_file_path);
|
||||
}
|
||||
}
|
||||
|
||||
void content_integrity::handle_download_canceled()
|
||||
{
|
||||
sys_log.notice("Database download canceled");
|
||||
}
|
||||
|
||||
void content_integrity::handle_download_error(const QString& error)
|
||||
{
|
||||
sys_log.error("", error.toStdString().c_str());
|
||||
}
|
||||
|
||||
QByteArray content_integrity::read_json(const QByteArray& data, bool after_download)
|
||||
{
|
||||
QJsonParseError error{};
|
||||
const QJsonDocument json_document = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
if (!json_document.isObject())
|
||||
{
|
||||
sys_log.error("Integrity database error - Invalid JSON: '%s'", error.errorString());
|
||||
return {};
|
||||
}
|
||||
|
||||
const QJsonObject json_data = json_document.object();
|
||||
const int return_code = json_data["return_code"].toInt(-255);
|
||||
|
||||
if (return_code < 0)
|
||||
{
|
||||
if (after_download)
|
||||
{
|
||||
std::string error_message;
|
||||
|
||||
switch (return_code)
|
||||
{
|
||||
case -1: error_message = "Server Error - Internal Error"; break;
|
||||
case -2: error_message = "Server Error - Maintenance Mode"; break;
|
||||
case -255: error_message = "Server Error - Return code not found"; break;
|
||||
default: error_message = "Server Error - Unknown Error"; break;
|
||||
}
|
||||
|
||||
sys_log.error("%s: return code %d", error_message, return_code);
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_log.error("Integrity database error - Invalid: return code %d", return_code);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!json_data[m_data_prefix.c_str()].isString())
|
||||
{
|
||||
sys_log.error("Integrity database error - Unusable string");
|
||||
return {};
|
||||
}
|
||||
|
||||
return QByteArray().fromStdString(json_data[m_data_prefix.c_str()].toString().toStdString());
|
||||
};
|
||||
@ -2,13 +2,15 @@
|
||||
|
||||
#include "downloader.h"
|
||||
|
||||
class iso_integrity : public QObject
|
||||
#include "Loader/content_validation.h"
|
||||
|
||||
class content_integrity : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// Handles download for the ISO integrity database
|
||||
iso_integrity(QWidget* parent);
|
||||
// Handles download for the content integrity database
|
||||
content_integrity(QWidget* parent, content_file_type file_type);
|
||||
|
||||
// Downloads and writes the database to file
|
||||
void download();
|
||||
@ -21,6 +23,9 @@ private Q_SLOTS:
|
||||
private:
|
||||
QByteArray read_json(const QByteArray& data, bool after_download);
|
||||
|
||||
QString m_filepath;
|
||||
content_file_type m_file_type;
|
||||
QString m_file_path;
|
||||
std::string m_url;
|
||||
std::string m_data_prefix;
|
||||
downloader* m_downloader = nullptr;
|
||||
};
|
||||
@ -23,6 +23,7 @@
|
||||
#include <QtConcurrent>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
@ -367,67 +368,167 @@ void game_list_actions::ShowGameInfoDialog(const std::vector<game_info>& games)
|
||||
QMessageBox::information(m_game_list_frame, tr("Game Info"), GetContentInfo(games).info);
|
||||
}
|
||||
|
||||
void game_list_actions::ShowGameIntegrityDialog(const game_info& game)
|
||||
void game_list_actions::ShowGameIntegrityDialog(content_file_type file_type, const game_info& game)
|
||||
{
|
||||
if (m_game_integrity_future.isRunning()) // Still running the last request
|
||||
return;
|
||||
|
||||
// Initialize the validator (set also file size etc.)
|
||||
m_iso_validator->init_hash(game->info.path);
|
||||
QStringList path_list;
|
||||
|
||||
switch (file_type)
|
||||
{
|
||||
case content_file_type::ISO:
|
||||
path_list.push_back(QString::fromStdString(game->info.path));
|
||||
break;
|
||||
default: // Auto-detect the file type
|
||||
const QString path_last_pkg = m_gui_settings->GetValue(gui::fd_install_pkg).toString();
|
||||
|
||||
path_list = QFileDialog::getOpenFileNames(nullptr, tr("Select package or rap file to check"),
|
||||
path_last_pkg, tr("All relevant (*.pkg *.PKG *.rap *.RAP *.edat *.EDAT);;Package files (*.pkg *.PKG);;Rap files (*.rap *.RAP);;Edat files (*.edat *.EDAT);;All files (*.*)"));
|
||||
|
||||
if (path_list.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Tell the progress bar thread how many hash will be checked
|
||||
m_game_validator->set_count(path_list.size());
|
||||
|
||||
// Game integrity check can take a while (in particular on non ssd/m.2 disks)
|
||||
// so run it on a concurrent thread avoiding to block the entire GUI
|
||||
m_game_integrity_future = QtConcurrent::run([this]()
|
||||
m_game_integrity_future = QtConcurrent::run([this, type = file_type, path_list]()
|
||||
{
|
||||
thread_base::set_name("Game Integrity");
|
||||
|
||||
QString text;
|
||||
std::string hash, game_name;
|
||||
bool info_dialog = false;
|
||||
content_file_type file_type = type;
|
||||
QString text_result;
|
||||
std::string db_id, hash, game_name;
|
||||
bool info_dialog = true;
|
||||
|
||||
if (m_iso_validator->calculate_hash(hash) != iso_hash_status::COMPLETED)
|
||||
for (int i = 0; i < path_list.size(); i++)
|
||||
{
|
||||
text = "Hash calculation failed!\n\nIntegrity check aborted";
|
||||
}
|
||||
else
|
||||
{
|
||||
text = "Integrity check completed!\n\n";
|
||||
bool use_fallback_db = false; // Set to "true" only for ".rap" and ".edat"
|
||||
|
||||
switch (m_iso_validator->check_integrity(hash, &game_name))
|
||||
if (file_type == content_file_type::ISO)
|
||||
{
|
||||
case iso_integrity_status::NO_MATCH:
|
||||
text += tr("Game check NOT PASSED\n\nNo match found on DB or game corrupted:\n - Hash: %0")
|
||||
.arg(QString::fromStdString(hash));
|
||||
break;
|
||||
case iso_integrity_status::FOUND_MATCH:
|
||||
text += tr("Game check PASSED\n\nMatch found on DB:\n - Game: %0\n - Hash: %1")
|
||||
.arg(QString::fromStdString(game_name))
|
||||
.arg(QString::fromStdString(hash));
|
||||
|
||||
info_dialog = true;
|
||||
break;
|
||||
default:
|
||||
text += tr("Error parsing DB");
|
||||
break;
|
||||
db_id = "REDUMP";
|
||||
}
|
||||
}
|
||||
|
||||
Emu.CallFromMainThread([this, text, info_dialog]()
|
||||
{
|
||||
if (info_dialog)
|
||||
else if (path_list[i].endsWith(".rap", Qt::CaseInsensitive) || path_list[i].endsWith(".edat", Qt::CaseInsensitive))
|
||||
{
|
||||
sys_log.success("%s", text.toStdString());
|
||||
QMessageBox::information(m_game_list_frame, tr("Game Integrity"), text);
|
||||
// NOTE: This is the default type for any ".rap" and ".edat" due to it's not possible to detect the type by file parsing.
|
||||
// If no match for ".rap" or ".edat" will be found on default "PSN Content" DB, we will try on "PSN DLC" DB
|
||||
file_type = content_file_type::PSN_CONTENT;
|
||||
db_id = "PSN CONTENT";
|
||||
use_fallback_db = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_log.error("%s", text.toStdString());
|
||||
QMessageBox::critical(m_game_list_frame, tr("Game Integrity"), text);
|
||||
const compat::package_info info = game_compatibility::GetPkgInfo(path_list[i], m_game_list_frame->GetGameCompatibility());
|
||||
|
||||
switch (info.type)
|
||||
{
|
||||
case compat::package_type::update:
|
||||
file_type = content_file_type::PSN_UPDATE;
|
||||
db_id = "PSN UPDATE";
|
||||
break;
|
||||
case compat::package_type::dlc:
|
||||
file_type = content_file_type::PSN_DLC;
|
||||
db_id = "PSN DLC";
|
||||
break;
|
||||
case compat::package_type::other:
|
||||
file_type = content_file_type::PSN_CONTENT;
|
||||
db_id = "PSN CONTENT";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the validator (set also file size etc.)
|
||||
m_game_validator->init_hash(path_list[i].toStdString());
|
||||
|
||||
if (m_game_validator->calculate_hash(hash) == content_hash_status::COMPLETED)
|
||||
{
|
||||
content_integrity_status integrity_status = m_game_validator->check_integrity(file_type, hash, &game_name);
|
||||
|
||||
// If no match for ".rap" or ".edat" is found on default "PSN Content" DB, try on "PSN DLC" DB
|
||||
if (integrity_status == content_integrity_status::NO_MATCH && use_fallback_db)
|
||||
{
|
||||
db_id += " -> PSN DLC";
|
||||
integrity_status = m_game_validator->check_integrity(content_file_type::PSN_DLC, hash, &game_name);
|
||||
}
|
||||
|
||||
switch (integrity_status)
|
||||
{
|
||||
case content_integrity_status::NO_MATCH:
|
||||
text_result += tr("Game check NOT PASSED\n\nNo match found on '%0' DB or game corrupted:\n - File: %1\n - Hash: %2")
|
||||
.arg(QString::fromStdString(db_id))
|
||||
.arg(QString::fromStdString(m_game_validator->get_name()))
|
||||
.arg(QString::fromStdString(hash));
|
||||
|
||||
info_dialog = false;
|
||||
break;
|
||||
case content_integrity_status::FOUND_MATCH:
|
||||
text_result += tr("Game check PASSED\n\nMatch found on '%0' DB:\n - File: %1\n - Hash: %2\n - Game: %3")
|
||||
.arg(QString::fromStdString(db_id))
|
||||
.arg(QString::fromStdString(m_game_validator->get_name()))
|
||||
.arg(QString::fromStdString(hash))
|
||||
.arg(QString::fromStdString(game_name));
|
||||
break;
|
||||
default:
|
||||
text_result += tr("Error parsing '%0' DB or DB not existing:\n - File: %1\n - Hash: %2")
|
||||
.arg(QString::fromStdString(db_id))
|
||||
.arg(QString::fromStdString(m_game_validator->get_name()))
|
||||
.arg(QString::fromStdString(hash));
|
||||
|
||||
info_dialog = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (i < path_list.size() - 1) // If it's not the last processed entry, add empty lines as separator
|
||||
{
|
||||
text_result += "\n\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (m_game_validator->get_status() == content_hash_status::ABORTED)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QString text_dialog;
|
||||
|
||||
if (m_game_validator->get_status() == content_hash_status::ABORTED)
|
||||
{
|
||||
text_dialog = "Hash calculation failed!\n\nIntegrity check aborted";
|
||||
info_dialog = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
text_dialog = "Integrity check completed!\n\n" + text_result;
|
||||
}
|
||||
|
||||
// Tell the progress bar thread to terminate
|
||||
m_game_validator->set_count(0);
|
||||
|
||||
Emu.CallFromMainThread([this, text_dialog, info_dialog]()
|
||||
{
|
||||
if (info_dialog)
|
||||
{
|
||||
sys_log.success("%s", text_dialog.toStdString());
|
||||
QMessageBox::information(m_game_list_frame, tr("Game Integrity"), text_dialog);
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_log.error("%s", text_dialog.toStdString());
|
||||
QMessageBox::critical(m_game_list_frame, tr("Game Integrity"), text_dialog);
|
||||
}
|
||||
}, nullptr, false);
|
||||
});
|
||||
|
||||
progress_dialog* pdlg = new progress_dialog(tr("ISO File Hash Calculation"), tr("Calculating hash"), tr("Cancel"),
|
||||
progress_dialog* pdlg = new progress_dialog(tr("File Hash Calculation"), "", tr("Cancel"),
|
||||
0, 100, false, m_game_list_frame);
|
||||
|
||||
pdlg->setAutoClose(false);
|
||||
@ -436,21 +537,22 @@ void game_list_actions::ShowGameIntegrityDialog(const game_info& game)
|
||||
|
||||
connect(pdlg, &progress_dialog::canceled, m_game_list_frame, [this]()
|
||||
{
|
||||
m_iso_validator->abort_hash();
|
||||
m_game_validator->abort_hash();
|
||||
});
|
||||
|
||||
QTimer* update_timer = new QTimer(m_game_list_frame);
|
||||
|
||||
connect(update_timer, &QTimer::timeout, m_game_list_frame, [this, pdlg, update_timer]()
|
||||
{
|
||||
if (m_iso_validator->get_status() == iso_hash_status::INITIALIZED)
|
||||
if (m_game_validator->get_count())
|
||||
{
|
||||
// Set progress in range 0-100
|
||||
const int progress = m_iso_validator->get_size() ?
|
||||
(static_cast<float>(m_iso_validator->get_bytes_read()) / m_iso_validator->get_size()) * 100 :
|
||||
const int progress = m_game_validator->get_size() ?
|
||||
(static_cast<float>(m_game_validator->get_bytes_read()) / m_game_validator->get_size()) * 100 :
|
||||
0;
|
||||
|
||||
pdlg->setValue(progress);
|
||||
pdlg->setLabelText(tr("Calculating hash: %0").arg(m_game_validator->get_name()));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
#include "gui_game_info.h"
|
||||
#include "shortcut_utils.h"
|
||||
#include "Loader/iso_validation.h"
|
||||
#include "Loader/content_validation.h"
|
||||
|
||||
#include <QFuture>
|
||||
#include <QObject>
|
||||
@ -55,7 +55,7 @@ public:
|
||||
|
||||
void ShowRemoveGameDialog(const std::vector<game_info>& games);
|
||||
void ShowGameInfoDialog(const std::vector<game_info>& games);
|
||||
void ShowGameIntegrityDialog(const game_info& game);
|
||||
void ShowGameIntegrityDialog(content_file_type file_type, const game_info& game);
|
||||
void ShowDiskUsageDialog();
|
||||
|
||||
// NOTES:
|
||||
@ -100,7 +100,7 @@ private:
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
QFuture<void> m_disk_usage_future;
|
||||
QFuture<void> m_game_integrity_future;
|
||||
std::shared_ptr<iso_file_validation> m_iso_validator = std::make_shared<iso_file_validation>();
|
||||
std::shared_ptr<content_validation> m_game_validator = std::make_shared<content_validation>();
|
||||
|
||||
// NOTE:
|
||||
// m_content_info is used by:
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
#include "Utilities/File.h"
|
||||
#include "Emu/system_utils.hpp"
|
||||
#include "Loader/ISO.h"
|
||||
#include "Loader/iso_validation.h"
|
||||
#include "Loader/content_validation.h"
|
||||
|
||||
#include "QApplication"
|
||||
#include "QClipboard"
|
||||
@ -602,41 +602,57 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
|
||||
|
||||
addSeparator();
|
||||
|
||||
// Check integrity
|
||||
// Check Integrity menu
|
||||
QMenu* check_integrity_menu = addMenu(tr("&Check Integrity"));
|
||||
|
||||
// Check disc game integrity
|
||||
if (QString::fromStdString(current_game.category) == cat::cat_disc_game)
|
||||
{
|
||||
const bool raw_archive = is_iso_file(current_game.path);
|
||||
const iso_type_status iso_type = iso_file_decryption::check_type(current_game.path);
|
||||
|
||||
// If it's an ISO file (e.g. even a decrypted ISO), always provide the entry on the context menu but disable
|
||||
// it if the ISO does not support integrity check (e.g. non Redump ISO) or no integrity DB is found.
|
||||
// That is to highlight a Redump ISO from a non Redump ISO
|
||||
if (iso_type != iso_type_status::NOT_ISO)
|
||||
if (raw_archive || iso_type != iso_type_status::NOT_ISO)
|
||||
{
|
||||
const iso_integrity_status iso_integrity = iso_file_validation::check_integrity("");
|
||||
|
||||
QAction* check_integrity = addAction(tr("&Check ISO Integrity"));
|
||||
QAction* check_iso = check_integrity_menu->addAction(tr("&Check ISO Integrity"));
|
||||
|
||||
// If it's a Redump ISO and the integrity DB exists
|
||||
if (iso_type == iso_type_status::REDUMP_ISO && iso_integrity != iso_integrity_status::ERROR_OPENING_DB)
|
||||
if ((raw_archive || iso_type == iso_type_status::REDUMP_ISO) &&
|
||||
content_validation::check_integrity(content_file_type::ISO, "") != content_integrity_status::ERROR_OPENING_DB)
|
||||
{
|
||||
connect(check_integrity, &QAction::triggered, this, [this, gameinfo]()
|
||||
connect(check_iso, &QAction::triggered, this, [this, gameinfo]()
|
||||
{
|
||||
m_game_list_actions->ShowGameIntegrityDialog(gameinfo);
|
||||
m_game_list_actions->ShowGameIntegrityDialog(content_file_type::ISO, gameinfo);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
check_integrity->setEnabled(false);
|
||||
check_iso->setEnabled(false);
|
||||
}
|
||||
|
||||
QAction* download_integrity = addAction(tr("&Download Integrity Database"));
|
||||
connect(download_integrity, &QAction::triggered, m_game_list_frame, [this]
|
||||
{
|
||||
ensure(m_game_list_frame->GetIsoIntegrity())->download();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check integrity for the other categories based on .PKG, .RAP and .EDAT (e.g. HDD game, DLC, Update)
|
||||
QAction* check_psn_content = check_integrity_menu->addAction(tr("&Check Packages/Raps/Edats Integrity"));
|
||||
|
||||
connect(check_psn_content, &QAction::triggered, this, [this, gameinfo]()
|
||||
{
|
||||
// File type different than ISO as passed here (PSN_CONTENT) will be properly detected in
|
||||
// ShowGameIntegrityDialog() based on the selected package file
|
||||
m_game_list_actions->ShowGameIntegrityDialog(content_file_type::PSN_CONTENT, gameinfo);
|
||||
});
|
||||
|
||||
QAction* download_integrity = addAction(tr("&Download Integrity Databases"));
|
||||
connect(download_integrity, &QAction::triggered, m_game_list_frame, [this]
|
||||
{
|
||||
ensure(m_game_list_frame->GetIsoIntegrity())->download();
|
||||
ensure(m_game_list_frame->GetPsnContentIntegrity())->download();
|
||||
ensure(m_game_list_frame->GetPsnDlcIntegrity())->download();
|
||||
ensure(m_game_list_frame->GetPsnUpdateIntegrity())->download();
|
||||
});
|
||||
|
||||
QAction* check_compat = addAction(tr("&Check Game Compatibility"));
|
||||
QAction* download_compat = addAction(tr("&Download Compatibility Database"));
|
||||
QAction* download_config_db = addAction(tr("&Download Config Database"));
|
||||
@ -1001,6 +1017,15 @@ void game_list_context_menu::show_multi_selection_context_menu(const std::vector
|
||||
|
||||
addSeparator();
|
||||
|
||||
QAction* download_integrity = addAction(tr("&Download Integrity Databases"));
|
||||
connect(download_integrity, &QAction::triggered, m_game_list_frame, [this]
|
||||
{
|
||||
ensure(m_game_list_frame->GetIsoIntegrity())->download();
|
||||
ensure(m_game_list_frame->GetPsnContentIntegrity())->download();
|
||||
ensure(m_game_list_frame->GetPsnDlcIntegrity())->download();
|
||||
ensure(m_game_list_frame->GetPsnUpdateIntegrity())->download();
|
||||
});
|
||||
|
||||
QAction* download_compat = addAction(tr("&Download Compatibility Database"));
|
||||
connect(download_compat, &QAction::triggered, m_game_list_frame, [this]
|
||||
{
|
||||
|
||||
@ -74,7 +74,10 @@ game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std
|
||||
m_game_list->installEventFilter(this);
|
||||
m_game_list->verticalScrollBar()->installEventFilter(this);
|
||||
|
||||
m_iso_integrity = new iso_integrity(this);
|
||||
m_iso_integrity = new content_integrity(this, content_file_type::ISO);
|
||||
m_psn_content_integrity = new content_integrity(this, content_file_type::PSN_CONTENT);
|
||||
m_psn_dlc_integrity = new content_integrity(this, content_file_type::PSN_DLC);
|
||||
m_psn_update_integrity = new content_integrity(this, content_file_type::PSN_UPDATE);
|
||||
m_game_compat = new game_compatibility(this);
|
||||
m_config_db = new config_database(this);
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include "game_list.h"
|
||||
#include "game_list_actions.h"
|
||||
#include "custom_dock_widget.h"
|
||||
#include "iso_integrity.h"
|
||||
#include "content_integrity.h"
|
||||
|
||||
#include "Utilities/lockless.h"
|
||||
#include "Utilities/mutex.h"
|
||||
@ -56,7 +56,10 @@ public:
|
||||
|
||||
void SetShowHidden(bool show);
|
||||
|
||||
iso_integrity* GetIsoIntegrity() const { return m_iso_integrity; }
|
||||
content_integrity* GetIsoIntegrity() const { return m_iso_integrity; }
|
||||
content_integrity* GetPsnContentIntegrity() const { return m_psn_content_integrity; }
|
||||
content_integrity* GetPsnDlcIntegrity() const { return m_psn_dlc_integrity; }
|
||||
content_integrity* GetPsnUpdateIntegrity() const { return m_psn_update_integrity; }
|
||||
game_compatibility* GetGameCompatibility() const { return ensure(m_game_compat); }
|
||||
config_database* GetConfigDatabase() const { return ensure(m_config_db); }
|
||||
const std::vector<game_info>& GetGameInfo() const { return m_game_data; }
|
||||
@ -156,7 +159,10 @@ private:
|
||||
|
||||
// Game List
|
||||
game_list_table* m_game_list = nullptr;
|
||||
iso_integrity* m_iso_integrity = nullptr;
|
||||
content_integrity* m_iso_integrity = nullptr;
|
||||
content_integrity* m_psn_content_integrity = nullptr;
|
||||
content_integrity* m_psn_dlc_integrity = nullptr;
|
||||
content_integrity* m_psn_update_integrity = nullptr;
|
||||
game_compatibility* m_game_compat = nullptr;
|
||||
config_database* m_config_db = nullptr;
|
||||
progress_dialog* m_progress_dialog = nullptr;
|
||||
|
||||
@ -1,113 +0,0 @@
|
||||
#include "iso_integrity.h"
|
||||
#include "gui_settings.h"
|
||||
|
||||
#include "Emu/system_utils.hpp"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
LOG_CHANNEL(iso_log, "ISO");
|
||||
|
||||
iso_integrity::iso_integrity(QWidget* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_filepath = QString::fromStdString(rpcs3::utils::get_redump_db_path());
|
||||
m_downloader = new downloader(parent);
|
||||
|
||||
connect(m_downloader, &downloader::signal_download_finished, this, &iso_integrity::handle_download_finished);
|
||||
connect(m_downloader, &downloader::signal_download_canceled, this, &iso_integrity::handle_download_canceled);
|
||||
connect(m_downloader, &downloader::signal_download_error, this, &iso_integrity::handle_download_error);
|
||||
}
|
||||
|
||||
void iso_integrity::download()
|
||||
{
|
||||
const std::string url = rpcs3::utils::get_redump_db_download_url();
|
||||
|
||||
iso_log.notice("Starting database download from: %s", url);
|
||||
|
||||
m_downloader->start(url, true, true, true, tr("Downloading database"));
|
||||
}
|
||||
|
||||
void iso_integrity::handle_download_finished(const QByteArray& content)
|
||||
{
|
||||
iso_log.notice("Database download finished");
|
||||
|
||||
// Write database to file
|
||||
if (QByteArray data = read_json(content, true); !data.isEmpty())
|
||||
{
|
||||
QFile file(m_filepath);
|
||||
|
||||
if (file.exists())
|
||||
{
|
||||
iso_log.notice("Database file found: %s", m_filepath);
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
iso_log.error("Failed to write database to file: %s", m_filepath);
|
||||
return;
|
||||
}
|
||||
|
||||
file.write(data);
|
||||
file.close();
|
||||
|
||||
iso_log.success("Database written to file: %s", m_filepath);
|
||||
}
|
||||
}
|
||||
|
||||
void iso_integrity::handle_download_canceled()
|
||||
{
|
||||
iso_log.notice("Database download canceled");
|
||||
}
|
||||
|
||||
void iso_integrity::handle_download_error(const QString& error)
|
||||
{
|
||||
iso_log.error("", error.toStdString().c_str());
|
||||
}
|
||||
|
||||
QByteArray iso_integrity::read_json(const QByteArray& data, bool after_download)
|
||||
{
|
||||
QJsonParseError error{};
|
||||
const QJsonDocument json_document = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
if (!json_document.isObject())
|
||||
{
|
||||
iso_log.error("ISO Integrity database error - Invalid JSON: '%s'", error.errorString());
|
||||
return {};
|
||||
}
|
||||
|
||||
const QJsonObject json_data = json_document.object();
|
||||
const int return_code = json_data["return_code"].toInt(-255);
|
||||
|
||||
if (return_code < 0)
|
||||
{
|
||||
if (after_download)
|
||||
{
|
||||
std::string error_message;
|
||||
|
||||
switch (return_code)
|
||||
{
|
||||
case -1: error_message = "Server Error - Internal Error"; break;
|
||||
case -2: error_message = "Server Error - Maintenance Mode"; break;
|
||||
case -255: error_message = "Server Error - Return code not found"; break;
|
||||
default: error_message = "Server Error - Unknown Error"; break;
|
||||
}
|
||||
|
||||
iso_log.error("%s: return code %d", error_message, return_code);
|
||||
}
|
||||
else
|
||||
{
|
||||
iso_log.error("ISO Integrity database error - Invalid: return code %d", return_code);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!json_data["redump"].isString())
|
||||
{
|
||||
iso_log.error("ISO Integrity database error - Unusable Redump string");
|
||||
return {};
|
||||
}
|
||||
|
||||
return QByteArray().fromStdString(json_data["redump"].toString().toStdString());
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user