修复 打包后ui脚本无法运行的问题

修复 打包后模块无法运行的问题
This commit is contained in:
hyb1996
2018-12-12 14:18:03 +08:00
parent c460425656
commit 05813499bc
33 changed files with 548 additions and 91 deletions

View File

@@ -32,7 +32,7 @@
<service
android:name="com.stardust.autojs.core.accessibility.AccessibilityService"
android:label="@string/_app_name"
android:label="@string/app_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>

View File

@@ -5,15 +5,12 @@ import android.view.View;
import com.stardust.autojs.BuildConfig;
import com.stardust.autojs.core.ui.ViewExtras;
import com.stardust.autojs.rhino.NativeJavaObjectWithPrototype;
import com.stardust.autojs.engine.module.AssetAndUrlModuleSourceProvider;
import com.stardust.autojs.rhino.RhinoAndroidHelper;
import com.stardust.autojs.rhino.TokenStream;
import com.stardust.autojs.rhino.TopLevelScope;
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.autojs.script.JavaScriptSource;
import com.stardust.autojs.script.StringScriptSource;
import com.stardust.automator.UiObjectCollection;
import com.stardust.pio.PFiles;
import com.stardust.pio.UncheckedIOException;
import org.mozilla.javascript.Context;

View File

@@ -0,0 +1,14 @@
package com.stardust.autojs.engine.encryption
import com.stardust.util.AdvancedEncryptionStandard
object ScriptEncryption {
private var mKey = ""
private var mInitVector = ""
fun decrypt(bytes: ByteArray, start: Int = 0, end: Int = bytes.size): ByteArray {
return AdvancedEncryptionStandard(mKey.toByteArray(), mInitVector).decrypt(bytes, start, end)
}
}

View File

@@ -1,20 +1,21 @@
package com.stardust.autojs.engine;
package com.stardust.autojs.engine.module;
import android.content.res.AssetManager;
import android.net.Uri;
import org.mozilla.javascript.Scriptable;
import com.stardust.autojs.engine.encryption.ScriptEncryption;
import com.stardust.autojs.script.EncryptedScriptFileHeader;
import org.mozilla.javascript.commonjs.module.provider.ModuleSource;
import org.mozilla.javascript.commonjs.module.provider.UrlModuleSourceProvider;
import java.io.File;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.net.URLConnection;
import java.util.List;
/**
@@ -49,4 +50,17 @@ public class AssetAndUrlModuleSourceProvider extends UrlModuleSourceProvider {
return super.loadFromPrivilegedLocations(moduleId, validator);
}
}
@Override
protected Reader getReader(URLConnection urlConnection) throws IOException {
InputStream stream = urlConnection.getInputStream();
byte[] bytes = new byte[stream.available()];
stream.read(bytes);
stream.close();
if (EncryptedScriptFileHeader.INSTANCE.isValidFile(bytes)) {
byte[] clearText = ScriptEncryption.INSTANCE.decrypt(bytes, EncryptedScriptFileHeader.BLOCK_SIZE, bytes.length);
return new InputStreamReader(new ByteArrayInputStream(clearText));
}
return new InputStreamReader(new ByteArrayInputStream(bytes));
}
}

View File

@@ -0,0 +1,364 @@
package com.stardust.autojs.engine.module;
import org.mozilla.javascript.commonjs.module.provider.DefaultUrlConnectionExpiryCalculator;
import org.mozilla.javascript.commonjs.module.provider.ModuleSource;
import org.mozilla.javascript.commonjs.module.provider.ModuleSourceProviderBase;
import org.mozilla.javascript.commonjs.module.provider.ParsedContentType;
import org.mozilla.javascript.commonjs.module.provider.UrlConnectionExpiryCalculator;
import org.mozilla.javascript.commonjs.module.provider.UrlConnectionSecurityDomainProvider;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.List;
/**
* A URL-based script provider that can load modules against a set of base
* privileged and fallback URIs. It is deliberately not named "URI provider"
* but a "URL provider" since it actually only works against those URIs that
* are URLs (and the JRE has a protocol handler for them). It creates cache
* validators that are suitable for use with both file: and http: URL
* protocols. Specifically, it is able to use both last-modified timestamps and
* ETags for cache revalidation, and follows the HTTP cache expiry calculation
* model, and allows for fallback heuristic expiry calculation when no server
* specified expiry is provided.
*
* @author Attila Szegedi
* @version $Id: UrlModuleSourceProvider.java,v 1.4 2011/04/07 20:26:12 hannes%helma.at Exp $
*/
public class UrlModuleSourceProvider extends ModuleSourceProviderBase {
private static final long serialVersionUID = 1L;
private final Iterable<URI> privilegedUris;
private final Iterable<URI> fallbackUris;
private final UrlConnectionSecurityDomainProvider
urlConnectionSecurityDomainProvider;
private final UrlConnectionExpiryCalculator urlConnectionExpiryCalculator;
/**
* Creates a new module script provider that loads modules against a set of
* privileged and fallback URIs. It will use a fixed default cache expiry
* of 60 seconds, and provide no security domain objects for the resource.
*
* @param privilegedUris an iterable providing the privileged URIs. Can be
* null if no privileged URIs are used.
* @param fallbackUris an iterable providing the fallback URIs. Can be
* null if no fallback URIs are used.
*/
public UrlModuleSourceProvider(Iterable<URI> privilegedUris,
Iterable<URI> fallbackUris) {
this(privilegedUris, fallbackUris,
new DefaultUrlConnectionExpiryCalculator(), null);
}
/**
* Creates a new module script provider that loads modules against a set of
* privileged and fallback URIs. It will use the specified heuristic cache
* expiry calculator and security domain provider.
*
* @param privilegedUris an iterable providing the privileged URIs. Can be
* null if no privileged URIs are used.
* @param fallbackUris an iterable providing the fallback URIs. Can be
* null if no fallback URIs are used.
* @param urlConnectionExpiryCalculator the calculator object for heuristic
* calculation of the resource expiry, used when no expiry is provided by
* the server of the resource. Can be null, in which case the maximum age
* of cached entries without validation will be zero.
* @param urlConnectionSecurityDomainProvider object that provides security
* domain objects for the loaded sources. Can be null, in which case the
* loaded sources will have no security domain associated with them.
*/
public UrlModuleSourceProvider(Iterable<URI> privilegedUris,
Iterable<URI> fallbackUris,
UrlConnectionExpiryCalculator urlConnectionExpiryCalculator,
UrlConnectionSecurityDomainProvider urlConnectionSecurityDomainProvider) {
this.privilegedUris = privilegedUris;
this.fallbackUris = fallbackUris;
this.urlConnectionExpiryCalculator = urlConnectionExpiryCalculator;
this.urlConnectionSecurityDomainProvider =
urlConnectionSecurityDomainProvider;
}
@Override
protected ModuleSource loadFromPrivilegedLocations(
String moduleId, Object validator)
throws IOException, URISyntaxException {
return loadFromPathList(moduleId, validator, privilegedUris);
}
@Override
protected ModuleSource loadFromFallbackLocations(
String moduleId, Object validator)
throws IOException, URISyntaxException {
return loadFromPathList(moduleId, validator, fallbackUris);
}
private ModuleSource loadFromPathList(String moduleId,
Object validator, Iterable<URI> paths)
throws IOException, URISyntaxException {
if (paths == null) {
return null;
}
for (URI path : paths) {
final ModuleSource moduleSource = loadFromUri(
path.resolve(moduleId), path, validator);
if (moduleSource != null) {
return moduleSource;
}
}
return null;
}
@Override
protected ModuleSource loadFromUri(URI uri, URI base, Object validator)
throws IOException, URISyntaxException {
// We expect modules to have a ".js" file name extension ...
URI fullUri = new URI(uri + ".js");
ModuleSource source = loadFromActualUri(fullUri, base, validator);
// ... but for compatibility we support modules without extension,
// or ids with explicit extension.
return source != null ?
source : loadFromActualUri(uri, base, validator);
}
protected ModuleSource loadFromActualUri(URI uri, URI base, Object validator)
throws IOException {
final URL url = new URL(base == null ? null : base.toURL(), uri.toString());
final long request_time = System.currentTimeMillis();
final URLConnection urlConnection = openUrlConnection(url);
final URLValidator applicableValidator;
if (validator instanceof URLValidator) {
final URLValidator uriValidator = ((URLValidator) validator);
applicableValidator = uriValidator.appliesTo(uri) ? uriValidator :
null;
} else {
applicableValidator = null;
}
if (applicableValidator != null) {
applicableValidator.applyConditionals(urlConnection);
}
try {
urlConnection.connect();
if (applicableValidator != null &&
applicableValidator.updateValidator(urlConnection,
request_time, urlConnectionExpiryCalculator)) {
close(urlConnection);
return NOT_MODIFIED;
}
return new ModuleSource(getReader(urlConnection),
getSecurityDomain(urlConnection), uri, base,
new URLValidator(uri, urlConnection, request_time,
urlConnectionExpiryCalculator));
} catch (FileNotFoundException e) {
return null;
} catch (RuntimeException e) {
close(urlConnection);
throw e;
} catch (IOException e) {
close(urlConnection);
throw e;
}
}
protected Reader getReader(URLConnection urlConnection)
throws IOException {
return new InputStreamReader(urlConnection.getInputStream(),
getCharacterEncoding(urlConnection));
}
protected String getCharacterEncoding(URLConnection urlConnection) {
final ParsedContentType pct = new ParsedContentType(
urlConnection.getContentType());
final String encoding = pct.getEncoding();
if (encoding != null) {
return encoding;
}
final String contentType = pct.getContentType();
if (contentType != null && contentType.startsWith("text/")) {
return "8859_1";
}
return "utf-8";
}
protected Object getSecurityDomain(URLConnection urlConnection) {
return urlConnectionSecurityDomainProvider == null ? null :
urlConnectionSecurityDomainProvider.getSecurityDomain(
urlConnection);
}
private void close(URLConnection urlConnection) {
try {
urlConnection.getInputStream().close();
} catch (IOException e) {
onFailedClosingUrlConnection(urlConnection, e);
}
}
/**
* Override if you want to get notified if the URL connection fails to
* close. Does nothing by default.
*
* @param urlConnection the connection
* @param cause the cause it failed to close.
*/
protected void onFailedClosingUrlConnection(URLConnection urlConnection,
IOException cause) {
}
/**
* Can be overridden in subclasses to customize the URL connection opening
* process. By default, just calls {@link URL#openConnection()}.
*
* @param url the URL
* @return a connection to the URL.
* @throws IOException if an I/O error occurs.
*/
protected URLConnection openUrlConnection(URL url) throws IOException {
return url.openConnection();
}
@Override
protected boolean entityNeedsRevalidation(Object validator) {
return !(validator instanceof URLValidator)
|| ((URLValidator) validator).entityNeedsRevalidation();
}
private static class URLValidator implements Serializable {
private static final long serialVersionUID = 1L;
private final URI uri;
private final long lastModified;
private final String entityTags;
private long expiry;
public URLValidator(URI uri, URLConnection urlConnection,
long request_time, UrlConnectionExpiryCalculator
urlConnectionExpiryCalculator) {
this.uri = uri;
this.lastModified = urlConnection.getLastModified();
this.entityTags = getEntityTags(urlConnection);
expiry = calculateExpiry(urlConnection, request_time,
urlConnectionExpiryCalculator);
}
boolean updateValidator(URLConnection urlConnection, long request_time,
UrlConnectionExpiryCalculator urlConnectionExpiryCalculator)
throws IOException {
boolean isResourceChanged = isResourceChanged(urlConnection);
if (!isResourceChanged) {
expiry = calculateExpiry(urlConnection, request_time,
urlConnectionExpiryCalculator);
}
return isResourceChanged;
}
private boolean isResourceChanged(URLConnection urlConnection)
throws IOException {
if (urlConnection instanceof HttpURLConnection) {
return ((HttpURLConnection) urlConnection).getResponseCode() ==
HttpURLConnection.HTTP_NOT_MODIFIED;
}
return lastModified != urlConnection.getLastModified();
}
private long calculateExpiry(URLConnection urlConnection,
long request_time, UrlConnectionExpiryCalculator
urlConnectionExpiryCalculator) {
if ("no-cache".equals(urlConnection.getHeaderField("Pragma"))) {
return 0L;
}
final String cacheControl = urlConnection.getHeaderField(
"Cache-Control");
if (cacheControl != null) {
if (cacheControl.indexOf("no-cache") != -1) {
return 0L;
}
final int max_age = getMaxAge(cacheControl);
if (-1 != max_age) {
final long response_time = System.currentTimeMillis();
final long apparent_age = Math.max(0, response_time -
urlConnection.getDate());
final long corrected_received_age = Math.max(apparent_age,
urlConnection.getHeaderFieldInt("Age", 0) * 1000L);
final long response_delay = response_time - request_time;
final long corrected_initial_age = corrected_received_age +
response_delay;
final long creation_time = response_time -
corrected_initial_age;
return max_age * 1000L + creation_time;
}
}
final long explicitExpiry = urlConnection.getHeaderFieldDate(
"Expires", -1L);
if (explicitExpiry != -1L) {
return explicitExpiry;
}
return urlConnectionExpiryCalculator == null ? 0L :
urlConnectionExpiryCalculator.calculateExpiry(urlConnection);
}
private int getMaxAge(String cacheControl) {
final int maxAgeIndex = cacheControl.indexOf("max-age");
if (maxAgeIndex == -1) {
return -1;
}
final int eq = cacheControl.indexOf('=', maxAgeIndex + 7);
if (eq == -1) {
return -1;
}
final int comma = cacheControl.indexOf(',', eq + 1);
final String strAge;
if (comma == -1) {
strAge = cacheControl.substring(eq + 1);
} else {
strAge = cacheControl.substring(eq + 1, comma);
}
try {
return Integer.parseInt(strAge);
} catch (NumberFormatException e) {
return -1;
}
}
private String getEntityTags(URLConnection urlConnection) {
final List<String> etags = urlConnection.getHeaderFields().get("ETag");
if (etags == null || etags.isEmpty()) {
return null;
}
final StringBuilder b = new StringBuilder();
final Iterator<String> it = etags.iterator();
b.append(it.next());
while (it.hasNext()) {
b.append(", ").append(it.next());
}
return b.toString();
}
boolean appliesTo(URI uri) {
return this.uri.equals(uri);
}
void applyConditionals(URLConnection urlConnection) {
if (lastModified != 0L) {
urlConnection.setIfModifiedSince(lastModified);
}
if (entityTags != null && entityTags.length() > 0) {
urlConnection.addRequestProperty("If-None-Match", entityTags);
}
}
boolean entityNeedsRevalidation() {
return System.currentTimeMillis() > expiry;
}
}
}

View File

@@ -12,7 +12,9 @@ data class ExecutionConfig(var workingDirectory: String = "",
var intentFlags: Int = 0,
var delay: Long = 0,
var interval: Long = 0,
var loopTimes: Int = 1) : Parcelable {
var loopTimes: Int = 1,
var uiMode: Boolean = false,
var features: Int = 0) : Parcelable {
private val mArguments = HashMap<String, Any>()
@@ -79,13 +81,16 @@ data class ExecutionConfig(var workingDirectory: String = "",
companion object CREATOR : Parcelable.Creator<ExecutionConfig> {
@JvmStatic
val tag = "execution.config"
@JvmStatic
val tag = "execution.config"
@JvmStatic
val default: ExecutionConfig
get() = ExecutionConfig()
@JvmStatic
val featureContinuation = 1
override fun createFromParcel(parcel: Parcel): ExecutionConfig {
return ExecutionConfig(parcel)
}

View File

@@ -118,8 +118,8 @@ public class ScriptExecuteActivity extends AppCompatActivity {
private void prepare() {
mScriptEngine.put("activity", this);
mScriptEngine.setTag("activity", this);
mScriptEngine.setTag(ScriptEngine.TAG_ENV_PATH, mScriptExecution.getConfig().getWorkingDirectory());
mScriptEngine.setTag(ScriptEngine.TAG_WORKING_DIRECTORY, mScriptExecution.getConfig().getPath());
mScriptEngine.setTag(ScriptEngine.TAG_ENV_PATH, mScriptExecution.getConfig().getPath());
mScriptEngine.setTag(ScriptEngine.TAG_WORKING_DIRECTORY, mScriptExecution.getConfig().getWorkingDirectory());
mScriptEngine.init();
}

View File

@@ -0,0 +1,47 @@
package com.stardust.autojs.script
import java.io.File
import java.io.FileInputStream
import java.io.OutputStream
object EncryptedScriptFileHeader {
const val FLAG_INVALID_FILE: Short = Short.MIN_VALUE
const val FLAG_EXECUTION_MODE_UI: Short = 0x0001
const val FLAG_EXECUTION_MODE_AUTO: Short = 0x0002
const val BLOCK_SIZE = 8
private val BLOCK = byteArrayOf(0x77, 0x01, 0x17, 0x7F, 0x12, 0x12)
fun getHeaderFlags(file: File): Short {
val fis = FileInputStream(file)
val bytes = ByteArray(BLOCK_SIZE)
if (fis.read(bytes) < BLOCK_SIZE) {
return FLAG_INVALID_FILE
}
if (!isValidFile(bytes)) {
return FLAG_INVALID_FILE
}
return (bytes[BLOCK.size].toShort() * 256 + bytes[BLOCK.size + 1]).toShort()
}
fun isValidFile(bytes: ByteArray): Boolean {
for (i in 0 until BLOCK.size) {
if (bytes[i] != BLOCK[i]) {
return false
}
}
return true
}
fun writeHeader(os: OutputStream, flags: Short = 0) {
os.write(BLOCK)
val byte6 = flags / 256
val byte7 = flags % 256
os.write(byte6)
os.write(byte7)
}
}

View File

@@ -43,6 +43,15 @@ public class JavaScriptFileSource extends JavaScriptSource {
return mScript;
}
@Override
protected int parseExecutionMode() {
short flags = EncryptedScriptFileHeader.INSTANCE.getHeaderFlags(mFile);
if (flags == EncryptedScriptFileHeader.FLAG_INVALID_FILE) {
return super.parseExecutionMode();
}
return flags;
}
@Override
public Reader getScriptReader() {
try {

View File

@@ -1,10 +1,15 @@
package com.stardust.autojs.script;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.stardust.autojs.rhino.TokenStream;
import com.stardust.util.MapBuilder;
import org.mozilla.javascript.Token;
import java.io.Reader;
import java.io.StringReader;
import java.util.Map;
@@ -23,11 +28,13 @@ public abstract class JavaScriptSource extends ScriptSource {
public static final int EXECUTION_MODE_UI = 0x00000001;
public static final int EXECUTION_MODE_AUTO = 0x00000002;
private static final String LOG_TAG = "JavaScriptSource";
private static final Map<String, Integer> EXECUTION_MODES = new MapBuilder<String, Integer>()
.put("ui", EXECUTION_MODE_UI)
.put("auto", EXECUTION_MODE_AUTO)
.build();
private static final int EXECUTION_MODE_STRING_MAX_LENGTH = 7;
private static final int PARSING_MAX_TOKEN = 300;
private int mExecutionMode = -1;
@@ -57,20 +64,35 @@ public abstract class JavaScriptSource extends ScriptSource {
public int getExecutionMode() {
if (mExecutionMode == -1) {
mExecutionMode = parseExecutionMode(getScript());
mExecutionMode = parseExecutionMode();
}
return mExecutionMode;
}
private int parseExecutionMode(String script) {
if (script == null || script.length() == 0)
return EXECUTION_MODE_NORMAL;
if(script.charAt(0) == '"'){
int i = script.lastIndexOf("\";", EXECUTION_MODE_STRING_MAX_LENGTH + 2);
if (i >= 0){
String modeString = script.substring(1, i);
return parseExecutionMode(modeString.split(" "));
protected int parseExecutionMode() {
String script = getScript();
TokenStream ts = new TokenStream(new StringReader(script), null, 1);
int token;
int count = 0;
try {
while (count <= PARSING_MAX_TOKEN && (token = ts.getToken()) != Token.EOF) {
count++;
if (token == Token.EOL || token == Token.COMMENT) {
continue;
}
if (token == Token.STRING && ts.getTokenLength() > 2) {
String tokenString = script.substring(ts.getTokenBeg() + 1, ts.getTokenEnd() - 1);
if (ts.getToken() != Token.SEMI) {
break;
}
Log.d(LOG_TAG, "string = " + tokenString);
return parseExecutionMode(tokenString.split(" "));
}
break;
}
} catch (Exception e) {
e.printStackTrace();
return EXECUTION_MODE_NORMAL;
}
return EXECUTION_MODE_NORMAL;

View File

@@ -1,10 +1,9 @@
package com.stardust.autojs.script;
import android.content.Context;
import android.util.Log;
import android.view.View;
import com.stardust.autojs.engine.AssetAndUrlModuleSourceProvider;
import com.stardust.autojs.engine.module.AssetAndUrlModuleSourceProvider;
import com.stardust.pio.PFiles;
import com.stardust.pio.UncheckedIOException;
@@ -18,7 +17,6 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

View File

@@ -1,5 +1,5 @@
<resources>
<string name="app_name">AutoJs</string>
<string name="app_name">Auto.js</string>
<string name="text_error">Error:</string>
<string name="text_start_running">Script Running</string>
<string name="text_path_is_empty">Path is empty</string>
@@ -13,7 +13,6 @@
<string name="text_console">Console</string>
<string name="text_no_floating_window_permission">No drawing overlay permission</string>
<string name="text_accessibility_service_description">Auto.js</string>
<string name="_app_name">AutoJs</string>
<string name="text_should_enable_key_observing">Key observing is disabled, please enable in settings</string>
<string name="no_write_settings_permissin">No writing settings permission</string>
<string name="exception_notification_service_disabled">通知服务未运行,请重新启用通知权限</string>

View File

@@ -1,5 +1,5 @@
<resources>
<string name="app_name">AutoJs</string>
<string name="app_name">Auto.js</string>
<string name="text_error">错误:</string>
<string name="text_start_running">开始运行</string>
<string name="text_path_is_empty">路径为空</string>
@@ -13,7 +13,6 @@
<string name="text_console">控制台</string>
<string name="text_no_floating_window_permission">没有悬浮窗权限</string>
<string name="text_accessibility_service_description">使脚本自动操作(点击、长按、滑动等)所需,若关闭则只能执行不涉及自动操作的脚本。</string>
<string name="_app_name">AutoJs</string>
<string name="text_should_enable_key_observing">按键监听未启用,请在软件设置中开启</string>
<string name="text_should_enable_gesture_observing">手势监听未启用,请在软件设置中开启</string>
<string name="no_write_settings_permissin">沒有修改系統设置权限</string>