1.Fix compatibility for Android M

2.SMAPI 3.4.0
3.Galaxy Store compat
This commit is contained in:
ZaneYork 2020-03-24 11:04:57 +08:00
parent 20ec03caa2
commit 5734a01260
17 changed files with 208 additions and 46 deletions

View File

@ -10,7 +10,7 @@ android {
applicationId "com.zane.smapiinstaller"
minSdkVersion 19
targetSdkVersion 28
versionCode 21
versionCode 23
versionName "1.3.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -86,6 +86,7 @@ dependencies {
implementation 'com.github.didikee:AndroidDonate:0.1.0'
implementation 'com.hjq:language:3.0'
implementation 'org.greenrobot:greendao:3.2.2'
implementation 'net.sourceforge.streamsupport:android-retrostreams:1.7.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'

View File

@ -51,8 +51,9 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java9.util.Optional;
import java.util.Set;
import java9.util.stream.StreamSupport;
/**
* Default implementation of {@link ApkSignerEngine}.
@ -437,9 +438,8 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
isDebuggable(entryName)) {
Optional<V1SchemeVerifier.NamedDigest> extractedDigest =
V1SchemeVerifier.getDigestsToVerify(
entry.getValue(), "-Digest", mMinSdkVersion, Integer.MAX_VALUE)
.stream()
StreamSupport.stream(V1SchemeVerifier.getDigestsToVerify(
entry.getValue(), "-Digest", mMinSdkVersion, Integer.MAX_VALUE))
.filter(d -> d.jcaDigestAlgorithm == alg)
.findFirst();

View File

@ -63,8 +63,10 @@ import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java9.util.function.Supplier;
import java9.util.stream.Collectors;
import java9.util.stream.StreamSupport;
public class ApkSigningBlockUtils {
@ -415,7 +417,7 @@ public class ApkSigningBlockUtils {
DataSource centralDir,
DataSource eocd) throws IOException, NoSuchAlgorithmException, DigestException {
Map<ContentDigestAlgorithm, byte[]> contentDigests = new HashMap<>();
Set<ContentDigestAlgorithm> oneMbChunkBasedAlgorithm = digestAlgorithms.stream()
Set<ContentDigestAlgorithm> oneMbChunkBasedAlgorithm = StreamSupport.stream(digestAlgorithms)
.filter(a -> a == ContentDigestAlgorithm.CHUNKED_SHA256 ||
a == ContentDigestAlgorithm.CHUNKED_SHA512)
.collect(Collectors.toSet());
@ -1100,7 +1102,7 @@ public class ApkSigningBlockUtils {
if (bestSigAlgorithmOnSdkVersion.isEmpty()) {
throw new NoSupportedSignaturesException("No supported signature");
}
return bestSigAlgorithmOnSdkVersion.values().stream()
return StreamSupport.stream(bestSigAlgorithmOnSdkVersion.values())
.sorted((sig1, sig2) -> Integer.compare(
sig1.algorithm.getId(), sig2.algorithm.getId()))
.collect(Collectors.toList());

View File

@ -32,6 +32,9 @@ import com.android.apksig.internal.pkcs7.SignedData;
import com.android.apksig.internal.pkcs7.SignerIdentifier;
import com.android.apksig.internal.pkcs7.SignerInfo;
import com.android.apksig.internal.util.Pair;
import net.fornwall.apksigner.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -46,7 +49,6 @@ import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -375,7 +377,7 @@ public abstract class V1SchemeSigner {
Attributes entryAttrs = new Attributes();
entryAttrs.putValue(
entryDigestAttributeName,
Base64.getEncoder().encodeToString(entryDigest));
Base64.encode(entryDigest));
ByteArrayOutputStream sectionOut = new ByteArrayOutputStream();
byte[] sectionBytes;
try {
@ -448,7 +450,7 @@ public abstract class V1SchemeSigner {
MessageDigest md = getMessageDigestInstance(manifestDigestAlgorithm);
mainAttrs.putValue(
getManifestDigestAttributeName(manifestDigestAlgorithm),
Base64.getEncoder().encodeToString(md.digest(manifest.contents)));
Base64.encode(md.digest(manifest.contents)));
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
SignatureFileWriter.writeMainSection(out, mainAttrs);
@ -464,7 +466,7 @@ public abstract class V1SchemeSigner {
Attributes attrs = new Attributes();
attrs.putValue(
entryDigestAttributeName,
Base64.getEncoder().encodeToString(sectionDigest));
Base64.encode(sectionDigest));
try {
SignatureFileWriter.writeIndividualSection(out, sectionName, attrs);

View File

@ -47,6 +47,8 @@ import com.android.apksig.util.DataSinks;
import com.android.apksig.util.DataSource;
import com.android.apksig.zip.ZipFormatException;
import net.fornwall.apksigner.Base64;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
@ -61,8 +63,6 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -1464,8 +1464,8 @@ public abstract class V1SchemeVerifier {
V1SchemeSigner.MANIFEST_ENTRY_NAME,
jcaDigestAlgorithm,
mSignatureFileEntry.getName(),
Base64.getEncoder().encodeToString(actual),
Base64.getEncoder().encodeToString(expected));
Base64.encode(actual),
Base64.encode(expected));
verified = false;
}
}
@ -1506,8 +1506,8 @@ public abstract class V1SchemeVerifier {
Issue.JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY,
jcaDigestAlgorithm,
mSignatureFileEntry.getName(),
Base64.getEncoder().encodeToString(actual),
Base64.getEncoder().encodeToString(expected));
Base64.encode(actual),
Base64.encode(expected));
}
}
}
@ -1559,8 +1559,8 @@ public abstract class V1SchemeVerifier {
entryName,
jcaDigestAlgorithm,
mSignatureFileEntry.getName(),
Base64.getEncoder().encodeToString(actual),
Base64.getEncoder().encodeToString(expected));
Base64.encode(actual),
Base64.encode(expected));
}
}
}
@ -1635,7 +1635,6 @@ public abstract class V1SchemeVerifier {
String digestAttrSuffix,
int minSdkVersion,
int maxSdkVersion) {
Decoder base64Decoder = Base64.getDecoder();
List<NamedDigest> result = new ArrayList<>(1);
if (minSdkVersion < AndroidSdkVersion.JELLY_BEAN_MR2) {
// Prior to JB MR2, Android platform's logic for picking a digest algorithm to verify is
@ -1664,7 +1663,7 @@ public abstract class V1SchemeVerifier {
continue;
}
// Supported digest algorithm
result.add(new NamedDigest(alg, base64Decoder.decode(digestBase64)));
result.add(new NamedDigest(alg, Base64.decode(digestBase64)));
break;
}
// No supported digests found -- this will fail to verify on pre-JB MR2 Androids.
@ -1683,7 +1682,7 @@ public abstract class V1SchemeVerifier {
// Attribute not found
continue;
}
byte[] digest = base64Decoder.decode(digestBase64);
byte[] digest = Base64.decode(digestBase64);
byte[] digestInResult = getDigest(result, alg);
if ((digestInResult == null) || (!Arrays.equals(digestInResult, digest))) {
result.add(new NamedDigest(alg, digest));
@ -1902,8 +1901,8 @@ public abstract class V1SchemeVerifier {
entryName,
expectedDigest.jcaDigestAlgorithm,
V1SchemeSigner.MANIFEST_ENTRY_NAME,
Base64.getEncoder().encodeToString(actualDigest),
Base64.getEncoder().encodeToString(expectedDigest.digest));
Base64.encode(actualDigest),
Base64.encode(expectedDigest.digest));
}
}
}

View File

@ -22,6 +22,8 @@ import com.android.apksig.internal.asn1.ber.BerDataValueReader;
import com.android.apksig.internal.asn1.ber.BerEncoding;
import com.android.apksig.internal.asn1.ber.ByteBufferBerDataValueReader;
import com.android.apksig.internal.util.ByteBufferUtils;
import com.zane.smapiinstaller.utils.MathUtils;
import com.zane.smapiinstaller.utils.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@ -311,7 +313,7 @@ public final class Asn1BerParser {
private static Asn1Type getContainerAsn1Type(Class<?> containerClass)
throws Asn1DecodingException {
Asn1Class containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class);
Asn1Class containerAnnotation = ReflectionUtils.getDeclaredAnnotation(containerClass, Asn1Class.class);
if (containerAnnotation == null) {
throw new Asn1DecodingException(
containerClass.getName() + " is not annotated with "
@ -332,7 +334,13 @@ public final class Asn1BerParser {
private static Class<?> getElementType(Field field)
throws Asn1DecodingException, ClassNotFoundException {
String type = field.getGenericType().getTypeName();
String type;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
type = field.getGenericType().getTypeName();
}
else {
type = field.getGenericType().toString();
}
int delimiterIndex = type.indexOf('<');
if (delimiterIndex == -1) {
throw new Asn1DecodingException("Not a container type: " + field.getGenericType());
@ -508,7 +516,7 @@ public final class Asn1BerParser {
private static int integerToInt(ByteBuffer encoded) throws Asn1DecodingException {
BigInteger value = integerToBigInteger(encoded);
try {
return value.intValue();
return MathUtils.intValueExact(value);
} catch (ArithmeticException e) {
throw new Asn1DecodingException(
String.format("INTEGER cannot be represented as int: %1$d (0x%1$x)", value), e);
@ -518,7 +526,7 @@ public final class Asn1BerParser {
private static long integerToLong(ByteBuffer encoded) throws Asn1DecodingException {
BigInteger value = integerToBigInteger(encoded);
try {
return value.longValue();
return MathUtils.longValueExact(value);
} catch (ArithmeticException e) {
throw new Asn1DecodingException(
String.format("INTEGER cannot be represented as long: %1$d (0x%1$x)", value),
@ -531,7 +539,7 @@ public final class Asn1BerParser {
Field[] declaredFields = containerClass.getDeclaredFields();
List<AnnotatedField> result = new ArrayList<>(declaredFields.length);
for (Field field : declaredFields) {
Asn1Field annotation = field.getDeclaredAnnotation(Asn1Field.class);
Asn1Field annotation = ReflectionUtils.getDeclaredAnnotation(field, Asn1Field.class);
if (annotation == null) {
continue;
}
@ -646,7 +654,7 @@ public final class Asn1BerParser {
case SEQUENCE:
{
Asn1Class containerAnnotation =
targetType.getDeclaredAnnotation(Asn1Class.class);
ReflectionUtils.getDeclaredAnnotation(targetType, Asn1Class.class);
if ((containerAnnotation != null)
&& (containerAnnotation.type() == Asn1Type.SEQUENCE)) {
return parseSequence(dataValue, targetType);
@ -656,7 +664,7 @@ public final class Asn1BerParser {
case CHOICE:
{
Asn1Class containerAnnotation =
targetType.getDeclaredAnnotation(Asn1Class.class);
ReflectionUtils.getDeclaredAnnotation(targetType, Asn1Class.class);
if ((containerAnnotation != null)
&& (containerAnnotation.type() == Asn1Type.CHOICE)) {
return parseChoice(dataValue, targetType);

View File

@ -17,8 +17,11 @@
package com.android.apksig.internal.asn1;
import com.android.apksig.internal.asn1.ber.BerEncoding;
import com.google.common.collect.Iterables;
import com.zane.smapiinstaller.utils.ReflectionUtils;
import java.io.ByteArrayOutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
@ -53,7 +56,7 @@ public final class Asn1DerEncoder {
*/
public static byte[] encode(Object container) throws Asn1EncodingException {
Class<?> containerClass = container.getClass();
Asn1Class containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class);
Asn1Class containerAnnotation = ReflectionUtils.getDeclaredAnnotation(containerClass, Asn1Class.class);
if (containerAnnotation == null) {
throw new Asn1EncodingException(
containerClass.getName() + " not annotated with " + Asn1Class.class.getName());
@ -216,7 +219,7 @@ public final class Asn1DerEncoder {
Field[] declaredFields = containerClass.getDeclaredFields();
List<AnnotatedField> result = new ArrayList<>(declaredFields.length);
for (Field field : declaredFields) {
Asn1Field annotation = field.getDeclaredAnnotation(Asn1Field.class);
Asn1Field annotation = ReflectionUtils.getDeclaredAnnotation(field, Asn1Field.class);
if (annotation == null) {
continue;
}
@ -561,7 +564,7 @@ public final class Asn1DerEncoder {
case SEQUENCE:
{
Asn1Class containerAnnotation =
sourceType.getDeclaredAnnotation(Asn1Class.class);
ReflectionUtils.getDeclaredAnnotation(sourceType, Asn1Class.class);
if ((containerAnnotation != null)
&& (containerAnnotation.type() == Asn1Type.SEQUENCE)) {
return toSequence(source);
@ -571,7 +574,7 @@ public final class Asn1DerEncoder {
case CHOICE:
{
Asn1Class containerAnnotation =
sourceType.getDeclaredAnnotation(Asn1Class.class);
ReflectionUtils.getDeclaredAnnotation(sourceType, Asn1Class.class);
if ((containerAnnotation != null)
&& (containerAnnotation.type() == Asn1Type.CHOICE)) {
return toChoice(source);

View File

@ -18,11 +18,15 @@ package com.android.apksig.internal.util;
import com.android.apksig.util.DataSink;
import com.android.apksig.util.DataSource;
import com.zane.smapiinstaller.utils.MathUtils;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java9.util.stream.Stream;
/** Pseudo {@link DataSource} that chains the given {@link DataSource} as a continuous one. */
public class ChainedDataSource implements DataSource {
@ -31,7 +35,7 @@ public class ChainedDataSource implements DataSource {
public ChainedDataSource(DataSource... sources) {
mSources = sources;
mTotalSize = Arrays.stream(sources).mapToLong(src -> src.size()).sum();
mTotalSize = Stream.of(sources).mapToLong(src -> src.size()).sum();
}
@Override
@ -86,7 +90,7 @@ public class ChainedDataSource implements DataSource {
ByteBuffer buffer = ByteBuffer.allocate(size);
for (; i < mSources.length && buffer.hasRemaining(); i++) {
long sizeToCopy = Math.min(mSources[i].size() - offset, buffer.remaining());
mSources[i].copyTo(offset, Math.toIntExact(sizeToCopy), buffer);
mSources[i].copyTo(offset, MathUtils.toIntExact(sizeToCopy), buffer);
offset = 0; // may not be zero for the first source, but reset after that.
}
buffer.rewind();

View File

@ -16,6 +16,8 @@
package com.android.apksig.internal.util;
import android.os.Build;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@ -212,6 +214,8 @@ public class DelegatingX509Certificate extends X509Certificate {
@Override
public void verify(PublicKey key, Provider sigProvider) throws CertificateException,
NoSuchAlgorithmException, InvalidKeyException, SignatureException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mDelegate.verify(key, sigProvider);
}
}
}

View File

@ -20,6 +20,7 @@ import com.android.apksig.internal.zip.ZipUtils;
import com.android.apksig.util.DataSink;
import com.android.apksig.util.DataSource;
import com.android.apksig.util.DataSources;
import com.zane.smapiinstaller.utils.MathUtils;
import java.io.IOException;
import java.nio.ByteBuffer;
@ -155,7 +156,7 @@ public class VerityTreeBuilder {
levelOffset[0] = 0;
for (int i = 0; i < levelSize.size(); i++) {
// We don't support verity tree if it is larger then Integer.MAX_VALUE.
levelOffset[i + 1] = levelOffset[i] + Math.toIntExact(
levelOffset[i + 1] = levelOffset[i] + MathUtils.toIntExact(
levelSize.get(levelSize.size() - i - 1));
}
return levelOffset;

View File

@ -22,6 +22,8 @@ import com.android.apksig.internal.asn1.Asn1DerEncoder;
import com.android.apksig.internal.asn1.Asn1EncodingException;
import com.android.apksig.internal.x509.Certificate;
import net.fornwall.apksigner.Base64;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@ -30,7 +32,6 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
/**
@ -261,7 +262,7 @@ public class X509CertificateUtils {
+ "valid certificate footer");
}
}
byte[] derEncoding = Base64.getDecoder().decode(pemEncoding.toString());
byte[] derEncoding = Base64.decode(pemEncoding.toString());
// consume any trailing whitespace in the byte buffer
int nextEncodedChar = certificateBuffer.position();
while (certificateBuffer.hasRemaining()) {

View File

@ -21,6 +21,10 @@ public class Constants {
* 安装包目标包名
*/
public static final String TARGET_PACKAGE_NAME = "com.zane.stardewvalley";
/**
* 安装包目标包名
*/
public static final String TARGET_PACKAGE_NAME_SAMSUNG = "com.zane.stardewvalleysamsung";
/**
* DLC下载路径
*/

View File

@ -161,7 +161,7 @@ public class ApkPatcher {
case "package":
if (packageName.get() == null) {
packageName.set(strObj);
attr.obj = Constants.TARGET_PACKAGE_NAME;
attr.obj = strObj.replace(ManifestPatchConstants.APP_PACKAGE_NAME, Constants.TARGET_PACKAGE_NAME);
}
break;
case "label":

View File

@ -29,15 +29,21 @@ public class GameLauncher {
Activity context = CommonLogic.getActivityFromView(root);
PackageManager packageManager = context.getPackageManager();
try {
PackageInfo packageInfo = packageManager.getPackageInfo(Constants.TARGET_PACKAGE_NAME, 0);
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(Constants.TARGET_PACKAGE_NAME, 0);
} catch (PackageManager.NameNotFoundException ignored) {
packageInfo = packageManager.getPackageInfo(Constants.TARGET_PACKAGE_NAME_SAMSUNG, 0);
}
if(!CommonLogic.unpackSmapiFiles(context, packageInfo.applicationInfo.publicSourceDir, true)) {
DialogUtils.showAlertDialog(root, R.string.error, R.string.error_failed_to_repair);
return;
}
ModAssetsManager modAssetsManager = new ModAssetsManager(root);
PackageInfo finalPackageInfo = packageInfo;
modAssetsManager.checkModEnvironment((isConfirm) -> {
if(isConfirm) {
Intent intent = packageManager.getLaunchIntentForPackage(Constants.TARGET_PACKAGE_NAME);
Intent intent = packageManager.getLaunchIntentForPackage(finalPackageInfo.packageName);
context.startActivity(intent);
}
});

View File

@ -0,0 +1,63 @@
package com.zane.smapiinstaller.utils;
import java.math.BigDecimal;
import java.math.BigInteger;
public class MathUtils {
/**
* Returns the value of the {@code long} argument;
* throwing an exception if the value overflows an {@code int}.
*
* @param value the long value
* @return the argument as an int
* @throws ArithmeticException if the {@code argument} overflows an int
* @since 1.8
*/
public static int toIntExact(long value) {
if ((int)value != value) {
throw new ArithmeticException("integer overflow");
}
return (int)value;
}
/**
* Converts this {@code BigInteger} to an {@code int}, checking
* for lost information. If the value of this {@code BigInteger}
* is out of the range of the {@code int} type, then an
* {@code ArithmeticException} is thrown.
*
* @return this {@code BigInteger} converted to an {@code int}.
* @throws ArithmeticException if the value of {@code this} will
* not exactly fit in a {@code int}.
* @see BigInteger#intValue
* @since 1.8
*/
public static int intValueExact(BigInteger value) {
if (value.bitLength() <= 31) {
return value.intValue();
} else {
throw new ArithmeticException("BigInteger out of int range");
}
}
/**
* Converts this {@code BigInteger} to a {@code long}, checking
* for lost information. If the value of this {@code BigInteger}
* is out of the range of the {@code long} type, then an
* {@code ArithmeticException} is thrown.
*
* @return this {@code BigInteger} converted to a {@code long}.
* @throws ArithmeticException if the value of {@code this} will
* not exactly fit in a {@code long}.
* @see BigInteger#longValue
* @since 1.8
*/
public static long longValueExact(BigInteger value) {
if (value.bitLength() <= 63) {
return value.longValue();
} else {
throw new ArithmeticException("BigInteger out of long range");
}
}
}

View File

@ -0,0 +1,34 @@
package com.zane.smapiinstaller.utils;
import android.os.Build;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
public class ReflectionUtils {
public static <T extends Annotation> T getDeclaredAnnotation(Field targetField, Class<T> targetAnnotation) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return targetField.getDeclaredAnnotation(targetAnnotation);
}
Annotation[] declaredAnnotations = targetField.getDeclaredAnnotations();
for (Annotation annotation : declaredAnnotations) {
if(targetAnnotation.isAssignableFrom(annotation.getClass())) {
return (T) annotation;
}
}
return null;
}
public static <T extends Annotation> T getDeclaredAnnotation(Class<?> targetClass, Class<T> targetAnnotation) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return targetClass.getDeclaredAnnotation(targetAnnotation);
}
Annotation[] declaredAnnotations = targetClass.getDeclaredAnnotations();
for (Annotation annotation : declaredAnnotations) {
if(targetAnnotation.isAssignableFrom(annotation.getClass())) {
return (T) annotation;
}
}
return null;
}
}

View File

@ -0,0 +1,30 @@
package net.fornwall.apksigner;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.spongycastle.util.encoders.Base64Encoder;
/** Base64 encoding handling in a portable way across Android and JSE. */
public class Base64 {
public static String encode(byte[] data) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
new Base64Encoder().encode(data, 0, data.length, baos);
} catch (IOException e) {
throw new RuntimeException(e);
}
return new String(baos.toByteArray());
}
public static byte[] decode(String data) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
new Base64Encoder().decode(data, baos);
} catch (IOException e) {
throw new RuntimeException(e);
}
return baos.toByteArray();
}
}