diff --git a/renderdoc/CMakeLists.txt b/renderdoc/CMakeLists.txt index 6f997ab41..637ccd66f 100644 --- a/renderdoc/CMakeLists.txt +++ b/renderdoc/CMakeLists.txt @@ -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 diff --git a/renderdoc/core/android.cpp b/renderdoc/core/android.cpp new file mode 100644 index 000000000..648f01bb1 --- /dev/null +++ b/renderdoc/core/android.cpp @@ -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 +#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 requirements; + vector 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 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 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 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 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; +} diff --git a/renderdoc/core/android.h b/renderdoc/core/android.h new file mode 100644 index 000000000..d4bfe7963 --- /dev/null +++ b/renderdoc/core/android.h @@ -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 +#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); +}; diff --git a/renderdoc/core/remote_server.cpp b/renderdoc/core/remote_server.cpp index 89c0d7a33..97b8968bc 100644 --- a/renderdoc/core/remote_server.cpp +++ b/renderdoc/core/remote_server.cpp @@ -26,6 +26,7 @@ #include #include #include "api/replay/renderdoc_replay.h" +#include "core/android.h" #include "core/core.h" #include "os/os_specific.h" #include "replay/replay_controller.h" diff --git a/renderdoc/core/target_control.cpp b/renderdoc/core/target_control.cpp index b5beda840..444cbe731 100644 --- a/renderdoc/core/target_control.cpp +++ b/renderdoc/core/target_control.cpp @@ -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" diff --git a/renderdoc/os/os_specific.h b/renderdoc/os/os_specific.h index 1d4d47503..40843de09 100644 --- a/renderdoc/os/os_specific.h +++ b/renderdoc/os/os_specific.h @@ -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); -} diff --git a/renderdoc/renderdoc.vcxproj b/renderdoc/renderdoc.vcxproj index b5733177a..fa492e684 100644 --- a/renderdoc/renderdoc.vcxproj +++ b/renderdoc/renderdoc.vcxproj @@ -135,6 +135,7 @@ + @@ -208,6 +209,7 @@ + diff --git a/renderdoc/renderdoc.vcxproj.filters b/renderdoc/renderdoc.vcxproj.filters index 99f4f216d..a45650a47 100644 --- a/renderdoc/renderdoc.vcxproj.filters +++ b/renderdoc/renderdoc.vcxproj.filters @@ -279,6 +279,9 @@ PCH + + Core + @@ -467,6 +470,7 @@ PCH + diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index 3094045af..355e40c40 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -23,10 +23,10 @@ * THE SOFTWARE. ******************************************************************************/ -#include #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 requirements; - vector 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 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 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 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 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 *myJSONsPtr, rdctype::array *otherJSONsPtr)