From 9a22b2cf46f74b7ada5e2d1392597ce94b5ce8f6 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 14 Oct 2022 10:33:51 +0100 Subject: [PATCH] Remove old android prototyping code --- qrenderdoc/Windows/Dialogs/CaptureDialog.cpp | 67 +- renderdoc/3rdparty/aosp/android_manifest.h | 251 -------- renderdoc/CMakeLists.txt | 3 - renderdoc/android/android.cpp | 28 + renderdoc/android/android_manifest.cpp | 630 ------------------- renderdoc/android/android_patch.cpp | 622 ------------------ renderdoc/android/android_utils.cpp | 51 ++ renderdoc/android/android_utils.h | 2 + renderdoc/api/replay/renderdoc_replay.h | 4 - renderdoc/renderdoc.vcxproj | 3 - renderdoc/renderdoc.vcxproj.filters | 12 - 11 files changed, 82 insertions(+), 1591 deletions(-) delete mode 100644 renderdoc/3rdparty/aosp/android_manifest.h delete mode 100644 renderdoc/android/android_manifest.cpp delete mode 100644 renderdoc/android/android_patch.cpp diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp index 4d9af8594..4423ce911 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp @@ -619,74 +619,9 @@ void CaptureDialog::androidWarn_mouseClick() QString msg = tr(R"(In order to debug on Android, the package must be debuggable.

On UE4 you must disable for distribution, on Unity enable development mode. -

-RenderDoc can try to add the flag for you, which will involve completely reinstalling your package -as well as re-signing it with a debug key. This method is prone to error and is -not recommended. It is instead advised to configure your app to be debuggable at build time. -

-Would you like RenderDoc to try patching your package? )"); - QMessageBox::StandardButton prompt = RDDialog::question(this, caption, msg, RDDialog::YesNoCancel); - - if(prompt == QMessageBox::Yes) - { - float progress = 0.0f; - bool patchSucceeded = false; - - // call into APK pull, patch, install routine, then continue - LambdaThread *patch = new LambdaThread([this, host, exe, &patchSucceeded, &progress]() { - AndroidFlags result = - RENDERDOC_MakeDebuggablePackage(host, exe, [&progress](float p) { progress = p; }); - - if(result & AndroidFlags::Debuggable) - { - // Sucess! - patchSucceeded = true; - - RDDialog::information(this, tr("Patch succeeded!"), - tr("The patch process succeeded and the package is ready to debug")); - } - else - { - QString failmsg = tr("Something has gone wrong and the patching process failed.

"); - - if(result == AndroidFlags::MissingTools) - { - failmsg += - tr("Tools required for the process were not found. Try configuring the path to your " - "android SDK or java JDK in the settings dialog."); - } - else if(result == AndroidFlags::ManifestPatchFailure) - { - failmsg += - tr("The package manifest could not be patched. This is not solveable, you will have " - "to rebuild the package with the debuggable flag."); - } - else if(result == AndroidFlags::RepackagingAPKFailure) - { - failmsg += tr("The package was patched but could not be repackaged and installed."); - } - - RDDialog::critical(this, tr("Failed to patch package"), failmsg); - } - }); - - patch->setName(lit("Android patch")); - patch->start(); - // wait a few ms before popping up a progress bar - patch->wait(500); - if(patch->isRunning()) - { - ShowProgressDialog(this, tr("Patching %1, please wait...").arg(exe), - [patch]() { return !patch->isRunning(); }, - [&progress]() { return progress; }); - } - patch->deleteLater(); - - if(patchSucceeded) - ui->androidWarn->setVisible(false); - } + RDDialog::information(this, caption, msg); } void CaptureDialog::lineEdit_keyPress(QKeyEvent *ev) diff --git a/renderdoc/3rdparty/aosp/android_manifest.h b/renderdoc/3rdparty/aosp/android_manifest.h deleted file mode 100644 index 8677752c9..000000000 --- a/renderdoc/3rdparty/aosp/android_manifest.h +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2005 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -////////////////////////////////////////////////////////////////////////////////// -// -// These constants are taken from ResourceTypes.h: -// https://android.googlesource.com/platform/frameworks/base/+/42ebcb80b50834a1ce4755cd4ca86918c96ca3c6/libs/androidfw/include/androidfw/ResourceTypes.h -// -// The ones we need are extracted here to not be dependent on the rest of the android framework. -// There are some slight modifications for ease of integration, but this is still all under the -// android license. - -enum class ResType : uint16_t -{ - Null = 0x0000, - StringPool = 0x0001, - XML = 0x0003, - NamespaceStart = 0x0100, - NamespaceEnd = 0x0101, - StartElement = 0x0102, - EndElement = 0x0103, - CDATA = 0x0104, - ResourceMap = 0x0180, -}; - -/** - * Header that appears at the front of every data chunk in a resource. - */ -struct ResChunk_header -{ - // Type identifier for this chunk. The meaning of this value depends - // on the containing chunk. - ResType type; - - // Size of the chunk header (in bytes). Adding this value to - // the address of the chunk allows you to find its associated data - // (if any). - uint16_t headerSize; - - // Total size of this chunk (in bytes). This is the chunkSize plus - // the size of any data associated with the chunk. Adding this value - // to the chunk allows you to completely skip its contents (including - // any child chunks). If this value is the same as chunkSize, there is - // no data associated with the chunk. - uint32_t size; -}; - -/** - * Reference to a string in a string pool. - */ -struct ResStringPool_ref -{ - // Index into the string pool table (uint32_t-offset from the indices - // immediately after ResStringPool_header) at which to find the location - // of the string data in the pool. - uint32_t index; -}; - -/** - * Representation of a value in a resource, supplying type - * information. - */ -struct Res_value -{ - // Number of bytes in this structure. - uint16_t size; - - // Always set to 0. - uint8_t res0; - - // Type of the data value. - enum class DataType : uint8_t - { - // The 'data' holds an index into the containing resource table's - // global value string pool. - String = 0x03, - // The 'data' is either 0 or 1, for input "false" or "true" respectively. - Boolean = 0x12, - } dataType; - - // The data for this item, as interpreted according to dataType. - uint32_t data; -}; - -/** - * Definition for a pool of strings. The data of this chunk is an - * array of uint32_t providing indices into the pool, relative to - * stringsStart. At stringsStart are all of the UTF-16 strings - * concatenated together; each starts with a uint16_t of the string's - * length and each ends with a 0x0000 terminator. If a string is > - * 32767 characters, the high bit of the length is set meaning to take - * those 15 bits as a high word and it will be followed by another - * uint16_t containing the low word. - * - * If styleCount is not zero, then immediately following the array of - * uint32_t indices into the string table is another array of indices - * into a style table starting at stylesStart. Each entry in the - * style table is an array of ResStringPool_span structures. - */ -struct ResStringPool_header -{ - struct ResChunk_header header; - - // Number of strings in this pool (number of uint32_t indices that follow - // in the data). - uint32_t stringCount; - - // Number of style span arrays in the pool (number of uint32_t indices - // follow the string indices). - uint32_t styleCount; - - // Flags. - enum StringFlags : uint32_t - { - // If set, the string index is sorted by the string values (based - // on strcmp16()). - SORTED_FLAG = 1 << 0, - - // String pool is encoded in UTF-8 - UTF8_FLAG = 1 << 8 - }; - StringFlags flags; - - // Index from header of the string data. - uint32_t stringsStart; - - // Index from header of the style data. - uint32_t stylesStart; -}; - -/** - * Basic XML tree node. A single item in the XML document. Extended info - * about the node can be found after header.headerSize. - */ -struct ResXMLTree_node -{ - struct ResChunk_header header; - - // Line number in original source file at which this element appeared. - uint32_t lineNumber; - - // Optional XML comment that was associated with this element; -1 if none. - ResStringPool_ref comment; -}; - -/** - * Extended XML tree node for namespace start/end nodes. - * Appears header.headerSize bytes after a ResXMLTree_node. - */ -struct ResXMLTree_namespaceExt -{ - // The prefix of the namespace. - struct ResStringPool_ref prefix; - - // The URI of the namespace. - struct ResStringPool_ref uri; -}; - -/** - * Extended XML tree node for element start/end nodes. - * Appears header.headerSize bytes after a ResXMLTree_node. - */ -struct ResXMLTree_endElementExt -{ - // String of the full namespace of this element. - struct ResStringPool_ref ns; - - // String name of this node if it is an ELEMENT; the raw - // character data if this is a CDATA node. - struct ResStringPool_ref name; -}; - -/** - * Extended XML tree node for start tags -- includes attribute - * information. - * Appears header.headerSize bytes after a ResXMLTree_node. - */ -struct ResXMLTree_attrExt -{ - // String of the full namespace of this element. - struct ResStringPool_ref ns; - - // String name of this node if it is an ELEMENT; the raw - // character data if this is a CDATA node. - struct ResStringPool_ref name; - - // Byte offset from the start of this structure where the attributes start. - uint16_t attributeStart; - - // Size of the ResXMLTree_attribute structures that follow. - uint16_t attributeSize; - - // Number of attributes associated with an ELEMENT. These are - // available as an array of ResXMLTree_attribute structures - // immediately following this node. - uint16_t attributeCount; - - // Index (1-based) of the "id" attribute. 0 if none. - uint16_t idIndex; - - // Index (1-based) of the "class" attribute. 0 if none. - uint16_t classIndex; - - // Index (1-based) of the "style" attribute. 0 if none. - uint16_t styleIndex; -}; - -struct ResXMLTree_attribute -{ - // Namespace of this attribute. - struct ResStringPool_ref ns; - - // Name of this attribute. - struct ResStringPool_ref name; - - // The original raw string value of this attribute. - struct ResStringPool_ref rawValue; - - // Processesd typed value of this attribute. - struct Res_value typedValue; -}; - -/** - * Extended XML tree node for CDATA tags -- includes the CDATA string. - * Appears header.headerSize bytes after a ResXMLTree_node. - */ -struct ResXMLTree_cdataExt -{ - // The raw CDATA character data. - struct ResStringPool_ref data; - - // The typed value of the character data if this is a CDATA node. - struct Res_value typedData; -}; diff --git a/renderdoc/CMakeLists.txt b/renderdoc/CMakeLists.txt index a26438d76..887895e56 100644 --- a/renderdoc/CMakeLists.txt +++ b/renderdoc/CMakeLists.txt @@ -150,10 +150,8 @@ set(sources core/bit_flag_iterator.h core/bit_flag_iterator_tests.cpp android/android.cpp - android/android_patch.cpp android/android_tools.cpp android/android_utils.cpp - android/android_manifest.cpp android/android.h android/android_utils.h android/jdwp.h @@ -218,7 +216,6 @@ set(sources 3rdparty/jpeg-compressor/jpgd.h 3rdparty/jpeg-compressor/jpge.cpp 3rdparty/jpeg-compressor/jpge.h - 3rdparty/aosp/android_manifest.h 3rdparty/catch/catch.cpp 3rdparty/catch/catch.hpp 3rdparty/pugixml/pugixml.cpp diff --git a/renderdoc/android/android.cpp b/renderdoc/android/android.cpp index 7276cbc24..43cd2dcc6 100644 --- a/renderdoc/android/android.cpp +++ b/renderdoc/android/android.cpp @@ -1478,3 +1478,31 @@ ExecuteResult AndroidRemoteServer::ExecuteAndInject(const rdcstr &packageAndActi AndroidController AndroidController::m_Inst; DeviceProtocolRegistration androidProtocol("adb", &AndroidController::Get); + +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage( + const rdcstr &URL, const rdcstr &packageAndActivity, AndroidFlags *flags) +{ + IDeviceProtocolHandler *adb = RenderDoc::Inst().GetDeviceProtocol("adb"); + + rdcstr deviceID = adb->GetDeviceID(URL); + + // Reset the flags each time we check + *flags = AndroidFlags::NoFlags; + + if(Android::IsDebuggable(deviceID, Android::GetPackageName(packageAndActivity))) + { + *flags |= AndroidFlags::Debuggable; + } + else + { + RDCLOG("%s is not debuggable", packageAndActivity.c_str()); + } + + if(Android::HasRootAccess(deviceID)) + { + RDCLOG("Root access detected"); + *flags |= AndroidFlags::RootAccess; + } + + return; +} diff --git a/renderdoc/android/android_manifest.cpp b/renderdoc/android/android_manifest.cpp deleted file mode 100644 index 5a0dfedf7..000000000 --- a/renderdoc/android/android_manifest.cpp +++ /dev/null @@ -1,630 +0,0 @@ -/****************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2019-2022 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 "aosp/android_manifest.h" -#include "core/core.h" -#include "strings/string_utils.h" -#include "android_utils.h" - -const uint32_t debuggableResourceId = 0x0101000f; -const uint32_t addingStringIndex = 0x8b8b8b8b; - -// the string pool always immediately follows the XML header, which is just an empty header. -const size_t stringpoolOffset = sizeof(ResChunk_header); - -namespace Android -{ -template -void SetFromBytes(bytebuf &bytes, size_t offs, const T &t) -{ - T *ptr = (T *)(bytes.data() + offs); - memcpy(ptr, &t, sizeof(T)); -} - -template -T GetFromBytes(bytebuf &bytes, size_t offs) -{ - T ret; - T *ptr = (T *)(bytes.data() + offs); - memcpy(&ret, ptr, sizeof(T)); - return ret; -} - -template -void InsertBytes(bytebuf &bytes, size_t offs, const T &data) -{ - byte *byteData = (byte *)&data; - - bytes.insert(offs, byteData, sizeof(T)); -} - -template <> -void InsertBytes(bytebuf &bytes, size_t offs, const bytebuf &data) -{ - bytes.insert(offs, data); -} - -rdcstr GetStringPoolValue(bytebuf &bytes, ResStringPool_ref ref) -{ - ResStringPool_header stringpool = GetFromBytes(bytes, stringpoolOffset); - - byte *base = bytes.data() + stringpoolOffset; - - uint32_t stringCount = stringpool.stringCount; - uint32_t *stringOffsets = (uint32_t *)(base + stringpool.header.headerSize); - byte *stringData = base + stringpool.stringsStart; - - if(ref.index == ~0U) - return ""; - - if(ref.index >= stringCount) - return "__invalid_string__"; - - byte *strdata = stringData + stringOffsets[ref.index]; - - // strdata now points at len characters of string. Check if it's UTF-8 or UTF-16 - if((stringpool.flags & ResStringPool_header::UTF8_FLAG) == 0) - { - uint16_t *str = (uint16_t *)strdata; - - uint32_t len = *(str++); - - // see comment above ResStringPool_header - if high bit is set, then this string is >32767 - // characters, so it's followed by another uint16_t with the low word - if(len & 0x8000) - { - len &= 0x7fff; - len <<= 16; - len |= *(str++); - } - - rdcwstr wstr(len); - - // wchar_t isn't always 2 bytes, so we iterate over the uint16_t and cast. - for(uint32_t i = 0; i < len; i++) - wstr[i] = wchar_t(str[i]); - - return StringFormat::Wide2UTF8(wstr); - } - else - { - byte *str = (byte *)strdata; - - uint32_t len = *(str++); - - // the length works similarly for UTF-8 data but with single bytes instead of uint16s. - if(len & 0x80) - { - len &= 0x7f; - len <<= 8; - len |= *(str++); - } - - // the length is encoded twice. I can only assume to preserve uint16 size although I don't see - // why that would be necessary - it can't be fully backwards compatible even with the alignment - // except with readers that ignore the length entirely and look for trailing NULLs. - if(len < 0x80) - str++; - else - str += 2; - - return rdcstr((char *)str, len); - } -} - -void ShiftStringPoolValue(ResStringPool_ref &ref, uint32_t insertedLocation) -{ - // if we found our added attribute, then set the index here (otherwise we'd remap it with the - // others!) - if(ref.index == addingStringIndex) - ref.index = insertedLocation; - else if(ref.index != ~0U && ref.index >= insertedLocation) - ref.index++; -} - -void ShiftStringPoolValue(Res_value &val, uint32_t insertedLocation) -{ - if(val.dataType == Res_value::DataType::String && val.data >= insertedLocation) - val.data++; -} - -bool PatchManifest(bytebuf &manifestBytes) -{ - if(manifestBytes.size() < sizeof(ResChunk_header)) - { - RDCERR("Manifest is truncated, %zu bytes doesn't contain full XML header", manifestBytes.size()); - return false; - } - - size_t cur = 0; - - ResChunk_header xmlroot = GetFromBytes(manifestBytes, cur); - - if(xmlroot.type != ResType::XML) - { - RDCERR("XML Header is malformed, type is %u expected %u", xmlroot.type, ResType::XML); - return false; - } - - if(xmlroot.headerSize != sizeof(xmlroot)) - { - RDCERR("XML Header is malformed, header size is reported as %u but expected %u", - xmlroot.headerSize, sizeof(xmlroot)); - return false; - } - - // this isn't necessarily fatal, but it is unexpected. - if(xmlroot.size != manifestBytes.size()) - RDCWARN("XML header is malformed, size is reported as %u but %zu bytes found", xmlroot.size, - manifestBytes.size()); - - cur += xmlroot.headerSize; - - ResStringPool_header stringpool = GetFromBytes(manifestBytes, cur); - - if(stringpool.header.type != ResType::StringPool) - { - RDCERR("Manifest format is unsupported, expected string pool but got %u", stringpool.header.type); - return false; - } - - if(stringpool.header.headerSize != sizeof(stringpool)) - { - RDCERR("String pool is malformed, header size is reported as %u but expected %u", - stringpool.header.headerSize, sizeof(stringpool)); - return false; - } - - if(cur + stringpool.header.size > manifestBytes.size()) - { - RDCERR("String pool is truncated, expected %u more bytes but only have %u", - stringpool.header.size, manifestBytes.size() - cur); - return false; - } - - cur += stringpool.header.size; - - ResChunk_header resMap = GetFromBytes(manifestBytes, cur); - const size_t resMapOffset = cur; - - if(resMap.type != ResType::ResourceMap) - { - RDCERR("Manifest format is unsupported, expected resource table but got %u", resMap.type); - return false; - } - - if(resMap.headerSize != sizeof(resMap)) - { - RDCERR("Resource map is malformed, header size is reported as %u but expected %u", - resMap.headerSize, sizeof(resMap)); - return false; - } - - if(cur + resMap.size > manifestBytes.size()) - { - RDCERR("Resource map is truncated, expected %u more bytes but only have %u", resMap.size, - manifestBytes.size() - cur); - return false; - } - - const uint32_t resourceMappingCount = (resMap.size - resMap.headerSize) / sizeof(uint32_t); - const rdcarray resourceMapping( - (const uint32_t *)(manifestBytes.data() + cur + resMap.headerSize), resourceMappingCount); - - cur += resMap.size; - - bool stringAdded = false; - - // now chunks will come along. There will likely first be a namespace begin, then XML tag open and - // close. Since the tag is only valid in one place in the XML we can just continue - // iterating until we find it - we don't actually need to care about the structure of the XML - // since we are identifying a unique tag and adding one attribute. - while(cur < manifestBytes.size()) - { - ResChunk_header node = GetFromBytes(manifestBytes, cur); - - if(node.type != ResType::StartElement) - { - cur += node.size; - continue; - } - - ResXMLTree_attrExt startElement = - GetFromBytes(manifestBytes, cur + node.headerSize); - - rdcstr name = GetStringPoolValue(manifestBytes, startElement.name); - - if(name != "application") - { - cur += node.size; - continue; - } - - // found the application tag! Now search its attribtues to see if it already has a debuggable - // attribute (that might be set explicitly to false instead of defaulting) - if(startElement.attributeSize != sizeof(ResXMLTree_attribute)) - { - RDCWARN("Declared attribute size %u doesn't match what we expect %zu", - startElement.attributeSize, sizeof(ResXMLTree_attribute)); - } - - if(startElement.attributeStart != sizeof(startElement)) - { - RDCWARN("Declared attribute start offset %u doesn't match what we expect %zu", - startElement.attributeStart, sizeof(startElement)); - } - - const size_t attributeStartOffset = cur + node.headerSize + startElement.attributeStart; - byte *attributesStart = manifestBytes.data() + attributeStartOffset; - - bool found = false; - - for(uint32_t i = 0; i < startElement.attributeCount; i++) - { - ResXMLTree_attribute *attribute = - (ResXMLTree_attribute *)(attributesStart + startElement.attributeSize * i); - - rdcstr attr = GetStringPoolValue(manifestBytes, attribute->name); - - if(attr != "debuggable") - continue; - - uint32_t resourceId = 0; - - if(attribute->name.index < resourceMappingCount) - { - resourceId = resourceMapping[attribute->name.index]; - } - else - { - RDCWARN("Found debuggable attribute, but it's not linked to any resource ID"); - - if(attribute->typedValue.dataType != Res_value::DataType::Boolean) - { - RDCERR("Found debuggable attribute that isn't boolean typed! Not modifying"); - return false; - } - else - { - RDCDEBUG("Setting non-resource ID debuggable attribute to true"); - attribute->typedValue.data = ~0U; - - if(attribute->rawValue.index != ~0U) - { - RDCWARN("attribute has raw value '%s' which we aren't patching", - GetStringPoolValue(manifestBytes, attribute->rawValue).c_str()); - } - - // we'll still add a debuggable attribute that is resource ID linked, so we don't mark the - // attribute as found and break out of the loop yet - continue; - } - } - - if(resourceId != debuggableResourceId) - { - RDCERR( - "Found debuggable attribute mapped to resource %x, not %x as we expect! Not modifying", - resourceId, debuggableResourceId); - return false; - } - - RDCDEBUG("Found debuggable attribute."); - - if(attribute->typedValue.dataType != Res_value::DataType::Boolean) - { - RDCERR("Found debuggable attribute that isn't boolean typed! Not modifying"); - return false; - } - else - { - RDCDEBUG("Setting resource ID debuggable attribute to true"); - attribute->typedValue.data = ~0U; - - if(attribute->rawValue.index != ~0U) - { - RDCWARN("attribute has raw value '%s' which we aren't patching", - GetStringPoolValue(manifestBytes, attribute->rawValue).c_str()); - } - } - - found = true; - break; - } - - if(found) - break; - - if(startElement.attributeSize != sizeof(ResXMLTree_attribute)) - { - RDCERR("Unexpected attribute size %u, can't add missing attribute", startElement.attributeSize); - return false; - } - - // default to an invalid value (the manifest would have to be GBs to have this as a valid string - // index. - // If we don't find the existing string to use, then this will be remapped below when we're - // remapping all the other indices. - ResStringPool_ref stringIndex = {addingStringIndex}; - - // we didn't find the attribute, so we need to search for the appropriate string, add it if not - // there, and add the attribute. - for(uint32_t i = 0; i < resourceMappingCount; i++) - { - if(resourceMapping[i] == debuggableResourceId) - { - rdcstr str = GetStringPoolValue(manifestBytes, {i}); - - if(str != "debuggable") - { - RDCWARN("Found debuggable resource ID, but it was linked to string '%s' not 'debuggable'", - str.c_str()); - continue; - } - - stringIndex = {i}; - } - } - - // declare the debuggable attribute - ResXMLTree_attribute debuggable; - debuggable.ns.index = ~0U; - debuggable.name = stringIndex; - debuggable.rawValue.index = ~0U; - debuggable.typedValue.size = sizeof(Res_value); - debuggable.typedValue.res0 = 0; - debuggable.typedValue.dataType = Res_value::DataType::Boolean; - debuggable.typedValue.data = ~0U; - - // search the stringpool for the schema, it should be there already. - for(uint32_t i = 0; i < stringpool.stringCount; i++) - { - rdcstr val = GetStringPoolValue(manifestBytes, {i}); - if(val == "http://schemas.android.com/apk/res/android") - { - debuggable.ns.index = i; - break; - } - } - - if(debuggable.ns.index == ~0U) - RDCWARN("Couldn't find android schema, declaring attribute without schema"); - - // it seems the attribute must be added so that the attributes are sorted in resource ID order. - // We assume the attributes are already sorted according to this order, so we insert at the - // index of the first attribute we encounter with either no resource ID (i.e. if we only - // encountered lower resource IDs then we hit a non-resource ID attribute), or a higher resource - // ID than ours (in which case we're inserting it in the right place). - uint32_t attributeInsertIndex = 0; - - for(uint32_t i = 0; i < startElement.attributeCount; i++) - { - ResXMLTree_attribute *attr = - (ResXMLTree_attribute *)(attributesStart + startElement.attributeSize * i); - - if(attr->name.index >= resourceMappingCount) - { - attributeInsertIndex = i; - RDCDEBUG("Inserting attribute before %s, with no resource ID", - GetStringPoolValue(manifestBytes, attr->name).c_str()); - break; - } - - uint32_t resourceId = resourceMapping[attr->name.index]; - - if(resourceId >= debuggableResourceId) - { - attributeInsertIndex = i; - RDCDEBUG("Inserting attribute before %s, with resource ID %x", - GetStringPoolValue(manifestBytes, attr->name).c_str(), resourceId); - break; - } - - RDCDEBUG("Skipping past attribute %s, with resource ID %x", - GetStringPoolValue(manifestBytes, attr->name).c_str(), resourceId); - } - - InsertBytes(manifestBytes, - attributeStartOffset + startElement.attributeSize * attributeInsertIndex, debuggable); - - // update header - node.size += sizeof(ResXMLTree_attribute); - SetFromBytes(manifestBytes, cur, node); - - startElement.attributeCount++; - SetFromBytes(manifestBytes, cur + node.headerSize, startElement); - - stringAdded = (stringIndex.index == addingStringIndex); - - break; - } - - // if we added the string, we need to update the string pool and resource map, then finally update - // all stringrefs in the nodes. We do this in reverse order so that we don't invalidate pointers - // with insertions - if(stringAdded) - { - uint32_t insertIdx = resourceMappingCount; - - // add to the resource map first because it's after the string pool, that way we don't have to - // account for string pool modifications in resMapOffset - { - InsertBytes(manifestBytes, resMapOffset + resMap.size, debuggableResourceId); - resMap.size += sizeof(uint32_t); - SetFromBytes(manifestBytes, resMapOffset, resMap); - } - - // add to the string pool next - { - // add the offset - stringpool.header.size += sizeof(uint32_t); - stringpool.stringCount++; - stringpool.stringsStart += sizeof(uint32_t); - // if we're adding a string we don't bother to do it sorted, so remove the sorted flag - stringpool.flags = - ResStringPool_header::StringFlags(stringpool.flags & ~ResStringPool_header::SORTED_FLAG); - - size_t stringpoolStringOffsetsOffset = stringpoolOffset + stringpool.header.headerSize; - - // we insert a zero offset at the position we're inserting. Then when we fix up that and all - // subsequent offsets - InsertBytes(manifestBytes, stringpoolStringOffsetsOffset + sizeof(uint32_t) * insertIdx, - uint32_t(0)); - - bytebuf stringbytes; - - // construct the string, with length prefix and trailing NULL - if(stringpool.flags & ResStringPool_header::UTF8_FLAG) - { - stringbytes = {0xA, 0xA, 'd', 'e', 'b', 'u', 'g', 'g', 'a', 'b', 'l', 'e', 0}; - } - else - { - stringbytes = {0xA, 0x0, 'd', 0, 'e', 0, 'b', 0, 'u', 0, 'g', 0, - 'g', 0, 'a', 0, 'b', 0, 'l', 0, 'e', 0, 0, 0}; - } - - // account for added string - stringpool.header.size += stringbytes.count(); - - // shift all the offsets *after* the string we inserted (we inserted precisely at that - // offset). - uint32_t *stringOffsets = (uint32_t *)(manifestBytes.data() + stringpoolStringOffsetsOffset); - - // the one we inserted will be inserted at the offset of whichever was previously at that - // index (which is now one further on) - stringOffsets[insertIdx] = stringOffsets[insertIdx + 1]; - - for(uint32_t i = insertIdx + 1; i < stringpool.stringCount; i++) - stringOffsets[i] += stringbytes.count(); - - // now insert the string bytes - InsertBytes(manifestBytes, - stringpoolOffset + stringpool.stringsStart + stringOffsets[insertIdx], stringbytes); - - // if the stringpool isn't integer aligned, add padding bytes - uint32_t alignedSize = AlignUp4(stringpool.header.size); - - if(alignedSize > stringpool.header.size) - { - uint32_t paddingLen = alignedSize - stringpool.header.size; - - RDCDEBUG("Inserting %u padding bytes to align %u up to %u", paddingLen, - stringpool.header.size, alignedSize); - - bytebuf padding; - padding.resize(paddingLen); - - InsertBytes(manifestBytes, stringpoolOffset + stringpool.header.size, padding); - - stringpool.header.size += paddingLen; - } - - // write the updated stringpool - SetFromBytes(manifestBytes, stringpoolOffset, stringpool); - } - - // now iterate over all nodes and fixup any stringrefs pointing after our insert point - byte *ptr = manifestBytes.data() + xmlroot.headerSize; - // skip string pool, whatever size it is now - ptr += ((ResChunk_header *)ptr)->size; - // skip resource map, whatever size it is now - ptr += ((ResChunk_header *)ptr)->size; - - while(ptr < manifestBytes.end()) - { - ResXMLTree_node *node = (ResXMLTree_node *)ptr; - - if(node->header.headerSize != sizeof(*node)) - RDCWARN("Headersize was reported as %u, but we expected ResXMLTree_node size %zu", - node->header.headerSize, sizeof(*node)); - - ShiftStringPoolValue(node->comment, insertIdx); - - switch(node->header.type) - { - // namespace start and end are identical - case ResType::NamespaceStart: - case ResType::NamespaceEnd: - { - ResXMLTree_namespaceExt *ns = (ResXMLTree_namespaceExt *)(ptr + node->header.headerSize); - - ShiftStringPoolValue(ns->prefix, insertIdx); - ShiftStringPoolValue(ns->uri, insertIdx); - break; - } - case ResType::EndElement: - { - ResXMLTree_endElementExt *endElement = - (ResXMLTree_endElementExt *)(ptr + node->header.headerSize); - - ShiftStringPoolValue(endElement->ns, insertIdx); - ShiftStringPoolValue(endElement->name, insertIdx); - break; - } - case ResType::CDATA: - { - ResXMLTree_cdataExt *cdata = (ResXMLTree_cdataExt *)(ptr + node->header.headerSize); - - ShiftStringPoolValue(cdata->data, insertIdx); - ShiftStringPoolValue(cdata->typedData, insertIdx); - break; - } - case ResType::StartElement: - { - ResXMLTree_attrExt *startElement = (ResXMLTree_attrExt *)(ptr + node->header.headerSize); - - ShiftStringPoolValue(startElement->ns, insertIdx); - ShiftStringPoolValue(startElement->name, insertIdx); - - // update attributes - byte *attributesStart = ptr + node->header.headerSize + startElement->attributeStart; - - for(uint32_t i = 0; i < startElement->attributeCount; i++) - { - ResXMLTree_attribute *attr = - (ResXMLTree_attribute *)(attributesStart + startElement->attributeSize * i); - - ShiftStringPoolValue(attr->ns, insertIdx); - ShiftStringPoolValue(attr->name, insertIdx); - ShiftStringPoolValue(attr->rawValue, insertIdx); - ShiftStringPoolValue(attr->typedValue, insertIdx); - } - break; - } - default: - RDCERR("Unhandled chunk %x, can't patch stringpool references", node->header.type); - return false; - } - - ptr += node->header.size; - } - } - - xmlroot.size = (uint32_t)manifestBytes.size(); - SetFromBytes(manifestBytes, 0, xmlroot); - - return true; -} -}; diff --git a/renderdoc/android/android_patch.cpp b/renderdoc/android/android_patch.cpp deleted file mode 100644 index 1802fc9b9..000000000 --- a/renderdoc/android/android_patch.cpp +++ /dev/null @@ -1,622 +0,0 @@ -/****************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2019-2022 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 "api/replay/version.h" -#include "core/core.h" -#include "miniz/miniz.h" -#include "replay/replay_driver.h" -#include "strings/string_utils.h" -#include "android_utils.h" - -static const char keystoreName[] = "renderdoc.keystore"; - -namespace Android -{ -bool RemoveAPKSignature(const rdcstr &apk) -{ - RDCLOG("Checking for existing signature"); - - rdcstr aapt = getToolPath(ToolDir::BuildTools, "aapt", false); - - // Get the list of files in META-INF - rdcstr 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; - - rdcarray lines; - split(fileList, lines, '\n'); - - for(rdcstr &line : lines) - { - line.trim(); - fileCount++; - if(line.beginsWith("META-INF")) - { - 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; - split(fileList, lines, '\n'); - for(rdcstr &line : lines) - { - line.trim(); - if(line.beginsWith("META-INF")) - { - RDCERR("Match found, that means removal failed! %s", line.c_str()); - return false; - } - } - return true; -} - -bool ExtractAndRemoveManifest(const rdcstr &apk, bytebuf &manifest) -{ - // pull out the manifest with miniz - mz_zip_archive zip; - RDCEraseEl(zip); - - mz_bool b = mz_zip_reader_init_file(&zip, apk.c_str(), 0); - - if(b) - { - mz_uint numfiles = mz_zip_reader_get_num_files(&zip); - - for(mz_uint i = 0; i < numfiles; i++) - { - mz_zip_archive_file_stat zstat; - mz_zip_reader_file_stat(&zip, i, &zstat); - - if(!strcmp(zstat.m_filename, "AndroidManifest.xml")) - { - size_t sz = 0; - byte *buf = (byte *)mz_zip_reader_extract_to_heap(&zip, i, &sz, 0); - - RDCLOG("Got manifest of %zu bytes", sz); - - manifest = bytebuf(buf, sz); - free(buf); - break; - } - } - } - else - { - RDCERR("Couldn't open %s", apk.c_str()); - } - - mz_zip_reader_end(&zip); - - if(manifest.empty()) - return false; - - rdcstr aapt = getToolPath(ToolDir::BuildTools, "aapt", false); - - RDCDEBUG("Removing AndroidManifest.xml"); - execCommand(aapt, "remove \"" + apk + "\" AndroidManifest.xml"); - - rdcstr fileList = execCommand(aapt, "list \"" + apk + "\"").strStdout; - rdcarray files; - split(fileList, files, ' '); - - for(rdcstr &f : files) - { - f.trim(); - if(f == "AndroidManifest.xml") - { - RDCERR("AndroidManifest.xml found, that means removal failed!"); - return false; - } - } - - return true; -} - -bool AddManifestToAPK(const rdcstr &apk, const rdcstr &tmpDir, const bytebuf &manifest) -{ - rdcstr aapt = getToolPath(ToolDir::BuildTools, "aapt", false); - - // write the manifest to disk - FileIO::WriteAll(tmpDir + "AndroidManifest.xml", manifest); - - // run aapt to add the manifest - Process::ProcessResult result = - execCommand(aapt, "add \"" + apk + "\" AndroidManifest.xml", tmpDir); - - if(result.strStdout.empty()) - { - RDCERR("Failed to add manifest to APK. STDERR: %s", result.strStderror.c_str()); - return false; - } - - return true; -} - -bool RealignAPK(const rdcstr &apk, rdcstr &alignedAPK, const rdcstr &tmpDir) -{ - rdcstr zipalign = getToolPath(ToolDir::BuildTools, "zipalign", false); - - // Re-align the APK for performance - RDCLOG("Realigning APK"); - rdcstr 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)) - { - RDCLOG("Aligned APK ready to go, continuing..."); - return true; - } - - Threading::Sleep(1000); - elapsed += 1000; - } - - RDCERR("Timeout reached aligning APK"); - return false; -} - -rdcstr GetAndroidDebugKey() -{ - rdcstr keystore = getToolPath(ToolDir::None, keystoreName, false); - - // if we found the keystore, use that. - if(FileIO::exists(keystore)) - return keystore; - - // otherwise, generate a temporary one - rdcstr key = FileIO::GetTempFolderFilename() + keystoreName; - - FileIO::Delete(key); - - // locate keytool and use it to generate a keystore - rdcstr create; - create += " -genkey"; - create += " -keystore \"" + key + "\""; - create += " -storepass android"; - create += " -alias rdocandroidkey"; - create += " -keypass android"; - create += " -keyalg RSA"; - create += " -keysize 2048"; - create += " -validity 10000"; - create += " -dname \"CN=, OU=, O=, L=, S=, C=\""; - - rdcstr keytool = getToolPath(ToolDir::Java, "keytool", false); - - Process::ProcessResult createResult = execCommand(keytool, create); - - Process::ProcessResult verifyResult; - - // if the keystore was created, check that the key we expect to be in it is there - if(FileIO::exists(key)) - { - rdcstr verify; - verify += " -list"; - verify += " -keystore \"" + key + "\""; - verify += " -storepass android"; - - verifyResult = execCommand(keytool, verify); - - if(verifyResult.strStdout.contains("rdocandroidkey")) - return key; - } - - RDCERR("Failed to create debug key: %s\n%s\n%s\n%s", createResult.strStdout.c_str(), - createResult.strStderror.c_str(), verifyResult.strStdout.c_str(), - verifyResult.strStderror.c_str()); - return ""; -} - -bool DebugSignAPK(const rdcstr &apk, const rdcstr &workDir) -{ - RDCLOG("Signing with debug key"); - - rdcstr aapt = getToolPath(ToolDir::BuildTools, "aapt", false); - rdcstr apksigner = getToolPath(ToolDir::BuildToolsLib, "apksigner.jar", false); - - rdcstr debugKey = GetAndroidDebugKey(); - - if(debugKey.empty()) - return false; - - rdcstr args; - args += " sign "; - args += " --ks \"" + debugKey + "\" "; - args += " --ks-pass pass:android "; - args += " --key-pass pass:android "; - args += " --ks-key-alias rdocandroidkey "; - args += "\"" + apk + "\""; - - if(!apksigner.contains(".jar")) - { - // if we found the non-jar version, then the jar wasn't located and we found the wrapper script - // in PATH. Execute it as a script - execScript(apksigner, args, workDir); - } - else - { - // otherwise, find and invoke java on the .jar - rdcstr java = getToolPath(ToolDir::Java, "java", false); - - rdcstr signerdir = get_dirname(FileIO::GetFullPathname(apksigner)); - - rdcstr javaargs; - javaargs += " \"-Djava.ext.dirs=" + signerdir + "\""; - javaargs += " -jar \"" + apksigner + "\""; - javaargs += args; - - execCommand(java, javaargs, workDir); - } - - // Check for signature - rdcstr list = execCommand(aapt, "list \"" + apk + "\"").strStdout; - - list.insert(0, '\n'); - - if(list.find("\nMETA-INF") >= 0) - { - RDCLOG("Signature found, continuing..."); - return true; - } - - RDCERR("re-sign of APK failed!"); - return false; -} - -bool UninstallOriginalAPK(const rdcstr &deviceID, const rdcstr &packageName, const rdcstr &workDir) -{ - RDCLOG("Uninstalling previous version of application"); - - adbExecCommand(deviceID, "uninstall " + packageName, workDir); - - // Wait until uninstall completes - rdcstr 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 rdcstr &deviceID, const rdcstr &apk, const rdcstr &abi, - const rdcstr &packageName, const rdcstr &workDir) -{ - RDCLOG("Reinstalling APK"); - - if(abi == "null" || abi.empty()) - adbExecCommand(deviceID, "install \"" + apk + "\"", workDir); - else - adbExecCommand(deviceID, "install --abi " + abi + " \"" + apk + "\"", workDir); - - // Wait until re-install completes - rdcstr 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 required tools for patching - rdcarray> requirements; - rdcarray missingTools; - requirements.push_back({ToolDir::BuildTools, "aapt"}); - requirements.push_back({ToolDir::BuildTools, "zipalign"}); - requirements.push_back({ToolDir::BuildToolsLib, "apksigner.jar"}); - requirements.push_back({ToolDir::Java, "java"}); - - for(uint32_t i = 0; i < requirements.size(); i++) - { - rdcstr tool = getToolPath(requirements[i].first, requirements[i].second, true); - - // if we located the tool, we're fine. - if(toolExists(tool)) - continue; - - // didn't find it. - missingTools.push_back(requirements[i].second); - } - - // keytool is special - we look for a debug key first - { - rdcstr key = getToolPath(ToolDir::None, keystoreName, true); - - if(key.empty()) - { - // if we don't have the debug key, check that we can find keytool. First in our normal search - rdcstr tool = getToolPath(ToolDir::Java, "keytool", true); - - if(tool.empty()) - { - // if not, it's missing too - missingTools.push_back("keytool"); - } - } - } - - 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 rdcstr &deviceID, const rdcstr &pkgPath, const rdcstr &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)) - { - RDCLOG("Original APK ready to go, continuing..."); - return true; - } - - Threading::Sleep(1000); - elapsed += 1000; - } - - RDCLOG("Failed to pull APK"); - return false; -} - -void CopyAPK(const rdcstr &deviceID, const rdcstr &pkgPath, const rdcstr ©Path) -{ - RDCLOG("Copying APK to %s", copyPath.c_str()); - adbExecCommand(deviceID, "shell cp " + pkgPath + " " + copyPath); -} - -void RemoveAPK(const rdcstr &deviceID, const rdcstr &path) -{ - RDCLOG("Removing APK from %s", path.c_str()); - adbExecCommand(deviceID, "shell rm -f " + path); -} - -bool HasRootAccess(const rdcstr &deviceID) -{ - RDCLOG("Checking for root access on %s", deviceID.c_str()); - - // 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. - - Process::ProcessResult result = adbExecCommand(deviceID, "root"); - - rdcstr whoami = adbExecCommand(deviceID, "shell whoami").strStdout.trimmed(); - if(whoami == "root") - return true; - - rdcstr checksu = - adbExecCommand(deviceID, "shell test -e /system/xbin/su && echo found").strStdout.trimmed(); - if(checksu == "found") - return true; - - return false; -} - -rdcstr GetFirstMatchingLine(const rdcstr &haystack, const rdcstr &needle) -{ - int needleOffset = haystack.find(needle); - - if(needleOffset == -1) - return rdcstr(); - - int nextLine = haystack.find('\n', needleOffset + 1); - - return haystack.substr(needleOffset, nextLine == -1 ? ~0U : size_t(nextLine - needleOffset)); -} - -bool IsDebuggable(const rdcstr &deviceID, const rdcstr &packageName) -{ - RDCLOG("Checking that APK is debuggable"); - - rdcstr info = adbExecCommand(deviceID, "shell dumpsys package " + packageName).strStdout; - - rdcstr pkgFlags = GetFirstMatchingLine(info, "pkgFlags=["); - - if(pkgFlags == "") - { - RDCERR("Couldn't get pkgFlags from adb"); - return false; - } - - return pkgFlags.contains("DEBUGGABLE"); -} -}; - -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage( - const rdcstr &URL, const rdcstr &packageAndActivity, AndroidFlags *flags) -{ - IDeviceProtocolHandler *adb = RenderDoc::Inst().GetDeviceProtocol("adb"); - - rdcstr deviceID = adb->GetDeviceID(URL); - - // Reset the flags each time we check - *flags = AndroidFlags::NoFlags; - - if(Android::IsDebuggable(deviceID, Android::GetPackageName(packageAndActivity))) - { - *flags |= AndroidFlags::Debuggable; - } - else - { - RDCLOG("%s is not debuggable", packageAndActivity.c_str()); - } - - if(Android::HasRootAccess(deviceID)) - { - RDCLOG("Root access detected"); - *flags |= AndroidFlags::RootAccess; - } - - return; -} - -extern "C" RENDERDOC_API AndroidFlags RENDERDOC_CC RENDERDOC_MakeDebuggablePackage( - const rdcstr &URL, const rdcstr &packageAndActivity, RENDERDOC_ProgressCallback progress) -{ - rdcstr package = Android::GetPackageName(packageAndActivity); - - Process::ProcessResult result = {}; - - IDeviceProtocolHandler *adb = RenderDoc::Inst().GetDeviceProtocol("adb"); - - rdcstr deviceID = adb->GetDeviceID(URL); - - // make sure progress is valid so we don't have to check it everywhere - if(!progress) - progress = [](float) {}; - - progress(0.0f); - - if(!Android::CheckPatchingRequirements()) - return AndroidFlags::MissingTools; - - progress(0.02f); - - rdcstr abi = Android::DetermineInstalledABI(deviceID, package); - - // Find the APK on the device - rdcstr pkgPath = Android::GetPathForPackage(deviceID, package) + "base.apk"; - - rdcstr tmpDir = FileIO::GetTempFolderFilename(); - rdcstr origAPK(tmpDir + package + ".orig.apk"); - rdcstr alignedAPK(origAPK + ".aligned.apk"); - bytebuf manifest; - - // Try the following steps, bailing if anything fails - if(!Android::PullAPK(deviceID, pkgPath, origAPK)) - { - // Copy the APK to public storage, then try to pull again - rdcstr copyPath = "/sdcard/" + package + ".copy.apk"; - Android::CopyAPK(deviceID, pkgPath, copyPath); - bool success = Android::PullAPK(deviceID, copyPath, origAPK); - Android::RemoveAPK(deviceID, copyPath); - if(!success) - { - return AndroidFlags::ManifestPatchFailure; - } - } - - progress(0.4f); - - if(!Android::RemoveAPKSignature(origAPK)) - return AndroidFlags::ManifestPatchFailure; - - progress(0.425f); - - if(!Android::ExtractAndRemoveManifest(origAPK, manifest)) - return AndroidFlags::ManifestPatchFailure; - - progress(0.45f); - - if(!Android::PatchManifest(manifest)) - return AndroidFlags::ManifestPatchFailure; - - progress(0.46f); - - if(!Android::AddManifestToAPK(origAPK, tmpDir, manifest)) - return AndroidFlags::ManifestPatchFailure; - - progress(0.475f); - - if(!Android::RealignAPK(origAPK, alignedAPK, tmpDir)) - return AndroidFlags::RepackagingAPKFailure; - - progress(0.5f); - - if(!Android::DebugSignAPK(alignedAPK, tmpDir)) - return AndroidFlags::RepackagingAPKFailure; - - progress(0.525f); - - if(!Android::UninstallOriginalAPK(deviceID, package, tmpDir)) - return AndroidFlags::RepackagingAPKFailure; - - progress(0.6f); - - if(!Android::ReinstallPatchedAPK(deviceID, alignedAPK, abi, package, tmpDir)) - return AndroidFlags::RepackagingAPKFailure; - - progress(0.95f); - - if(!Android::IsDebuggable(deviceID, package)) - return AndroidFlags::ManifestPatchFailure; - - progress(1.0f); - - // All clean! - return AndroidFlags::Debuggable; -} diff --git a/renderdoc/android/android_utils.cpp b/renderdoc/android/android_utils.cpp index 2d5048859..6c56dbe3e 100644 --- a/renderdoc/android/android_utils.cpp +++ b/renderdoc/android/android_utils.cpp @@ -274,6 +274,57 @@ rdcstr GetFriendlyName(const rdcstr &deviceID) return combined; } +bool HasRootAccess(const rdcstr &deviceID) +{ + RDCLOG("Checking for root access on %s", deviceID.c_str()); + + // 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. + + Process::ProcessResult result = adbExecCommand(deviceID, "root"); + + rdcstr whoami = adbExecCommand(deviceID, "shell whoami").strStdout.trimmed(); + if(whoami == "root") + return true; + + rdcstr checksu = + adbExecCommand(deviceID, "shell test -e /system/xbin/su && echo found").strStdout.trimmed(); + if(checksu == "found") + return true; + + return false; +} + +rdcstr GetFirstMatchingLine(const rdcstr &haystack, const rdcstr &needle) +{ + int needleOffset = haystack.find(needle); + + if(needleOffset == -1) + return rdcstr(); + + int nextLine = haystack.find('\n', needleOffset + 1); + + return haystack.substr(needleOffset, nextLine == -1 ? ~0U : size_t(nextLine - needleOffset)); +} + +bool IsDebuggable(const rdcstr &deviceID, const rdcstr &packageName) +{ + RDCLOG("Checking that APK is debuggable"); + + rdcstr info = adbExecCommand(deviceID, "shell dumpsys package " + packageName).strStdout; + + rdcstr pkgFlags = GetFirstMatchingLine(info, "pkgFlags=["); + + if(pkgFlags == "") + { + RDCERR("Couldn't get pkgFlags from adb"); + return false; + } + + return pkgFlags.contains("DEBUGGABLE"); +} + // on android only when we hit this function we write a marker that isn't a standard log. The // purpose is to always try and have a unique message in the last N lines so that we can detect if // we ever lose messages. diff --git a/renderdoc/android/android_utils.h b/renderdoc/android/android_utils.h index 437722b40..6f6cc7f4e 100644 --- a/renderdoc/android/android_utils.h +++ b/renderdoc/android/android_utils.h @@ -48,6 +48,8 @@ enum class ToolDir rdcstr getToolPath(ToolDir subdir, const rdcstr &toolname, bool checkExist); bool toolExists(const rdcstr &path); +bool IsDebuggable(const rdcstr &deviceID, const rdcstr &packageName); +bool HasRootAccess(const rdcstr &deviceID); rdcstr GetFirstMatchingLine(const rdcstr &haystack, const rdcstr &needle); bool IsSupported(rdcstr deviceID); diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 912038b13..53b5949e3 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -2166,10 +2166,6 @@ DOCUMENT("INTERNAL: Check remote Android package for requirements"); extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage( const rdcstr &URL, const rdcstr &packageAndActivity, AndroidFlags *flags); -DOCUMENT("INTERNAL: Patch an APK to add debuggable flag."); -extern "C" RENDERDOC_API AndroidFlags RENDERDOC_CC RENDERDOC_MakeDebuggablePackage( - const rdcstr &URL, const rdcstr &packageAndActivity, RENDERDOC_ProgressCallback progress); - DOCUMENT("An interface for enumerating and controlling remote devices."); struct IDeviceProtocolController { diff --git a/renderdoc/renderdoc.vcxproj b/renderdoc/renderdoc.vcxproj index f2ce743a7..8688bbb53 100644 --- a/renderdoc/renderdoc.vcxproj +++ b/renderdoc/renderdoc.vcxproj @@ -108,7 +108,6 @@ - @@ -526,8 +525,6 @@ true - - diff --git a/renderdoc/renderdoc.vcxproj.filters b/renderdoc/renderdoc.vcxproj.filters index 4b2d29f83..78323c672 100644 --- a/renderdoc/renderdoc.vcxproj.filters +++ b/renderdoc/renderdoc.vcxproj.filters @@ -112,9 +112,6 @@ {f9ed2873-6120-4b34-a3c2-e57b2de31eb9} - - {6e798cbc-0eae-4f48-9d7c-f3fe58bd32dc} - {0362a726-a8e6-436e-b523-893693163440} @@ -432,9 +429,6 @@ Android - - 3rdparty\android - 3rdparty\interceptor-lib @@ -881,9 +875,6 @@ Android - - Android - Android @@ -893,9 +884,6 @@ Android - - Android - 3rdparty\interceptor-lib