Move android code into separate file for organisation's sake

This commit is contained in:
baldurk
2017-08-18 14:44:14 +01:00
parent bc98513088
commit 2053f21462
9 changed files with 1036 additions and 972 deletions
+2
View File
@@ -83,6 +83,8 @@ set(sources
core/remote_server.cpp
core/replay_proxy.cpp
core/replay_proxy.h
core/android.cpp
core/android.h
core/resource_manager.cpp
core/resource_manager.h
core/socket_helpers.h
+991
View File
@@ -0,0 +1,991 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2017 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "android.h"
#include <sstream>
#include "core/core.h"
#include "serialise/string_utils.h"
namespace Android
{
bool IsHostADB(const char *hostname)
{
return !strncmp(hostname, "adb:", 4);
}
void extractDeviceIDAndIndex(const string &hostname, int &index, string &deviceID)
{
if(!IsHostADB(hostname.c_str()))
return;
const char *c = hostname.c_str();
c += 4;
index = atoi(c);
c = strchr(c, ':');
if(!c)
{
index = 0;
return;
}
c++;
deviceID = c;
}
Process::ProcessResult execScript(const string &script, const string &args,
const string &workDir = ".")
{
RDCLOG("SCRIPT: %s", script.c_str());
Process::ProcessResult result;
Process::LaunchScript(script.c_str(), workDir.c_str(), args.c_str(), &result);
return result;
}
Process::ProcessResult execCommand(const string &cmd, const string &workDir = ".")
{
RDCLOG("COMMAND: %s", cmd.c_str());
size_t firstSpace = cmd.find(" ");
string exe = cmd.substr(0, firstSpace);
string args = cmd.substr(firstSpace + 1, cmd.length());
Process::ProcessResult result;
Process::LaunchProcess(exe.c_str(), workDir.c_str(), args.c_str(), &result);
return result;
}
Process::ProcessResult adbExecCommand(const string &device, const string &args)
{
string adbExePath = RenderDoc::Inst().GetConfigSetting("adbExePath");
if(adbExePath.empty())
{
string exepath;
FileIO::GetExecutableFilename(exepath);
string exedir = dirname(FileIO::GetFullPathname(exepath));
string adbpath = exedir + "/android/adb.exe";
if(FileIO::exists(adbpath.c_str()))
adbExePath = adbpath;
if(adbExePath.empty())
{
static bool warnPath = true;
if(warnPath)
{
RDCWARN("adbExePath not set, attempting to call 'adb' in working env");
warnPath = false;
}
adbExePath = "adb";
}
}
Process::ProcessResult result;
string deviceArgs;
if(device.empty())
deviceArgs = args;
else
deviceArgs = StringFormat::Fmt("-s %s %s", device.c_str(), args.c_str());
return execCommand(string(adbExePath + " " + deviceArgs).c_str());
}
string adbGetDeviceList()
{
return adbExecCommand("", "devices").strStdout;
}
void adbForwardPorts(int index, const std::string &deviceID)
{
int offs = RenderDoc_AndroidPortOffset * (index + 1);
adbExecCommand(deviceID,
StringFormat::Fmt("forward tcp:%i tcp:%i", RenderDoc_RemoteServerPort + offs,
RenderDoc_RemoteServerPort));
adbExecCommand(deviceID,
StringFormat::Fmt("forward tcp:%i tcp:%i", RenderDoc_FirstTargetControlPort + offs,
RenderDoc_FirstTargetControlPort));
}
uint32_t StartAndroidPackageForCapture(const char *host, const char *package)
{
int index = 0;
std::string deviceID;
Android::extractDeviceIDAndIndex(host, index, deviceID);
string packageName = basename(string(package)); // Remove leading '/' if any
adbExecCommand(deviceID, "shell am force-stop " + packageName);
adbForwardPorts(index, deviceID);
adbExecCommand(deviceID, "shell setprop debug.vulkan.layers VK_LAYER_RENDERDOC_Capture");
// Creating the capture file
adbExecCommand(deviceID,
"shell pm grant " + packageName + " android.permission.WRITE_EXTERNAL_STORAGE");
// Reading the capture thumbnail
adbExecCommand(deviceID,
"shell pm grant " + packageName + " android.permission.READ_EXTERNAL_STORAGE");
adbExecCommand(deviceID,
"shell monkey -p " + packageName + " -c android.intent.category.LAUNCHER 1");
uint32_t ret = RenderDoc_FirstTargetControlPort + RenderDoc_AndroidPortOffset * (index + 1);
uint32_t elapsed = 0,
timeout = 1000 *
RDCMAX(5, atoi(RenderDoc::Inst().GetConfigSetting("MaxConnectTimeout").c_str()));
while(elapsed < timeout)
{
// Check if the target app has started yet and we can connect to it.
ITargetControl *control = RENDERDOC_CreateTargetControl(host, ret, "testConnection", false);
if(control)
{
control->Shutdown();
break;
}
Threading::Sleep(1000);
elapsed += 1000;
}
// Let the app pickup the setprop before we turn it back off for replaying.
adbExecCommand(deviceID, "shell setprop debug.vulkan.layers :");
return ret;
}
bool SearchForAndroidLayer(const string &deviceID, const string &location, const string &layerName)
{
RDCLOG("Checking for layers in: %s", location.c_str());
string findLayer =
adbExecCommand(deviceID, "shell find " + location + " -name " + layerName).strStdout;
if(!findLayer.empty())
{
RDCLOG("Found RenderDoc layer in %s", location.c_str());
return true;
}
return false;
}
bool RemoveAPKSignature(const string &apk)
{
RDCLOG("Checking for existing signature");
// Get the list of files in META-INF
string fileList = execCommand("aapt list " + apk).strStdout;
if(fileList.empty())
return false;
// Walk through the output. If it starts with META-INF, remove it.
uint32_t fileCount = 0;
uint32_t matchCount = 0;
std::istringstream contents(fileList);
string line;
string prefix("META-INF");
while(std::getline(contents, line))
{
line = trim(line);
fileCount++;
if(line.compare(0, prefix.size(), prefix) == 0)
{
RDCDEBUG("Match found, removing %s", line.c_str());
execCommand("aapt remove " + apk + " " + line);
matchCount++;
}
}
RDCLOG("%d files searched, %d removed", fileCount, matchCount);
// Ensure no hits on second pass through
RDCDEBUG("Walk through file list again, ensure signature removed");
fileList = execCommand("aapt list " + apk).strStdout;
std::istringstream recheck(fileList);
while(std::getline(recheck, line))
{
if(line.compare(0, prefix.size(), prefix) == 0)
{
RDCERR("Match found, that means removal failed! %s", line.c_str());
return false;
}
}
return true;
}
bool AddLayerToAPK(const string &apk, const string &layerPath, const string &layerName,
const string &abi, const string &tmpDir)
{
RDCLOG("Adding RenderDoc layer");
// Run aapt from the directory containing "lib" so the relative paths are good
string relativeLayer("lib/" + abi + "/" + layerName);
string workDir = removeFromEnd(layerPath, relativeLayer);
Process::ProcessResult result = execCommand("aapt add " + apk + " " + relativeLayer, workDir);
if(result.strStdout.empty())
{
RDCERR("Failed to add layer to APK. STDERR: %s", result.strStderror.c_str());
return false;
}
return true;
}
bool RealignAPK(const string &apk, string &alignedAPK, const string &tmpDir)
{
// Re-align the APK for performance
RDCLOG("Realigning APK");
string errOut = execCommand("zipalign -f 4 " + apk + " " + alignedAPK, tmpDir).strStderror;
if(!errOut.empty())
return false;
// Wait until the aligned version exists to proceed
uint32_t elapsed = 0;
uint32_t timeout = 10000; // 10 seconds
while(elapsed < timeout)
{
if(FileIO::exists(alignedAPK.c_str()))
{
RDCLOG("Aligned APK ready to go, continuing...");
return true;
}
Threading::Sleep(1000);
elapsed += 1000;
}
RDCERR("Timeout reached aligning APK");
return false;
}
string GetAndroidDebugKey()
{
string key = FileIO::GetTempFolderFilename() + "debug.keystore";
if(FileIO::exists(key.c_str()))
return key;
string create = "keytool";
create += " -genkey";
create += " -keystore " + key;
create += " -storepass android";
create += " -alias androiddebugkey";
create += " -keypass android";
create += " -keyalg RSA";
create += " -keysize 2048";
create += " -validity 10000";
create += " -dname \"CN=, OU=, O=, L=, S=, C=\"";
Process::ProcessResult result = execCommand(create);
if(!result.strStderror.empty())
RDCERR("Failed to create debug key");
return key;
}
bool DebugSignAPK(const string &apk, const string &workDir)
{
RDCLOG("Signing with debug key");
string debugKey = GetAndroidDebugKey();
string args;
args += " sign ";
args += " --ks " + debugKey + " ";
args += " --ks-pass pass:android ";
args += " --key-pass pass:android ";
args += " --ks-key-alias androiddebugkey ";
args += apk;
execScript("apksigner", args.c_str(), workDir.c_str());
// Check for signature
string list = execCommand("aapt list " + apk).strStdout;
// Walk through the output. If it starts with META-INF, we're good
std::istringstream contents(list);
string line;
string prefix("META-INF");
while(std::getline(contents, line))
{
if(line.compare(0, prefix.size(), prefix) == 0)
{
RDCLOG("Signature found, continuing...");
return true;
}
}
RDCERR("re-sign of APK failed!");
return false;
}
bool UninstallOriginalAPK(const string &deviceID, const string &packageName, const string &workDir)
{
RDCLOG("Uninstalling previous version of application");
execCommand("adb uninstall " + packageName, workDir);
// Wait until uninstall completes
string uninstallResult;
uint32_t elapsed = 0;
uint32_t timeout = 10000; // 10 seconds
while(elapsed < timeout)
{
uninstallResult = adbExecCommand(deviceID, "shell pm path " + packageName).strStdout;
if(uninstallResult.empty())
{
RDCLOG("Package removed");
return true;
}
Threading::Sleep(1000);
elapsed += 1000;
}
RDCERR("Uninstallation of APK failed!");
return false;
}
bool ReinstallPatchedAPK(const string &deviceID, const string &apk, const string &abi,
const string &packageName, const string &workDir)
{
RDCLOG("Reinstalling APK");
execCommand("adb install --abi " + abi + " " + apk, workDir);
// Wait until re-install completes
string reinstallResult;
uint32_t elapsed = 0;
uint32_t timeout = 10000; // 10 seconds
while(elapsed < timeout)
{
reinstallResult = adbExecCommand(deviceID, "shell pm path " + packageName).strStdout;
if(!reinstallResult.empty())
{
RDCLOG("Patched APK reinstalled, continuing...");
return true;
}
Threading::Sleep(1000);
elapsed += 1000;
}
RDCERR("Reinstallation of APK failed!");
return false;
}
bool CheckPatchingRequirements()
{
// check for aapt, zipalign, apksigner, debug key
vector<string> requirements;
vector<string> missingTools;
requirements.push_back("aapt");
requirements.push_back("zipalign");
requirements.push_back("keytool");
requirements.push_back("apksigner");
requirements.push_back("java");
for(uint32_t i = 0; i < requirements.size(); i++)
{
if(FileIO::FindFileInPath(requirements[i]).empty())
missingTools.push_back(requirements[i]);
}
if(missingTools.size() > 0)
{
for(uint32_t i = 0; i < missingTools.size(); i++)
RDCERR("Missing %s", missingTools[i].c_str());
return false;
}
return true;
}
bool PullAPK(const string &deviceID, const string &pkgPath, const string &apk)
{
RDCLOG("Pulling APK to patch");
adbExecCommand(deviceID, "pull " + pkgPath + " " + apk);
// Wait until the apk lands
uint32_t elapsed = 0;
uint32_t timeout = 10000; // 10 seconds
while(elapsed < timeout)
{
if(FileIO::exists(apk.c_str()))
{
RDCLOG("Original APK ready to go, continuing...");
return true;
}
Threading::Sleep(1000);
elapsed += 1000;
}
RDCERR("Failed to pull APK");
return false;
}
bool CheckPermissions(const string &dump)
{
if(dump.find("android.permission.WRITE_EXTERNAL_STORAGE") == string::npos)
{
RDCWARN("APK missing WRITE_EXTERNAL_STORAGE permission");
return false;
}
if(dump.find("android.permission.INTERNET") == string::npos)
{
RDCWARN("APK missing INTERNET permission");
return false;
}
return true;
}
bool CheckAPKPermissions(const string &apk)
{
RDCLOG("Checking that APK can be can write to sdcard");
string badging = execCommand("aapt dump badging " + apk).strStdout;
if(badging.empty())
{
RDCERR("Unable to aapt dump %s", apk.c_str());
return false;
}
return CheckPermissions(badging);
}
bool CheckDebuggable(const string &apk)
{
RDCLOG("Checking that APK s debuggable");
string badging = execCommand("aapt dump badging " + apk).strStdout;
if(badging.find("application-debuggable"))
{
RDCERR("APK is not debuggable");
return false;
}
return true;
}
} // namespace Android
using namespace Android;
bool installRenderDocServer(const string &deviceID)
{
string targetApk = "RenderDocCmd.apk";
string serverApk;
// Check known paths for RenderDoc server
string exePath;
FileIO::GetExecutableFilename(exePath);
string exeDir = dirname(FileIO::GetFullPathname(exePath));
std::vector<std::string> paths;
#if defined(RENDERDOC_APK_PATH)
string customPath(RENDERDOC_APK_PATH);
RDCLOG("Custom APK path: %s", customPath.c_str());
if(FileIO::IsRelativePath(customPath))
customPath = exeDir + "/" + customPath;
// Check to see if APK name was included in custom path
if(!endswith(customPath, targetApk))
{
if(customPath.back() != '/')
customPath += "/";
customPath += targetApk;
}
paths.push_back(customPath);
#endif
paths.push_back(exeDir + "/android/apk/" + targetApk); // Windows install
paths.push_back(exeDir + "/../share/renderdoc/android/apk/" + targetApk); // Linux install
paths.push_back(exeDir + "/../../build-android/bin/" + targetApk); // Local build
paths.push_back(exeDir + "/../../../../../build-android/bin/" + targetApk); // macOS build
for(uint32_t i = 0; i < paths.size(); i++)
{
RDCLOG("Checking for server APK in %s", paths[i].c_str());
if(FileIO::exists(paths[i].c_str()))
{
serverApk = paths[i];
RDCLOG("APK found!: %s", serverApk.c_str());
break;
}
}
if(serverApk.empty())
{
RDCERR(
"%s missing! RenderDoc for Android will not work without it. "
"Build your Android ABI in build-android in the root to have it "
"automatically found and installed.",
targetApk.c_str());
return false;
}
// Build a map so we can switch on the string that returns from adb calls
enum AndroidAbis
{
Android_armeabi,
Android_armeabi_v7a,
Android_arm64_v8a,
Android_x86,
Android_x86_64,
Android_mips,
Android_mips64,
Android_numAbis
};
// clang-format off
static std::map<std::string, AndroidAbis> abi_string_map;
abi_string_map["armeabi"] = Android_armeabi;
abi_string_map["armeabi-v7a"] = Android_armeabi_v7a;
abi_string_map["arm64-v8a"] = Android_arm64_v8a;
abi_string_map["x86"] = Android_x86;
abi_string_map["x86_64"] = Android_x86_64;
abi_string_map["mips"] = Android_mips;
abi_string_map["mips64"] = Android_mips64;
// clang-format on
// 32-bit server works for 32 and 64 bit apps, so install 32-bit matching ABI
string adbAbi = trim(adbExecCommand(deviceID, "shell getprop ro.product.cpu.abi").strStdout);
string adbInstall;
switch(abi_string_map[adbAbi])
{
case Android_armeabi_v7a:
case Android_arm64_v8a:
adbInstall = adbExecCommand(deviceID, "install -r --abi armeabi-v7a " + serverApk).strStdout;
break;
case Android_armeabi:
case Android_x86:
case Android_x86_64:
case Android_mips:
case Android_mips64:
default:
{
RDCERR("Unsupported target ABI: %s", adbAbi.c_str());
return false;
}
}
// Ensure installation succeeded
string adbCheck =
adbExecCommand(deviceID, "shell pm list packages org.renderdoc.renderdoccmd").strStdout;
if(adbCheck.empty())
{
RDCERR("Installation of RenderDocCmd.apk failed!");
return false;
}
return true;
}
bool CheckInstalledPermissions(const string &deviceID, const string &packageName)
{
RDCLOG("Checking installed permissions for %s", packageName.c_str());
string dump = adbExecCommand(deviceID, "shell pm dump " + packageName).strStdout;
if(dump.empty())
RDCERR("Unable to pm dump %s", packageName.c_str());
return CheckPermissions(dump);
}
bool CheckRootAccess(const string &deviceID)
{
RDCLOG("Checking for root access on %s", deviceID.c_str());
Process::ProcessResult result = {};
// Try switching adb to root and check a few indicators for success
// Nothing will fall over if we get a false positive here, it just enables
// additional methods of getting things set up.
result = adbExecCommand(deviceID, "root");
string whoami = trim(adbExecCommand(deviceID, "shell whoami").strStdout);
if(whoami == "root")
return true;
string checksu =
trim(adbExecCommand(deviceID, "shell test -e /system/xbin/su && echo found").strStdout);
if(checksu == "found")
return true;
return false;
}
string DetermineInstalledABI(const string &deviceID, const string &packageName)
{
RDCLOG("Checking installed ABI for %s", packageName.c_str());
string abi;
string dump = adbExecCommand(deviceID, "shell pm dump " + packageName).strStdout;
if(dump.empty())
RDCERR("Unable to pm dump %s", packageName.c_str());
// Walk through the output and look for primaryCpuAbi
std::istringstream contents(dump);
string line;
string prefix("primaryCpuAbi=");
while(std::getline(contents, line))
{
line = trim(line);
if(line.compare(0, prefix.size(), prefix) == 0)
{
// Extract the abi
abi = line.substr(line.find_last_of("=") + 1);
RDCLOG("primaryCpuAbi found: %s", abi.c_str());
break;
}
}
if(abi.empty())
RDCERR("Unable to determine installed abi for: %s", packageName.c_str());
return abi;
}
string FindAndroidLayer(const string &abi, const string &layerName)
{
string layer;
// Check known paths for RenderDoc layer
string exePath;
FileIO::GetExecutableFilename(exePath);
string exeDir = dirname(FileIO::GetFullPathname(exePath));
std::vector<std::string> paths;
#if defined(RENDERDOC_LAYER_PATH)
string customPath(RENDERDOC_LAYER_PATH);
RDCLOG("Custom layer path: %s", customPath.c_str());
if(FileIO::IsRelativePath(customPath))
customPath = exeDir + "/" + customPath;
if(!endswith(customPath, "/"))
customPath += "/";
// Custom path must point to directory containing ABI folders
customPath += abi;
if(!FileIO::exists(customPath.c_str()))
{
RDCWARN("Custom layer path does not contain required ABI");
}
paths.push_back(customPath + "/" + layerName);
#endif
string windows = "/android/lib/";
string linux = "/../share/renderdoc/android/lib/";
string local = "/../../build-android/renderdoccmd/libs/lib/";
string macOS = "/../../../../../build-android/renderdoccmd/libs/lib/";
paths.push_back(exeDir + windows + abi + "/" + layerName);
paths.push_back(exeDir + linux + abi + "/" + layerName);
paths.push_back(exeDir + local + abi + "/" + layerName);
paths.push_back(exeDir + macOS + abi + "/" + layerName);
for(uint32_t i = 0; i < paths.size(); i++)
{
RDCLOG("Checking for layer in %s", paths[i].c_str());
if(FileIO::exists(paths[i].c_str()))
{
layer = paths[i];
RDCLOG("Layer found!: %s", layer.c_str());
break;
}
}
if(layer.empty())
{
RDCERR(
"%s missing! RenderDoc for Android will not work without it. "
"Build your Android ABI in build-android in the root to have it "
"automatically found and installed.",
layer.c_str());
}
return layer;
}
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_GetAndroidFriendlyName(const rdctype::str &device,
rdctype::str &friendly)
{
if(!IsHostADB(device.c_str()))
{
RDCERR("Calling RENDERDOC_GetAndroidFriendlyName with non-android device: %s", device.c_str());
return;
}
int index = 0;
std::string deviceID;
Android::extractDeviceIDAndIndex(device.c_str(), index, deviceID);
if(deviceID.empty())
{
RDCERR("Failed to get android device and index from: %s", device.c_str());
return;
}
string manuf = trim(adbExecCommand(deviceID, "shell getprop ro.product.manufacturer").strStdout);
string model = trim(adbExecCommand(deviceID, "shell getprop ro.product.model").strStdout);
std::string combined;
if(manuf.empty() && model.empty())
combined = "";
else if(manuf.empty() && !model.empty())
combined = model;
else if(!manuf.empty() && model.empty())
combined = manuf + " device";
else if(!manuf.empty() && !model.empty())
combined = manuf + " " + model;
if(combined.empty())
friendly = "";
else
friendly = combined;
}
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdctype::str *deviceList)
{
string adbStdout = adbGetDeviceList();
int idx = 0;
using namespace std;
istringstream stdoutStream(adbStdout);
string ret;
string line;
while(getline(stdoutStream, line))
{
vector<string> tokens;
split(line, tokens, '\t');
if(tokens.size() == 2 && trim(tokens[1]) == "device")
{
if(ret.length())
ret += ",";
ret += StringFormat::Fmt("adb:%d:%s", idx, tokens[0].c_str());
// Forward the ports so we can see if a remoteserver/captured app is already running.
adbForwardPorts(idx, tokens[0]);
idx++;
}
}
*deviceList = ret;
}
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_StartAndroidRemoteServer(const char *device)
{
int index = 0;
std::string deviceID;
// legacy code - delete when C# UI is gone. Handle a NULL or empty device string
if(device || device[0] == '\0')
Android::extractDeviceIDAndIndex(device, index, deviceID);
// We should hook up versioning of the server, then re-install if the version is old or mismatched
// But for now, just install it, if not already present
string adbPackage =
adbExecCommand(deviceID, "shell pm list packages org.renderdoc.renderdoccmd").strStdout;
if(adbPackage.empty())
{
if(!installRenderDocServer(deviceID))
return;
}
adbExecCommand(deviceID, "shell am force-stop org.renderdoc.renderdoccmd");
adbForwardPorts(index, deviceID);
adbExecCommand(deviceID, "shell setprop debug.vulkan.layers :");
adbExecCommand(
deviceID,
"shell am start -n org.renderdoc.renderdoccmd/.Loader -e renderdoccmd remoteserver");
}
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(const char *host,
const char *exe,
AndroidFlags *flags)
{
string packageName(basename(string(exe)));
int index = 0;
std::string deviceID;
Android::extractDeviceIDAndIndex(host, index, deviceID);
// Find the path to package
string pkgPath = trim(adbExecCommand(deviceID, "shell pm path " + packageName).strStdout);
pkgPath.erase(pkgPath.begin(), pkgPath.begin() + strlen("package:"));
pkgPath.erase(pkgPath.end() - strlen("base.apk"), pkgPath.end());
pkgPath += "lib";
string layerName = "libVkLayer_GLES_RenderDoc.so";
// Reset the flags each time we check
*flags = AndroidFlags::NoFlags;
bool found = false;
// First, see if the application contains the layer
if(SearchForAndroidLayer(deviceID, pkgPath, layerName))
found = true;
// Next, check a debug location only usable by rooted devices
if(!found && SearchForAndroidLayer(deviceID, "/data/local/debug/vulkan", layerName))
found = true;
// TODO: Add any future layer locations
if(!found)
{
RDCWARN("No RenderDoc layer for Vulkan or GLES was found");
*flags |= AndroidFlags::MissingLibrary;
}
// Next check permissions of the installed application (without pulling the APK)
if(!CheckInstalledPermissions(deviceID, packageName))
{
RDCWARN("Android application does not have required permissions");
*flags |= AndroidFlags::MissingPermissions;
}
if(CheckRootAccess(deviceID))
{
RDCLOG("Root access detected");
*flags |= AndroidFlags::RootAccess;
}
return;
}
extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_PushLayerToInstalledAndroidApp(const char *host,
const char *exe)
{
Process::ProcessResult result = {};
string packageName(basename(string(exe)));
RDCLOG("Attempting to push RenderDoc layer to %s", packageName.c_str());
int index = 0;
std::string deviceID;
Android::extractDeviceIDAndIndex(host, index, deviceID);
// Detect which ABI was installed on the device
string abi = DetermineInstalledABI(deviceID, packageName);
// Find the layer on host
string layerName("libVkLayer_GLES_RenderDoc.so");
string layerPath = FindAndroidLayer(abi, layerName);
if(layerPath.empty())
return false;
string layerDst = "/data/data/" + packageName + "/lib/";
result = adbExecCommand(deviceID, "push " + layerPath + " " + layerDst);
// Ensure the push succeeded
return SearchForAndroidLayer(deviceID, layerDst, layerName);
}
extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_AddLayerToAndroidPackage(const char *host,
const char *exe,
float *progress)
{
Process::ProcessResult result = {};
string packageName(basename(string(exe)));
int index = 0;
std::string deviceID;
Android::extractDeviceIDAndIndex(host, index, deviceID);
*progress = 0.0f;
if(!CheckPatchingRequirements())
return false;
*progress = 0.11f;
// Detect which ABI was installed on the device
string abi = DetermineInstalledABI(deviceID, packageName);
// Find the layer on host
string layerName("libVkLayer_GLES_RenderDoc.so");
string layerPath = FindAndroidLayer(abi, layerName);
if(layerPath.empty())
return false;
// Find the APK on the device
string pkgPath = trim(adbExecCommand(deviceID, "shell pm path " + packageName).strStdout);
pkgPath.erase(pkgPath.begin(), pkgPath.begin() + strlen("package:"));
string tmpDir = FileIO::GetTempFolderFilename();
string origAPK(tmpDir + packageName + ".orig.apk");
string alignedAPK(origAPK + ".aligned.apk");
*progress = 0.21f;
// Try the following steps, bailing if anything fails
if(!PullAPK(deviceID, pkgPath, origAPK))
return false;
*progress = 0.31f;
if(!CheckAPKPermissions(origAPK))
return false;
*progress = 0.41f;
if(!RemoveAPKSignature(origAPK))
return false;
*progress = 0.51f;
if(!AddLayerToAPK(origAPK, layerPath, layerName, abi, tmpDir))
return false;
*progress = 0.61f;
if(!RealignAPK(origAPK, alignedAPK, tmpDir))
return false;
*progress = 0.71f;
if(!DebugSignAPK(alignedAPK, tmpDir))
return false;
*progress = 0.81f;
if(!UninstallOriginalAPK(deviceID, packageName, tmpDir))
return false;
*progress = 0.91f;
if(!ReinstallPatchedAPK(deviceID, alignedAPK, abi, packageName, tmpDir))
return false;
*progress = 1.0f;
// All clean!
return true;
}
+34
View File
@@ -0,0 +1,34 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2017 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include <string>
#include "os/os_specific.h"
namespace Android
{
bool IsHostADB(const char *hostname);
uint32_t StartAndroidPackageForCapture(const char *host, const char *package);
Process::ProcessResult adbExecCommand(const std::string &deviceID, const std::string &args);
void extractDeviceIDAndIndex(const std::string &hostname, int &index, std::string &deviceID);
};
+1
View File
@@ -26,6 +26,7 @@
#include <sstream>
#include <utility>
#include "api/replay/renderdoc_replay.h"
#include "core/android.h"
#include "core/core.h"
#include "os/os_specific.h"
#include "replay/replay_controller.h"
+1
View File
@@ -24,6 +24,7 @@
******************************************************************************/
#include "api/replay/renderdoc_replay.h"
#include "core/android.h"
#include "core/core.h"
#include "jpeg-compressor/jpgd.h"
#include "os/os_specific.h"
-8
View File
@@ -415,11 +415,3 @@ inline uint64_t CountLeadingZeroes(uint64_t value);
#else
#error Undefined Platform!
#endif
namespace Android
{
bool IsHostADB(const char *hostname);
uint32_t StartAndroidPackageForCapture(const char *host, const char *package);
Process::ProcessResult adbExecCommand(const string &deviceID, const string &args);
void extractDeviceIDAndIndex(const string &hostname, int &index, string &deviceID);
}
+2
View File
@@ -135,6 +135,7 @@
<ClInclude Include="common\threading.h" />
<ClInclude Include="common\timing.h" />
<ClInclude Include="common\wrapped_pool.h" />
<ClInclude Include="core\android.h" />
<ClInclude Include="core\core.h" />
<ClInclude Include="core\crash_handler.h" />
<ClInclude Include="core\precompiled.h" />
@@ -208,6 +209,7 @@
</ClCompile>
<ClCompile Include="common\common.cpp" />
<ClCompile Include="common\dds_readwrite.cpp" />
<ClCompile Include="core\android.cpp" />
<ClCompile Include="core\core.cpp" />
<ClCompile Include="core\image_viewer.cpp" />
<ClCompile Include="core\precompiled.cpp">
+4
View File
@@ -279,6 +279,9 @@
<ClInclude Include="core\precompiled.h">
<Filter>PCH</Filter>
</ClInclude>
<ClInclude Include="core\android.h">
<Filter>Core</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="maths\camera.cpp">
@@ -467,6 +470,7 @@
<ClCompile Include="core\precompiled.cpp">
<Filter>PCH</Filter>
</ClCompile>
<ClCompile Include="core\android.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="os\win32\comexport.def">
+1 -964
View File
@@ -23,10 +23,10 @@
* THE SOFTWARE.
******************************************************************************/
#include <sstream>
#include "api/replay/renderdoc_replay.h"
#include "api/replay/version.h"
#include "common/common.h"
#include "core/android.h"
#include "core/core.h"
#include "maths/camera.h"
#include "maths/formatpacking.h"
@@ -477,969 +477,6 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EndSelfHostCapture(const ch
rdoc->EndFrameCapture(NULL, NULL);
}
namespace Android
{
bool IsHostADB(const char *hostname)
{
return !strncmp(hostname, "adb:", 4);
}
void extractDeviceIDAndIndex(const string &hostname, int &index, string &deviceID)
{
if(!IsHostADB(hostname.c_str()))
return;
const char *c = hostname.c_str();
c += 4;
index = atoi(c);
c = strchr(c, ':');
if(!c)
{
index = 0;
return;
}
c++;
deviceID = c;
}
Process::ProcessResult execScript(const string &script, const string &args,
const string &workDir = ".")
{
RDCLOG("SCRIPT: %s", script.c_str());
Process::ProcessResult result;
Process::LaunchScript(script.c_str(), workDir.c_str(), args.c_str(), &result);
return result;
}
Process::ProcessResult execCommand(const string &cmd, const string &workDir = ".")
{
RDCLOG("COMMAND: %s", cmd.c_str());
size_t firstSpace = cmd.find(" ");
string exe = cmd.substr(0, firstSpace);
string args = cmd.substr(firstSpace + 1, cmd.length());
Process::ProcessResult result;
Process::LaunchProcess(exe.c_str(), workDir.c_str(), args.c_str(), &result);
return result;
}
Process::ProcessResult adbExecCommand(const string &device, const string &args)
{
string adbExePath = RenderDoc::Inst().GetConfigSetting("adbExePath");
if(adbExePath.empty())
{
string exepath;
FileIO::GetExecutableFilename(exepath);
string exedir = dirname(FileIO::GetFullPathname(exepath));
string adbpath = exedir + "/android/adb.exe";
if(FileIO::exists(adbpath.c_str()))
adbExePath = adbpath;
if(adbExePath.empty())
{
static bool warnPath = true;
if(warnPath)
{
RDCWARN("adbExePath not set, attempting to call 'adb' in working env");
warnPath = false;
}
adbExePath = "adb";
}
}
Process::ProcessResult result;
string deviceArgs;
if(device.empty())
deviceArgs = args;
else
deviceArgs = StringFormat::Fmt("-s %s %s", device.c_str(), args.c_str());
return execCommand(string(adbExePath + " " + deviceArgs).c_str());
}
string adbGetDeviceList()
{
return adbExecCommand("", "devices").strStdout;
}
void adbForwardPorts(int index, const std::string &deviceID)
{
int offs = RenderDoc_AndroidPortOffset * (index + 1);
adbExecCommand(deviceID,
StringFormat::Fmt("forward tcp:%i tcp:%i", RenderDoc_RemoteServerPort + offs,
RenderDoc_RemoteServerPort));
adbExecCommand(deviceID,
StringFormat::Fmt("forward tcp:%i tcp:%i", RenderDoc_FirstTargetControlPort + offs,
RenderDoc_FirstTargetControlPort));
}
uint32_t StartAndroidPackageForCapture(const char *host, const char *package)
{
int index = 0;
std::string deviceID;
Android::extractDeviceIDAndIndex(host, index, deviceID);
string packageName = basename(string(package)); // Remove leading '/' if any
adbExecCommand(deviceID, "shell am force-stop " + packageName);
adbForwardPorts(index, deviceID);
adbExecCommand(deviceID, "shell setprop debug.vulkan.layers VK_LAYER_RENDERDOC_Capture");
// Creating the capture file
adbExecCommand(deviceID,
"shell pm grant " + packageName + " android.permission.WRITE_EXTERNAL_STORAGE");
// Reading the capture thumbnail
adbExecCommand(deviceID,
"shell pm grant " + packageName + " android.permission.READ_EXTERNAL_STORAGE");
adbExecCommand(deviceID,
"shell monkey -p " + packageName + " -c android.intent.category.LAUNCHER 1");
uint32_t ret = RenderDoc_FirstTargetControlPort + RenderDoc_AndroidPortOffset * (index + 1);
uint32_t elapsed = 0,
timeout = 1000 *
RDCMAX(5, atoi(RenderDoc::Inst().GetConfigSetting("MaxConnectTimeout").c_str()));
while(elapsed < timeout)
{
// Check if the target app has started yet and we can connect to it.
ITargetControl *control = RENDERDOC_CreateTargetControl(host, ret, "testConnection", false);
if(control)
{
control->Shutdown();
break;
}
Threading::Sleep(1000);
elapsed += 1000;
}
// Let the app pickup the setprop before we turn it back off for replaying.
adbExecCommand(deviceID, "shell setprop debug.vulkan.layers :");
return ret;
}
bool SearchForAndroidLayer(const string &deviceID, const string &location, const string &layerName)
{
RDCLOG("Checking for layers in: %s", location.c_str());
string findLayer =
adbExecCommand(deviceID, "shell find " + location + " -name " + layerName).strStdout;
if(!findLayer.empty())
{
RDCLOG("Found RenderDoc layer in %s", location.c_str());
return true;
}
return false;
}
bool RemoveAPKSignature(const string &apk)
{
RDCLOG("Checking for existing signature");
// Get the list of files in META-INF
string fileList = execCommand("aapt list " + apk).strStdout;
if(fileList.empty())
return false;
// Walk through the output. If it starts with META-INF, remove it.
uint32_t fileCount = 0;
uint32_t matchCount = 0;
std::istringstream contents(fileList);
string line;
string prefix("META-INF");
while(std::getline(contents, line))
{
line = trim(line);
fileCount++;
if(line.compare(0, prefix.size(), prefix) == 0)
{
RDCDEBUG("Match found, removing %s", line.c_str());
execCommand("aapt remove " + apk + " " + line);
matchCount++;
}
}
RDCLOG("%d files searched, %d removed", fileCount, matchCount);
// Ensure no hits on second pass through
RDCDEBUG("Walk through file list again, ensure signature removed");
fileList = execCommand("aapt list " + apk).strStdout;
std::istringstream recheck(fileList);
while(std::getline(recheck, line))
{
if(line.compare(0, prefix.size(), prefix) == 0)
{
RDCERR("Match found, that means removal failed! %s", line.c_str());
return false;
}
}
return true;
}
bool AddLayerToAPK(const string &apk, const string &layerPath, const string &layerName,
const string &abi, const string &tmpDir)
{
RDCLOG("Adding RenderDoc layer");
// Run aapt from the directory containing "lib" so the relative paths are good
string relativeLayer("lib/" + abi + "/" + layerName);
string workDir = removeFromEnd(layerPath, relativeLayer);
Process::ProcessResult result = execCommand("aapt add " + apk + " " + relativeLayer, workDir);
if(result.strStdout.empty())
{
RDCERR("Failed to add layer to APK. STDERR: %s", result.strStderror.c_str());
return false;
}
return true;
}
bool RealignAPK(const string &apk, string &alignedAPK, const string &tmpDir)
{
// Re-align the APK for performance
RDCLOG("Realigning APK");
string errOut = execCommand("zipalign -f 4 " + apk + " " + alignedAPK, tmpDir).strStderror;
if(!errOut.empty())
return false;
// Wait until the aligned version exists to proceed
uint32_t elapsed = 0;
uint32_t timeout = 10000; // 10 seconds
while(elapsed < timeout)
{
if(FileIO::exists(alignedAPK.c_str()))
{
RDCLOG("Aligned APK ready to go, continuing...");
return true;
}
Threading::Sleep(1000);
elapsed += 1000;
}
RDCERR("Timeout reached aligning APK");
return false;
}
string GetAndroidDebugKey()
{
string key = FileIO::GetTempFolderFilename() + "debug.keystore";
if(FileIO::exists(key.c_str()))
return key;
string create = "keytool";
create += " -genkey";
create += " -keystore " + key;
create += " -storepass android";
create += " -alias androiddebugkey";
create += " -keypass android";
create += " -keyalg RSA";
create += " -keysize 2048";
create += " -validity 10000";
create += " -dname \"CN=, OU=, O=, L=, S=, C=\"";
Process::ProcessResult result = execCommand(create);
if(!result.strStderror.empty())
RDCERR("Failed to create debug key");
return key;
}
bool DebugSignAPK(const string &apk, const string &workDir)
{
RDCLOG("Signing with debug key");
string debugKey = GetAndroidDebugKey();
string args;
args += " sign ";
args += " --ks " + debugKey + " ";
args += " --ks-pass pass:android ";
args += " --key-pass pass:android ";
args += " --ks-key-alias androiddebugkey ";
args += apk;
execScript("apksigner", args.c_str(), workDir.c_str());
// Check for signature
string list = execCommand("aapt list " + apk).strStdout;
// Walk through the output. If it starts with META-INF, we're good
std::istringstream contents(list);
string line;
string prefix("META-INF");
while(std::getline(contents, line))
{
if(line.compare(0, prefix.size(), prefix) == 0)
{
RDCLOG("Signature found, continuing...");
return true;
}
}
RDCERR("re-sign of APK failed!");
return false;
}
bool UninstallOriginalAPK(const string &deviceID, const string &packageName, const string &workDir)
{
RDCLOG("Uninstalling previous version of application");
execCommand("adb uninstall " + packageName, workDir);
// Wait until uninstall completes
string uninstallResult;
uint32_t elapsed = 0;
uint32_t timeout = 10000; // 10 seconds
while(elapsed < timeout)
{
uninstallResult = adbExecCommand(deviceID, "shell pm path " + packageName).strStdout;
if(uninstallResult.empty())
{
RDCLOG("Package removed");
return true;
}
Threading::Sleep(1000);
elapsed += 1000;
}
RDCERR("Uninstallation of APK failed!");
return false;
}
bool ReinstallPatchedAPK(const string &deviceID, const string &apk, const string &abi,
const string &packageName, const string &workDir)
{
RDCLOG("Reinstalling APK");
execCommand("adb install --abi " + abi + " " + apk, workDir);
// Wait until re-install completes
string reinstallResult;
uint32_t elapsed = 0;
uint32_t timeout = 10000; // 10 seconds
while(elapsed < timeout)
{
reinstallResult = adbExecCommand(deviceID, "shell pm path " + packageName).strStdout;
if(!reinstallResult.empty())
{
RDCLOG("Patched APK reinstalled, continuing...");
return true;
}
Threading::Sleep(1000);
elapsed += 1000;
}
RDCERR("Reinstallation of APK failed!");
return false;
}
bool CheckPatchingRequirements()
{
// check for aapt, zipalign, apksigner, debug key
vector<string> requirements;
vector<string> missingTools;
requirements.push_back("aapt");
requirements.push_back("zipalign");
requirements.push_back("keytool");
requirements.push_back("apksigner");
requirements.push_back("java");
for(uint32_t i = 0; i < requirements.size(); i++)
{
if(FileIO::FindFileInPath(requirements[i]).empty())
missingTools.push_back(requirements[i]);
}
if(missingTools.size() > 0)
{
for(uint32_t i = 0; i < missingTools.size(); i++)
RDCERR("Missing %s", missingTools[i].c_str());
return false;
}
return true;
}
bool PullAPK(const string &deviceID, const string &pkgPath, const string &apk)
{
RDCLOG("Pulling APK to patch");
adbExecCommand(deviceID, "pull " + pkgPath + " " + apk);
// Wait until the apk lands
uint32_t elapsed = 0;
uint32_t timeout = 10000; // 10 seconds
while(elapsed < timeout)
{
if(FileIO::exists(apk.c_str()))
{
RDCLOG("Original APK ready to go, continuing...");
return true;
}
Threading::Sleep(1000);
elapsed += 1000;
}
RDCERR("Failed to pull APK");
return false;
}
bool CheckPermissions(const string &dump)
{
if(dump.find("android.permission.WRITE_EXTERNAL_STORAGE") == string::npos)
{
RDCWARN("APK missing WRITE_EXTERNAL_STORAGE permission");
return false;
}
if(dump.find("android.permission.INTERNET") == string::npos)
{
RDCWARN("APK missing INTERNET permission");
return false;
}
return true;
}
bool CheckAPKPermissions(const string &apk)
{
RDCLOG("Checking that APK can be can write to sdcard");
string badging = execCommand("aapt dump badging " + apk).strStdout;
if(badging.empty())
{
RDCERR("Unable to aapt dump %s", apk.c_str());
return false;
}
return CheckPermissions(badging);
}
bool CheckDebuggable(const string &apk)
{
RDCLOG("Checking that APK s debuggable");
string badging = execCommand("aapt dump badging " + apk).strStdout;
if(badging.find("application-debuggable"))
{
RDCERR("APK is not debuggable");
return false;
}
return true;
}
} // namespace Android
using namespace Android;
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_GetAndroidFriendlyName(const rdctype::str &device,
rdctype::str &friendly)
{
if(!IsHostADB(device.c_str()))
{
RDCERR("Calling RENDERDOC_GetAndroidFriendlyName with non-android device: %s", device.c_str());
return;
}
int index = 0;
std::string deviceID;
Android::extractDeviceIDAndIndex(device.c_str(), index, deviceID);
if(deviceID.empty())
{
RDCERR("Failed to get android device and index from: %s", device.c_str());
return;
}
string manuf = trim(adbExecCommand(deviceID, "shell getprop ro.product.manufacturer").strStdout);
string model = trim(adbExecCommand(deviceID, "shell getprop ro.product.model").strStdout);
std::string combined;
if(manuf.empty() && model.empty())
combined = "";
else if(manuf.empty() && !model.empty())
combined = model;
else if(!manuf.empty() && model.empty())
combined = manuf + " device";
else if(!manuf.empty() && !model.empty())
combined = manuf + " " + model;
if(combined.empty())
friendly = "";
else
friendly = combined;
}
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdctype::str *deviceList)
{
string adbStdout = adbGetDeviceList();
int idx = 0;
using namespace std;
istringstream stdoutStream(adbStdout);
string ret;
string line;
while(getline(stdoutStream, line))
{
vector<string> tokens;
split(line, tokens, '\t');
if(tokens.size() == 2 && trim(tokens[1]) == "device")
{
if(ret.length())
ret += ",";
ret += StringFormat::Fmt("adb:%d:%s", idx, tokens[0].c_str());
// Forward the ports so we can see if a remoteserver/captured app is already running.
adbForwardPorts(idx, tokens[0]);
idx++;
}
}
*deviceList = ret;
}
bool installRenderDocServer(const string &deviceID)
{
string targetApk = "RenderDocCmd.apk";
string serverApk;
// Check known paths for RenderDoc server
string exePath;
FileIO::GetExecutableFilename(exePath);
string exeDir = dirname(FileIO::GetFullPathname(exePath));
std::vector<std::string> paths;
#if defined(RENDERDOC_APK_PATH)
string customPath(RENDERDOC_APK_PATH);
RDCLOG("Custom APK path: %s", customPath.c_str());
if(FileIO::IsRelativePath(customPath))
customPath = exeDir + "/" + customPath;
// Check to see if APK name was included in custom path
if(!endswith(customPath, targetApk))
{
if(customPath.back() != '/')
customPath += "/";
customPath += targetApk;
}
paths.push_back(customPath);
#endif
paths.push_back(exeDir + "/android/apk/" + targetApk); // Windows install
paths.push_back(exeDir + "/../share/renderdoc/android/apk/" + targetApk); // Linux install
paths.push_back(exeDir + "/../../build-android/bin/" + targetApk); // Local build
paths.push_back(exeDir + "/../../../../../build-android/bin/" + targetApk); // macOS build
for(uint32_t i = 0; i < paths.size(); i++)
{
RDCLOG("Checking for server APK in %s", paths[i].c_str());
if(FileIO::exists(paths[i].c_str()))
{
serverApk = paths[i];
RDCLOG("APK found!: %s", serverApk.c_str());
break;
}
}
if(serverApk.empty())
{
RDCERR(
"%s missing! RenderDoc for Android will not work without it. "
"Build your Android ABI in build-android in the root to have it "
"automatically found and installed.",
targetApk.c_str());
return false;
}
// Build a map so we can switch on the string that returns from adb calls
enum AndroidAbis
{
Android_armeabi,
Android_armeabi_v7a,
Android_arm64_v8a,
Android_x86,
Android_x86_64,
Android_mips,
Android_mips64,
Android_numAbis
};
// clang-format off
static std::map<std::string, AndroidAbis> abi_string_map;
abi_string_map["armeabi"] = Android_armeabi;
abi_string_map["armeabi-v7a"] = Android_armeabi_v7a;
abi_string_map["arm64-v8a"] = Android_arm64_v8a;
abi_string_map["x86"] = Android_x86;
abi_string_map["x86_64"] = Android_x86_64;
abi_string_map["mips"] = Android_mips;
abi_string_map["mips64"] = Android_mips64;
// clang-format on
// 32-bit server works for 32 and 64 bit apps, so install 32-bit matching ABI
string adbAbi = trim(adbExecCommand(deviceID, "shell getprop ro.product.cpu.abi").strStdout);
string adbInstall;
switch(abi_string_map[adbAbi])
{
case Android_armeabi_v7a:
case Android_arm64_v8a:
adbInstall = adbExecCommand(deviceID, "install -r --abi armeabi-v7a " + serverApk).strStdout;
break;
case Android_armeabi:
case Android_x86:
case Android_x86_64:
case Android_mips:
case Android_mips64:
default:
{
RDCERR("Unsupported target ABI: %s", adbAbi.c_str());
return false;
}
}
// Ensure installation succeeded
string adbCheck =
adbExecCommand(deviceID, "shell pm list packages org.renderdoc.renderdoccmd").strStdout;
if(adbCheck.empty())
{
RDCERR("Installation of RenderDocCmd.apk failed!");
return false;
}
return true;
}
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_StartAndroidRemoteServer(const char *device)
{
int index = 0;
std::string deviceID;
// legacy code - delete when C# UI is gone. Handle a NULL or empty device string
if(device || device[0] == '\0')
Android::extractDeviceIDAndIndex(device, index, deviceID);
// We should hook up versioning of the server, then re-install if the version is old or mismatched
// But for now, just install it, if not already present
string adbPackage =
adbExecCommand(deviceID, "shell pm list packages org.renderdoc.renderdoccmd").strStdout;
if(adbPackage.empty())
{
if(!installRenderDocServer(deviceID))
return;
}
adbExecCommand(deviceID, "shell am force-stop org.renderdoc.renderdoccmd");
adbForwardPorts(index, deviceID);
adbExecCommand(deviceID, "shell setprop debug.vulkan.layers :");
adbExecCommand(
deviceID,
"shell am start -n org.renderdoc.renderdoccmd/.Loader -e renderdoccmd remoteserver");
}
bool CheckInstalledPermissions(const string &deviceID, const string &packageName)
{
RDCLOG("Checking installed permissions for %s", packageName.c_str());
string dump = adbExecCommand(deviceID, "shell pm dump " + packageName).strStdout;
if(dump.empty())
RDCERR("Unable to pm dump %s", packageName.c_str());
return CheckPermissions(dump);
}
bool CheckRootAccess(const string &deviceID)
{
RDCLOG("Checking for root access on %s", deviceID.c_str());
Process::ProcessResult result = {};
// Try switching adb to root and check a few indicators for success
// Nothing will fall over if we get a false positive here, it just enables
// additional methods of getting things set up.
result = adbExecCommand(deviceID, "root");
string whoami = trim(adbExecCommand(deviceID, "shell whoami").strStdout);
if(whoami == "root")
return true;
string checksu =
trim(adbExecCommand(deviceID, "shell test -e /system/xbin/su && echo found").strStdout);
if(checksu == "found")
return true;
return false;
}
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(const char *host,
const char *exe,
AndroidFlags *flags)
{
string packageName(basename(string(exe)));
int index = 0;
std::string deviceID;
Android::extractDeviceIDAndIndex(host, index, deviceID);
// Find the path to package
string pkgPath = trim(adbExecCommand(deviceID, "shell pm path " + packageName).strStdout);
pkgPath.erase(pkgPath.begin(), pkgPath.begin() + strlen("package:"));
pkgPath.erase(pkgPath.end() - strlen("base.apk"), pkgPath.end());
pkgPath += "lib";
string layerName = "libVkLayer_GLES_RenderDoc.so";
// Reset the flags each time we check
*flags = AndroidFlags::NoFlags;
bool found = false;
// First, see if the application contains the layer
if(SearchForAndroidLayer(deviceID, pkgPath, layerName))
found = true;
// Next, check a debug location only usable by rooted devices
if(!found && SearchForAndroidLayer(deviceID, "/data/local/debug/vulkan", layerName))
found = true;
// TODO: Add any future layer locations
if(!found)
{
RDCWARN("No RenderDoc layer for Vulkan or GLES was found");
*flags |= AndroidFlags::MissingLibrary;
}
// Next check permissions of the installed application (without pulling the APK)
if(!CheckInstalledPermissions(deviceID, packageName))
{
RDCWARN("Android application does not have required permissions");
*flags |= AndroidFlags::MissingPermissions;
}
if(CheckRootAccess(deviceID))
{
RDCLOG("Root access detected");
*flags |= AndroidFlags::RootAccess;
}
return;
}
string DetermineInstalledABI(const string &deviceID, const string &packageName)
{
RDCLOG("Checking installed ABI for %s", packageName.c_str());
string abi;
string dump = adbExecCommand(deviceID, "shell pm dump " + packageName).strStdout;
if(dump.empty())
RDCERR("Unable to pm dump %s", packageName.c_str());
// Walk through the output and look for primaryCpuAbi
std::istringstream contents(dump);
string line;
string prefix("primaryCpuAbi=");
while(std::getline(contents, line))
{
line = trim(line);
if(line.compare(0, prefix.size(), prefix) == 0)
{
// Extract the abi
abi = line.substr(line.find_last_of("=") + 1);
RDCLOG("primaryCpuAbi found: %s", abi.c_str());
break;
}
}
if(abi.empty())
RDCERR("Unable to determine installed abi for: %s", packageName.c_str());
return abi;
}
string FindAndroidLayer(const string &abi, const string &layerName)
{
string layer;
// Check known paths for RenderDoc layer
string exePath;
FileIO::GetExecutableFilename(exePath);
string exeDir = dirname(FileIO::GetFullPathname(exePath));
std::vector<std::string> paths;
#if defined(RENDERDOC_LAYER_PATH)
string customPath(RENDERDOC_LAYER_PATH);
RDCLOG("Custom layer path: %s", customPath.c_str());
if(FileIO::IsRelativePath(customPath))
customPath = exeDir + "/" + customPath;
if(!endswith(customPath, "/"))
customPath += "/";
// Custom path must point to directory containing ABI folders
customPath += abi;
if(!FileIO::exists(customPath.c_str()))
{
RDCWARN("Custom layer path does not contain required ABI");
}
paths.push_back(customPath + "/" + layerName);
#endif
string windows = "/android/lib/";
string linux = "/../share/renderdoc/android/lib/";
string local = "/../../build-android/renderdoccmd/libs/lib/";
string macOS = "/../../../../../build-android/renderdoccmd/libs/lib/";
paths.push_back(exeDir + windows + abi + "/" + layerName);
paths.push_back(exeDir + linux + abi + "/" + layerName);
paths.push_back(exeDir + local + abi + "/" + layerName);
paths.push_back(exeDir + macOS + abi + "/" + layerName);
for(uint32_t i = 0; i < paths.size(); i++)
{
RDCLOG("Checking for layer in %s", paths[i].c_str());
if(FileIO::exists(paths[i].c_str()))
{
layer = paths[i];
RDCLOG("Layer found!: %s", layer.c_str());
break;
}
}
if(layer.empty())
{
RDCERR(
"%s missing! RenderDoc for Android will not work without it. "
"Build your Android ABI in build-android in the root to have it "
"automatically found and installed.",
layer.c_str());
}
return layer;
}
extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_PushLayerToInstalledAndroidApp(const char *host,
const char *exe)
{
Process::ProcessResult result = {};
string packageName(basename(string(exe)));
RDCLOG("Attempting to push RenderDoc layer to %s", packageName.c_str());
int index = 0;
std::string deviceID;
Android::extractDeviceIDAndIndex(host, index, deviceID);
// Detect which ABI was installed on the device
string abi = DetermineInstalledABI(deviceID, packageName);
// Find the layer on host
string layerName("libVkLayer_GLES_RenderDoc.so");
string layerPath = FindAndroidLayer(abi, layerName);
if(layerPath.empty())
return false;
string layerDst = "/data/data/" + packageName + "/lib/";
result = adbExecCommand(deviceID, "push " + layerPath + " " + layerDst);
// Ensure the push succeeded
return SearchForAndroidLayer(deviceID, layerDst, layerName);
}
extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_AddLayerToAndroidPackage(const char *host,
const char *exe,
float *progress)
{
Process::ProcessResult result = {};
string packageName(basename(string(exe)));
int index = 0;
std::string deviceID;
Android::extractDeviceIDAndIndex(host, index, deviceID);
*progress = 0.0f;
if(!CheckPatchingRequirements())
return false;
*progress = 0.11f;
// Detect which ABI was installed on the device
string abi = DetermineInstalledABI(deviceID, packageName);
// Find the layer on host
string layerName("libVkLayer_GLES_RenderDoc.so");
string layerPath = FindAndroidLayer(abi, layerName);
if(layerPath.empty())
return false;
// Find the APK on the device
string pkgPath = trim(adbExecCommand(deviceID, "shell pm path " + packageName).strStdout);
pkgPath.erase(pkgPath.begin(), pkgPath.begin() + strlen("package:"));
string tmpDir = FileIO::GetTempFolderFilename();
string origAPK(tmpDir + packageName + ".orig.apk");
string alignedAPK(origAPK + ".aligned.apk");
*progress = 0.21f;
// Try the following steps, bailing if anything fails
if(!PullAPK(deviceID, pkgPath, origAPK))
return false;
*progress = 0.31f;
if(!CheckAPKPermissions(origAPK))
return false;
*progress = 0.41f;
if(!RemoveAPKSignature(origAPK))
return false;
*progress = 0.51f;
if(!AddLayerToAPK(origAPK, layerPath, layerName, abi, tmpDir))
return false;
*progress = 0.61f;
if(!RealignAPK(origAPK, alignedAPK, tmpDir))
return false;
*progress = 0.71f;
if(!DebugSignAPK(alignedAPK, tmpDir))
return false;
*progress = 0.81f;
if(!UninstallOriginalAPK(deviceID, packageName, tmpDir))
return false;
*progress = 0.91f;
if(!ReinstallPatchedAPK(deviceID, alignedAPK, abi, packageName, tmpDir))
return false;
*progress = 1.0f;
// All clean!
return true;
}
extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_NeedVulkanLayerRegistration(
VulkanLayerFlags *flagsPtr, rdctype::array<rdctype::str> *myJSONsPtr,
rdctype::array<rdctype::str> *otherJSONsPtr)