You've already forked android_translation_layer
mirror of
https://gitlab.com/android_translation_layer/android_translation_layer.git
synced 2025-10-27 11:48:10 -07:00
remove ARSCLib from the source tree
Also remove references to ARSCLib from doc/Architecture.md
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
#### directory structure
|
#### directory structure
|
||||||
`src/ARSCLib/` - Java .arsc library used to parse binary xml resources in apks
|
|
||||||
`doc/` - documentation
|
`doc/` - documentation
|
||||||
`src/api-impl/` - Java code implementing the android APIs
|
`src/api-impl/` - Java code implementing the android APIs
|
||||||
`src/api-impl-jni/` - C code implementing things which it doesn't make sense to do in Java (ideally this would be most things)
|
`src/api-impl-jni/` - C code implementing things which it doesn't make sense to do in Java (ideally this would be most things)
|
||||||
@@ -36,9 +35,7 @@ against)
|
|||||||
2. GtkApplication glue parses the cmdline and calls `static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* hint, struct jni_callback_data *d)`
|
2. GtkApplication glue parses the cmdline and calls `static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* hint, struct jni_callback_data *d)`
|
||||||
3. `static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* hint, struct jni_callback_data *d)`:
|
3. `static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* hint, struct jni_callback_data *d)`:
|
||||||
1. constructs the classpath from the following:
|
1. constructs the classpath from the following:
|
||||||
- the path to api-impl.jar (contains the following, renamed to classes{2}.dex so that art loads them)
|
- the path to api-impl.jar (contains all the implementations of android framework functions)
|
||||||
- `hax_arsc_parser.dex` is dalvik bytecode implementing .arsc parsing duties (to be replaced by C code eventually)
|
|
||||||
- `hax.dex` contains all the implementations of android framework functions
|
|
||||||
- the path to the app's apk (passed to us on cmdline), making the bytecode within (and resources.arsc, which is currently the only other file read straight from the apk) available in classpath
|
- the path to the app's apk (passed to us on cmdline), making the bytecode within (and resources.arsc, which is currently the only other file read straight from the apk) available in classpath
|
||||||
- the path to a microG apk, needed for apps with a dependency on GSF; this is specified after the app's apk so that the the app's apk is the first zip file in the classpath (needed for getting the right resources.arsc, TODO: ask for the classloader which loaded the activity that was specified on the cmdline)
|
- the path to a microG apk, needed for apps with a dependency on GSF; this is specified after the app's apk so that the the app's apk is the first zip file in the classpath (needed for getting the right resources.arsc, TODO: ask for the classloader which loaded the activity that was specified on the cmdline)
|
||||||
2. contructs other options (mainly library path) for and launches the dalvik virtual machine
|
2. contructs other options (mainly library path) for and launches the dalvik virtual machine
|
||||||
|
|||||||
@@ -157,21 +157,16 @@ executable('android-translation-layer', [
|
|||||||
],
|
],
|
||||||
install_rpath: get_option('prefix') / get_option('libdir') / 'art:' + get_option('prefix') / get_option('libdir') / 'java/dex/android_translation_layer/natives')
|
install_rpath: get_option('prefix') / get_option('libdir') / 'art:' + get_option('prefix') / get_option('libdir') / 'java/dex/android_translation_layer/natives')
|
||||||
|
|
||||||
# hax_arsc_lib.dex (named as classes2.dex so it works inside a jar)
|
|
||||||
subdir('src/ARSCLib')
|
|
||||||
hax_arsc_lib_dex = custom_target('hax_arsc_lib.dex', build_by_default: true, input: [hax_arsc_lib_jar], output: ['classes2.dex'],
|
|
||||||
command: ['dx', '--verbose', '--dex', '--min-sdk-version', '26', '--output='+join_paths(builddir_base, 'classes2.dex'), hax_arsc_lib_jar.full_path()])
|
|
||||||
|
|
||||||
# hax.dex (named as classes.dex so it works inside a jar)
|
# hax.dex (named as classes.dex so it works inside a jar)
|
||||||
subdir('src/api-impl')
|
subdir('src/api-impl')
|
||||||
hax_dex = custom_target('hax.dex', build_by_default: true, input: [hax_jar], output: ['classes.dex'],
|
hax_dex = custom_target('hax.dex', build_by_default: true, input: [hax_jar], output: ['classes.dex'],
|
||||||
command: ['dx', '--verbose', '--dex', '--output='+join_paths(builddir_base, 'classes.dex'), hax_jar.full_path()])
|
command: ['dx', '--verbose', '--dex', '--output='+join_paths(builddir_base, 'classes.dex'), hax_jar.full_path()])
|
||||||
|
|
||||||
# api-impl.jar
|
# api-impl.jar
|
||||||
custom_target('api-impl.jar', build_by_default: true, input: [hax_dex, hax_arsc_lib_dex], output: ['api-impl.jar'],
|
custom_target('api-impl.jar', build_by_default: true, input: [hax_dex], output: ['api-impl.jar'],
|
||||||
install: true,
|
install: true,
|
||||||
install_dir : get_option('libdir') / 'java/dex/android_translation_layer',
|
install_dir : get_option('libdir') / 'java/dex/android_translation_layer',
|
||||||
command: ['jar', '-cvf', join_paths(builddir_base, 'api-impl.jar'), '-C', builddir_base, hax_dex, '-C', builddir_base, hax_arsc_lib_dex])
|
command: ['jar', '-cvf', join_paths(builddir_base, 'api-impl.jar'), '-C', builddir_base, hax_dex])
|
||||||
|
|
||||||
#framework-res.apk
|
#framework-res.apk
|
||||||
subdir('res')
|
subdir('res')
|
||||||
|
|||||||
@@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright (C) 2022 github.com/REAndroid
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
taken from https://github.com/REAndroid/ARSCLib
|
|
||||||
licensed under Apache 2.0
|
|
||||||
|
|
||||||
# ARSCLib
|
|
||||||
A tool for decoding Android resources.arsc file using java, for decoding binary XML resources from apk files.
|
|
||||||
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2006 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package android.content.res;
|
|
||||||
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
|
||||||
|
|
||||||
public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
|
|
||||||
String getAttributeNamespace (int index);
|
|
||||||
public void close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2006 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package android.util;
|
|
||||||
|
|
||||||
public interface AttributeSet {
|
|
||||||
public int getAttributeCount();
|
|
||||||
/*default*/ String getAttributeNamespace (int index); /* {
|
|
||||||
return null;
|
|
||||||
}*/
|
|
||||||
public String getAttributeName(int index);
|
|
||||||
public String getAttributeValue(int index);
|
|
||||||
public String getAttributeValue(String namespace, String name);
|
|
||||||
public String getPositionDescription();
|
|
||||||
public int getAttributeNameResource(int index);
|
|
||||||
public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue);
|
|
||||||
public boolean getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue);
|
|
||||||
public int getAttributeResourceValue(String namespace, String attribute, int defaultValue);
|
|
||||||
public int getAttributeIntValue(String namespace, String attribute, int defaultValue);
|
|
||||||
public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue);
|
|
||||||
public float getAttributeFloatValue(String namespace, String attribute, float defaultValue);
|
|
||||||
public int getAttributeListValue(int index, String[] options, int defaultValue);
|
|
||||||
public boolean getAttributeBooleanValue(int index, boolean defaultValue);
|
|
||||||
public int getAttributeResourceValue(int index, int defaultValue);
|
|
||||||
public int getAttributeIntValue(int index, int defaultValue);
|
|
||||||
public int getAttributeUnsignedIntValue(int index, int defaultValue);
|
|
||||||
public float getAttributeFloatValue(int index, float defaultValue);
|
|
||||||
public String getIdAttribute();
|
|
||||||
public String getClassAttribute();
|
|
||||||
public int getIdAttributeResourceValue(int defaultValue);
|
|
||||||
public int getStyleAttribute();
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.org.kxml2.io;
|
|
||||||
|
|
||||||
// Taken from libcore.internal.StringPool
|
|
||||||
|
|
||||||
class LibCoreStringPool {
|
|
||||||
|
|
||||||
private final String[] pool = new String[512];
|
|
||||||
|
|
||||||
public LibCoreStringPool() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean contentEquals(String s, char[] chars, int start, int length) {
|
|
||||||
if (s.length() != length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
if (chars[start + i] != s.charAt(i)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string equal to {@code new String(array, start, length)}.
|
|
||||||
*/
|
|
||||||
public String get(char[] array, int start, int length) {
|
|
||||||
// Compute an arbitrary hash of the content
|
|
||||||
int hashCode = 0;
|
|
||||||
for (int i = start; i < start + length; i++) {
|
|
||||||
hashCode = (hashCode * 31) + array[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pick a bucket using Doug Lea's supplemental secondaryHash function (from HashMap)
|
|
||||||
hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12);
|
|
||||||
hashCode ^= (hashCode >>> 7) ^ (hashCode >>> 4);
|
|
||||||
int index = hashCode & (pool.length - 1);
|
|
||||||
|
|
||||||
String pooled = pool[index];
|
|
||||||
if (pooled != null && contentEquals(pooled, array, start, length)) {
|
|
||||||
return pooled;
|
|
||||||
}
|
|
||||||
|
|
||||||
String result = new String(array, start, length);
|
|
||||||
pool[index] = result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 github.com/REAndroid
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.reandroid.apk;
|
|
||||||
|
|
||||||
public interface APKLogger {
|
|
||||||
void logMessage(String msg);
|
|
||||||
void logError(String msg, Throwable tr);
|
|
||||||
void logVerbose(String msg);
|
|
||||||
}
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 github.com/REAndroid
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.reandroid.apk;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class AndroidFrameworks {
|
|
||||||
private static Map<Integer, String> resource_paths;
|
|
||||||
private static FrameworkApk mCurrent;
|
|
||||||
|
|
||||||
public static void setCurrent(FrameworkApk current){
|
|
||||||
synchronized (AndroidFrameworks.class){
|
|
||||||
mCurrent = current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static FrameworkApk getCurrent(){
|
|
||||||
FrameworkApk current = mCurrent;
|
|
||||||
if(current==null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if(current.isDestroyed()){
|
|
||||||
mCurrent = null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
public static FrameworkApk getLatest() throws IOException {
|
|
||||||
Map<Integer, String> pathMap = getResourcePaths();
|
|
||||||
synchronized (AndroidFrameworks.class){
|
|
||||||
int latest = getHighestVersion();
|
|
||||||
FrameworkApk current = getCurrent();
|
|
||||||
if(current!=null && latest==current.getVersionCode()){
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
String path = pathMap.get(latest);
|
|
||||||
if(path == null){
|
|
||||||
throw new IOException("Could not get latest framework");
|
|
||||||
}
|
|
||||||
return loadResource(latest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static FrameworkApk getBestMatch(int version) throws IOException {
|
|
||||||
Map<Integer, String> pathMap = getResourcePaths();
|
|
||||||
synchronized (AndroidFrameworks.class){
|
|
||||||
int best = getBestMatchVersion(version);
|
|
||||||
FrameworkApk current = getCurrent();
|
|
||||||
if(current!=null && best==current.getVersionCode()){
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
String path = pathMap.get(best);
|
|
||||||
if(path == null){
|
|
||||||
throw new IOException("Could not get framework for version = "+version);
|
|
||||||
}
|
|
||||||
return loadResource(best);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static void destroyCurrent(){
|
|
||||||
synchronized (AndroidFrameworks.class){
|
|
||||||
FrameworkApk current = mCurrent;
|
|
||||||
if(current==null){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
current.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static int getHighestVersion() {
|
|
||||||
Map<Integer, String> pathMap = getResourcePaths();
|
|
||||||
int highest = 0;
|
|
||||||
for(int id:pathMap.keySet()){
|
|
||||||
if(highest==0){
|
|
||||||
highest = id;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(id>highest){
|
|
||||||
highest = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return highest;
|
|
||||||
}
|
|
||||||
private static int getBestMatchVersion(int version) {
|
|
||||||
Map<Integer, String> pathMap = getResourcePaths();
|
|
||||||
if(pathMap.containsKey(version)){
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
int highest = 0;
|
|
||||||
int best = 0;
|
|
||||||
int prevDifference = 0;
|
|
||||||
for(int id:pathMap.keySet()){
|
|
||||||
if(highest==0){
|
|
||||||
highest = id;
|
|
||||||
best = id;
|
|
||||||
prevDifference = version*2 + 1000;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(id>highest){
|
|
||||||
highest = id;
|
|
||||||
}
|
|
||||||
int diff = id-version;
|
|
||||||
if(diff<0){
|
|
||||||
diff=-diff;
|
|
||||||
}
|
|
||||||
if(diff<prevDifference || (diff==prevDifference && id>best)){
|
|
||||||
best = id;
|
|
||||||
prevDifference = diff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
public static FrameworkApk loadResource(int version) throws IOException {
|
|
||||||
String path = getResourcePath(version);
|
|
||||||
if(path == null){
|
|
||||||
throw new IOException("No resource found for version: "+version);
|
|
||||||
}
|
|
||||||
String simpleName = toSimpleName(path);
|
|
||||||
return FrameworkApk.loadApkBuffer(simpleName, AndroidFrameworks.class.getResourceAsStream(path));
|
|
||||||
}
|
|
||||||
private static String getResourcePath(int version){
|
|
||||||
return getResourcePaths().get(version);
|
|
||||||
}
|
|
||||||
private static Map<Integer, String> getResourcePaths(){
|
|
||||||
if(resource_paths!=null){
|
|
||||||
return resource_paths;
|
|
||||||
}
|
|
||||||
synchronized (AndroidFrameworks.class){
|
|
||||||
resource_paths = scanAvailableResourcePaths();
|
|
||||||
return resource_paths;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static Map<Integer, String> scanAvailableResourcePaths(){
|
|
||||||
Map<Integer, String> results = new HashMap<>();
|
|
||||||
int maxSearch = 50;
|
|
||||||
for(int version=20; version<maxSearch; version++){
|
|
||||||
String path = toResourcePath(version);
|
|
||||||
if(!isAvailable(path)){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
results.put(version, path);
|
|
||||||
maxSearch = version + 20;
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
private static String toSimpleName(String path){
|
|
||||||
int i = path.lastIndexOf('/');
|
|
||||||
if(i<0){
|
|
||||||
i = path.lastIndexOf(File.separatorChar);
|
|
||||||
}
|
|
||||||
if(i>0){
|
|
||||||
i++;
|
|
||||||
path = path.substring(i);
|
|
||||||
}
|
|
||||||
i = path.lastIndexOf('.');
|
|
||||||
if(i>=0){
|
|
||||||
path = path.substring(0, i);
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
private static int parseVersion(String name){
|
|
||||||
int i = name.lastIndexOf('/');
|
|
||||||
if(i<0){
|
|
||||||
i = name.lastIndexOf(File.separatorChar);
|
|
||||||
}
|
|
||||||
if(i>0){
|
|
||||||
i++;
|
|
||||||
name = name.substring(i);
|
|
||||||
}
|
|
||||||
i = name.lastIndexOf('-');
|
|
||||||
if(i>=0){
|
|
||||||
i++;
|
|
||||||
name = name.substring(i);
|
|
||||||
}
|
|
||||||
i = name.indexOf('.');
|
|
||||||
if(i>=0){
|
|
||||||
name = name.substring(0, i);
|
|
||||||
}
|
|
||||||
return Integer.parseInt(name);
|
|
||||||
}
|
|
||||||
private static boolean isAvailable(String path){
|
|
||||||
InputStream inputStream = AndroidFrameworks.class.getResourceAsStream(path);
|
|
||||||
if(inputStream==null){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
closeQuietly(inputStream);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
private static void closeQuietly(InputStream stream){
|
|
||||||
if(stream == null){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
stream.close();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static String toResourcePath(int version){
|
|
||||||
return ANDROID_RESOURCE_DIRECTORY + ANDROID_PACKAGE
|
|
||||||
+ '-' + version
|
|
||||||
+FRAMEWORK_EXTENSION;
|
|
||||||
}
|
|
||||||
private static final String ANDROID_RESOURCE_DIRECTORY = "/frameworks/android/";
|
|
||||||
private static final String ANDROID_PACKAGE = "android";
|
|
||||||
private static final String FRAMEWORK_EXTENSION = ".apk";
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 github.com/REAndroid
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.reandroid.apk;
|
|
||||||
|
|
||||||
import com.reandroid.archive.APKArchive;
|
|
||||||
import com.reandroid.archive2.block.ApkSignatureBlock;
|
|
||||||
import com.reandroid.arsc.chunk.TableBlock;
|
|
||||||
import com.reandroid.arsc.pool.TableStringPool;
|
|
||||||
import com.reandroid.arsc.pool.builder.StringPoolMerger;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class ApkBundle {
|
|
||||||
private final Map<String, ApkModule> mModulesMap;
|
|
||||||
private APKLogger apkLogger;
|
|
||||||
public ApkBundle(){
|
|
||||||
this.mModulesMap=new HashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApkModule mergeModules() throws IOException {
|
|
||||||
List<ApkModule> moduleList=getApkModuleList();
|
|
||||||
if(moduleList.size()==0){
|
|
||||||
throw new FileNotFoundException("Nothing to merge, empty modules");
|
|
||||||
}
|
|
||||||
ApkModule result = new ApkModule(generateMergedModuleName(), new APKArchive());
|
|
||||||
result.setAPKLogger(apkLogger);
|
|
||||||
result.setLoadDefaultFramework(false);
|
|
||||||
|
|
||||||
mergeStringPools(result);
|
|
||||||
|
|
||||||
ApkModule base=getBaseModule();
|
|
||||||
if(base==null){
|
|
||||||
base=getLargestTableModule();
|
|
||||||
}
|
|
||||||
result.merge(base);
|
|
||||||
ApkSignatureBlock signatureBlock = null;
|
|
||||||
for(ApkModule module:moduleList){
|
|
||||||
ApkSignatureBlock asb = module.getApkSignatureBlock();
|
|
||||||
if(module==base){
|
|
||||||
if(asb != null){
|
|
||||||
signatureBlock = asb;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(signatureBlock == null){
|
|
||||||
signatureBlock = asb;
|
|
||||||
}
|
|
||||||
result.merge(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.setApkSignatureBlock(signatureBlock);
|
|
||||||
|
|
||||||
if(result.hasTableBlock()){
|
|
||||||
TableBlock tableBlock=result.getTableBlock();
|
|
||||||
tableBlock.sortPackages();
|
|
||||||
tableBlock.refresh();
|
|
||||||
}
|
|
||||||
result.getApkArchive().autoSortApkFiles();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
private void mergeStringPools(ApkModule mergedModule) throws IOException {
|
|
||||||
if(!hasOneTableBlock() || mergedModule.hasTableBlock()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logMessage("Merging string pools ... ");
|
|
||||||
TableBlock createdTable = new TableBlock();
|
|
||||||
BlockInputSource<TableBlock> inputSource=
|
|
||||||
new BlockInputSource<>(TableBlock.FILE_NAME, createdTable);
|
|
||||||
mergedModule.getApkArchive().add(inputSource);
|
|
||||||
|
|
||||||
StringPoolMerger poolMerger = new StringPoolMerger();
|
|
||||||
|
|
||||||
for(ApkModule apkModule:getModules()){
|
|
||||||
if(!apkModule.hasTableBlock()){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
TableStringPool stringPool = apkModule.getVolatileTableStringPool();
|
|
||||||
poolMerger.add(stringPool);
|
|
||||||
}
|
|
||||||
|
|
||||||
poolMerger.mergeTo(createdTable.getTableStringPool());
|
|
||||||
|
|
||||||
logMessage("Merged string pools="+poolMerger.getMergedPools()
|
|
||||||
+", style="+poolMerger.getMergedStyleStrings()
|
|
||||||
+", strings="+poolMerger.getMergedStrings());
|
|
||||||
}
|
|
||||||
private String generateMergedModuleName(){
|
|
||||||
Set<String> moduleNames=mModulesMap.keySet();
|
|
||||||
String merged="merged";
|
|
||||||
int i=1;
|
|
||||||
String name=merged;
|
|
||||||
while (moduleNames.contains(name)){
|
|
||||||
name=merged+"_"+i;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
private ApkModule getLargestTableModule(){
|
|
||||||
ApkModule apkModule=null;
|
|
||||||
int chunkSize=0;
|
|
||||||
for(ApkModule module:getApkModuleList()){
|
|
||||||
if(!module.hasTableBlock()){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
TableBlock tableBlock=module.getTableBlock();
|
|
||||||
int size=tableBlock.getHeaderBlock().getChunkSize();
|
|
||||||
if(apkModule==null || size>chunkSize){
|
|
||||||
chunkSize=size;
|
|
||||||
apkModule=module;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return apkModule;
|
|
||||||
}
|
|
||||||
public ApkModule getBaseModule(){
|
|
||||||
for(ApkModule module:getApkModuleList()){
|
|
||||||
if(module.isBaseModule()){
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
public List<ApkModule> getApkModuleList(){
|
|
||||||
return new ArrayList<>(mModulesMap.values());
|
|
||||||
}
|
|
||||||
public void loadApkDirectory(File dir) throws IOException{
|
|
||||||
loadApkDirectory(dir, false);
|
|
||||||
}
|
|
||||||
public void loadApkDirectory(File dir, boolean recursive) throws IOException {
|
|
||||||
if(!dir.isDirectory()){
|
|
||||||
throw new FileNotFoundException("No such directory: "+dir);
|
|
||||||
}
|
|
||||||
List<File> apkList;
|
|
||||||
if(recursive){
|
|
||||||
apkList = ApkUtil.recursiveFiles(dir, ".apk");
|
|
||||||
}else {
|
|
||||||
apkList = ApkUtil.listFiles(dir, ".apk");
|
|
||||||
}
|
|
||||||
if(apkList.size()==0){
|
|
||||||
throw new FileNotFoundException("No '*.apk' files in directory: "+dir);
|
|
||||||
}
|
|
||||||
logMessage("Found apk files: "+apkList.size());
|
|
||||||
for(File file:apkList){
|
|
||||||
logVerbose("Loading: "+file.getName());
|
|
||||||
String name = ApkUtil.toModuleName(file);
|
|
||||||
ApkModule module = ApkModule.loadApkFile(file, name);
|
|
||||||
module.setAPKLogger(apkLogger);
|
|
||||||
addModule(module);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void addModule(ApkModule apkModule){
|
|
||||||
apkModule.setLoadDefaultFramework(false);
|
|
||||||
String name = apkModule.getModuleName();
|
|
||||||
mModulesMap.remove(name);
|
|
||||||
mModulesMap.put(name, apkModule);
|
|
||||||
}
|
|
||||||
public boolean containsApkModule(String moduleName){
|
|
||||||
return mModulesMap.containsKey(moduleName);
|
|
||||||
}
|
|
||||||
public ApkModule removeApkModule(String moduleName){
|
|
||||||
return mModulesMap.remove(moduleName);
|
|
||||||
}
|
|
||||||
public ApkModule getApkModule(String moduleName){
|
|
||||||
return mModulesMap.get(moduleName);
|
|
||||||
}
|
|
||||||
public List<String> listModuleNames(){
|
|
||||||
return new ArrayList<>(mModulesMap.keySet());
|
|
||||||
}
|
|
||||||
public int countModules(){
|
|
||||||
return mModulesMap.size();
|
|
||||||
}
|
|
||||||
public Collection<ApkModule> getModules(){
|
|
||||||
return mModulesMap.values();
|
|
||||||
}
|
|
||||||
private boolean hasOneTableBlock(){
|
|
||||||
for(ApkModule apkModule:getModules()){
|
|
||||||
if(apkModule.hasTableBlock()){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
public void setAPKLogger(APKLogger logger) {
|
|
||||||
this.apkLogger = logger;
|
|
||||||
}
|
|
||||||
private void logMessage(String msg) {
|
|
||||||
if(apkLogger!=null){
|
|
||||||
apkLogger.logMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void logError(String msg, Throwable tr) {
|
|
||||||
if(apkLogger!=null){
|
|
||||||
apkLogger.logError(msg, tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void logVerbose(String msg) {
|
|
||||||
if(apkLogger!=null){
|
|
||||||
apkLogger.logVerbose(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 github.com/REAndroid
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.reandroid.apk;
|
|
||||||
|
|
||||||
import com.reandroid.archive.InputSource;
|
|
||||||
import com.reandroid.archive2.block.ApkSignatureBlock;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public abstract class ApkDecoder {
|
|
||||||
private final Set<String> mDecodedPaths;
|
|
||||||
private APKLogger apkLogger;
|
|
||||||
private boolean mLogErrors;
|
|
||||||
|
|
||||||
public ApkDecoder(){
|
|
||||||
mDecodedPaths = new HashSet<>();
|
|
||||||
}
|
|
||||||
public final void decodeTo(File outDir) throws IOException{
|
|
||||||
reset();
|
|
||||||
onDecodeTo(outDir);
|
|
||||||
}
|
|
||||||
abstract void onDecodeTo(File outDir) throws IOException;
|
|
||||||
|
|
||||||
boolean containsDecodedPath(String path){
|
|
||||||
return mDecodedPaths.contains(path);
|
|
||||||
}
|
|
||||||
void addDecodedPath(String path){
|
|
||||||
mDecodedPaths.add(path);
|
|
||||||
}
|
|
||||||
void writePathMap(File dir, Collection<? extends InputSource> sourceList) throws IOException {
|
|
||||||
PathMap pathMap = new PathMap();
|
|
||||||
pathMap.add(sourceList);
|
|
||||||
File file = new File(dir, PathMap.JSON_FILE);
|
|
||||||
pathMap.toJson().write(file);
|
|
||||||
}
|
|
||||||
void dumpSignatures(File outDir, ApkSignatureBlock signatureBlock) throws IOException {
|
|
||||||
if(signatureBlock == null){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logMessage("Dumping signatures ...");
|
|
||||||
File dir = new File(outDir, ApkUtil.SIGNATURE_DIR_NAME);
|
|
||||||
signatureBlock.writeSplitRawToDirectory(dir);
|
|
||||||
}
|
|
||||||
void logOrThrow(String message, IOException exception) throws IOException{
|
|
||||||
if(isLogErrors()){
|
|
||||||
logError(message, exception);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(message == null && exception == null){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(exception == null){
|
|
||||||
exception = new IOException(message);
|
|
||||||
}
|
|
||||||
throw exception;
|
|
||||||
}
|
|
||||||
private void reset(){
|
|
||||||
mDecodedPaths.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLogErrors() {
|
|
||||||
return mLogErrors;
|
|
||||||
}
|
|
||||||
public void setLogErrors(boolean logErrors) {
|
|
||||||
this.mLogErrors = logErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setApkLogger(APKLogger apkLogger) {
|
|
||||||
this.apkLogger = apkLogger;
|
|
||||||
}
|
|
||||||
APKLogger getApkLogger() {
|
|
||||||
return apkLogger;
|
|
||||||
}
|
|
||||||
void logMessage(String msg) {
|
|
||||||
APKLogger apkLogger = this.apkLogger;
|
|
||||||
if(apkLogger!=null){
|
|
||||||
apkLogger.logMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void logError(String msg, Throwable tr) {
|
|
||||||
APKLogger apkLogger = this.apkLogger;
|
|
||||||
if(apkLogger == null || (msg == null && tr == null)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
apkLogger.logError(msg, tr);
|
|
||||||
}
|
|
||||||
void logVerbose(String msg) {
|
|
||||||
APKLogger apkLogger = this.apkLogger;
|
|
||||||
if(apkLogger!=null){
|
|
||||||
apkLogger.logVerbose(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 github.com/REAndroid
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.reandroid.apk;
|
|
||||||
|
|
||||||
import com.reandroid.archive.InputSource;
|
|
||||||
import com.reandroid.archive2.block.ApkSignatureBlock;
|
|
||||||
import com.reandroid.arsc.chunk.TableBlock;
|
|
||||||
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
|
|
||||||
import com.reandroid.arsc.chunk.xml.ResXmlDocument;
|
|
||||||
import com.reandroid.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class ApkJsonDecoder {
|
|
||||||
private final ApkModule apkModule;
|
|
||||||
private final Set<String> decodedPaths;
|
|
||||||
private final boolean splitTypes;
|
|
||||||
public ApkJsonDecoder(ApkModule apkModule, boolean splitTypes){
|
|
||||||
this.apkModule = apkModule;
|
|
||||||
this.splitTypes = splitTypes;
|
|
||||||
this.decodedPaths = new HashSet<>();
|
|
||||||
}
|
|
||||||
public ApkJsonDecoder(ApkModule apkModule){
|
|
||||||
this(apkModule, false);
|
|
||||||
}
|
|
||||||
public void sanitizeFilePaths(){
|
|
||||||
PathSanitizer sanitizer = PathSanitizer.create(apkModule);
|
|
||||||
sanitizer.sanitize();
|
|
||||||
}
|
|
||||||
public File writeToDirectory(File dir) throws IOException {
|
|
||||||
this.decodedPaths.clear();
|
|
||||||
writeUncompressed(dir);
|
|
||||||
writeManifest(dir);
|
|
||||||
writeTable(dir);
|
|
||||||
//writeResourceIds(dir);
|
|
||||||
//writePublicXml(dir);
|
|
||||||
writeResources(dir);
|
|
||||||
writeRootFiles(dir);
|
|
||||||
writePathMap(dir);
|
|
||||||
dumpSignatures(dir);
|
|
||||||
return new File(dir, apkModule.getModuleName());
|
|
||||||
}
|
|
||||||
private void dumpSignatures(File outDir) throws IOException {
|
|
||||||
ApkSignatureBlock signatureBlock = apkModule.getApkSignatureBlock();
|
|
||||||
if(signatureBlock == null){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
apkModule.logMessage("Dumping signatures ...");
|
|
||||||
File dir = toSignatureDir(outDir);
|
|
||||||
signatureBlock.writeSplitRawToDirectory(dir);
|
|
||||||
}
|
|
||||||
private void writePathMap(File dir) throws IOException {
|
|
||||||
PathMap pathMap = new PathMap();
|
|
||||||
pathMap.add(apkModule.getApkArchive());
|
|
||||||
File file = toPathMapJsonFile(dir);
|
|
||||||
pathMap.toJson().write(file);
|
|
||||||
}
|
|
||||||
private void writeUncompressed(File dir) throws IOException {
|
|
||||||
File file=toUncompressedJsonFile(dir);
|
|
||||||
UncompressedFiles uncompressedFiles=new UncompressedFiles();
|
|
||||||
uncompressedFiles.addCommonExtensions();
|
|
||||||
uncompressedFiles.addPath(apkModule.getApkArchive());
|
|
||||||
uncompressedFiles.toJson().write(file);
|
|
||||||
}
|
|
||||||
private void writeResources(File dir) throws IOException {
|
|
||||||
for(ResFile resFile:apkModule.listResFiles()){
|
|
||||||
writeResource(dir, resFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void writeResource(File dir, ResFile resFile) throws IOException {
|
|
||||||
if(resFile.isBinaryXml()){
|
|
||||||
writeResourceJson(dir, resFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void writeResourceJson(File dir, ResFile resFile) throws IOException {
|
|
||||||
InputSource inputSource= resFile.getInputSource();
|
|
||||||
String path=inputSource.getAlias();
|
|
||||||
File file=toResJson(dir, path);
|
|
||||||
ResXmlDocument resXmlDocument =new ResXmlDocument();
|
|
||||||
resXmlDocument.readBytes(inputSource.openStream());
|
|
||||||
JSONObject jsonObject= resXmlDocument.toJson();
|
|
||||||
jsonObject.write(file);
|
|
||||||
addDecoded(path);
|
|
||||||
}
|
|
||||||
private void writeRootFiles(File dir) throws IOException {
|
|
||||||
for(InputSource inputSource:apkModule.getApkArchive().listInputSources()){
|
|
||||||
writeRootFile(dir, inputSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void writeRootFile(File dir, InputSource inputSource) throws IOException {
|
|
||||||
String path=inputSource.getAlias();
|
|
||||||
if(hasDecoded(path)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File file=toRootFile(dir, path);
|
|
||||||
File parent=file.getParentFile();
|
|
||||||
if(parent!=null && !parent.exists()){
|
|
||||||
parent.mkdirs();
|
|
||||||
}
|
|
||||||
FileOutputStream outputStream=new FileOutputStream(file);
|
|
||||||
inputSource.write(outputStream);
|
|
||||||
outputStream.close();
|
|
||||||
addDecoded(path);
|
|
||||||
}
|
|
||||||
private void writeTable(File dir) throws IOException {
|
|
||||||
if(!splitTypes){
|
|
||||||
writeTableSingle(dir);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writeTableSplit(dir);
|
|
||||||
}
|
|
||||||
private void writeTableSplit(File dir) throws IOException {
|
|
||||||
if(!apkModule.hasTableBlock()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TableBlock tableBlock = apkModule.getTableBlock();
|
|
||||||
File splitDir= toJsonTableSplitDir(dir);
|
|
||||||
TableBlockJson tableBlockJson=new TableBlockJson(tableBlock);
|
|
||||||
tableBlockJson.writeJsonFiles(splitDir);
|
|
||||||
addDecoded(TableBlock.FILE_NAME);
|
|
||||||
}
|
|
||||||
private void writeTableSingle(File dir) throws IOException {
|
|
||||||
if(!apkModule.hasTableBlock()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TableBlock tableBlock = apkModule.getTableBlock();
|
|
||||||
File file= toJsonTableFile(dir);
|
|
||||||
tableBlock.toJson().write(file);
|
|
||||||
addDecoded(TableBlock.FILE_NAME);
|
|
||||||
}
|
|
||||||
private void writeResourceIds(File dir) throws IOException {
|
|
||||||
if(!apkModule.hasTableBlock()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TableBlock tableBlock = apkModule.getTableBlock();
|
|
||||||
ResourceIds resourceIds=new ResourceIds();
|
|
||||||
resourceIds.loadTableBlock(tableBlock);
|
|
||||||
JSONObject jsonObject= resourceIds.toJson();
|
|
||||||
File file=toResourceIds(dir);
|
|
||||||
jsonObject.write(file);
|
|
||||||
}
|
|
||||||
private void writePublicXml(File dir) throws IOException {
|
|
||||||
if(!apkModule.hasTableBlock()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TableBlock tableBlock = apkModule.getTableBlock();
|
|
||||||
ResourceIds resourceIds=new ResourceIds();
|
|
||||||
resourceIds.loadTableBlock(tableBlock);
|
|
||||||
File file=toResourceIdsXml(dir);
|
|
||||||
resourceIds.writeXml(file);
|
|
||||||
}
|
|
||||||
private void writeManifest(File dir) throws IOException {
|
|
||||||
if(!apkModule.hasAndroidManifestBlock()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AndroidManifestBlock manifestBlock = apkModule.getAndroidManifestBlock();
|
|
||||||
File file = toJsonManifestFile(dir);
|
|
||||||
manifestBlock.toJson().write(file);
|
|
||||||
addDecoded(AndroidManifestBlock.FILE_NAME);
|
|
||||||
}
|
|
||||||
private boolean hasDecoded(String path){
|
|
||||||
return decodedPaths.contains(path);
|
|
||||||
}
|
|
||||||
private void addDecoded(String path){
|
|
||||||
this.decodedPaths.add(path);
|
|
||||||
}
|
|
||||||
private File toJsonTableFile(File dir){
|
|
||||||
File file=new File(dir, apkModule.getModuleName());
|
|
||||||
String name = TableBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION;
|
|
||||||
return new File(file, name);
|
|
||||||
}
|
|
||||||
private File toJsonTableSplitDir(File dir){
|
|
||||||
File file=new File(dir, apkModule.getModuleName());
|
|
||||||
return new File(file, ApkUtil.SPLIT_JSON_DIRECTORY);
|
|
||||||
}
|
|
||||||
private File toResourceIds(File dir){
|
|
||||||
File file=new File(dir, apkModule.getModuleName());
|
|
||||||
String name = "resource-ids.json";
|
|
||||||
return new File(file, name);
|
|
||||||
}
|
|
||||||
private File toResourceIdsXml(File dir){
|
|
||||||
File file=new File(dir, apkModule.getModuleName());
|
|
||||||
String name = "public.xml";
|
|
||||||
return new File(file, name);
|
|
||||||
}
|
|
||||||
private File toSignatureDir(File dir){
|
|
||||||
dir = new File(dir, apkModule.getModuleName());
|
|
||||||
return new File(dir, ApkUtil.SIGNATURE_DIR_NAME);
|
|
||||||
}
|
|
||||||
private File toPathMapJsonFile(File dir){
|
|
||||||
File file = new File(dir, apkModule.getModuleName());
|
|
||||||
return new File(file, PathMap.JSON_FILE);
|
|
||||||
}
|
|
||||||
private File toUncompressedJsonFile(File dir){
|
|
||||||
File file = new File(dir, apkModule.getModuleName());
|
|
||||||
return new File(file, UncompressedFiles.JSON_FILE);
|
|
||||||
}
|
|
||||||
private File toJsonManifestFile(File dir){
|
|
||||||
File file=new File(dir, apkModule.getModuleName());
|
|
||||||
String name = AndroidManifestBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION;
|
|
||||||
return new File(file, name);
|
|
||||||
}
|
|
||||||
private File toResJson(File dir, String path){
|
|
||||||
File file=new File(dir, apkModule.getModuleName());
|
|
||||||
file=new File(file, ApkUtil.RES_JSON_NAME);
|
|
||||||
path=path + ApkUtil.JSON_FILE_EXTENSION;
|
|
||||||
path=path.replace('/', File.separatorChar);
|
|
||||||
return new File(file, path);
|
|
||||||
}
|
|
||||||
private File toRootFile(File dir, String path){
|
|
||||||
File file=new File(dir, apkModule.getModuleName());
|
|
||||||
file=new File(file, ApkUtil.ROOT_NAME);
|
|
||||||
path=path.replace('/', File.separatorChar);
|
|
||||||
return new File(file, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 github.com/REAndroid
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.reandroid.apk;
|
|
||||||
|
|
||||||
import com.reandroid.archive.APKArchive;
|
|
||||||
import com.reandroid.archive.FileInputSource;
|
|
||||||
import com.reandroid.archive2.block.ApkSignatureBlock;
|
|
||||||
import com.reandroid.arsc.chunk.TableBlock;
|
|
||||||
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
|
|
||||||
import com.reandroid.json.JSONArray;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ApkJsonEncoder {
|
|
||||||
private APKArchive apkArchive;
|
|
||||||
private APKLogger apkLogger;
|
|
||||||
public ApkJsonEncoder(){
|
|
||||||
}
|
|
||||||
public ApkModule scanDirectory(File moduleDir){
|
|
||||||
this.apkArchive=new APKArchive();
|
|
||||||
String moduleName=moduleDir.getName();
|
|
||||||
scanManifest(moduleDir);
|
|
||||||
scanTable(moduleDir);
|
|
||||||
scanResJsonDirs(moduleDir);
|
|
||||||
scanRootDirs(moduleDir);
|
|
||||||
ApkModule module=new ApkModule(moduleName, apkArchive);
|
|
||||||
module.setLoadDefaultFramework(false);
|
|
||||||
module.setAPKLogger(apkLogger);
|
|
||||||
loadUncompressed(module, moduleDir);
|
|
||||||
//applyResourceId(module, moduleDir);
|
|
||||||
restorePathMap(moduleDir, module);
|
|
||||||
restoreSignatures(moduleDir, module);
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
private void restoreSignatures(File dir, ApkModule apkModule){
|
|
||||||
File sigDir = new File(dir, ApkUtil.SIGNATURE_DIR_NAME);
|
|
||||||
if(!sigDir.isDirectory()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logMessage("Loading signatures ...");
|
|
||||||
ApkSignatureBlock signatureBlock = new ApkSignatureBlock();
|
|
||||||
try {
|
|
||||||
signatureBlock.scanSplitFiles(sigDir);
|
|
||||||
apkModule.setApkSignatureBlock(signatureBlock);
|
|
||||||
} catch (IOException exception){
|
|
||||||
logError("Failed to load signatures: ", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void restorePathMap(File dir, ApkModule apkModule){
|
|
||||||
File file = new File(dir, PathMap.JSON_FILE);
|
|
||||||
if(!file.isFile()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logMessage("Restoring file path ...");
|
|
||||||
PathMap pathMap = new PathMap();
|
|
||||||
FileInputStream inputStream = null;
|
|
||||||
try {
|
|
||||||
inputStream = new FileInputStream(file);
|
|
||||||
} catch (FileNotFoundException exception) {
|
|
||||||
logError("Failed to load path-map", exception);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JSONArray jsonArray = new JSONArray(inputStream);
|
|
||||||
pathMap.fromJson(jsonArray);
|
|
||||||
pathMap.restore(apkModule);
|
|
||||||
}
|
|
||||||
private void applyResourceId(ApkModule apkModule, File moduleDir) {
|
|
||||||
if(!apkModule.hasTableBlock()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File pubXml=toResourceIdsXml(moduleDir);
|
|
||||||
if(!pubXml.isFile()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ResourceIds resourceIds=new ResourceIds();
|
|
||||||
try {
|
|
||||||
resourceIds.fromXml(pubXml);
|
|
||||||
resourceIds.applyTo(apkModule.getTableBlock());
|
|
||||||
} catch (IOException exception) {
|
|
||||||
throw new IllegalArgumentException(exception.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void loadUncompressed(ApkModule module, File moduleDir){
|
|
||||||
File jsonFile=toUncompressedJsonFile(moduleDir);
|
|
||||||
UncompressedFiles uf= module.getUncompressedFiles();
|
|
||||||
try {
|
|
||||||
uf.fromJson(jsonFile);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void scanRootDirs(File moduleDir){
|
|
||||||
File rootDir=toRootDir(moduleDir);
|
|
||||||
List<File> jsonFileList=ApkUtil.recursiveFiles(rootDir);
|
|
||||||
for(File file:jsonFileList){
|
|
||||||
scanRootFile(rootDir, file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void scanRootFile(File rootDir, File file){
|
|
||||||
String path=ApkUtil.toArchivePath(rootDir, file);
|
|
||||||
FileInputSource inputSource=new FileInputSource(file, path);
|
|
||||||
apkArchive.add(inputSource);
|
|
||||||
}
|
|
||||||
private void scanResJsonDirs(File moduleDir){
|
|
||||||
File resJsonDir=toResJsonDir(moduleDir);
|
|
||||||
List<File> jsonFileList=ApkUtil.recursiveFiles(resJsonDir);
|
|
||||||
for(File file:jsonFileList){
|
|
||||||
scanResJsonFile(resJsonDir, file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void scanResJsonFile(File resJsonDir, File file){
|
|
||||||
JsonXmlInputSource inputSource=JsonXmlInputSource.fromFile(resJsonDir, file);
|
|
||||||
apkArchive.add(inputSource);
|
|
||||||
}
|
|
||||||
private void scanManifest(File moduleDir){
|
|
||||||
File file=toJsonManifestFile(moduleDir);
|
|
||||||
if(!file.isFile()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonManifestInputSource inputSource=JsonManifestInputSource.fromFile(moduleDir, file);
|
|
||||||
inputSource.setAPKLogger(apkLogger);
|
|
||||||
apkArchive.add(inputSource);
|
|
||||||
}
|
|
||||||
private void scanTable(File moduleDir) {
|
|
||||||
boolean splitFound=scanTableSplitJson(moduleDir);
|
|
||||||
if(splitFound){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scanTableSingleJson(moduleDir);
|
|
||||||
}
|
|
||||||
private boolean scanTableSplitJson(File moduleDir) {
|
|
||||||
File dir=toJsonTableSplitDir(moduleDir);
|
|
||||||
if(!dir.isDirectory()){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
SplitJsonTableInputSource inputSource=new SplitJsonTableInputSource(dir);
|
|
||||||
inputSource.setAPKLogger(apkLogger);
|
|
||||||
apkArchive.add(inputSource);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
private void scanTableSingleJson(File moduleDir) {
|
|
||||||
File file=toJsonTableFile(moduleDir);
|
|
||||||
if(!file.isFile()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SingleJsonTableInputSource inputSource= SingleJsonTableInputSource.fromFile(moduleDir, file);
|
|
||||||
inputSource.setAPKLogger(apkLogger);
|
|
||||||
apkArchive.add(inputSource);
|
|
||||||
}
|
|
||||||
private File toJsonTableFile(File dir){
|
|
||||||
String name = TableBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION;
|
|
||||||
return new File(dir, name);
|
|
||||||
}
|
|
||||||
private File toJsonManifestFile(File dir){
|
|
||||||
String name = AndroidManifestBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION;
|
|
||||||
return new File(dir, name);
|
|
||||||
}
|
|
||||||
private File toResourceIdsXml(File dir){
|
|
||||||
String name = "public.xml";
|
|
||||||
return new File(dir, name);
|
|
||||||
}
|
|
||||||
private File toUncompressedJsonFile(File dir){
|
|
||||||
return new File(dir, UncompressedFiles.JSON_FILE);
|
|
||||||
}
|
|
||||||
private File toJsonTableSplitDir(File dir){
|
|
||||||
return new File(dir, ApkUtil.SPLIT_JSON_DIRECTORY);
|
|
||||||
}
|
|
||||||
private File toResJsonDir(File dir){
|
|
||||||
return new File(dir, ApkUtil.RES_JSON_NAME);
|
|
||||||
}
|
|
||||||
private File toRootDir(File dir){
|
|
||||||
return new File(dir, ApkUtil.ROOT_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAPKLogger(APKLogger logger) {
|
|
||||||
this.apkLogger = logger;
|
|
||||||
}
|
|
||||||
private void logMessage(String msg) {
|
|
||||||
if(apkLogger!=null){
|
|
||||||
apkLogger.logMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void logError(String msg, Throwable tr) {
|
|
||||||
if(apkLogger!=null){
|
|
||||||
apkLogger.logError(msg, tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void logVerbose(String msg) {
|
|
||||||
if(apkLogger!=null){
|
|
||||||
apkLogger.logVerbose(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,320 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 github.com/REAndroid
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.reandroid.apk;
|
|
||||||
|
|
||||||
import com.reandroid.apk.xmldecoder.*;
|
|
||||||
import com.reandroid.archive.InputSource;
|
|
||||||
import com.reandroid.arsc.chunk.PackageBlock;
|
|
||||||
import com.reandroid.arsc.chunk.TableBlock;
|
|
||||||
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
|
|
||||||
import com.reandroid.arsc.chunk.xml.ResXmlDocument;
|
|
||||||
import com.reandroid.arsc.container.SpecTypePair;
|
|
||||||
import com.reandroid.arsc.value.*;
|
|
||||||
import com.reandroid.identifiers.PackageIdentifier;
|
|
||||||
import com.reandroid.json.JSONObject;
|
|
||||||
import com.reandroid.xml.XMLDocument;
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public class ApkModuleXmlDecoder extends ApkDecoder implements Predicate<Entry> {
|
|
||||||
private final ApkModule apkModule;
|
|
||||||
private final Map<Integer, Set<ResConfig>> decodedEntries;
|
|
||||||
|
|
||||||
private ResXmlDocumentSerializer documentSerializer;
|
|
||||||
private XMLEntryDecoderSerializer entrySerializer;
|
|
||||||
|
|
||||||
|
|
||||||
public ApkModuleXmlDecoder(ApkModule apkModule){
|
|
||||||
super();
|
|
||||||
this.apkModule = apkModule;
|
|
||||||
this.decodedEntries = new HashMap<>();
|
|
||||||
super.setApkLogger(apkModule.getApkLogger());
|
|
||||||
}
|
|
||||||
public void sanitizeFilePaths(){
|
|
||||||
PathSanitizer sanitizer = PathSanitizer.create(apkModule);
|
|
||||||
sanitizer.sanitize();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
void onDecodeTo(File outDir) throws IOException{
|
|
||||||
this.decodedEntries.clear();
|
|
||||||
logMessage("Decoding ...");
|
|
||||||
|
|
||||||
if(!apkModule.hasTableBlock()){
|
|
||||||
logOrThrow(null, new IOException("Don't have resource table"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeUncompressedFiles(outDir);
|
|
||||||
|
|
||||||
TableBlock tableBlock = apkModule.getTableBlock();
|
|
||||||
|
|
||||||
this.entrySerializer = new XMLEntryDecoderSerializer(tableBlock);
|
|
||||||
this.entrySerializer.setDecodedEntries(this);
|
|
||||||
|
|
||||||
decodeAndroidManifest(outDir, apkModule.getAndroidManifestBlock());
|
|
||||||
decodeTableBlock(outDir, tableBlock);
|
|
||||||
|
|
||||||
logMessage("Decoding resource files ...");
|
|
||||||
List<ResFile> resFileList = apkModule.listResFiles();
|
|
||||||
for(ResFile resFile:resFileList){
|
|
||||||
decodeResFile(outDir, resFile);
|
|
||||||
}
|
|
||||||
decodeValues(outDir, tableBlock);
|
|
||||||
|
|
||||||
extractRootFiles(outDir);
|
|
||||||
|
|
||||||
writePathMap(outDir, apkModule.getApkArchive().listInputSources());
|
|
||||||
|
|
||||||
dumpSignatures(outDir, apkModule.getApkSignatureBlock());
|
|
||||||
}
|
|
||||||
private void decodeTableBlock(File outDir, TableBlock tableBlock) throws IOException {
|
|
||||||
try{
|
|
||||||
decodePackageInfo(outDir, tableBlock);
|
|
||||||
decodePublicXml(tableBlock, outDir);
|
|
||||||
addDecodedPath(TableBlock.FILE_NAME);
|
|
||||||
}catch (IOException exception){
|
|
||||||
logOrThrow("Error decoding resource table", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void decodePackageInfo(File outDir, TableBlock tableBlock) throws IOException {
|
|
||||||
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
|
||||||
decodePackageInfo(outDir, packageBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void decodePackageInfo(File outDir, PackageBlock packageBlock) throws IOException {
|
|
||||||
File pkgDir = new File(outDir, getPackageDirName(packageBlock));
|
|
||||||
File packageJsonFile = new File(pkgDir, PackageBlock.JSON_FILE_NAME);
|
|
||||||
JSONObject jsonObject = packageBlock.toJson(false);
|
|
||||||
jsonObject.write(packageJsonFile);
|
|
||||||
}
|
|
||||||
private void decodeUncompressedFiles(File outDir)
|
|
||||||
throws IOException {
|
|
||||||
File file=new File(outDir, UncompressedFiles.JSON_FILE);
|
|
||||||
UncompressedFiles uncompressedFiles = apkModule.getUncompressedFiles();
|
|
||||||
uncompressedFiles.toJson().write(file);
|
|
||||||
}
|
|
||||||
private void decodeResFile(File outDir, ResFile resFile)
|
|
||||||
throws IOException{
|
|
||||||
if(resFile.isBinaryXml()){
|
|
||||||
decodeResXml(outDir, resFile);
|
|
||||||
}else {
|
|
||||||
decodeResRaw(outDir, resFile);
|
|
||||||
}
|
|
||||||
addDecodedPath(resFile.getFilePath());
|
|
||||||
}
|
|
||||||
private void decodeResRaw(File outDir, ResFile resFile)
|
|
||||||
throws IOException {
|
|
||||||
Entry entry = resFile.pickOne();
|
|
||||||
PackageBlock packageBlock= entry.getPackageBlock();
|
|
||||||
|
|
||||||
File pkgDir=new File(outDir, getPackageDirName(packageBlock));
|
|
||||||
String alias = resFile.buildPath(ApkUtil.RES_DIR_NAME);
|
|
||||||
String path = alias.replace('/', File.separatorChar);
|
|
||||||
File file=new File(pkgDir, path);
|
|
||||||
File dir=file.getParentFile();
|
|
||||||
if(!dir.exists()){
|
|
||||||
dir.mkdirs();
|
|
||||||
}
|
|
||||||
FileOutputStream outputStream=new FileOutputStream(file);
|
|
||||||
resFile.getInputSource().write(outputStream);
|
|
||||||
outputStream.close();
|
|
||||||
resFile.setFilePath(alias);
|
|
||||||
|
|
||||||
addDecodedEntry(entry);
|
|
||||||
}
|
|
||||||
private void decodeResXml(File outDir, ResFile resFile)
|
|
||||||
throws IOException{
|
|
||||||
Entry entry = resFile.pickOne();
|
|
||||||
PackageBlock packageBlock = entry.getPackageBlock();
|
|
||||||
|
|
||||||
File pkgDir = new File(outDir, getPackageDirName(packageBlock));
|
|
||||||
String alias = resFile.buildPath(ApkUtil.RES_DIR_NAME);
|
|
||||||
String path = alias.replace('/', File.separatorChar);
|
|
||||||
path = path.replace('/', File.separatorChar);
|
|
||||||
File file = new File(pkgDir, path);
|
|
||||||
|
|
||||||
logVerbose("Decoding: " + path);
|
|
||||||
serializeXml(packageBlock.getId(), resFile.getInputSource(), file);
|
|
||||||
|
|
||||||
resFile.setFilePath(alias);
|
|
||||||
addDecodedEntry(entry);
|
|
||||||
}
|
|
||||||
private ResXmlDocumentSerializer getDocumentSerializer(){
|
|
||||||
if(documentSerializer == null){
|
|
||||||
documentSerializer = new ResXmlDocumentSerializer(apkModule);
|
|
||||||
documentSerializer.setValidateXmlNamespace(true);
|
|
||||||
}
|
|
||||||
return documentSerializer;
|
|
||||||
}
|
|
||||||
private void decodePublicXml(TableBlock tableBlock, File outDir)
|
|
||||||
throws IOException{
|
|
||||||
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
|
||||||
decodePublicXml(packageBlock, outDir);
|
|
||||||
}
|
|
||||||
if(tableBlock.getPackageArray().childesCount()==0){
|
|
||||||
decodeEmptyTable(outDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void decodeEmptyTable(File outDir) throws IOException {
|
|
||||||
logMessage("Decoding empty table ...");
|
|
||||||
String pkgName = apkModule.getPackageName();
|
|
||||||
if(pkgName==null){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File pkgDir = new File(outDir, "0-"+pkgName);
|
|
||||||
File resDir = new File(pkgDir, ApkUtil.RES_DIR_NAME);
|
|
||||||
File values = new File(resDir, "values");
|
|
||||||
File pubXml = new File(values, ApkUtil.FILE_NAME_PUBLIC_XML);
|
|
||||||
XMLDocument xmlDocument = new XMLDocument("resources");
|
|
||||||
xmlDocument.save(pubXml, false);
|
|
||||||
}
|
|
||||||
private void decodePublicXml(PackageBlock packageBlock, File outDir)
|
|
||||||
throws IOException {
|
|
||||||
String packageDirName=getPackageDirName(packageBlock);
|
|
||||||
logMessage("Decoding public.xml: "+packageDirName);
|
|
||||||
File file=new File(outDir, packageDirName);
|
|
||||||
file=new File(file, ApkUtil.RES_DIR_NAME);
|
|
||||||
file=new File(file, "values");
|
|
||||||
file=new File(file, ApkUtil.FILE_NAME_PUBLIC_XML);
|
|
||||||
PackageIdentifier packageIdentifier = new PackageIdentifier();
|
|
||||||
packageIdentifier.load(packageBlock);
|
|
||||||
packageIdentifier.writePublicXml(file);
|
|
||||||
}
|
|
||||||
private void decodeAndroidManifest(File outDir, AndroidManifestBlock manifestBlock)
|
|
||||||
throws IOException {
|
|
||||||
if(!apkModule.hasAndroidManifestBlock()){
|
|
||||||
logMessage("Don't have: "+ AndroidManifestBlock.FILE_NAME);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File file=new File(outDir, AndroidManifestBlock.FILE_NAME);
|
|
||||||
logMessage("Decoding: "+file.getName());
|
|
||||||
int currentPackageId = manifestBlock.guessCurrentPackageId();
|
|
||||||
serializeXml(currentPackageId, manifestBlock, file);
|
|
||||||
addDecodedPath(AndroidManifestBlock.FILE_NAME);
|
|
||||||
}
|
|
||||||
private void serializeXml(int currentPackageId, ResXmlDocument document, File outFile)
|
|
||||||
throws IOException {
|
|
||||||
XMLNamespaceValidator.validateNamespaces(document);
|
|
||||||
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
|
||||||
if(currentPackageId != 0){
|
|
||||||
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
serializer.write(document, outFile);
|
|
||||||
} catch (XmlPullParserException ex) {
|
|
||||||
throw new IOException("Error: "+outFile.getName(), ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void serializeXml(int currentPackageId, InputSource inputSource, File outFile)
|
|
||||||
throws IOException {
|
|
||||||
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
|
||||||
if(currentPackageId != 0){
|
|
||||||
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
serializer.write(inputSource, outFile);
|
|
||||||
} catch (XmlPullParserException ex) {
|
|
||||||
throw new IOException("Error: "+outFile.getName(), ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void addDecodedEntry(Entry entry){
|
|
||||||
if(entry.isNull()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int resourceId= entry.getResourceId();
|
|
||||||
Set<ResConfig> resConfigSet=decodedEntries.get(resourceId);
|
|
||||||
if(resConfigSet==null){
|
|
||||||
resConfigSet=new HashSet<>();
|
|
||||||
decodedEntries.put(resourceId, resConfigSet);
|
|
||||||
}
|
|
||||||
resConfigSet.add(entry.getResConfig());
|
|
||||||
}
|
|
||||||
private boolean containsDecodedEntry(Entry entry){
|
|
||||||
Set<ResConfig> resConfigSet=decodedEntries.get(entry.getResourceId());
|
|
||||||
if(resConfigSet==null){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return resConfigSet.contains(entry.getResConfig());
|
|
||||||
}
|
|
||||||
private void decodeValues(File outDir, TableBlock tableBlock) throws IOException {
|
|
||||||
for(PackageBlock packageBlock:tableBlock.listPackages()){
|
|
||||||
decodeValues(outDir, packageBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void decodeValues(File outDir, PackageBlock packageBlock) throws IOException {
|
|
||||||
logMessage("Decoding values: "
|
|
||||||
+ getPackageDirName(packageBlock));
|
|
||||||
|
|
||||||
packageBlock.sortTypes();
|
|
||||||
|
|
||||||
File pkgDir = new File(outDir, getPackageDirName(packageBlock));
|
|
||||||
File resDir = new File(pkgDir, ApkUtil.RES_DIR_NAME);
|
|
||||||
|
|
||||||
for(SpecTypePair specTypePair : packageBlock.listSpecTypePairs()){
|
|
||||||
decodeValues(resDir, specTypePair);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void decodeValues(File outDir, SpecTypePair specTypePair) throws IOException {
|
|
||||||
entrySerializer.decode(outDir, specTypePair);
|
|
||||||
}
|
|
||||||
private String getPackageDirName(PackageBlock packageBlock){
|
|
||||||
String name = ApkUtil.sanitizeForFileName(packageBlock.getName());
|
|
||||||
if(name==null){
|
|
||||||
name="package";
|
|
||||||
}
|
|
||||||
TableBlock tableBlock = packageBlock.getTableBlock();
|
|
||||||
int index = packageBlock.getIndex();
|
|
||||||
String prefix;
|
|
||||||
if(index < 10 && tableBlock.countPackages() > 10){
|
|
||||||
prefix = "0" + index;
|
|
||||||
}else {
|
|
||||||
prefix = Integer.toString(index);
|
|
||||||
}
|
|
||||||
return prefix + "-" + name;
|
|
||||||
}
|
|
||||||
private void extractRootFiles(File outDir) throws IOException {
|
|
||||||
logMessage("Extracting root files");
|
|
||||||
File rootDir = new File(outDir, "root");
|
|
||||||
for(InputSource inputSource:apkModule.getApkArchive().listInputSources()){
|
|
||||||
if(containsDecodedPath(inputSource.getAlias())){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
extractRootFiles(rootDir, inputSource);
|
|
||||||
addDecodedPath(inputSource.getAlias());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void extractRootFiles(File rootDir, InputSource inputSource) throws IOException {
|
|
||||||
String path=inputSource.getAlias();
|
|
||||||
path=path.replace(File.separatorChar, '/');
|
|
||||||
File file=new File(rootDir, path);
|
|
||||||
File dir=file.getParentFile();
|
|
||||||
if(!dir.exists()){
|
|
||||||
dir.mkdirs();
|
|
||||||
}
|
|
||||||
FileOutputStream outputStream=new FileOutputStream(file);
|
|
||||||
inputSource.write(outputStream);
|
|
||||||
outputStream.close();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean test(Entry entry) {
|
|
||||||
return !containsDecodedEntry(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 github.com/REAndroid
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.reandroid.apk;
|
|
||||||
|
|
||||||
import com.reandroid.archive.APKArchive;
|
|
||||||
import com.reandroid.archive.FileInputSource;
|
|
||||||
import com.reandroid.apk.xmlencoder.RESEncoder;
|
|
||||||
import com.reandroid.archive2.block.ApkSignatureBlock;
|
|
||||||
import com.reandroid.arsc.chunk.TableBlock;
|
|
||||||
import com.reandroid.arsc.pool.TableStringPool;
|
|
||||||
import com.reandroid.json.JSONArray;
|
|
||||||
import com.reandroid.xml.XMLException;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ApkModuleXmlEncoder {
|
|
||||||
private final RESEncoder resEncoder;
|
|
||||||
public ApkModuleXmlEncoder(){
|
|
||||||
this.resEncoder = new RESEncoder();
|
|
||||||
}
|
|
||||||
public ApkModuleXmlEncoder(ApkModule module, TableBlock tableBlock){
|
|
||||||
this.resEncoder = new RESEncoder(module, tableBlock);
|
|
||||||
}
|
|
||||||
public void scanDirectory(File mainDirectory) throws IOException, XMLException {
|
|
||||||
loadUncompressedFiles(mainDirectory);
|
|
||||||
resEncoder.scanDirectory(mainDirectory);
|
|
||||||
File rootDir=new File(mainDirectory, "root");
|
|
||||||
scanRootDir(rootDir);
|
|
||||||
restorePathMap(mainDirectory);
|
|
||||||
restoreSignatures(mainDirectory);
|
|
||||||
sortFiles();
|
|
||||||
TableStringPool tableStringPool = getApkModule().getTableBlock().getTableStringPool();
|
|
||||||
tableStringPool.removeUnusedStrings();
|
|
||||||
}
|
|
||||||
private void restoreSignatures(File dir) throws IOException {
|
|
||||||
File sigDir = new File(dir, ApkUtil.SIGNATURE_DIR_NAME);
|
|
||||||
if(!sigDir.isDirectory()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ApkModule apkModule = getApkModule();
|
|
||||||
apkModule.logMessage("Loading signatures ...");
|
|
||||||
ApkSignatureBlock signatureBlock = new ApkSignatureBlock();
|
|
||||||
signatureBlock.scanSplitFiles(sigDir);
|
|
||||||
apkModule.setApkSignatureBlock(signatureBlock);
|
|
||||||
}
|
|
||||||
private void restorePathMap(File dir) throws IOException{
|
|
||||||
File file = new File(dir, PathMap.JSON_FILE);
|
|
||||||
if(!file.isFile()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PathMap pathMap = new PathMap();
|
|
||||||
JSONArray jsonArray = new JSONArray(file);
|
|
||||||
pathMap.fromJson(jsonArray);
|
|
||||||
pathMap.restore(getApkModule());
|
|
||||||
}
|
|
||||||
public ApkModule getApkModule(){
|
|
||||||
return resEncoder.getApkModule();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scanRootDir(File rootDir){
|
|
||||||
APKArchive archive=getApkModule().getApkArchive();
|
|
||||||
List<File> rootFileList=ApkUtil.recursiveFiles(rootDir);
|
|
||||||
for(File file:rootFileList){
|
|
||||||
String path=ApkUtil.toArchivePath(rootDir, file);
|
|
||||||
FileInputSource inputSource=new FileInputSource(file, path);
|
|
||||||
archive.add(inputSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void sortFiles(){
|
|
||||||
APKArchive archive = getApkModule().getApkArchive();
|
|
||||||
archive.autoSortApkFiles();
|
|
||||||
}
|
|
||||||
private void loadUncompressedFiles(File mainDirectory) throws IOException {
|
|
||||||
File file=new File(mainDirectory, UncompressedFiles.JSON_FILE);
|
|
||||||
UncompressedFiles uncompressedFiles = getApkModule().getUncompressedFiles();
|
|
||||||
uncompressedFiles.fromJson(file);
|
|
||||||
}
|
|
||||||
public void setApkLogger(APKLogger apkLogger) {
|
|
||||||
this.resEncoder.setAPKLogger(apkLogger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 github.com/REAndroid
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.reandroid.apk;
|
|
||||||
|
|
||||||
import com.reandroid.archive.InputSource;
|
|
||||||
import com.reandroid.archive2.block.ApkSignatureBlock;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class ApkUtil {
|
|
||||||
public static String sanitizeForFileName(String name){
|
|
||||||
if(name==null){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
char[] chars = name.toCharArray();
|
|
||||||
boolean skipNext = true;
|
|
||||||
int length = 0;
|
|
||||||
int lengthMax = MAX_FILE_NAME_LENGTH;
|
|
||||||
for(int i=0;i<chars.length;i++){
|
|
||||||
if(length>=lengthMax){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
char ch = chars[i];
|
|
||||||
if(isGoodFileNameSymbol(ch)){
|
|
||||||
if(!skipNext){
|
|
||||||
builder.append(ch);
|
|
||||||
length++;
|
|
||||||
}
|
|
||||||
skipNext=true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(!isGoodFileNameChar(ch)){
|
|
||||||
skipNext = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
builder.append(ch);
|
|
||||||
length++;
|
|
||||||
skipNext=false;
|
|
||||||
}
|
|
||||||
if(length==0){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
private static boolean isGoodFileNameSymbol(char ch){
|
|
||||||
return ch == '.'
|
|
||||||
|| ch == '+'
|
|
||||||
|| ch == '-'
|
|
||||||
|| ch == '_'
|
|
||||||
|| ch == '#';
|
|
||||||
}
|
|
||||||
private static boolean isGoodFileNameChar(char ch){
|
|
||||||
return (ch >= '0' && ch <= '9')
|
|
||||||
|| (ch >= 'A' && ch <= 'Z')
|
|
||||||
|| (ch >= 'a' && ch <= 'z');
|
|
||||||
}
|
|
||||||
public static int parseHex(String hex){
|
|
||||||
long l=Long.decode(hex);
|
|
||||||
return (int) l;
|
|
||||||
}
|
|
||||||
public static String replaceRootDir(String path, String dirName){
|
|
||||||
int i=path.indexOf('/')+1;
|
|
||||||
path=path.substring(i);
|
|
||||||
if(dirName != null && dirName.length()>0){
|
|
||||||
if(!dirName.endsWith("/")){
|
|
||||||
dirName=dirName+"/";
|
|
||||||
}
|
|
||||||
path=dirName+path;
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
public static String toArchiveResourcePath(File dir, File file){
|
|
||||||
String path = toArchivePath(dir, file);
|
|
||||||
if(path.endsWith(ApkUtil.JSON_FILE_EXTENSION)){
|
|
||||||
int i2=path.length()- ApkUtil.JSON_FILE_EXTENSION.length();
|
|
||||||
path=path.substring(0, i2);
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
public static String toArchivePath(File dir, File file){
|
|
||||||
String dirPath = dir.getAbsolutePath()+File.separator;
|
|
||||||
String path = file.getAbsolutePath().substring(dirPath.length());
|
|
||||||
path=path.replace(File.separatorChar, '/');
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
public static List<File> recursiveFiles(File dir, String ext){
|
|
||||||
List<File> results=new ArrayList<>();
|
|
||||||
if(dir.isFile()){
|
|
||||||
if(hasExtension(dir, ext)){
|
|
||||||
results.add(dir);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
if(!dir.isDirectory()){
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
File[] files=dir.listFiles();
|
|
||||||
if(files==null){
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
for(File file:files){
|
|
||||||
if(file.isFile()){
|
|
||||||
if(!hasExtension(file, ext)){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
results.add(file);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
results.addAll(recursiveFiles(file, ext));
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
public static List<File> recursiveFiles(File dir){
|
|
||||||
return recursiveFiles(dir, null);
|
|
||||||
}
|
|
||||||
public static List<File> listDirectories(File dir){
|
|
||||||
List<File> results=new ArrayList<>();
|
|
||||||
File[] files=dir.listFiles();
|
|
||||||
if(files==null){
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
for(File file:files){
|
|
||||||
if(file.isDirectory()){
|
|
||||||
results.add(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
public static List<File> listFiles(File dir, String ext){
|
|
||||||
List<File> results=new ArrayList<>();
|
|
||||||
File[] files=dir.listFiles();
|
|
||||||
if(files==null){
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
for(File file:files){
|
|
||||||
if(file.isFile()){
|
|
||||||
if(!hasExtension(file, ext)){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
results.add(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
private static boolean hasExtension(File file, String ext){
|
|
||||||
if(ext==null){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
String name=file.getName().toLowerCase();
|
|
||||||
ext=ext.toLowerCase();
|
|
||||||
return name.endsWith(ext);
|
|
||||||
}
|
|
||||||
public static String toModuleName(File file){
|
|
||||||
String name=file.getName();
|
|
||||||
int i=name.lastIndexOf('.');
|
|
||||||
if(i>0){
|
|
||||||
name=name.substring(0,i);
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
public static Map<String, InputSource> toAliasMap(Collection<InputSource> sourceList){
|
|
||||||
Map<String, InputSource> results=new HashMap<>();
|
|
||||||
for(InputSource inputSource:sourceList){
|
|
||||||
results.put(inputSource.getAlias(), inputSource);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
public static final String JSON_FILE_EXTENSION=".json";
|
|
||||||
public static final String RES_JSON_NAME="res-json";
|
|
||||||
public static final String ROOT_NAME="root";
|
|
||||||
public static final String SPLIT_JSON_DIRECTORY="resources";
|
|
||||||
public static final String DEF_MODULE_NAME="base";
|
|
||||||
public static final String NAME_value_type="value_type";
|
|
||||||
public static final String NAME_data="data";
|
|
||||||
public static final String RES_DIR_NAME="res";
|
|
||||||
public static final String FILE_NAME_PUBLIC_XML ="public.xml";
|
|
||||||
|
|
||||||
public static final String TAG_STRING_ARRAY = "string-array";
|
|
||||||
public static final String TAG_INTEGER_ARRAY = "integer-array";
|
|
||||||
|
|
||||||
public static final String SIGNATURE_FILE_NAME = "signatures" + ApkSignatureBlock.FILE_EXT;
|
|
||||||
public static final String SIGNATURE_DIR_NAME = "signatures";
|
|
||||||
|
|
||||||
private static final int MAX_FILE_NAME_LENGTH = 50;
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 github.com/REAndroid
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.reandroid.apk;
|
|
||||||
|
|
||||||
import com.reandroid.archive.ByteInputSource;
|
|
||||||
import com.reandroid.arsc.base.Block;
|
|
||||||
import com.reandroid.arsc.chunk.Chunk;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public class BlockInputSource<T extends Chunk<?>> extends ByteInputSource{
|
|
||||||
private final T mBlock;
|
|
||||||
public BlockInputSource(String name, T block) {
|
|
||||||
super(new byte[0], name);
|
|
||||||
this.mBlock=block;
|
|
||||||
}
|
|
||||||
public T getBlock() {
|
|
||||||
mBlock.refresh();
|
|
||||||
return mBlock;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public long getLength() throws IOException{
|
|
||||||
Block block = getBlock();
|
|
||||||
return block.countBytes();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public long getCrc() throws IOException{
|
|
||||||
Block block = getBlock();
|
|
||||||
CrcOutputStream outputStream=new CrcOutputStream();
|
|
||||||
block.writeBytes(outputStream);
|
|
||||||
return outputStream.getCrcValue();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public long write(OutputStream outputStream) throws IOException {
|
|
||||||
return getBlock().writeBytes(outputStream);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public byte[] getBytes() {
|
|
||||||
return getBlock().getBytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user