@@ -31,8 +31,6 @@ const char *faulty_reason_to_string(FaultyFileDetector::FaultyReason reason) {
3131 return " Missing file" ;
3232 case FaultyFileDetector::FaultyReason::Invalid:
3333 return " Invalid file" ;
34- case FaultyFileDetector::FaultyReason::ShouldBeEncrypted:
35- return " PAK should be encrypted" ;
3634 default :
3735 return " Unknown reason" ;
3836 }
@@ -532,7 +530,6 @@ void FaultyFileDetector::on_draw_ui() {
532530 {FaultyReason::Unknown, {" Unknown" , ImVec4 (0 .8f , 0 .8f , 0 .8f , 1 .0f )}},
533531 {FaultyReason::MissingFile, {" Missing File" , ImVec4 (1 .0f , 1 .0f , 0 .0f , 1 .0f )}},
534532 {FaultyReason::Invalid, {" Invalid File" , ImVec4 (1 .0f , 0 .5f , 0 .0f , 1 .0f )}},
535- {FaultyReason::ShouldBeEncrypted, {" Should Be Encrypted" , ImVec4 (1 .0f , 0 .0f , 0 .0f , 1 .0f )}},
536533 };
537534
538535 if (ImGui::TreeNode (" Show recent##ShowRecentFaultyFiles" )) {
@@ -575,243 +572,14 @@ void FaultyFileDetector::on_draw_ui() {
575572 }
576573}
577574
578- void FaultyFileDetector::on_pak_load_result (bool success, std::wstring_view pak_name) {
579- FaultyBufferEntry faulty_entry{};
580- if (success) {
581- faulty_entry = detect_if_success_pak_still_suspicious (pak_name);
582- } else {
583- faulty_entry = determine_pak_faulty_tier (pak_name);
584- }
585- if (faulty_entry.tier != FaultyTier::None) {
586- if (s_instance == nullptr ) {
587- faulty_entry.filename = std::wstring{pak_name};
588- s_buffered_faulties.push_back (faulty_entry);
589- } else {
590- s_instance->try_add_to_faulty_list (pak_name, faulty_entry.tier , faulty_entry.reason );
591- }
592- }
593- }
594-
595- std::optional<int > FaultyFileDetector::extract_patch_version_from_pak_name (std::wstring_view pak_name) {
596- auto pak_name_copy = std::wstring{pak_name};
597- std::wsmatch match;
598-
599- if (std::regex_match (pak_name_copy, match, s_extract_patch_version_regex)) {
600- if (match.size () >= 3 ) {
601- try {
602- return std::stoi (match[2 ].str ());
603- } catch (const std::exception& e) {
604- spdlog::warn (" [FaultyFileDetector]: Failed to parse patch version from PAK name: {}" , utility::narrow (pak_name));
605- }
606- }
607- }
608-
609- return std::nullopt ;
610- }
611-
612- bool FaultyFileDetector::check_is_stock_patch_pak (std::wstring_view pak_name) {
613- cache_patch_version ();
614-
615- auto version_opt = extract_patch_version_from_pak_name (pak_name);
616- if (version_opt.has_value ()) {
617- int pak_version = version_opt.value ();
618- if (s_patch_version_cached && pak_version <= s_patch_version) {
619- return true ;
620- }
621- }
622- return false ;
623- }
624-
625- FaultyFileDetector::FaultyBufferEntry FaultyFileDetector::detect_if_success_pak_still_suspicious (std::wstring_view pak_name) {
626- struct PAKHeaderSimple {
627- char signature[4 ];
628- uint8_t major_version;
629- uint8_t minor_version;
630- uint8_t flags;
631- };
632-
633- enum PAKFlags : uint8_t {
634- Encrypted = 1 << 3 ,
635- };
636-
637- const char *signature = " KPKA" ;
638-
639- auto game = utility::get_executable ();
640- auto game_path_opt = utility::get_module_path (game);
641-
642- FaultyFileDetector::FaultyBufferEntry result{};
643-
644- if (game_path_opt.has_value ()) {
645- auto game_path = std::filesystem::path (game_path_opt.value ()).parent_path ();
646- auto pak_path = game_path / pak_name;
647-
648- if (!std::filesystem::exists (pak_path)) {
649- result.tier = FaultyFileDetector::FaultyTier::Severe;
650- result.reason = FaultyFileDetector::FaultyReason::MissingFile;
651-
652- return result;
653- } else {
654- // Read PAK header
655- std::ifstream pak_file_stream{pak_path, std::ios::binary};
656- if (!pak_file_stream.is_open ()) {
657- spdlog::warn (" [FaultyFileDetector]: Failed to open PAK file for reading: {}" , utility::narrow (pak_path.wstring ()));
658- result.tier = FaultyFileDetector::FaultyTier::Warning;
659- result.reason = FaultyFileDetector::FaultyReason::MissingFile;
660-
661- return result;
662- } else {
663- PAKHeaderSimple header{};
664- pak_file_stream.read (reinterpret_cast <char *>(&header), sizeof (PAKHeaderSimple));
665-
666- if (!pak_file_stream) {
667- spdlog::warn (" [FaultyFileDetector]: Failed to read PAK header from file: {}" , utility::narrow (pak_path.wstring ()));
668- result.tier = FaultyFileDetector::FaultyTier::Warning;
669- result.reason = FaultyFileDetector::FaultyReason::MissingFile;
670- return result;
671- }
672-
673- if (std::memcmp (header.signature , signature, 4 ) != 0 ) {
674- spdlog::warn (" [FaultyFileDetector]: Invalid PAK signature in file: {}" , utility::narrow (pak_path.wstring ()));
675- result.tier = FaultyFileDetector::FaultyTier::Severe;
676- result.reason = FaultyFileDetector::FaultyReason::Invalid;
677- return result;
678- }
679-
680- if (pak_name.contains (L" patch" )) {
681- bool is_stock_patch = check_is_stock_patch_pak (pak_name);
682- if (is_stock_patch) {
683- // Stock patch PAK should be encrypted (put it as warning for now)
684- if (!(header.flags & PAKFlags::Encrypted)) {
685- spdlog::warn (" [FaultyFileDetector]: Stock patch PAK is not encrypted: {}" , utility::narrow (pak_path.wstring ()));
686- result.tier = FaultyFileDetector::FaultyTier::Warning;
687- result.reason = FaultyFileDetector::FaultyReason::ShouldBeEncrypted;
688- return result;
689- }
690- }
691- }
692-
693- return result;
694- }
695- }
696- } else {
697- return result;
698- }
699- }
700-
701- FaultyFileDetector::FaultyBufferEntry FaultyFileDetector::determine_pak_faulty_tier (std::wstring_view pak_name) {
702- FaultyFileDetector::FaultyBufferEntry result{};
703-
704- if (pak_name.contains (L" dlc" )) {
705- result.tier = FaultyTier::Severe;
706- result.reason = FaultyReason::Invalid;
707-
708- return result;
709- }
710-
711- auto patch_position_in_name = pak_name.find (L" patch" );
712-
713- if (patch_position_in_name != std::wstring_view::npos) {
714- cache_patch_version ();
715-
716- auto game_module = utility::get_executable ();
717- auto game_path_opt = utility::get_module_path (game_module);
718-
719- if (game_path_opt.has_value ()) {
720- auto game_path = std::filesystem::path (game_path_opt.value ()).parent_path ();
721- auto filename_before_patch = pak_name.substr (0 , std::max<int >(0 , (int )patch_position_in_name - 1 ));
722-
723- auto patch_target_pak = game_path / filename_before_patch;
724- if (!std::filesystem::exists (patch_target_pak)) {
725- // Target patch PAK does not exist, false positive
726- return result;
727- } else {
728- // Target patch PAK exists, check if its existence is necessary first
729- bool is_stock_patch_pak = check_is_stock_patch_pak (pak_name);
730-
731- if (is_stock_patch_pak) {
732- result.tier = FaultyTier::Severe;
733-
734- if (std::filesystem::exists (game_path / pak_name)) {
735- result.reason = FaultyReason::Invalid;
736- } else {
737- result.reason = FaultyReason::MissingFile;
738- }
739-
740- return result;
741- } else {
742- // The PAK is probably in modded PAK range, if it does not exist then its fine
743- auto pak_path = game_path / pak_name;
744- if (std::filesystem::exists (pak_path)) {
745- result.tier = FaultyTier::Severe;
746- result.reason = FaultyReason::Invalid;
747-
748- return result;
749- } else {
750- return result;
751- }
752- }
753- }
754- }
755- } else {
756- result.tier = FaultyTier::Severe;
757- result.reason = FaultyReason::Invalid;
758- }
759-
760- return result;
761- }
762-
763- void FaultyFileDetector::cache_patch_version () {
764- if (s_patch_version_cached) {
765- return ;
766- }
767-
768- const wchar_t *patch_version_prefix = L" /Environment/Package/PatchVersion:" ;
769-
770- auto argument_count_method = sdk::find_method_definition (" via.Application" , " getArgumentCount" );
771- if (argument_count_method == nullptr ) {
772- spdlog::warn (" [FaultyFileDetector]: Failed to find via.Application::getArgumentCount method for patch version detection" );
773- return ;
774- }
775-
776- int argument_count = argument_count_method->call <int >();
777- for (int i = 0 ; i < argument_count; i++) {
778- auto argument_value_method = sdk::find_method_definition (" via.Application" , " getArgument" );
779- if (argument_value_method == nullptr ) {
780- spdlog::warn (" [FaultyFileDetector]: Failed to find via.Application::getArgument method for patch version detection" );
781- return ;
782- }
783-
784- auto arg_value_obj = argument_value_method->call <SystemString*>(sdk::get_thread_context (), i);
785- if (arg_value_obj != nullptr ) {
786- auto arg_str = utility::re_string::get_view (arg_value_obj);
787- spdlog::info (" [FaultyFileDetector]: Detected argument: {}" , utility::narrow (arg_str));
788- if (arg_str.starts_with (patch_version_prefix)) {
789- auto version_str = arg_str.substr (wcslen (patch_version_prefix));
790- try {
791- s_patch_version = std::stoi (utility::narrow (version_str));
792- s_patch_version_cached = true ;
793-
794- spdlog::info (" [FaultyFileDetector]: Detected patch version: {}" , s_patch_version);
795- } catch (const std::exception& e) {
796- spdlog::warn (" [FaultyFileDetector]: Failed to parse patch version from argument: {}" , utility::narrow (arg_str));
797- }
798- break ;
799- }
800- } else {
801- spdlog::warn (" [FaultyFileDetector]: Argument value at index {} is null" , i);
802- }
803- }
804- }
805-
806575void FaultyFileDetector::early_init () {
807576 if (g_faulty_detector_instance == nullptr ) {
808577 g_faulty_detector_instance = std::make_unique<FaultyFileDetector>();
809578 }
810579
811- IntegrityCheckBypass::add_pak_load_result_listener (&FaultyFileDetector::on_pak_load_result);
812580 g_faulty_detector_instance->initialize_impl ();
813581}
814582
815- std::shared_ptr<FaultyFileDetector>& FaultyFileDetector::get_existing_instance () {
583+ std::shared_ptr<FaultyFileDetector>& FaultyFileDetector::get () {
816584 return g_faulty_detector_instance;
817585}
0 commit comments