@@ -10,6 +10,19 @@ using namespace BinaryNinja::DSC;
1010
1111static const char * VIEW_METADATA_KEY = " shared_cache_view" ;
1212
13+ static bool IsBndbPath (const std::string& path)
14+ {
15+ return std::filesystem::path (path).extension () == " .bndb" ;
16+ }
17+
18+
19+ static bool IsUsablePrimaryCachePath (const std::string& path)
20+ {
21+ std::error_code ec;
22+ return !path.empty () && !IsBndbPath (path) && std::filesystem::exists (path, ec)
23+ && std::filesystem::is_regular_file (path, ec);
24+ }
25+
1326SharedCacheViewType::SharedCacheViewType () : BinaryViewType(VIEW_NAME , VIEW_NAME ) {}
1427
1528// We register all our one-shot stuff here, such as the object destructor.
@@ -103,6 +116,16 @@ Ref<Settings> SharedCacheViewType::GetLoadSettingsForData(BinaryView* data)
103116 "description" : "Add function starts sourced from the Function Starts tables to the core for analysis."
104117 })" );
105118
119+ settings->RegisterSetting (" loader.dsc.primaryFilePath" ,
120+ R"( {
121+ "title" : "Primary Shared Cache File Path",
122+ "type" : "string",
123+ "default" : "",
124+ "description" : "Path to the primary dyld shared cache file to use when opening this database. This is useful for headless or scripted database loading.",
125+ "ignore" : ["SettingsUserScope", "SettingsProjectScope"],
126+ "uiSelectionAction" : "file"
127+ })" );
128+
106129 // Place the synthetic sections well after the shared cache to ensure they do
107130 // not collide with any images that are later loaded from the shared cache.
108131 // We do not have easy access to the size of the shared cache's mapping here
@@ -1004,17 +1027,106 @@ void SharedCacheView::LogSecondaryFileName(std::string secondaryFileName)
10041027std::optional<std::string> SharedCacheView::GetPrimaryFilePath ()
10051028{
10061029 auto viewFile = GetFile ();
1030+
1031+ auto settings = GetLoadSettings (GetTypeName ());
1032+ if (settings && settings->Contains (" loader.dsc.primaryFilePath" ))
1033+ {
1034+ auto configuredPrimaryFilePath = settings->Get <std::string>(" loader.dsc.primaryFilePath" , this );
1035+ if (!configuredPrimaryFilePath.empty ())
1036+ {
1037+ settings->Reset (" loader.dsc.primaryFilePath" , this , SettingsResourceScope);
1038+ if (!IsUsablePrimaryCachePath (configuredPrimaryFilePath))
1039+ {
1040+ m_logger->LogErrorF (
1041+ " Configured primary shared cache file path is invalid: '{}'" , configuredPrimaryFilePath);
1042+ if (!IsUIEnabled ())
1043+ return std::nullopt ;
1044+ }
1045+ else
1046+ {
1047+ SetPrimaryFileName (BaseFileName (configuredPrimaryFilePath));
1048+ return configuredPrimaryFilePath;
1049+ }
1050+ }
1051+ }
1052+
1053+ auto promptForPrimaryFile = [&]() -> std::optional<std::string> {
1054+ if (!IsUIEnabled ())
1055+ {
1056+ m_logger->LogErrorF (
1057+ " Primary shared cache file '{}' could not be resolved. Provide loader.dsc.primaryFilePath when loading this database headlessly." ,
1058+ m_primaryFileName);
1059+ return std::nullopt ;
1060+ }
1061+
1062+ ShowMessageBox (" Select Primary Shared Cache File" ,
1063+ " Binary Ninja needs the original primary dyld shared cache file to reopen this database. "
1064+ " Select the primary dyld_shared_cache file, not another .bndb database." , OKButtonSet, InformationIcon);
1065+
1066+ std::string newPrimaryFilePath;
1067+ std::string prompt = " Select primary shared cache file" ;
1068+ if (!m_primaryFileName.empty ())
1069+ prompt += " '" + m_primaryFileName + " '" ;
1070+ if (!GetOpenFileNameInput (newPrimaryFilePath, prompt))
1071+ return std::nullopt ;
1072+
1073+ if (IsBndbPath (newPrimaryFilePath))
1074+ {
1075+ m_logger->LogAlertF (
1076+ " Selected primary shared cache path is a Binary Ninja database, not a dyld shared cache file: '{}'" ,
1077+ newPrimaryFilePath);
1078+ return std::nullopt ;
1079+ }
1080+
1081+ if (!IsUsablePrimaryCachePath (newPrimaryFilePath))
1082+ {
1083+ m_logger->LogAlertF (" Selected primary shared cache path is not a usable file: '{}'" , newPrimaryFilePath);
1084+ return std::nullopt ;
1085+ }
1086+
1087+ return newPrimaryFilePath;
1088+ };
1089+
10071090 // 1. Try and get the primary file path using `GetOriginalFilename`.
10081091 auto primaryFilePath = viewFile->GetOriginalFilename ();
10091092
10101093 // 2. If the original file name is not a usable file path then prompt the user to select one.
1011- if (primaryFilePath. empty () || ! std::filesystem::exists (primaryFilePath))
1094+ if (! IsUsablePrimaryCachePath (primaryFilePath))
10121095 {
1013- if (!GetOpenFileNameInput (primaryFilePath, " Please select the primary shared cache file" ))
1096+ std::vector<std::string> candidateNames;
1097+ if (!m_primaryFileName.empty ())
1098+ candidateNames.push_back (m_primaryFileName);
1099+ auto originalBaseName = BaseFileName (primaryFilePath);
1100+ if (!originalBaseName.empty () && originalBaseName != m_primaryFileName)
1101+ candidateNames.push_back (originalBaseName);
1102+
1103+ for (const auto & candidateName : candidateNames)
1104+ {
1105+ auto candidatePath = (std::filesystem::path (viewFile->GetFilename ()).parent_path () / candidateName).string ();
1106+ if (IsUsablePrimaryCachePath (candidatePath))
1107+ {
1108+ primaryFilePath = candidatePath;
1109+ break ;
1110+ }
1111+ }
1112+
1113+ if (!IsUsablePrimaryCachePath (primaryFilePath))
1114+ {
1115+ auto promptedPrimaryFilePath = promptForPrimaryFile ();
1116+ if (!promptedPrimaryFilePath)
1117+ return std::nullopt ;
1118+ primaryFilePath = *promptedPrimaryFilePath;
1119+ }
1120+
1121+ if (IsBndbPath (primaryFilePath))
1122+ {
1123+ m_logger->LogAlertF (
1124+ " Primary shared cache path is a Binary Ninja database, not a dyld shared cache file: '{}'" ,
1125+ primaryFilePath);
10141126 return std::nullopt ;
1127+ }
1128+
10151129 SetPrimaryFileName (BaseFileName (primaryFilePath));
1016- // Update so next load we don't need to prompt the user.
1017- viewFile->SetOriginalFilename (primaryFilePath);
10181130 }
10191131
10201132 // 3. If we are not in a project, we can go ahead and return the file path, it does not need to be resolved from project.
@@ -1047,16 +1159,23 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
10471159 return pj->GetPathOnDisk ();
10481160 }
10491161
1162+ if (IsUsablePrimaryCachePath (primaryFilePath) && BaseFileName (primaryFilePath) == m_primaryFileName)
1163+ return primaryFilePath;
1164+
10501165 // 6. If we fail to resolve the project file given the `m_primaryFileName` than we fall back to asking the user.
1051- std::string newPrimaryFilePath ;
1052- if (!GetOpenFileNameInput (newPrimaryFilePath, " Please select the primary shared cache file " ) )
1166+ auto promptedPrimaryFilePath = promptForPrimaryFile () ;
1167+ if (!promptedPrimaryFilePath )
10531168 return std::nullopt ;
1169+ std::string newPrimaryFilePath = *promptedPrimaryFilePath;
10541170
10551171 // TODO: We likely want to verify that the project file exists in the same directory as the BNDB.
10561172 // TODO: We currently require the database to exist in the same directory as the files.
10571173 // Update the primary file name for later loads, otherwise we would keep prompting to select a file.
10581174 primaryProjectFile = project->GetFileByPathOnDisk (newPrimaryFilePath);
1059- SetPrimaryFileName (primaryProjectFile->GetName ());
1175+ if (primaryProjectFile)
1176+ SetPrimaryFileName (primaryProjectFile->GetName ());
1177+ else
1178+ SetPrimaryFileName (BaseFileName (newPrimaryFilePath));
10601179 return newPrimaryFilePath;
10611180}
10621181
0 commit comments