feat: merge iBotPeaches/Apktool

This commit is contained in:
oSumAtrIX 2022-09-23 04:22:53 +02:00
commit 9443f6fefb
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
24 changed files with 168 additions and 100 deletions

28
.github/workflows/analyze.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Analyze
on:
push:
branches: [master]
pull_request:
branches: [master]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: java
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@ -98,22 +98,3 @@ jobs:
with: with:
name: apktool.jar name: apktool.jar
path: brut.apktool/apktool-cli/build/libs/apktool-*-small.jar path: brut.apktool/apktool-cli/build/libs/apktool-*-small.jar
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -253,6 +253,11 @@ original as they were.
As cheesy as it is, just follow this [downloading](https://source.android.com/source/downloading.html) link in order As cheesy as it is, just follow this [downloading](https://source.android.com/source/downloading.html) link in order
to get the source downloaded. This is no small download, expect to use 150-250GB. to get the source downloaded. This is no small download, expect to use 150-250GB.
Some optimization techniques for a smaller clone:
* `~/bin/repo init -u https://android.googlesource.com/platform/manifest -b master --partial-clone` - Partial clone
* `repo sync -c` - Only current branch
After that, you need to build AOSP via this [documentation](https://source.android.com/source/building.html) guide. Now After that, you need to build AOSP via this [documentation](https://source.android.com/source/building.html) guide. Now
we aren't building the entire AOSP package, the initial build is to just see if you are capable of building it. we aren't building the entire AOSP package, the initial build is to just see if you are capable of building it.

View File

@ -49,3 +49,14 @@ Folks have requested running Apktool on device itself. This has been a challenge
that would be placed on the aapt2/aapt binaries. that would be placed on the aapt2/aapt binaries.
Suggestions: [#2811](https://github.com/iBotPeaches/Apktool/issues/2811) Suggestions: [#2811](https://github.com/iBotPeaches/Apktool/issues/2811)
## Split APK Support
Applications are further getting split on qualifiers. Apktool has been built on the assumption of one apk.
Suggestions: [#2283](https://github.com/iBotPeaches/Apktool/issues/2283), [#2218](https://github.com/iBotPeaches/Apktool/issues/2218), [#2880](https://github.com/iBotPeaches/Apktool/issues/2880)
## Dummy Resources
Folks want the ability to stop the auto generation of dummy resources.
Suggestions: [#2683](https://github.com/iBotPeaches/Apktool/issues/2683), [#2104](https://github.com/iBotPeaches/Apktool/issues/2104)
Pull Request(s): [#2463](https://github.com/iBotPeaches/Apktool/pull/2463)

View File

@ -337,7 +337,7 @@ public class Androlib {
throw new AndrolibException(ex.getMessage()); throw new AndrolibException(ex.getMessage());
} }
} }
LOGGER.fine("Built apk..."); LOGGER.fine("Built apk into: " + outFile.getPath());
} }
private void buildManifestFile(File appDir, File manifest, File manifestOriginal) private void buildManifestFile(File appDir, File manifest, File manifestOriginal)

View File

@ -67,6 +67,9 @@ final public class AndrolibResources {
ResPackage pkg; ResPackage pkg;
switch (pkgs.length) { switch (pkgs.length) {
case 0:
pkg = null;
break;
case 1: case 1:
pkg = pkgs[0]; pkg = pkgs[0];
break; break;

View File

@ -104,13 +104,13 @@ public class ResValueFactory {
return new ResPluralsValue(parentVal, items); return new ResPluralsValue(parentVal, items);
} }
if (ResTypeSpec.RES_TYPE_NAME_STYLES.equals(resTypeName)) {
return new ResStyleValue(parentVal, items, this);
}
if (ResTypeSpec.RES_TYPE_NAME_ATTR.equals(resTypeName)) { if (ResTypeSpec.RES_TYPE_NAME_ATTR.equals(resTypeName)) {
return new ResAttr(parentVal, 0, null, null, null); return new ResAttr(parentVal, 0, null, null, null);
} }
if (resTypeName.startsWith(ResTypeSpec.RES_TYPE_NAME_STYLES)) {
return new ResStyleValue(parentVal, items, this);
}
throw new AndrolibException("unsupported res type name for bags. Found: " + resTypeName); throw new AndrolibException("unsupported res type name for bags. Found: " + resTypeName);
} }

View File

@ -33,6 +33,7 @@ import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -159,7 +160,7 @@ public class ARSCDecoder {
for (int i = 0; i < libraryCount; i++) { for (int i = 0; i < libraryCount; i++) {
packageId = mIn.readInt(); packageId = mIn.readInt();
packageName = mIn.readNullEndedString(128, true); packageName = mIn.readNullEndedString(128, true);
LOGGER.fine(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId)); LOGGER.fine(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId));
} }
while(nextChunk().type == Header.XML_TYPE_TYPE) { while(nextChunk().type == Header.XML_TYPE_TYPE) {
@ -232,7 +233,7 @@ public class ARSCDecoder {
mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount)); mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount));
} }
/* flags */mIn.skipBytes(entryCount * 4); /* flags */mIn.skipBytes(entryCount * 4);
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount); mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
mPkg.addType(mTypeSpec); mPkg.addType(mTypeSpec);
return mTypeSpec; return mTypeSpec;
@ -250,8 +251,7 @@ public class ARSCDecoder {
/* reserved */mIn.skipBytes(2); /* reserved */mIn.skipBytes(2);
int entryCount = mIn.readInt(); int entryCount = mIn.readInt();
int entriesStart = mIn.readInt(); int entriesStart = mIn.readInt();
mMissingResSpecs = new boolean[entryCount]; mMissingResSpecMap = new LinkedHashMap();
Arrays.fill(mMissingResSpecs, true);
ResConfigFlags flags = readConfigFlags(); ResConfigFlags flags = readConfigFlags();
int position = (mHeader.startPosition + entriesStart) - (entryCount * 4); int position = (mHeader.startPosition + entriesStart) - (entryCount * 4);
@ -263,10 +263,18 @@ public class ARSCDecoder {
mIn.skipBytes(position - mCountIn.getCount()); mIn.skipBytes(position - mCountIn.getCount());
} }
if (typeFlags == 1) { if ((typeFlags & 0x01) != 0) {
LOGGER.fine("Sparse type flags detected: " + mTypeSpec.getName()); LOGGER.fine("Sparse type flags detected: " + mTypeSpec.getName());
}
HashMap<Integer, Integer> entryOffsetMap = new LinkedHashMap();
for (int i = 0; i < entryCount; i++) {
if ((typeFlags & 0x01) != 0) {
entryOffsetMap.put(mIn.readUnsignedShort(), mIn.readUnsignedShort());
} else {
entryOffsetMap.put(i, mIn.readInt());
}
} }
int[] entryOffsets = mIn.readIntArray(entryCount);
if (flags.isInvalid) { if (flags.isInvalid) {
String resName = mTypeSpec.getName() + flags.getQualifiers(); String resName = mTypeSpec.getName() + flags.getQualifiers();
@ -278,23 +286,15 @@ public class ARSCDecoder {
} }
mType = flags.isInvalid && !mKeepBroken ? null : mPkg.getOrCreateConfig(flags); mType = flags.isInvalid && !mKeepBroken ? null : mPkg.getOrCreateConfig(flags);
HashMap<Integer, EntryData> offsetsToEntryData = new HashMap<>();
for (int offset : entryOffsets) { for (int i : entryOffsetMap.keySet()) {
if (offset == -1 || offsetsToEntryData.containsKey(offset)) { int offset = entryOffsetMap.get(i);
if (offset == -1) {
continue; continue;
} }
mMissingResSpecMap.put(i, false);
offsetsToEntryData.put(offset, readEntryData()); mResId = (mResId & 0xffff0000) | i;
} readEntry(readEntryData());
for (int i = 0; i < entryOffsets.length; i++) {
if (entryOffsets[i] != -1) {
mMissingResSpecs[i] = false;
mResId = (mResId & 0xffff0000) | i;
EntryData entryData = offsetsToEntryData.get(entryOffsets[i]);
readEntry(entryData);
}
} }
return mType; return mType;
@ -384,8 +384,8 @@ public class ARSCDecoder {
} }
private ResIntBasedValue readValue() throws IOException, AndrolibException { private ResIntBasedValue readValue() throws IOException, AndrolibException {
/* size */mIn.skipCheckShort((short) 8); /* size */mIn.skipCheckShort((short) 8);
/* zero */mIn.skipCheckByte((byte) 0); /* zero */mIn.skipCheckByte((byte) 0);
byte type = mIn.readByte(); byte type = mIn.readByte();
int data = mIn.readInt(); int data = mIn.readInt();
@ -418,13 +418,13 @@ public class ARSCDecoder {
byte keyboard = mIn.readByte(); byte keyboard = mIn.readByte();
byte navigation = mIn.readByte(); byte navigation = mIn.readByte();
byte inputFlags = mIn.readByte(); byte inputFlags = mIn.readByte();
/* inputPad0 */mIn.skipBytes(1); /* inputPad0 */mIn.skipBytes(1);
short screenWidth = mIn.readShort(); short screenWidth = mIn.readShort();
short screenHeight = mIn.readShort(); short screenHeight = mIn.readShort();
short sdkVersion = mIn.readShort(); short sdkVersion = mIn.readShort();
/* minorVersion, now must always be 0 */mIn.skipBytes(2); /* minorVersion, now must always be 0 */mIn.skipBytes(2);
byte screenLayout = 0; byte screenLayout = 0;
byte uiMode = 0; byte uiMode = 0;
@ -533,14 +533,12 @@ public class ARSCDecoder {
private void addMissingResSpecs() throws AndrolibException { private void addMissingResSpecs() throws AndrolibException {
int resId = mResId & 0xffff0000; int resId = mResId & 0xffff0000;
for (int i = 0; i < mMissingResSpecs.length; i++) { for (int i : mMissingResSpecMap.keySet()) {
if (!mMissingResSpecs[i]) { if (mMissingResSpecMap.get(i)) continue;
continue;
}
ResResSpec spec = new ResResSpec(new ResID(resId | i), "APKTOOL_DUMMY_" + Integer.toHexString(i), mPkg, mTypeSpec); ResResSpec spec = new ResResSpec(new ResID(resId | i), "APKTOOL_DUMMY_" + Integer.toHexString(i), mPkg, mTypeSpec);
// If we already have this resID dont add it again. // If we already have this resID don't add it again.
if (! mPkg.hasResSpec(new ResID(resId | i))) { if (! mPkg.hasResSpec(new ResID(resId | i))) {
mPkg.addResSpec(spec); mPkg.addResSpec(spec);
mTypeSpec.addResSpec(spec); mTypeSpec.addResSpec(spec);
@ -598,7 +596,7 @@ public class ARSCDecoder {
private ResType mType; private ResType mType;
private int mResId; private int mResId;
private int mTypeIdOffset = 0; private int mTypeIdOffset = 0;
private boolean[] mMissingResSpecs; private HashMap<Integer, Boolean> mMissingResSpecMap;
private final HashMap<Integer, ResTypeSpec> mResTypeSpecs = new HashMap<>(); private final HashMap<Integer, ResTypeSpec> mResTypeSpecs = new HashMap<>();
private final static short ENTRY_FLAG_COMPLEX = 0x0001; private final static short ENTRY_FLAG_COMPLEX = 0x0001;
@ -685,7 +683,7 @@ public class ARSCDecoder {
throw new AndrolibException("Arsc file contains zero packages"); throw new AndrolibException("Arsc file contains zero packages");
} else if (mPackages.length != 1) { } else if (mPackages.length != 1) {
int id = findPackageWithMostResSpecs(); int id = findPackageWithMostResSpecs();
LOGGER.fine("Arsc file contains multiple packages. Using package " LOGGER.fine("Arsc file contains multiple packages. Using package "
+ mPackages[id].getName() + " as default."); + mPackages[id].getName() + " as default.");
return mPackages[id]; return mPackages[id];
@ -714,4 +712,4 @@ public class ARSCDecoder {
private final FlagsOffset[] mFlagsOffsets; private final FlagsOffset[] mFlagsOffsets;
private final ResTable mResTable; private final ResTable mResTable;
} }
} }

View File

@ -336,16 +336,26 @@ public class AXmlResourceParser implements XmlResourceParser {
} }
String value = m_strings.getString(name); String value = m_strings.getString(name);
String namespace = getAttributeNamespace(index);
// some attributes will return "", we must rely on the resource_id and refer to the frameworks // If attribute name is lacking or a private namespace emerges,
// to match the resource id to the name. ex: 0x101021C = versionName // retrieve the exact attribute name by its id.
if (value.length() == 0 || android_ns.equals(getAttributeNamespace(index))) { if (value == null || value.length() == 0) {
try { try {
int resourceId = getAttributeNameResource(index); value = mAttrDecoder.decodeManifestAttr(getAttributeNameResource(index));
if (resourceId != 0) { if (value == null) {
value = mAttrDecoder.decodeManifestAttr(getAttributeNameResource(index)); value = "";
} }
} catch (AndrolibException | NullPointerException ignored) {} } catch (AndrolibException e) {
value = "";
}
} else if (! namespace.equals(android_ns)) {
try {
String obfuscatedName = mAttrDecoder.decodeManifestAttr(getAttributeNameResource(index));
if (! (obfuscatedName == null || obfuscatedName.equals(value))) {
value = obfuscatedName;
}
} catch (AndrolibException ignored) {}
} }
return value; return value;
} }
@ -381,11 +391,27 @@ public class AXmlResourceParser implements XmlResourceParser {
if (mAttrDecoder != null) { if (mAttrDecoder != null) {
try { try {
String value = valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars(m_strings.getString(valueRaw));
String obfuscatedValue = mAttrDecoder.decodeManifestAttr(valueData);
if (! (value == null || obfuscatedValue == null)) {
int slashPos = value.lastIndexOf("/");
if (slashPos != -1) {
// Handle a value with a format of "@yyy/xxx"
String dir = value.substring(0, slashPos);
value = dir + "/"+ obfuscatedValue;
} else if (! value.equals(obfuscatedValue)) {
value = obfuscatedValue;
}
}
return mAttrDecoder.decode( return mAttrDecoder.decode(
valueType, valueType,
valueData, valueData,
valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars(m_strings.getString(valueRaw)), value,
getAttributeNameResource(index)); getAttributeNameResource(index)
);
} catch (AndrolibException ex) { } catch (AndrolibException ex) {
setFirstError(ex); setFirstError(ex);
LOGGER.log(Level.WARNING, String.format("Could not decode attr value, using undecoded value " LOGGER.log(Level.WARNING, String.format("Could not decode attr value, using undecoded value "
@ -807,9 +833,9 @@ public class AXmlResourceParser implements XmlResourceParser {
if (m_strings == null) { if (m_strings == null) {
m_reader.skipCheckInt(CHUNK_AXML_FILE, CHUNK_AXML_FILE_BROKEN); m_reader.skipCheckInt(CHUNK_AXML_FILE, CHUNK_AXML_FILE_BROKEN);
/* /*
* chunkSize * chunkSize
*/ */
m_reader.skipInt(); m_reader.skipInt();
m_strings = StringBlock.read(m_reader); m_strings = StringBlock.read(m_reader);
m_namespaces.increaseDepth(); m_namespaces.increaseDepth();
@ -863,9 +889,9 @@ public class AXmlResourceParser implements XmlResourceParser {
} }
// Common header. // Common header.
/* chunkSize */m_reader.skipInt(); /* chunkSize */m_reader.skipInt();
int lineNumber = m_reader.readInt(); int lineNumber = m_reader.readInt();
/* 0xFFFFFFFF */m_reader.skipInt(); /* 0xFFFFFFFF */m_reader.skipInt();
if (chunkType == CHUNK_XML_START_NAMESPACE || chunkType == CHUNK_XML_END_NAMESPACE) { if (chunkType == CHUNK_XML_START_NAMESPACE || chunkType == CHUNK_XML_END_NAMESPACE) {
if (chunkType == CHUNK_XML_START_NAMESPACE) { if (chunkType == CHUNK_XML_START_NAMESPACE) {
@ -873,8 +899,8 @@ public class AXmlResourceParser implements XmlResourceParser {
int uri = m_reader.readInt(); int uri = m_reader.readInt();
m_namespaces.push(prefix, uri); m_namespaces.push(prefix, uri);
} else { } else {
/* prefix */m_reader.skipInt(); /* prefix */m_reader.skipInt();
/* uri */m_reader.skipInt(); /* uri */m_reader.skipInt();
m_namespaces.pop(); m_namespaces.pop();
} }
continue; continue;
@ -885,7 +911,7 @@ public class AXmlResourceParser implements XmlResourceParser {
if (chunkType == CHUNK_XML_START_TAG) { if (chunkType == CHUNK_XML_START_TAG) {
m_namespaceUri = m_reader.readInt(); m_namespaceUri = m_reader.readInt();
m_name = m_reader.readInt(); m_name = m_reader.readInt();
/* flags? */m_reader.skipInt(); /* flags? */m_reader.skipInt();
int attributeCount = m_reader.readInt(); int attributeCount = m_reader.readInt();
m_idAttribute = (attributeCount >>> 16) - 1; m_idAttribute = (attributeCount >>> 16) - 1;
attributeCount &= 0xFFFF; attributeCount &= 0xFFFF;
@ -912,8 +938,8 @@ public class AXmlResourceParser implements XmlResourceParser {
if (chunkType == CHUNK_XML_TEXT) { if (chunkType == CHUNK_XML_TEXT) {
m_name = m_reader.readInt(); m_name = m_reader.readInt();
/* ? */m_reader.skipInt(); /* ? */m_reader.skipInt();
/* ? */m_reader.skipInt(); /* ? */m_reader.skipInt();
m_event = TEXT; m_event = TEXT;
break; break;
} }
@ -927,10 +953,10 @@ public class AXmlResourceParser implements XmlResourceParser {
} }
// ///////////////////////////////// data // ///////////////////////////////// data
/* /*
* All values are essentially indices, e.g. m_name is an index of name in * All values are essentially indices, e.g. m_name is an index of name in
* m_strings. * m_strings.
*/ */
private ExtDataInput m_reader; private ExtDataInput m_reader;
private ResAttrDecoder mAttrDecoder; private ResAttrDecoder mAttrDecoder;
private AndrolibException mFirstError; private AndrolibException mFirstError;

View File

@ -37,6 +37,9 @@ public class AndroidManifestResourceParser extends AXmlResourceParser {
@Override @Override
public String getAttributeValue(int index) { public String getAttributeValue(int index) {
String value = super.getAttributeValue(index); String value = super.getAttributeValue(index);
if (value == null) {
return "";
}
if (!isNumericStringMetadataAttributeValue(index, value)) { if (!isNumericStringMetadataAttributeValue(index, value)) {
return value; return value;
@ -46,7 +49,7 @@ public class AndroidManifestResourceParser extends AXmlResourceParser {
// Otherwise, when the decoded app is rebuilt, aapt will incorrectly encode // Otherwise, when the decoded app is rebuilt, aapt will incorrectly encode
// the value as an int or float (depending on aapt version), breaking the original // the value as an int or float (depending on aapt version), breaking the original
// app functionality. // app functionality.
return "\\ " + super.getAttributeValue(index).trim(); return "\\ " + value.trim();
} }
private boolean isNumericStringMetadataAttributeValue(int index, String value) { private boolean isNumericStringMetadataAttributeValue(int index, String value) {

View File

@ -18,6 +18,7 @@ package brut.androlib.res.decoder;
import brut.androlib.AndrolibException; import brut.androlib.AndrolibException;
import brut.androlib.err.UndefinedResObjectException; import brut.androlib.err.UndefinedResObjectException;
import brut.androlib.res.data.ResID;
import brut.androlib.res.data.ResPackage; import brut.androlib.res.data.ResPackage;
import brut.androlib.res.data.ResResSpec; import brut.androlib.res.data.ResResSpec;
import brut.androlib.res.data.value.ResAttr; import brut.androlib.res.data.value.ResAttr;
@ -48,10 +49,24 @@ public class ResAttrDecoder {
throws AndrolibException { throws AndrolibException {
if (attrResId != 0) { if (attrResId != 0) {
ResResSpec resResSpec = getCurrentPackage().getResTable().getResSpec(attrResId); int attrId = attrResId;
if (resResSpec != null) { // See also: brut.androlib.res.data.ResTable.getResSpec
return resResSpec.getName(); if (attrId >> 24 == 0) {
ResPackage pkg = getCurrentPackage();
int packageId = pkg.getId();
int pkgId = (packageId == 0 ? 2 : packageId);
attrId = (0xFF000000 & (pkgId << 24)) | attrId;
}
// Retrieve the ResSpec in a package by id
ResID resId = new ResID(attrId);
ResPackage pkg = getCurrentPackage();
if (pkg.hasResSpec(resId)) {
ResResSpec resResSpec = pkg.getResSpec(resId);
if (resResSpec != null) {
return resResSpec.getName();
}
} }
} }

View File

@ -129,6 +129,10 @@ public final class ResXmlPatcher {
* Creates a modified network security config file that is more permissive * Creates a modified network security config file that is more permissive
* *
* @param file network security config file * @param file network security config file
* @throws TransformerException XML file could not be edited
* @throws IOException XML file could not be located
* @throws SAXException XML file could not be read
* @throws ParserConfigurationException XML nodes could be written
*/ */
public static void modNetworkSecurityConfig(File file) public static void modNetworkSecurityConfig(File file)
throws ParserConfigurationException, TransformerException, IOException, SAXException { throws ParserConfigurationException, TransformerException, IOException, SAXException {
@ -184,13 +188,10 @@ public final class ResXmlPatcher {
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i); Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes(); NamedNodeMap attrs = node.getAttributes();
Node provider = attrs.getNamedItem("android:authorities");
if (attrs != null) { if (provider != null) {
Node provider = attrs.getNamedItem("android:authorities"); saved = isSaved(file, saved, provider);
if (provider != null) {
saved = isSaved(file, saved, provider);
}
} }
} }
@ -204,13 +205,10 @@ public final class ResXmlPatcher {
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i); Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes(); NamedNodeMap attrs = node.getAttributes();
Node provider = attrs.getNamedItem("android:scheme");
if (attrs != null) { if (provider != null) {
Node provider = attrs.getNamedItem("android:scheme"); saved = isSaved(file, saved, provider);
if (provider != null) {
saved = isSaved(file, saved, provider);
}
} }
} }

View File

@ -6,7 +6,7 @@ chcp 65001 2>nul >nul
set java_exe=java.exe set java_exe=java.exe
if defined JAVA_HOME ( if defined JAVA_HOME (
set java_exe="%JAVA_HOME%\bin\java.exe" set "java_exe=%JAVA_HOME%\bin\java.exe"
) )
rem Find the highest version .jar available in the same directory as the script rem Find the highest version .jar available in the same directory as the script
@ -36,7 +36,7 @@ if "%ATTR:~0,1%"=="-" if "%~x1"==".apk" (
) )
:load :load
%java_exe% -jar -Duser.language=en -Dfile.encoding=UTF8 "%~dp0%BASENAME%%max%.jar" %fastCommand% %* "%java_exe%" -jar -Duser.language=en -Dfile.encoding=UTF8 "%~dp0%BASENAME%%max%.jar" %fastCommand% %*
rem Pause when ran non interactively rem Pause when ran non interactively
for /f "tokens=2" %%# in ("%cmdcmdline%") do if /i "%%#" equ "/c" pause for /f "tokens=2" %%# in ("%cmdcmdline%") do if /i "%%#" equ "/c" pause