Skip to content

Commit 43cee06

Browse files
committed
Improve UX when loading shared cache.
Closes #8020 and #8021.
1 parent 83e9452 commit 43cee06

1 file changed

Lines changed: 126 additions & 7 deletions

File tree

view/sharedcache/core/SharedCacheView.cpp

Lines changed: 126 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ using namespace BinaryNinja::DSC;
1010

1111
static 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+
1326
SharedCacheViewType::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)
10041027
std::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

Comments
 (0)