[TOOL] DexPatcher: Modify Android applications at source-level in Android Studio

Search This thread

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
UPDATE: added the 'patched-dex' sample

this sample sidesteps the android build system completely and only patches dex code (an Android framework file in this case) without dealing with resources, manifests, etc. a plugin set for this kind of workflow would probably be justified, but for now this sample is based on an ad-hoc Gradle build script. as always, a correctly configured local.properties file is needed in the project root dir.

The sample is here: https://github.com/DexPatcher/dexpatcher-gradle-samples/tree/master/patched-dex


ATT: @CosmicDan
 
  • Like
Reactions: CosmicDan

cryptyk

Senior Member
Jul 2, 2007
858
3,992
Hi,
I have a couple of basic questions and hoping the answers are pretty straight forward so I can decide whether to go deeper.

I'm the author of a modestly successful Xposed module called Amplify. I'd like to move away from Xposed as a requirement and it sounds like DexPatcher might be the right way to do so. Just want to make sure I understand what's possible before I dive deep.

Some questions:
- Can I patch system functions (e.g. PowerManager when apps try to acquire wakelocks)?
- Does the signature validation mess that up? Assume that the user has root. Would I also just patch the system signature verifier?
- How do I handle different Android versions, roms, etc?
- What does installation look like for the user? They install the app from the app store, then what? The app applies the scripts, then I reboot the device?
- Any other big watch-outs as you move from Xposed to DexPatcher?

Thanks!
Ryan
 
Last edited:

kanlo

Member
Nov 27, 2010
38
4
hi!

> fatal: exception: lanchon.multidexlib2.MultiDexDetectedException: C:\Users\Klaus\Desktop\dexpatcher\dexpatcher-gradle-samples-master\patched-app\patched\build\intermediates\exploded-aar\patched-app\source\unspecified\dexpatcher\dex

this directory:
C:\Users\Klaus\Desktop\dexpatcher\dexpatcher-gradle-samples-master\patched-app\patched\build\intermediates\exploded-aar\patched-app\source\unspecified\dexpatcher\dex
has a multidex and multidex is not enabled in your build script. hence the error.

but you are building patched-app sample!
that dir is 'exploded-aar', where the apk library built in subproject 'source' is expanded to.
the 'source' is not a multidex app, so this shouldnt be happening!

how did you modify the sample? what do you want to do?

thanks
Hi Lanchon,
setting multiDex to true was the solution. Thank you very much. Dexpatcher is amazing :good:
Kind regards
 
  • Like
Reactions: Lanchon

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
Hi,
I have a couple of basic questions and hoping the answers are pretty straight forward so I can decide whether to go deeper.

I'm the author of a modestly successful Xposed module called Amplify. I'd like to move away from Xposed as a requirement and it sounds like DexPatcher might be the right way to do so. Just want to make sure I understand what's possible before I dive deep.

Some questions:
- Can I patch system functions (e.g. PowerManager when apps try to acquire wakelocks)?
- Does the signature validation mess that up? Assume that the user has root. Would I also just patch the system signature verifier?
- How do I handle different Android versions, roms, etc?
- What does installation look like for the user? They install the app from the app store, then what? The app applies the scripts, then I reboot the device?
- Any other big watch-outs as you move from Xposed to DexPatcher?

Thanks!
Ryan

hey,

thanks for your interest in dexpatcher!

tl;dr: dxp is not an xposed replacement and you probably don't want to move away from xposed. dxp will continue to be a fringe product for some nerds until some things change.

let me start by explaining what dxp is: dxp-tool is a tool to reengineer dex files. it is akin to disassembling dex using baksmali, developing in smali, then reassembling with smali. the difference is, in dxp you work exclusively in java, making the process orders of magnitud simpler, and thus patches can be orders of magnitud more complex in reality. besides that, it is also a way to build a self-contained dex patch that can be published and is trivial to apply. (in smali the closest would be a smali diff patch, which is extremely brittle and contains copyrighted code.)

compared to xposed, dxp is more powerful in expressiveness (what you can do) and much, much simpler to use with its extensive static type checking.

the problem is, patching dex files is not something regular users do! they install apps, and root OSes, and some install custom roms, and that's about it. so dxp is not useful for the average user at all.

besides the capability to patch dex say of an app, you also need to extract the dex, patch it, rebuild the app, and resign and zip align. and on top you probably want to alter android resources, java resources, the manifest, etc etc. and after that you still need to deal with the changed signature, and sometimes anti-tamper code such as what dexguard can inject.

besides dxp-tool, the core technology, there's dxp-gradle, which in lines of code is a much bigger project.

dxp-gradle mods the android build system to patched apps inside IDEA/Android Studio, and allows patching of code (via dxp-tool) but also doctoring of resources and manifests. you can do stuff like adding activities (code plus their resources) and other high level android objects, all the way to bringing in full android libraries (.aar files). in all cases, dxp-gradle will blend in all resources and code from all the different sources and produce a working app. it can applies mods to the app manifest via android manifest merger mechanism. the original code from the app is never back-translated to java bytecode and resources used by the original app are kept with their numeric ids constant (but they can both be changed).

the signature of course has to change, but you can use signature spoofing if your rom supports it.

dxp-gradle is waaaaay too complex to run on-device. it requires a full android developing environment and more, so running dxp-gradle on a phone is as impossible as developing android apps (today) on android (much less recovery). you can't expect people to use dxp-gradle to patch their own apps, it is more like a system i use to end up with patched apps.

on the other hand, there is some light at the end of the tunnel.

at some point i made dxp-live, a prototype live patcher. i renamed the dex2oat binary and added a shell script in its place that patched just-in-time the file being compiled before passing it to dex2oat. i would keep a dir structure within /data/dexpatcher/patches/ where i put patches i want to apply to anything in the system, for example in /data/dexpatcher/patches/data/app/com.google.suck/suck.apk/my-googlesuck-patch1.dex. the script looks for corresponding patches and run them before invoking dex2oat. and because patching happens while compiling, app signatures are not compromised. it's close to being a perfect deal.

the prototype worked, but it requires an app to front end the install of patches, a patch market, and who knows what else to compete with xposed. i don't have that kind of free time.

another issue with dxp-live is that most roms are odexed, so dxp-live can only patch apps unless you fully deodex your rom. (xposed for ART is similar in this regard, and it deodexes the full rom at install time (i guess using smali) but that's a big headache i don't want to have.) but i heard, android 7 and later no longer come odexed, which relieves me of this deodex crap.

dxp-live would be much easier to use, more powerful, much safer (app patches can only affect the app, they are not root!), can reload app patches by simply killing the app, it's much faster when executing patched code, it doesn't cripple ART optimizations, and it doesn't punch holes in android security. the only problem is... i'm lazy and i'm not developing it :)

a disadvantage is that if you add or remove a system patch, the whole framework has to be recompiled, as it is typically compiled as a single block of code. another issue is android 8's JIT: apparently oreo can run code without compiling it first. how this behavior can be handled is up for debate. (one way is disabling the interpreter, of course, and fall back to android 7 behavior.) if the interpreter can be used, then patching the framework becomes fast, because it can run before compilation (only patching is required pre-run).
 
  • Like
Reactions: PerfectSlayer

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
Hi Lanchon,
setting multiDex to true was the solution. Thank you very much. Dexpatcher is amazing :good:
Kind regards

lol ok, i assume you changed the apk of the 'patched-app' sample but kept on using its skeleton.
ok, then all is good, you dropped in a multidex app and so you just needed to enable multidex support.

thanks!
 

cryptyk

Senior Member
Jul 2, 2007
858
3,992
Wow, thank you for the detailed reply! I have a way better sense for what's possible today, the overall power of what's possible in the future, and what the challenges are. As an app developer who also has a full-time job, I know what you mean about having vision, but not enough time :)

It sounds like DXP isn't the right path, but since you know a LOT about how the underlying system works, let me ask: If I've already written an Xposed module that hooks some methods in the system process and I want to remove the requirement for my users to have Xposed installed, what options do I have?

Ideally there would be a re-distributable, self-contained library that developers could include in their projects to get the same behaviors of hooking/patching other apps with the only requirement being a rooted device. It sounds like there's no such thing, but as a dev, that's what I would wish for :) The requirement to have users install xposed/magisk is painful for a lot of people.

Thanks again for your time!
 
  • Like
Reactions: Lanchon

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
Wow, thank you for the detailed reply! I have a way better sense for what's possible today, the overall power of what's possible in the future, and what the challenges are. As an app developer who also has a full-time job, I know what you mean about having vision, but not enough time :)

It sounds like DXP isn't the right path, but since you know a LOT about how the underlying system works, let me ask: If I've already written an Xposed module that hooks some methods in the system process and I want to remove the requirement for my users to have Xposed installed, what options do I have?

Ideally there would be a re-distributable, self-contained library that developers could include in their projects to get the same behaviors of hooking/patching other apps with the only requirement being a rooted device. It sounds like there's no such thing, but as a dev, that's what I would wish for :) The requirement to have users install xposed/magisk is painful for a lot of people.

Thanks again for your time!

xposed is the route today. instead of apps patching code by themselves, i like having a centralized control center that i trust. ideally app patches shouldnt be root and would not have more rights than the patched app, but that's not true of xposed, unfortunately.

installing xposed is fairly simple, and it gives great service for devs such as yourself. if you took over xposed and rolled your own patcher inside your app, imagine the huge issues u'd have with all sort of phones and the support you'd need to give! you dont want that. not to mention, what would be the interaction between different patchers in a system? :)

thanks!
 

cryptyk

Senior Member
Jul 2, 2007
858
3,992
xposed is the route today. instead of apps patching code by themselves, i like having a centralized control center that i trust. ideally app patches shouldnt be root and would not have more rights than the patched app, but that's not true of xposed, unfortunately.

installing xposed is fairly simple, and it gives great service for devs such as yourself. if you took over xposed and rolled your own patcher inside your app, imagine the huge issues u'd have with all sort of phones and the support you'd need to give! you dont want that. not to mention, what would be the interaction between different patchers in a system? :)

thanks!

Roger. Sounds like that's just the state of affairs today. I look forward to a future where something a little more secure like dxp-live is in place :)
Enjoy your weekend!
Ryan
 
  • Like
Reactions: Lanchon

CosmicDan

Senior Member
Jun 19, 2009
5,902
7,741
35
Sydney
Google Pixel 3 XL
Xiaomi Poco X3 Pro
the prototype worked, but it requires an app to front end the install of patches, a patch market, and who knows what else to compete with xposed. i don't have that kind of free time.

I know how to make apps fairly well and also have admin rights to a reliable dedicated server that can be used for hosting some kind of repository, so I'd love to be notified and possibly help if you ever decide to take another look at this ;)

Sent from my Redmi Note 4 using Tapatalk
 

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
I know how to make apps fairly well and also have admin rights to a reliable dedicated server that can be used for hosting some kind of repository, so I'd love to be notified and possibly help if you ever decide to take another look at this ;)

Sent from my Redmi Note 4 using Tapatalk

thanks :) ill keep it in mind!

did you try the sample i added for you? i think it's what you needed
 

CosmicDan

Senior Member
Jun 19, 2009
5,902
7,741
35
Sydney
Google Pixel 3 XL
Xiaomi Poco X3 Pro
thanks :) ill keep it in mind!

did you try the sample i added for you? i think it's what you needed

First I got a weird error about constructor ambiguity with lines 75 and 76 in build.gradle, so I explicitly cast the first arg to String, and opened fine.

I then had to manually refresh the gradle project, and running dexpatcher produced a patch and patched dex that indeed matches expected results.

Was there supposed to be a way to browse the source dex code/signatures i.e. service.jar (like with the other samples) though? Or is that part of the plugin? Not a big deal if so, there are many ways to view decompiled sources/signatures.

EDIT: I see the "importSourceSymbols" thing in build.gradle, so this suggests to me that it should indeed be showing the source jar in libraries...
 
Last edited:

kanlo

Member
Nov 27, 2010
38
4
I have got a problem building a specific app:

Code:
FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':patched:mergeDebugResources'.
> D:\Android studioProjects\dexpatcher-gradle-samples-master\patched-app\patched\build\intermediates\exploded-aar\patched-app\source\unspecified\res\drawable-v21\$avd_hide_password__0.xml: Error: '$' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore

* Try:
Run with --info or --debug option to get more log output.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':patched:mergeDebugResources'.
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:69)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
	at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
	at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:66)
	at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
	at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:52)
	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
	at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:203)
	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:185)
	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:66)
	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:50)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:25)
	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:110)
	at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
	at org.gradle.execution.DefaultBuildExecuter.access$000(DefaultBuildExecuter.java:23)
	at org.gradle.execution.DefaultBuildExecuter$1.proceed(DefaultBuildExecuter.java:43)
	at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:30)
	at org.gradle.initialization.DefaultGradleLauncher$4.run(DefaultGradleLauncher.java:153)
	at org.gradle.internal.Factories$1.create(Factories.java:22)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:53)
	at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:150)
	at org.gradle.initialization.DefaultGradleLauncher.access$200(DefaultGradleLauncher.java:32)
	at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:98)
	at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:92)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:63)
	at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:92)
	at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:83)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:99)
	at org.gradle.tooling.internal.provider.runner.BuildModelActionRunner.run(BuildModelActionRunner.java:46)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.tooling.internal.provider.runner.SubscribableBuildActionRunner.run(SubscribableBuildActionRunner.java:58)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:48)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30)
	at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:81)
	at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:46)
	at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:52)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
	at org.gradle.util.Swapper.swap(Swapper.java:38)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.health.DaemonHealthTracker.execute(DaemonHealthTracker.java:47)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:60)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:72)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.health.HintGCAfterBuild.execute(HintGCAfterBuild.java:41)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
	at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:237)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
	at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
Caused by: com.android.build.gradle.tasks.ResourceException: D:\Android studioProjects\dexpatcher-gradle-samples-master\patched-app\patched\build\intermediates\exploded-aar\patched-app\source\unspecified\res\drawable-v21\$avd_hide_password__0.xml: Error: '$' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore
	at com.android.build.gradle.tasks.MergeResources.doFullTaskAction(MergeResources.java:161)
	at com.android.build.gradle.internal.tasks.IncrementalTask.taskAction(IncrementalTask.java:88)
	at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:75)
	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.doExecute(AnnotationProcessingTaskFactory.java:245)
	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:221)
	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.execute(AnnotationProcessingTaskFactory.java:232)
	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:210)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
	... 70 more
Caused by: D:\Android studioProjects\dexpatcher-gradle-samples-master\patched-app\patched\build\intermediates\exploded-aar\patched-app\source\unspecified\res\drawable-v21\$avd_hide_password__0.xml: Error: '$' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore
	at com.android.ide.common.res2.MergingException.throwIfNonEmpty(MergingException.java:152)
	at com.android.ide.common.res2.DataSet.loadFromFiles(DataSet.java:257)
	at com.android.ide.common.res2.ResourceSet.loadFromFiles(ResourceSet.java:53)
	at com.android.build.gradle.tasks.MergeResources.doFullTaskAction(MergeResources.java:134)
	... 78 more


BUILD FAILED

I thought I had to change the compileSdk version but the error does not go away. Why is this happening?
The modified sample-project can be found here: https://www.dropbox.com/s/8sb0fsqbf2m6you/patched-app.zip?dl=1

Thanky you very much!
 

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
First I got a weird error about constructor ambiguity with lines 75 and 76 in build.gradle, so I explicitly cast the first arg to String, and opened fine.

I then had to manually refresh the gradle project, and running dexpatcher produced a patch and patched dex that indeed matches expected results.

Was there supposed to be a way to browse the source dex code/signatures i.e. service.jar (like with the other samples) though? Or is that part of the plugin? Not a big deal if so, there are many ways to view decompiled sources/signatures.

EDIT: I see the "importSourceSymbols" thing in build.gradle, so this suggests to me that it should indeed be showing the source jar in libraries...

> First I got a weird error about constructor ambiguity with lines 75 and 76 in build.gradle, so I explicitly cast the first arg to String, and opened fine.

hmmm... i wish you could post the exact error. i can't reproduce it of course, which is puzzling because groovy is part of the gradle distro, so we should have the same. have you upgraded the gradle wrapper for this project? are you using the wrapper? could you run "./gradlew build" from the command line? i'd like to repro and understand the problem instead of just putting the casts there blindly.

> Was there supposed to be a way to browse the source dex

good catch! i just tested, i'm being too clever in the way i inject the symbols. gradle's javac task sees them, but IDEA doesn't at all: it doesn't show them in the browser as you noted, and it also doesn't import them into the intellitype system. i have to fix this.
 

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
I have got a problem building a specific app:

Code:
FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':patched:mergeDebugResources'.
> D:\Android studioProjects\dexpatcher-gradle-samples-master\patched-app\patched\build\intermediates\exploded-aar\patched-app\source\unspecified\res\drawable-v21\$avd_hide_password__0.xml: Error: '$' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore

* Try:
Run with --info or --debug option to get more log output.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':patched:mergeDebugResources'.
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:69)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
	at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
	at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:66)
	at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
	at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:52)
	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
	at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:203)
	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:185)
	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:66)
	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:50)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:25)
	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:110)
	at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
	at org.gradle.execution.DefaultBuildExecuter.access$000(DefaultBuildExecuter.java:23)
	at org.gradle.execution.DefaultBuildExecuter$1.proceed(DefaultBuildExecuter.java:43)
	at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:30)
	at org.gradle.initialization.DefaultGradleLauncher$4.run(DefaultGradleLauncher.java:153)
	at org.gradle.internal.Factories$1.create(Factories.java:22)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:53)
	at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:150)
	at org.gradle.initialization.DefaultGradleLauncher.access$200(DefaultGradleLauncher.java:32)
	at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:98)
	at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:92)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:63)
	at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:92)
	at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:83)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:99)
	at org.gradle.tooling.internal.provider.runner.BuildModelActionRunner.run(BuildModelActionRunner.java:46)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.tooling.internal.provider.runner.SubscribableBuildActionRunner.run(SubscribableBuildActionRunner.java:58)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:48)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30)
	at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:81)
	at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:46)
	at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:52)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
	at org.gradle.util.Swapper.swap(Swapper.java:38)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.health.DaemonHealthTracker.execute(DaemonHealthTracker.java:47)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:60)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:72)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.health.HintGCAfterBuild.execute(HintGCAfterBuild.java:41)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
	at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
	at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:237)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
	at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
Caused by: com.android.build.gradle.tasks.ResourceException: D:\Android studioProjects\dexpatcher-gradle-samples-master\patched-app\patched\build\intermediates\exploded-aar\patched-app\source\unspecified\res\drawable-v21\$avd_hide_password__0.xml: Error: '$' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore
	at com.android.build.gradle.tasks.MergeResources.doFullTaskAction(MergeResources.java:161)
	at com.android.build.gradle.internal.tasks.IncrementalTask.taskAction(IncrementalTask.java:88)
	at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:75)
	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.doExecute(AnnotationProcessingTaskFactory.java:245)
	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:221)
	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.execute(AnnotationProcessingTaskFactory.java:232)
	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:210)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
	... 70 more
Caused by: D:\Android studioProjects\dexpatcher-gradle-samples-master\patched-app\patched\build\intermediates\exploded-aar\patched-app\source\unspecified\res\drawable-v21\$avd_hide_password__0.xml: Error: '$' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore
	at com.android.ide.common.res2.MergingException.throwIfNonEmpty(MergingException.java:152)
	at com.android.ide.common.res2.DataSet.loadFromFiles(DataSet.java:257)
	at com.android.ide.common.res2.ResourceSet.loadFromFiles(ResourceSet.java:53)
	at com.android.build.gradle.tasks.MergeResources.doFullTaskAction(MergeResources.java:134)
	... 78 more


BUILD FAILED

I thought I had to change the compileSdk version but the error does not go away. Why is this happening?
The modified sample-project can be found here: https://www.dropbox.com/s/8sb0fsqbf2m6you/patched-app.zip?dl=1

Thanky you very much!

hi,

> Caused by: D:\Android studioProjects\dexpatcher-gradle-samples-master\patched-app\patched\build\intermediates\exploded-aar\patched-app\source\unspecified\res\drawable-v21\$avd_hide_password__0.xml: Error: '$' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore
> at com.android.ide.common.res2.MergingException.throwIfNonEmpty(MergingException.java:152)

in your apk, in the res/drawable-v21 directory there are several resource files named with a starting '$', and this is supposedly illegal. how they ended up there i don't know.

at first i thought it came from an older android build system that doesn't check the condition, and was going to suggest you try older android plugins. you can try all the way back to 1.5.0.

but then i found this:
https://github.com/iBotPeaches/Apktool/issues/1520#issuecomment-312359148
meaning it might be caused by using aapt2 in the original app. you can try enabling it in the 'patched' project by setting 'android.enableAapt2=true' in your 'gradle.properties' file and restarting the Gradle daemon by running './gradlew --stop' from the command line. i don't think it'll work, but worth taking a shot.
 

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
Was there supposed to be a way to browse the source dex code/signatures i.e. service.jar (like with the other samples) though?

i fixed the source symbols visibility, but i realized that the same problem stands for the android API boot classpath (if enabled).
i'll take a look into this other issue now.

EDIT: i added an ugly hack to make the Android API visible (but couldn't remove the JDK). i couldn't find a better way to do this.
 
Last edited:
  • Like
Reactions: CosmicDan

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
Viewing the code of the APK/DEX being patched as decompiled Java

when using DexPatcher Gradle plugins in Android Studio or IntelliJ IDEA, if you keep the importSymbols = true setting (as per defaults), then you will be able to browse just-in-time decompilation of APK/DEX classes by either of these methods:

- in the source code of the patch, position the cursor over the name of an APK class and type CTRL-B to go to its definition.
- in the 'project' pane at the bottom you will find an 'External Libraries' section, and within it there will be a 'classes.jar' that contains all APK classes.

the IDE uses the FernFlower decompiler to do JIT decompilation. FernFlower sometimes cannot decompile classes; the CFR decompiler is much better in that regard. you can use CFR instead of FernFlower by installing this IDE plugin.

thanks!
 
  • Like
Reactions: bigsupersquid

bigsupersquid

Senior Member
Sep 22, 2010
2,253
1,671
BFE, MO
Viewing the code of the APK/DEX being patched as decompiled Java

when using DexPatcher Gradle plugins in Android Studio or IntelliJ IDEA, if you keep the importSymbols = true setting (as per defaults), then you will be able to browse just-in-time decompilation of APK/DEX classes by either of these methods:

- in the source code of the patch, position the cursor over the name of an APK class and type CTRL-B to go to its definition.
- in the 'project' pane at the bottom you will find an 'External Libraries' section, and within it there will be a 'classes.jar' that contains all APK classes.

the IDE uses the FernFlower decompiler to do JIT decompilation. FernFlower sometimes cannot decompile classes; the CFR decompiler is much better in that regard. you can use CFR instead of FernFlower by installing this IDE plugin.

thanks!
That's lovely information. Thanks!

While I'm posting anyway, I've got a possibly stupid question, but hey, that only stops me when I already know better.
I have an obfuscated ice cream sandwich rom that I'd really like to pick certain functionality out of without having to shim ancient stock functions. Specifically, the camera app and libraries do secret things with talking to the kernel, a little differently than anything else in the rom does. I could get in there and RE the data flow and addressing and stuff by making the kernel really talkative, but I'd like to have some code outlines from the camera apk and anything related I can pick out of the framework as well, if I can get those.
I'm not wanting to adapt the apk itself to modern android... But your mention of the decompiler plugins (I've messed with both decompilers by themselves with limited success a few years back) made this question surface in my mind again.
After all the long winded explanation, the 50 dollar question:

Will I run into any obvious issues picking apart ICS with this nifty toolkit of yours, considering how out of date the android version is?
 

CosmicDan

Senior Member
Jun 19, 2009
5,902
7,741
35
Sydney
Google Pixel 3 XL
Xiaomi Poco X3 Pro
That's lovely information. Thanks!

While I'm posting anyway, I've got a possibly stupid question, but hey, that only stops me when I already know better.
I have an obfuscated ice cream sandwich rom that I'd really like to pick certain functionality out of without having to shim ancient stock functions. Specifically, the camera app and libraries do secret things with talking to the kernel, a little differently than anything else in the rom does. I could get in there and RE the data flow and addressing and stuff by making the kernel really talkative, but I'd like to have some code outlines from the camera apk and anything related I can pick out of the framework as well, if I can get those.
I'm not wanting to adapt the apk itself to modern android... But your mention of the decompiler plugins (I've messed with both decompilers by themselves with limited success a few years back) made this question surface in my mind again.
After all the long winded explanation, the 50 dollar question:

Will I run into any obvious issues picking apart ICS with this nifty toolkit of yours, considering how out of date the android version is?
What nifty toolkit?

You'd be better off with a dedicated decompiling tool for this, DexPatcher doesn't do anything special in this regard. The built-in IDEA decompiler(s) are bare bones and fragile. I'd start with something like http://bytecodeviewer.com for serious reverse engineering. Another alternative is to convert via dex2jar and use one of the many other Java decompilers out there, some even commercial. I've had great success with procyon decompiler which is free. As for commercial ones, nothing beats JEB. It never fails decompiling in my experience, but will create Java-psuedo code that won't recompile instead - yet is relatively easy to understand (much easier than smali anyway).

Sent from my Redmi Note 4 using Tapatalk
 
  • Like
Reactions: bigsupersquid

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
Will I run into any obvious issues picking apart ICS with this nifty toolkit of yours, considering how out of date the android version is?

to analyze the camera apk or parts of the framework i would recommend:
- use dex2jar to convert them to jars,
- then use the enigma GUI to analyze the jars.

old enigma can be found here:
http://www.cuchazinteractive.com/enigma/versions.php

and this is a forked and supposedly improved enigma:
https://bitbucket.org/Earthcomputer/enigma

thanks!
 
  • Like
Reactions: bigsupersquid

Lanchon

Senior Member
Jun 19, 2011
2,715
4,468
What nifty toolkit?

You'd be better off with a dedicated decompiling tool for this, DexPatcher doesn't do anything special in this regard. The built-in IDEA decompiler(s) are bare bones and fragile. I'd start with something like http://bytecodeviewer.com for serious reverse engineering. Another alternative is to convert via dex2jar and use one of the many other Java decompilers out there, some even commercial. I've had great success with procyon decompiler which is free. As for commercial ones, nothing beats JEB. It never fails decompiling in my experience, but will create Java-psuedo code that won't recompile instead - yet is relatively easy to understand (much easier than smali anyway).

Sent from my Redmi Note 4 using Tapatalk

yes, bytecode-viewer and helios are options too, but enigma lets you rename identifiers as you go.
and there are a couple of free, closed source, online decompilers which i never tried.
 
  • Like
Reactions: bigsupersquid

Top Liked Posts

  • There are no posts matching your filters.
  • 56
    DexPatcher
    A toolchain for modifying Android APK files at source-level using Java, graphical resource editors, and the full power of Android Studio in all major platforms.

    • Fully integrated with Android Studio and the Gradle build system.
    • Includes support for coding assistance and on-demand class decompilation.
    • Patch Java code in Java using declarative syntax provided by the DexPatcher tool.
    • Manifest merging enables piece-wise changes to the original app manifest.
    • Modify existing resources or create new ones using Android Studio's standard resource editors.
    • Use Android Studio's code template wizards for creating activities, etc.
    • Pull in Android libraries (a.k.a. '.aar' Android archives) and have their manifests, code and resources automatically merged into the patched app.
    • And enjoy debugging support.
    • All in your favorite platform: Linux, Windows or macOS.




    DexPatcher tool (Dalvik bytecode patcher)
    Release notes: https://github.com/DexPatcher/dexpatcher-tool/releases
    Sources: https://github.com/DexPatcher/dexpatcher-tool

    DexPatcher Gradle plugins (Android build system and Android Studio integration)
    Release notes: https://github.com/DexPatcher/dexpatcher-gradle/releases
    Artifacts: https://plugins.gradle.org/search?term=dexpatcher
    Sources: https://github.com/DexPatcher/dexpatcher-gradle
    Samples: https://github.com/DexPatcher/dexpatcher-gradle-samples

    Deprecated tools
    Release notes for DexPatcher Gradle v1 plugins: https://github.com/DexPatcher/dexpatcher-gradle/releases?after=v2.0.0-alpha1
    Support tools for DexPatcher Gradle v1 plugins: https://github.com/DexPatcher/dexpatcher-gradle-tools
    Workflow automation scripts (Linux-only): https://github.com/DexPatcher/dexpatcher-tool-scripts

    License
    GPL v3 (or later)


    THANK YOU ! - The DexPatcher tool uses JesusFreke's dexlib2 (part of smali) to read and write dex files. Many thanks to him for repeatedly helping me in #smali on freenode. When creating apk libraries, the DexPatcher Gradle plugin uses iBotPeaches' Apktool to decode compiled resources and pxb1988's dex2jar to translate Dalvik bytecode. DexPatcher could not exist without the invaluable work of these guys.


    Documentation

    PATCHING JAVA CODE IN JAVA

    The DexPatcher tool uses declarative semantics based on Java annotations to patch the bytecode of the source application. There is no formal definition but hopefully you will find everything you need in this sample:

    START HERE !

    With the new DexPatcher Gradle v2 plugins

    Get the sample code working, here is how:
    1. Clone the samples.
    2. Start with the 'patched-app' sample: open the project with Android Studio, disable instant run, and run or debug your patched app!

    Please review the release notes of recent versions of the Gradle plugins for more information on tool compatibility and environment setup.

    In the 'patched-app' sample, browse the two 'build.gradle' files (main and app) to get an idea of what is happening. The plugins used are briefly described here. Next get inside the 'app' subproject and take a look at its manifest and resource files. These files are merged with the ones coming from the source app, which is located in the 'apk' directory. Finally look into the 'MainActivity.java' file to see how the compiled code of the app is patched using Java. This is handled by the DexPatcher tool, a key piece of the DexPatcher toolchain. See the section 'Patching Java code in Java' above for more details on this tool, and please review its recent release notes.

    The DexPatcher Gradle plugins use Apktool to decode applications and optionally create APK libraries. They optionally use dex2jar to display decompiled application code and to import application code symbols into patch projects. They use the DexPatcher tool to patch the Dalvik bytecode of applications. And finally they use the Andoird build system to merge manifests, code, resources, assets, and extra files, and to repackage applications.


    With the old DexPatcher Gradle v1 plugins

    To get the old sample code working:
    1. Install the support tools by cloning the repo anywhere you like.
    2. Clone the 'v1' branch of the samples.
    3. Set the 'dexpatcher.dir=<support-tool-dir>' property in identical files named 'local.properties' in the root directory of each sample so that it points to your local clone of the support tools. Create the files if necessary, or have Android Studio create them for you by opening the samples.
    4. Start with the 'patched-app' sample: open the project with Android Studio, disable instant run, and run or debug your patched app!

    Please review the old release notes of the Gradle v1 plugins for more information on tool compatibility and environment setup.


    OLD NEWS: DexPatcher Featured On XDA Portal !

    GermainZ wrote an excellent introductory article for XDA that walks you though the complete process of modding an app using the old deprecated Linux-only workflow automation scripts. This only covers the DexPatcher tool itself, ie: only patching of code, not resources. The workflow is deprecated, but the Java patch code and accompanying explanations are very valuable and continue to be current. I am grateful to him for having taken the time to do this. Please make sure you give it a read:

    Also available: [WARNING: Deprecated, Linux-only]


    LICENSING UPDATE:

    tl;dr: DexPatcher patches are no longer forcefully covered by the GPL.

    Recent versions of DexPatcher no longer require that users bundle the DexPatcher annotations with every patch (although continuing to do so has no ill effects). The DexPatcher licensing terms no longer impose licensing restrictions on patches, as long as users refrain from bundling the DexPatcher annotations with them. In particular, DexPatcher patches are no longer considered to be derivative works of DexPatcher and thus are no longer automatically covered by the GPL. Legals aside, the DexPatcher project urges you not to use copyright laws to introduce artificial scarcity in the world. Please give back to the community: share your work.



    XDA:DevDB Information
    DexPatcher, Tool/Utility for all devices (see above for details)

    Contributors
    Lanchon
    Source Code: https://github.com/DexPatcher


    Version Information
    Status: Stable

    Created 2015-03-21
    Last Updated 2019-11-09
    6
    UPDATE: DexPatcher-gradle v1.0.0 is out, finally!

    also, the 'patched-dex' sample has been heavily updated to match.

    https://github.com/DexPatcher/dexpatcher-gradle/releases
    https://github.com/DexPatcher/dexpatcher-gradle-samples/blob/master/patched-dex/build.gradle
    5
    The Big TO-DO List

    The Big TO-DO List

    ...of things I would like done in this project, but that I will probably not have the time to implement myself.

    DexPatcher-tool

    Moved here: https://github.com/Lanchon/DexPatcher-tool/issues

    DexPatcher-gadle

    Moved here: https://github.com/Lanchon/DexPatcher-gradle/issues

    DexPatcher and javac

    • Modify javac so that all source symbols -even those defined inside edited classes- are available during compilation. This could produce a patched javac executable, or the standard javac could be patched in-memory using java agents, or its in-memory data structures hacked from an annotation processor (a la Project Lombok).
    • Decide if dex2jar and jar2dex/dx are transparent and mature enough to move DexPatcher 2.0 to the java bytecode realm, and stop operating at the dalvik bytecode level. [UPDATE: It is not.] Or a VM abstraction could be implemented as a backend to DexPatcher so that the same font end could be used for both dex and class files.
    • If the project is moved to the java bytecode realm, decide if the new DexPatcher should be a standalone tool like it is today, or a java agent/annotation processor that modifies javac to do the patching during compile time. This would make the tool very easy to integrate into most build systems.
    4
    DEPRECATED: Workflow Automation Scripts HOW-TO

    DEPRECATED: Workflow Automation Scripts HOW-TO

    These scripts bundle several tools (including DexPatcher) and automate boilerplate actions that are part of a typical workflow. They are available only for Linux. Take the leap today, get yourself free, get Linux Mint Cinnamon 64-bit.

    Installation

    Code:
    # cd to a suitable install dir such as '~/opt' or '~/android/tools'.
    
    git clone [url]https://github.com/Lanchon/DexPatcher-scripts.git[/url] dexpatcher
    cd dexpatcher
    chmod +x dxp-*
    
    # edit 'dxp.config':
    # -set the path to android sdk (defaults to '~/android/sdk').
    # -disable bundled tools to use the ones in $PATH if desired.
    
    # add 'dxp-*' scripts to $PATH:
    # -symlink the 'dxp-*' scripts in a suitable dir in the $PATH such as '~/bin'.
    # -or add the 'dexpatcher' dir to $PATH.

    Sample Workflow

    Code:
    # cd to a suitable workspace dir.
    
    dxp-setup-for-apk path/to/TheApk.apk 
    cd TheApk
    dxp-create-keystore
    
    # in 'src-cfr' you will find decompilation of the app to Java (with errors).
    # you can use these files to plan your changes.
    
    # in 'src-cfr-nocode' you will find decompilation of empty method stubs.
    # you can use selected files as a basis for your patch.
     
    # create your patch as Java files in 'src'.
    # the symbols of the original app will be accessible to your patch code.
    
    dxp-make
    adb install -r patched.apk
    4
    UPDATE: version 1.0.0-beta2 released!

    Changelog in the previous link. Do you use DexPatcher? Say hi on this thread!