diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..6924c75e5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "native/src/main/jni/a7zip/p7zip/p7zip"] + path = native/src/main/jni/a7zip/p7zip/p7zip + url = https://github.com/seven332/p7zip.git +[submodule "native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo"] + path = native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo + url = https://gitlab.com/NekoInverter/libjpeg-turbo.git +[submodule "native/src/main/jni/image/giflib/giflib"] + path = native/src/main/jni/image/giflib/giflib + url = https://gitlab.com/NekoInverter/giflib.git +[submodule "native/src/main/jni/image/libpng/libpng"] + path = native/src/main/jni/image/libpng/libpng + url = https://gitlab.com/NekoInverter/libpng.git diff --git a/app/build.gradle b/app/build.gradle index 46d5ff7ce..4dba7c130 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,6 +5,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 32 buildToolsVersion "32.0.0" + signingConfigs { def keystorePwd = null def alias = null @@ -122,7 +123,6 @@ dependencies { implementation 'com.google.android.material:material:1.5.0' implementation 'com.getkeepsafe.relinker:relinker:1.4.4' implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3' - implementation 'com.github.seven332.a7zip:extract-lite:cba0ce9586' implementation 'com.github.seven332:beerbelly:0.1.4' implementation 'com.github.seven332:glgallery:0.1.2' implementation 'com.github.seven332:glview:0.1.0' @@ -139,7 +139,6 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.2' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.2' implementation 'com.takisoft.preferencex:preferencex:1.1.0' - implementation 'com.github.NekoInverter:Image:8fd6c1e1df' implementation 'com.gitlab.NekoInverter:yorozuya:723dc867a9' implementation 'dev.rikka.rikkax.appcompat:appcompat:1.4.1' implementation 'dev.rikka.rikkax.core:core:1.3.3' @@ -152,6 +151,7 @@ dependencies { implementation 'org.ccil.cowan.tagsoup:tagsoup:1.2.1' implementation 'org.greenrobot:greendao:3.3.0' implementation 'org.jsoup:jsoup:1.14.3' + implementation project(':native') testImplementation 'junit:junit:4.12' testImplementation 'org.robolectric:robolectric:4.2.1' testImplementation 'org.jooq:joor:0.9.6' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 325fc16f8..de0582a27 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -13,15 +13,11 @@ -keepattributes SourceFile,LineNumberTable --keep class com.hippo.a7zip.* { *; } - -keep class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { } -keep class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { } -keepnames class com.hippo.ehviewer.ui.scene.* { } --keep class com.hippo.image.* { *; } - -keep class com.hippo.ehviewer.dao.* { *; } -dontwarn net.sqlcipher.database.** @@ -35,4 +31,4 @@ -dontwarn org.bouncycastle.jsse.BCSSLParameters -dontwarn org.conscrypt.Conscrypt -dontwarn org.openjsse.javax.net.ssl.SSLSocket --dontwarn org.openjsse.net.ssl.OpenJSSE \ No newline at end of file +-dontwarn org.openjsse.net.ssl.OpenJSSE diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index d0800ac9d..ba16519f9 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -37,7 +37,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.collection.LruCache; -import com.hippo.a7zip.A7Zip; +import com.hippo.Native; import com.hippo.beerbelly.SimpleDiskCache; import com.hippo.conaco.Conaco; import com.hippo.ehviewer.client.EhClient; @@ -334,8 +334,9 @@ public class EhApplication extends SceneApplication { EhDB.initialize(this); EhEngine.initialize(); BitmapUtils.initialize(this); - Image.initialize(this); - A7Zip.initialize(this); + Native.initialize(this); + //Image.initialize(this); + //A7Zip.initialize(this); if (EhDB.needMerge()) { EhDB.mergeOldDB(this); diff --git a/gradle.properties b/gradle.properties index 027ef9db8..f78fe6dce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,8 @@ # any settings specified in this file. # For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html +# http://wwwCMakeLists.txt +#main.c.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. diff --git a/native/.gitignore b/native/.gitignore new file mode 100644 index 000000000..9f3b1a4f4 --- /dev/null +++ b/native/.gitignore @@ -0,0 +1,8 @@ +/build +/src/main/java-gen +manifest-merger-release-report.txt +/src/main/libs +/src/main/obj +# Intellij +*.iml +/.cxx diff --git a/native/build.gradle b/native/build.gradle new file mode 100644 index 000000000..239ddd811 --- /dev/null +++ b/native/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 32 + buildToolsVersion "32.0.0" + ndkVersion "21.4.7075529" + + defaultConfig { + minSdkVersion 23 + targetSdkVersion 32 + versionCode 1 + versionName "1.0.0" + consumerProguardFiles 'proguard-rules.pro' + + externalNativeBuild { + cmake { + version '3.10.2' + arguments '-DANDROID_STL=c++_static', '-DANDROID_PLATFORM=android-23', "-j=16" + } + } + } + + buildTypes { + release { + minifyEnabled false + } + } + + externalNativeBuild { + cmake { + path 'src/main/jni/CMakeLists.txt' + } + } + + buildFeatures { + buildConfig false + } +} + +dependencies { + implementation 'androidx.annotation:annotation:1.3.0' + implementation 'com.getkeepsafe.relinker:relinker:1.4.4' +} diff --git a/native/proguard-rules.pro b/native/proguard-rules.pro new file mode 100644 index 000000000..8f14d3812 --- /dev/null +++ b/native/proguard-rules.pro @@ -0,0 +1,2 @@ +-keep class com.hippo.a7zip.* { *; } +-keep class com.hippo.image.* { *; } diff --git a/native/src/main/AndroidManifest.xml b/native/src/main/AndroidManifest.xml new file mode 100644 index 000000000..ff18c0210 --- /dev/null +++ b/native/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/native/src/main/java/com/hippo/Native.java b/native/src/main/java/com/hippo/Native.java new file mode 100644 index 000000000..a1babb87d --- /dev/null +++ b/native/src/main/java/com/hippo/Native.java @@ -0,0 +1,12 @@ +package com.hippo; + +import android.content.Context; + +import com.getkeepsafe.relinker.ReLinker; + +public class Native { + + public static void initialize(Context context) { + ReLinker.loadLibrary(context, "ehviewer"); + } +} diff --git a/native/src/main/java/com/hippo/a7zip/ArchiveException.java b/native/src/main/java/com/hippo/a7zip/ArchiveException.java new file mode 100644 index 000000000..08e0d3925 --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/ArchiveException.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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.hippo.a7zip; + +public class ArchiveException extends Exception { + + public ArchiveException(String message) { + super(message); + } + + public ArchiveException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/native/src/main/java/com/hippo/a7zip/FileSeekableInputStream.java b/native/src/main/java/com/hippo/a7zip/FileSeekableInputStream.java new file mode 100644 index 000000000..a1539076d --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/FileSeekableInputStream.java @@ -0,0 +1,76 @@ +/* + * Copyright 2020 Hippo Seven + * + * 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.hippo.a7zip; + +import androidx.annotation.NonNull; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class FileSeekableInputStream extends SeekableInputStream { + + private final RandomAccessFile file; + + public FileSeekableInputStream(String path) throws FileNotFoundException { + this(new File(path)); + } + + public FileSeekableInputStream(File file) throws FileNotFoundException { + this(new RandomAccessFile(file, "r")); + } + + public FileSeekableInputStream(RandomAccessFile file) { + this.file = file; + } + + @Override + public void seek(long pos) throws IOException { + file.seek(pos); + } + + @Override + public long tell() throws IOException { + return file.getFilePointer(); + } + + @Override + public long size() throws IOException { + return file.length(); + } + + @Override + public int read() throws IOException { + return file.read(); + } + + @Override + public int read(@NonNull byte[] b) throws IOException { + return file.read(b); + } + + @Override + public int read(@NonNull byte[] b, int off, int len) throws IOException { + return file.read(b, off, len); + } + + @Override + public void close() throws IOException { + file.close(); + } +} diff --git a/native/src/main/java/com/hippo/a7zip/FileSeekableOutputStream.java b/native/src/main/java/com/hippo/a7zip/FileSeekableOutputStream.java new file mode 100644 index 000000000..c67506d7d --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/FileSeekableOutputStream.java @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Hippo Seven + * + * 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.hippo.a7zip; + +import androidx.annotation.NonNull; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class FileSeekableOutputStream extends SeekableOutputStream { + + private final RandomAccessFile file; + + public FileSeekableOutputStream(String path) throws FileNotFoundException { + this(new File(path)); + } + + public FileSeekableOutputStream(File file) throws FileNotFoundException { + this(new RandomAccessFile(file, "rw")); + } + + public FileSeekableOutputStream(RandomAccessFile file) { + this.file = file; + } + + @Override + public void seek(long pos) throws IOException { + file.seek(pos); + } + + @Override + public long tell() throws IOException { + return file.getFilePointer(); + } + + @Override + public long size() throws IOException { + return file.length(); + } + + @Override + public void truncate(long size) throws IOException { + file.setLength(size); + } + + @Override + public void write(int b) throws IOException { + file.write(b); + } + + @Override + public void write(@NonNull byte[] b) throws IOException { + file.write(b); + } + + @Override + public void write(@NonNull byte[] b, int off, int len) throws IOException { + file.write(b, off, len); + } + + @Override + public void close() throws IOException { + file.close(); + } +} diff --git a/native/src/main/java/com/hippo/a7zip/InArchive.java b/native/src/main/java/com/hippo/a7zip/InArchive.java new file mode 100644 index 000000000..219d96ba3 --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/InArchive.java @@ -0,0 +1,456 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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.hippo.a7zip; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.Closeable; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +public class InArchive implements Closeable { + + private long nativePtr; + @Nullable + private final Charset charset; + @Nullable + private final String password; + + private InArchive(long nativePtr, @Nullable Charset charset, @Nullable String password) { + this.nativePtr = nativePtr; + this.charset = charset; + this.password = password; + } + + private void checkClosed() { + if (nativePtr == 0) { + throw new IllegalStateException("This InArchive is closed."); + } + } + + // Sometimes p7zip returns string in the original charset, sometimes in utf-16. + // If it's in the original charset, each byte stores in each char, + // so every char in the string is smaller than 0xFF. + // But if every char in the string is smaller than 0xFF, + // it's hard to tell the string is in utf-16 or the original charset. + // TODO Let p7zip tell whether it have encoded the string. + private static String applyCharsetToString(String str, Charset charset) { + if (str == null || charset == null) { + return str; + } + + int length = str.length(); + for (int i = 0; i < length; i++) { + char c = str.charAt(i); + if (c > 0xFF) { + // It's not a pure byte list, can't apply charset + return str; + } + } + + byte[] bytes = new byte[length]; + for (int i = 0; i < length; i++) { + bytes[i] = (byte) str.charAt(i); + } + + return new String(bytes, charset); + } + + private static String applyCharsetToPassword(String str, Charset charset) { + if (str == null || charset == null) { + return str; + } + + byte[] bytes = str.getBytes(charset); + char[] chars = new char[bytes.length]; + for (int i = 0; i < chars.length; i++) { + chars[i] = (char) (bytes[i] & 0xFF); + } + return new String(chars); + } + + /** + * Returns the format name of this archive. + * Empty string if can't get the name. + */ + @NonNull + public String getFormatName() { + checkClosed(); + return nativeGetFormatName(nativePtr); + } + + /** + * Returns the number of entries in this archive. + * {@code -1} if get error. + */ + public int getNumberOfEntries() { + checkClosed(); + return nativeGetNumberOfEntries(nativePtr); + } + + /** + * Returns the type of the property for the archive. + * + * @param propID the id of the property + * @return one of {@link PropType} + */ + public PropType getArchivePropertyType(PropID propID) { + int type = nativeGetArchivePropertyType(nativePtr, propID.ordinal()); + if (type >= 0 && type < PropType.values().length) { + return PropType.values()[type]; + } else { + return PropType.UNKNOWN; + } + } + + /** + * Returns boolean property for the archive. + * + * @param propID the id of the property + * @return the boolean property, {@code false} if get error + */ + public boolean getArchiveBooleanProperty(PropID propID) { + checkClosed(); + return nativeGetArchiveBooleanProperty(nativePtr, propID.ordinal()); + } + + /** + * Returns int property for the archive. + * + * @param propID the id of the property + * @return the int property, {@code 0} if get error + */ + public int getArchiveIntProperty(PropID propID) { + checkClosed(); + return nativeGetArchiveIntProperty(nativePtr, propID.ordinal()); + } + + /** + * Returns long property for the archive. + * + * @param propID the id of the property + * @return the long property, {@code 0} if get error + */ + public long getArchiveLongProperty(PropID propID) { + checkClosed(); + return nativeGetArchiveLongProperty(nativePtr, propID.ordinal()); + } + + /** + * Returns string property for the archive. + * + * @param propID the id of the property + * @return the string property, empty string if get error + */ + @NonNull + public String getArchiveStringProperty(PropID propID) { + return getArchiveStringProperty(propID, charset); + } + + /** + * Returns string property for the archive. + * + * @param propID the id of the property + * @param charset the charset of the string, {@code null} to let p7zip handle it + * @return the string property, empty string if get error + */ + @NonNull + public String getArchiveStringProperty(PropID propID, @Nullable Charset charset) { + checkClosed(); + String str = nativeGetArchiveStringProperty(nativePtr, propID.ordinal()); + str = applyCharsetToString(str, charset); + return str != null ? str : ""; + } + + /** + * Returns the type of the property for the archive. + * + * @param propID the id of the property + * @return one of {@link PropType} + */ + public PropType getEntryPropertyType(int index, PropID propID) { + int type = nativeGetEntryPropertyType(nativePtr, index, propID.ordinal()); + if (type >= 0 && type < PropType.values().length) { + return PropType.values()[type]; + } else { + return PropType.UNKNOWN; + } + } + + /** + * Returns boolean property for the entry. + * + * @param index the index of the entry + * @param propID the id of the property + * @return the boolean property, {@code false} if get error + */ + public boolean getEntryBooleanProperty(int index, PropID propID) { + checkClosed(); + return nativeGetEntryBooleanProperty(nativePtr, index, propID.ordinal()); + } + + /** + * Returns int property for the entry. + * + * @param index the index of the entry + * @param propID the id of the property + * @return the int property, {@code 0} if get error + */ + public int getEntryIntProperty(int index, PropID propID) { + checkClosed(); + return nativeGetEntryIntProperty(nativePtr, index, propID.ordinal()); + } + + /** + * Returns long property for the entry. + * + * @param index the index of the entry + * @param propID the id of the property + * @return the long property, {@code 0} if get error + */ + public long getEntryLongProperty(int index, PropID propID) { + checkClosed(); + return nativeGetEntryLongProperty(nativePtr, index, propID.ordinal()); + } + + /** + * Returns string property for the entry. + * + * @param index the index of the entry + * @param propID the id of the property + * @return the string property, empty string if get error + */ + @NonNull + public String getEntryStringProperty(int index, PropID propID) { + return getEntryStringProperty(index, propID, charset); + } + + /** + * Returns string property for the entry. + * + * @param index the index of the entry + * @param propID the id of the property + * @param charset the charset of the string, {@code null} to let p7zip handle it + * @return the string property, empty string if get error + */ + @NonNull + public String getEntryStringProperty(int index, PropID propID, @Nullable Charset charset) { + checkClosed(); + String str = nativeGetEntryStringProperty(nativePtr, index, propID.ordinal()); + str = applyCharsetToString(str, charset); + return str != null ? str : ""; + } + + /** + * Returns the path of the entry. + * + * @param index the index of the entry + * @return the path, empty string if get error + */ + @NonNull + public String getEntryPath(int index) { + return getEntryPath(index, charset); + } + + /** + * Returns the path of the entry. + * + * @param index the index of the entry + * @param charset the charset of the string, {@code null} to let p7zip handle it + * @return the path, empty string if get error + */ + @NonNull + public String getEntryPath(int index, @Nullable Charset charset) { + return getEntryStringProperty(index, PropID.PATH, charset); + } + + /** + * Returns the stream of the entry. Only a few archive formats support it. + * + * @param index the index of the entry + * @return the stream of the entry + * @throws ArchiveException if the archive format doesn't support the operation or get error + * @see #extractEntry(int, OutputStream) + * @see #extractEntry(int, String, OutputStream) + */ + @NonNull + public InputStream getEntryStream(int index) throws ArchiveException { + return nativeGetEntryStream(nativePtr, index); + } + + /** + * Extracts the context of the entry into the output stream. + * + * @param index the index of the entry + * @param os the output steam to receive the content, + * it will be closed at the end of this method + * @throws ArchiveException if get error + * @see #getEntryStream(int) + */ + public void extractEntry(int index, @NonNull OutputStream os) throws ArchiveException { + extractEntry(index, password, os); + } + + /** + * Extracts the context of the entry into the output stream. + * + * @param index the index of the entry + * @param password the password of the entry + * @param os the output steam to receive the content, + * it will be closed at the end of this method + * @throws ArchiveException if get error + */ + @SuppressWarnings("ThrowFromFinallyBlock") + public void extractEntry(int index, String password, @NonNull OutputStream os) throws ArchiveException { + try { + checkClosed(); + nativeExtractEntry(nativePtr, index, password, os); + } finally { + try { + os.close(); + } catch (IOException e) { + throw new ArchiveException("Catch IOException while closing the OutputStream", e); + } + } + } + + @Override + public void close() { + if (nativePtr != 0) { + nativeClose(nativePtr); + nativePtr = 0; + } + } + + /** + * Returns {@code true} if the archive is closed. + */ + public boolean isClosed() { + return nativePtr == 0; + } + + @NonNull + public static InArchive open(File file) throws ArchiveException { + try { + return open(new FileSeekableInputStream(file), null, null, file.getName(), new OpenVolumeInDirCallback(file.getParentFile())); + } catch (FileNotFoundException e) { + throw new ArchiveException("Can't open the archive: " + file.getPath(), e); + } + } + + @NonNull + public static InArchive open(SeekableInputStream stream) throws ArchiveException { + return open(stream, null, null, null, null); + } + + /** + * Opens an archive to read from the specified stream. + *

+ * {@code charset} is for password and string property. + * The charset of string property can reset in + * {@link InArchive#getArchiveStringProperty(PropID, Charset)}. + */ + @NonNull + public static InArchive open( + SeekableInputStream stream, + @Nullable Charset charset, + @Nullable String password, + @Nullable String filename, + @Nullable OpenVolumeCallback openVolumeCallback + ) throws ArchiveException { + password = applyCharsetToPassword(password, charset); + long nativePtr = nativeOpen(stream, password, filename, openVolumeCallback); + + if (nativePtr == 0) { + // It should not be 0 + throw new ArchiveException("a7zip is buggy"); + } + + return new InArchive(nativePtr, charset, password); + } + + @Keep + public interface OpenVolumeCallback { + @NonNull + SeekableInputStream openVolume(String filename) throws ArchiveException; + } + + public static class OpenVolumeInDirCallback implements OpenVolumeCallback { + + private final File dir; + + public OpenVolumeInDirCallback(File dir) { + this.dir = dir; + } + + @NonNull + @Override + public SeekableInputStream openVolume(String filename) throws ArchiveException { + try { + return new FileSeekableInputStream(new File(dir, filename)); + } catch (FileNotFoundException e) { + throw new ArchiveException("Can't find the file<" + filename + "> in dir<" + dir.getPath() + ">", e); + } + } + } + + private static native long nativeOpen( + SeekableInputStream stream, + String password, + String filename, + OpenVolumeCallback openVolumeCallback + ) throws ArchiveException; + + private static native String nativeGetFormatName(long nativePtr); + + private static native int nativeGetNumberOfEntries(long nativePtr); + + private static native int nativeGetArchivePropertyType(long nativePtr, int propID); + + private static native boolean nativeGetArchiveBooleanProperty(long nativePtr, int propID); + + private static native int nativeGetArchiveIntProperty(long nativePtr, int propID); + + private static native long nativeGetArchiveLongProperty(long nativePtr, int propID); + + @Nullable + private static native String nativeGetArchiveStringProperty(long nativePtr, int propID); + + private static native int nativeGetEntryPropertyType(long nativePtr, int index, int propID); + + private static native boolean nativeGetEntryBooleanProperty(long nativePtr, int index, int propID); + + private static native int nativeGetEntryIntProperty(long nativePtr, int index, int propID); + + private static native long nativeGetEntryLongProperty(long nativePtr, int index, int propID); + + @Nullable + private static native String nativeGetEntryStringProperty(long nativePtr, int index, int propID); + + @NonNull + private static native InputStream nativeGetEntryStream(long nativePtr, int index) throws ArchiveException; + + private static native void nativeExtractEntry(long nativePtr, int index, String password, OutputStream os) throws ArchiveException; + + private static native void nativeClose(long nativePtr); +} diff --git a/native/src/main/java/com/hippo/a7zip/NativeInputStream.java b/native/src/main/java/com/hippo/a7zip/NativeInputStream.java new file mode 100644 index 000000000..3816c090f --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/NativeInputStream.java @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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.hippo.a7zip; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.InputStream; + +class NativeInputStream extends InputStream { + + private long nativePtr; + private final byte[] scratch = new byte[8]; + + private NativeInputStream(long nativePtr) { + this.nativePtr = nativePtr; + } + + private void checkClosed() { + if (nativePtr == 0) { + throw new IllegalStateException("This JavaInputStream is closed."); + } + } + + @Override + public int read() throws IOException { + return (read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1; + } + + @Override + public int read(@NonNull byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + checkClosed(); + return nativeRead(nativePtr, b, off, len); + } + + @Override + public void close() throws IOException { + if (nativePtr != 0) { + nativeClose(nativePtr); + nativePtr = 0; + } + } + + private static native int nativeRead(long nativePtr, byte[] b, int off, int len) throws IOException; + + private static native void nativeClose(long nativePtr) throws IOException; +} diff --git a/native/src/main/java/com/hippo/a7zip/NativeSeekableInputStream.java b/native/src/main/java/com/hippo/a7zip/NativeSeekableInputStream.java new file mode 100644 index 000000000..327e0dca7 --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/NativeSeekableInputStream.java @@ -0,0 +1,89 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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.hippo.a7zip; + +import androidx.annotation.NonNull; + +import java.io.IOException; + +class NativeSeekableInputStream extends SeekableInputStream { + + private long nativePtr; + private final byte[] scratch = new byte[8]; + + private NativeSeekableInputStream(long nativePtr) { + this.nativePtr = nativePtr; + } + + private void checkClosed() { + if (nativePtr == 0) { + throw new IllegalStateException("This JavaSeekableInputStream is closed."); + } + } + + @Override + public void seek(long pos) throws IOException { + checkClosed(); + nativeSeek(nativePtr, pos); + } + + @Override + public long tell() throws IOException { + checkClosed(); + return nativeTell(nativePtr); + } + + @Override + public long size() throws IOException { + checkClosed(); + return nativeSize(nativePtr); + } + + @Override + public int read() throws IOException { + return (read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1; + } + + @Override + public int read(@NonNull byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + checkClosed(); + return nativeRead(nativePtr, b, off, len); + } + + @Override + public void close() throws IOException { + if (nativePtr != 0) { + nativeClose(nativePtr); + nativePtr = 0; + } + } + + private static native void nativeSeek(long nativePtr, long pos) throws IOException; + + private static native long nativeTell(long nativePtr) throws IOException; + + private static native long nativeSize(long nativePtr) throws IOException; + + private static native int nativeRead(long nativePtr, byte[] b, int off, int len) throws IOException; + + private static native void nativeClose(long nativePtr) throws IOException; +} diff --git a/native/src/main/java/com/hippo/a7zip/PasswordException.java b/native/src/main/java/com/hippo/a7zip/PasswordException.java new file mode 100644 index 000000000..d9b1a51ec --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/PasswordException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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.hippo.a7zip; + +import androidx.annotation.Keep; + +@Keep +public class PasswordException extends ArchiveException { + + public PasswordException(String message) { + super(message); + } + + public PasswordException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/native/src/main/java/com/hippo/a7zip/PropID.java b/native/src/main/java/com/hippo/a7zip/PropID.java new file mode 100644 index 000000000..8ada4f7dc --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/PropID.java @@ -0,0 +1,116 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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.hippo.a7zip; + +public enum PropID { + NO_PROPERTY, + MAIN_SUBFILE, + HANDLER_ITEM_INDEX, + PATH, + NAME, + EXTENSION, + IS_DIR, + SIZE, + PACK_SIZE, + ATTRIB, + C_TIME, + A_TIME, + M_TIME, + SOLID, + COMMENTED, + ENCRYPTED, + SPLIT_BEFORE, + SPLIT_AFTER, + DICTIONARY_SIZE, + CRC, + TYPE, + IS_ANTI, + METHOD, + HOST_OS, + FILE_SYSTEM, + USER, + GROUP, + BLOCK, + COMMENT, + POSITION, + PREFIX, + NUM_SUB_DIRS, + NUM_SUB_FILES, + UNPACK_VER, + VOLUME, + IS_VOLUME, + OFFSET, + LINKS, + NUM_BLOCKS, + NUM_VOLUMES, + TIME_TYPE, + BIT64, + BIG_ENDIAN, + CPU, + PHY_SIZE, + HEADERS_SIZE, + CHECKSUM, + CHARACTS, + VA, + ID, + SHORT_NAME, + CREATOR_APP, + SECTOR_SIZE, + POSIX_ATTRIB, + SYM_LINK, + ERROR, + TOTAL_SIZE, + FREE_SPACE, + CLUSTER_SIZE, + VOLUME_NAME, + LOCAL_NAME, + PROVIDER, + NT_SECURE, + IS_ALT_STREAM, + IS_AUX, + IS_DELETED, + IS_TREE, + SHA1, + SHA256, + ERROR_TYPE, + NUM_ERRORS, + ERROR_FLAGS, + WARNING_FLAGS, + WARNING, + NUM_STREAMS, + NUM_ALT_STREAMS, + ALT_STREAMS_SIZE, + VIRTUAL_SIZE, + UNPACK_SIZE, + TOTAL_PHY_SIZE, + VOLUME_INDEX, + SUB_TYPE, + SHORT_COMMENT, + CODE_PAGE, + IS_NOT_ARC_TYPE, + PHY_SIZE_CANT_BE_DETECTED, + ZEROS_TAIL_IS_ALLOWED, + TAIL_SIZE, + EMBEDDED_STUB_SIZE, + NT_REPARSE, + HARD_LINK, + INODE, + STREAM_ID, + READ_ONLY, + OUT_NAME, + COPY_LINK, +} diff --git a/native/src/main/java/com/hippo/a7zip/PropType.java b/native/src/main/java/com/hippo/a7zip/PropType.java new file mode 100644 index 000000000..9c1a92c28 --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/PropType.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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.hippo.a7zip; + +public enum PropType { + UNKNOWN, + EMPTY, + BOOL, + INT, + LONG, + FLOAT, + DOUBLE, + STRING, +} diff --git a/native/src/main/java/com/hippo/a7zip/SeekableInputStream.java b/native/src/main/java/com/hippo/a7zip/SeekableInputStream.java new file mode 100644 index 000000000..4c98ac4c8 --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/SeekableInputStream.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Hippo Seven + * + * 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.hippo.a7zip; + +import java.io.IOException; +import java.io.InputStream; + +public abstract class SeekableInputStream extends InputStream { + + /** + * Sets the position, measured from the beginning, + * at which the next read occurs. The offset may be + * set beyond the end of the file. + */ + public abstract void seek(long pos) throws IOException; + + /** + * Returns current position, measured from the beginning. + */ + public abstract long tell() throws IOException; + + /** + * Returns the size. + */ + public abstract long size() throws IOException; +} diff --git a/native/src/main/java/com/hippo/a7zip/SeekableOutputStream.java b/native/src/main/java/com/hippo/a7zip/SeekableOutputStream.java new file mode 100644 index 000000000..feaf30454 --- /dev/null +++ b/native/src/main/java/com/hippo/a7zip/SeekableOutputStream.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Hippo Seven + * + * 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.hippo.a7zip; + +import java.io.IOException; +import java.io.OutputStream; + +public abstract class SeekableOutputStream extends OutputStream { + + /** + * Sets the position, measured from the beginning, + * at which the next write occurs. The offset may be + * set beyond the end of the file. + */ + public abstract void seek(long pos) throws IOException; + + /** + * Returns current position, measured from the beginning. + */ + public abstract long tell() throws IOException; + + /** + * Returns the size. + */ + public abstract long size() throws IOException; + + /** + * Sets the size. + */ + public abstract void truncate(long size) throws IOException; +} diff --git a/native/src/main/java/com/hippo/image/Image.java b/native/src/main/java/com/hippo/image/Image.java new file mode 100644 index 000000000..a202894ac --- /dev/null +++ b/native/src/main/java/com/hippo/image/Image.java @@ -0,0 +1,298 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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.hippo.image; + +import android.graphics.Bitmap; + +import androidx.annotation.Nullable; + +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The {@code Image} is a image which stored pixel data in native heap + */ +public final class Image { + + /** + * Unknown image format + */ + public static final int FORMAT_UNKNOWN = -1; + + /** + * Plain image format, for {@code Image} from {@link #create(Bitmap)} + */ + public static final int FORMAT_PLAIN = 0; + + /** + * JPEG image format + */ + public static final int FORMAT_JPEG = 1; + + /** + * PNG image format + */ + public static final int FORMAT_PNG = 2; + + /** + * GIF image format + */ + public static final int FORMAT_GIF = 3; + + private static final AtomicInteger sImageCount = new AtomicInteger(); + + private long mNativePtr; + private final int mFormat; + private final int mWidth; + private final int mHeight; + + private Throwable mRecycleTracker; + + private Image(long nativePtr, int format, int width, int height) { + mNativePtr = nativePtr; + mFormat = format; + mWidth = width; + mHeight = height; + + sImageCount.getAndIncrement(); + } + + /** + * Return the format of the image + */ + public int getFormat() { + return mFormat; + } + + /** + * Return the width of the image + */ + public int getWidth() { + return mWidth; + } + + /** + * Return the height of the image + */ + public int getHeight() { + return mHeight; + } + + /** + * Return the minimum number of bytes that can be used to store this image's pixels. + */ + public int getByteCount() { + checkRecycled(); + return nativeGetByteCount(mNativePtr, mFormat); + } + + private void checkRecycled() { + if (mNativePtr == 0) { + if (mRecycleTracker != null) { + throw new IllegalStateException("The image is recycled.", mRecycleTracker); + } else { + throw new IllegalStateException("The image is recycled."); + } + } + } + + /** + * Complete the image decoding + */ + public boolean complete() { + checkRecycled(); + return nativeComplete(mNativePtr, mFormat); + } + + /** + * Is the image decoding completed + */ + public boolean isCompleted() { + checkRecycled(); + return nativeIsCompleted(mNativePtr, mFormat); + } + + /** + * Render the image to {@code Bitmap} + */ + public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY, + int width, int height, boolean fillBlank, int defaultColor) { + checkRecycled(); + nativeRender(mNativePtr, mFormat, srcX, srcY, dst, dstX, dstY, + width, height, fillBlank, defaultColor); + } + + /** + * Call {@code glTexImage2D} for init is true and + * call {@code glTexSubImage2D} for init is false. + * width * height must <= 512 * 512 or do nothing + */ + public void texImage(boolean init, int offsetX, int offsetY, int width, int height) { + checkRecycled(); + nativeTexImage(mNativePtr, mFormat, init, offsetX, offsetY, width, height); + } + + /** + * Move to next frame. Do nothing for non-animation image + */ + public void advance() { + checkRecycled(); + nativeAdvance(mNativePtr, mFormat); + } + + /** + * Return current frame delay. 0 for non-animation image + */ + public int getDelay() { + checkRecycled(); + int delay = nativeGetDelay(mNativePtr, mFormat); + return delay <= 10 ? 100 : delay; + } + + /** + * Return frame count. 1 for non-animation image + */ + public int getFrameCount() { + checkRecycled(); + return nativeFrameCount(mNativePtr, mFormat); + } + + /** + * Return is the image opaque + */ + public boolean isOpaque() { + checkRecycled(); + return nativeIsOpaque(mNativePtr, mFormat); + } + + /** + * Return {@code true} if the image is gray. + */ + public boolean isGray(int error) { + checkRecycled(); + return nativeIsGray(mNativePtr, mFormat, error); + } + + /** + * Improves contrast in this image with CLAHE. + */ + public void clahe(boolean toGray) { + checkRecycled(); + nativeClahe(mNativePtr, mFormat, toGray); + } + + /** + * Free the native object associated with this image. + * It must be called when the image will not be used. + * The image can't be used after this method is called. + */ + public void recycle() { + if (mNativePtr != 0) { + nativeRecycle(mNativePtr, mFormat); + mNativePtr = 0; + + sImageCount.getAndDecrement(); + + mRecycleTracker = new Throwable("It's a ImageRecycleTracker"); + } + } + + /** + * Returns true if this image has been recycled. + */ + public boolean isRecycled() { + return mNativePtr == 0; + } + + /** + * Decode image from {@code InputStream} + */ + @Nullable + public static Image decode(InputStream is, boolean partially) { + if (!(is instanceof BufferedInputStream)) { + is = new BufferedInputStream(is); + } + return nativeDecode(is, partially); + } + + /** + * Create a plain image from Bitmap + */ + @Nullable + public static Image create(Bitmap bitmap) { + return nativeCreate(bitmap); + } + + /** + * Return all un-recycled {@code Image} instance count. + * It is useful for debug. + */ + public static int getImageCount() { + return sImageCount.get(); + } + + /** + * Return all supported image formats, exclude {@link #FORMAT_PLAIN} + */ + public static int[] getSupportedImageFormats() { + return nativeGetSupportedImageFormats(); + } + + /** + * Return decoder description of the image format, + * {@code null} for invalid image format. + */ + public static String getDecoderDescription(int format) { + return nativeGetDecoderDescription(format); + } + + private static native Image nativeDecode(InputStream is, boolean partially); + + private static native Image nativeCreate(Bitmap bitmap); + + private static native int nativeGetByteCount(long nativePtr, int format); + + private static native boolean nativeComplete(long nativePtr, int format); + + private static native boolean nativeIsCompleted(long nativePtr, int format); + + private static native void nativeRender(long nativePtr, int format, + int srcX, int srcY, Bitmap dst, int dstX, int dstY, + int width, int height, boolean fillBlank, int defaultColor); + + private static native void nativeTexImage(long nativePtr, int format, + boolean init, int offsetX, int offsetY, int width, int height); + + private static native void nativeAdvance(long nativePtr, int format); + + private static native int nativeGetDelay(long nativePtr, int format); + + private static native int nativeFrameCount(long nativePtr, int format); + + private static native boolean nativeIsOpaque(long nativePtr, int format); + + private static native boolean nativeIsGray(long nativePtr, int format, int error); + + private static native void nativeClahe(long nativePtr, int format, boolean toGray); + + private static native void nativeRecycle(long nativePtr, int format); + + private static native int[] nativeGetSupportedImageFormats(); + + private static native String nativeGetDecoderDescription(int format); +} diff --git a/native/src/main/java/com/hippo/image/ImageBitmap.java b/native/src/main/java/com/hippo/image/ImageBitmap.java new file mode 100644 index 000000000..e3a1339c4 --- /dev/null +++ b/native/src/main/java/com/hippo/image/ImageBitmap.java @@ -0,0 +1,349 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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.hippo.image; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Animatable; +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * A image with {@link Image} for data and {@link Bitmap} for render. + */ +public final class ImageBitmap implements Animatable, Runnable { + + private static final Handler HANDLER = new Handler(Looper.getMainLooper()); + + @Nullable + private Image mImage; + @NonNull + private final Bitmap mBitmap; + private final int mFormat; + private final boolean mIsOpaque; + private final int mByteCount; + private final int mFrameCount; + private int mReferences; + private int mAnimationReferences; + private boolean mRunning; + private final Set> mCallbackSet = new LinkedHashSet<>(); + + private ImageBitmap(@NonNull Image image) throws OutOfMemoryError { + int width = image.getWidth(); + int height = image.getHeight(); + mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + image.render(0, 0, mBitmap, 0, 0, width, height, false, 0); + mFormat = image.getFormat(); + mIsOpaque = image.isOpaque(); + mByteCount = image.getByteCount(); + mFrameCount = image.getFrameCount(); + + if (mFrameCount > 1) { + // For animated image, save image object + mImage = image; + } else { + // Free the image + image.recycle(); + } + } + + private ImageBitmap(@NonNull Bitmap bitmap) { + mFormat = Image.FORMAT_PLAIN; + mBitmap = bitmap; + mIsOpaque = !bitmap.hasAlpha(); + mByteCount = bitmap.getRowBytes() * bitmap.getHeight(); + mFrameCount = 1; + } + + /** + * Decode {@code InputStream}, then create image. + */ + @Nullable + public static ImageBitmap decode(@NonNull InputStream is) { + Image image = Image.decode(is, false); + if (image != null) { + return create(image); + } else { + return null; + } + } + + /** + * Create {@code ImageBitmap} from {@code Image}. + * It is not recommended. Use {@link #decode(InputStream)} if you can. + * + * @param image the image should not be used before and + * it must not be recycled. And the image should + * not be used directly anymore. + */ + @Nullable + public static ImageBitmap create(@NonNull Image image) { + if (!image.isRecycled()) { + try { + return new ImageBitmap(image); + } catch (OutOfMemoryError e) { + image.recycle(); + return null; + } + } else { + return null; + } + } + + /** + * Create {@code ImageBitmap} from {@code Bitmap}. + * + * @param bitmap the bitmap should not be recycled + */ + @Nullable + public static ImageBitmap create(@NonNull Bitmap bitmap) { + if (!bitmap.isRecycled()) { + return new ImageBitmap(bitmap); + } else { + return null; + } + } + + /** + * Obtain the image bitmap + * + * @return false for the image is recycled and obtain failed + */ + public synchronized boolean obtain() { + if (mBitmap.isRecycled()) { + return false; + } else { + ++mReferences; + return true; + } + } + + /** + * Release the image bitmap + */ + public synchronized void release() { + --mReferences; + if (mReferences <= 0 && !mBitmap.isRecycled()) { + mBitmap.recycle(); + if (mImage != null) { + mImage.recycle(); + } + } + } + + /** + * Add a callback for invalidating + */ + public void addCallback(@NonNull Callback callback) { + final Iterator> iterator = mCallbackSet.iterator(); + Callback c; + while (iterator.hasNext()) { + c = iterator.next().get(); + if (c == null) { + // Remove from the set if the reference has been cleared or + // it can't be used. + iterator.remove(); + } else if (c == callback) { + return; + } + } + + mCallbackSet.add(new WeakReference<>(callback)); + } + + /** + * Remove a callback + */ + public void removeCallback(@NonNull Callback callback) { + final Iterator> iterator = mCallbackSet.iterator(); + Callback c; + while (iterator.hasNext()) { + c = iterator.next().get(); + if (c == null) { + // Remove from the set if the reference has been cleared or + // it can't be used. + iterator.remove(); + } else if (c == callback) { + iterator.remove(); + return; + } + } + } + + /** + * Return the format of the image + */ + public int getFormat() { + return mFormat; + } + + /** + * Return image width + */ + public int getWidth() { + return mBitmap.getWidth(); + } + + /** + * Return image height + */ + public int getHeight() { + return mBitmap.getHeight(); + } + + /** + * Return image is opaque + */ + public boolean isOpaque() { + return mIsOpaque; + } + + /** + * Return byte count of image + */ + public int getByteCount() { + return mByteCount; + } + + /** + * Return image is animated + */ + public boolean isAnimated() { + return mImage != null; + } + + /** + * Return image frame count + */ + public int getFrameCount() { + return mFrameCount; + } + + /** + * Draw image to canvas + */ + public void draw(Canvas canvas, float left, float top, @Nullable Paint paint) { + if (!mBitmap.isRecycled()) { + canvas.drawBitmap(mBitmap, left, top, paint); + } + } + + /** + * Draw image to canvas + */ + public void draw(Canvas canvas, @Nullable Rect src, @NonNull Rect dst, @Nullable Paint paint) { + if (!mBitmap.isRecycled()) { + canvas.drawBitmap(mBitmap, src, dst, paint); + } + } + + /** + * Draw image to canvas + */ + public void draw(Canvas canvas, @Nullable Rect src, @NonNull RectF dst, @Nullable Paint paint) { + if (!mBitmap.isRecycled()) { + canvas.drawBitmap(mBitmap, src, dst, paint); + } + } + + /** + * {@code start()} and {@code stop()} is a pair + */ + @Override + public void start() { + mAnimationReferences++; + if (mBitmap.isRecycled() || mImage == null || mRunning) { + return; + } + mRunning = true; + HANDLER.postDelayed(this, Math.max(0, mImage.getDelay())); + } + + /** + * {@code start()} and {@code stop()} is a pair + */ + @Override + public void stop() { + mAnimationReferences--; + if (mAnimationReferences <= 0) { + mRunning = false; + HANDLER.removeCallbacks(this); + } + } + + @Override + public boolean isRunning() { + return mRunning; + } + + private boolean notifyUpdate() { + boolean hasCallback = false; + final Iterator> iterator = mCallbackSet.iterator(); + Callback callback; + while (iterator.hasNext()) { + callback = iterator.next().get(); + if (callback != null) { + // Render bitmap int the first time + if (!hasCallback) { + hasCallback = true; + mImage.render(0, 0, mBitmap, 0, 0, mImage.getWidth(), mImage.getHeight(), false, 0); + } + callback.invalidateImage(this); + } else { + // Remove from the set if the reference has been cleared or + // it can't be used. + iterator.remove(); + } + } + return hasCallback; + } + + @Override + public void run() { + // Check recycled + if (mBitmap.isRecycled() || mImage == null) { + mRunning = false; + return; + } + + mImage.advance(); + + if (notifyUpdate()) { + if (mRunning) { + HANDLER.postDelayed(this, Math.max(0, mImage.getDelay())); + } + } else { + mRunning = false; + } + } + + public interface Callback { + void invalidateImage(ImageBitmap who); + } +} diff --git a/native/src/main/java/com/hippo/image/ImageDrawable.java b/native/src/main/java/com/hippo/image/ImageDrawable.java new file mode 100644 index 000000000..1bd30493b --- /dev/null +++ b/native/src/main/java/com/hippo/image/ImageDrawable.java @@ -0,0 +1,100 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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.hippo.image; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; + +/** + * A drawable to draw {@link ImageBitmap} + */ +public class ImageDrawable extends Drawable implements Animatable, ImageBitmap.Callback { + + private final ImageBitmap mImageBitmap; + private final Paint mPaint; + + public ImageDrawable(ImageBitmap imageBitmap) throws RecycledException { + if (!imageBitmap.obtain()) { + throw new RecycledException(); + } + + mImageBitmap = imageBitmap; + mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); + + // Add callback + imageBitmap.addCallback(this); + } + + public void recycle() { + mImageBitmap.removeCallback(this); + mImageBitmap.release(); + } + + @Override + public void draw(Canvas canvas) { + mImageBitmap.draw(canvas, null, getBounds(), mPaint); + } + + @Override + public void setAlpha(int alpha) { + mPaint.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mPaint.setColorFilter(colorFilter); + } + + @Override + public int getOpacity() { + return mImageBitmap.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.UNKNOWN; + } + + @Override + public int getIntrinsicWidth() { + return mImageBitmap.getWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mImageBitmap.getHeight(); + } + + @Override + public void start() { + mImageBitmap.start(); + } + + @Override + public void stop() { + mImageBitmap.stop(); + } + + @Override + public boolean isRunning() { + return mImageBitmap.isRunning(); + } + + @Override + public void invalidateImage(ImageBitmap who) { + invalidateSelf(); + } +} diff --git a/native/src/main/java/com/hippo/image/RecycledException.java b/native/src/main/java/com/hippo/image/RecycledException.java new file mode 100644 index 000000000..012658abd --- /dev/null +++ b/native/src/main/java/com/hippo/image/RecycledException.java @@ -0,0 +1,23 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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.hippo.image; + +/** + * Image is recycled. + */ +public class RecycledException extends Exception { +} diff --git a/native/src/main/jni/CMakeLists.txt b/native/src/main/jni/CMakeLists.txt new file mode 100644 index 000000000..78b5cba57 --- /dev/null +++ b/native/src/main/jni/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.4.1) + +set(CMAKE_CXX_FLAGS "-std=c++14 -DANDROID -g") +set(CMAKE_C_FLAGS "-w -std=c11 -DANDROID -D_LARGEFILE_SOURCE=1 -g") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-rtti -fno-stack-protector") + +add_subdirectory(a7zip) +add_subdirectory(image) +add_subdirectory(integrity) + +set(NATIVE_LIB "ehviewer") + +add_library(${NATIVE_LIB} SHARED main.c) + +target_compile_options(${NATIVE_LIB} PUBLIC -fvisibility=hidden -fvisibility-inlines-hidden -ffast-math -Oz -funroll-loops -ffast-math -fno-strict-aliasing -fno-math-errno) + +target_include_directories(${NATIVE_LIB} PUBLIC + a7zip/a7zip + image/image + integrity) + +target_link_libraries(${NATIVE_LIB} -Wl,--no-whole-archive + a7zip + image + genuine) diff --git a/native/src/main/jni/a7zip/CMakeLists.txt b/native/src/main/jni/a7zip/CMakeLists.txt new file mode 100644 index 000000000..fab08a813 --- /dev/null +++ b/native/src/main/jni/a7zip/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.4.1) + +project(a7zip C CXX) + +add_subdirectory(p7zip) + +set(A_SEVEN_ZIP_SOURCES + a7zip/BlackHole.cpp + a7zip/InArchive.cpp + a7zip/JavaEnv.cpp + a7zip/JavaHelper.cpp + a7zip/JavaInArchive.cpp + a7zip/JavaInitA7Zip.cpp + a7zip/JavaInputStream.cpp + a7zip/JavaSeekableInputStream.cpp + a7zip/OpenVolumeCallback.cpp + a7zip/OutputStream.cpp + a7zip/SeekableInputStream.cpp + a7zip/SevenZip.cpp + ) + + +add_library(a7zip STATIC ${A_SEVEN_ZIP_SOURCES}) +target_link_libraries(a7zip PUBLIC p7zip log) +target_compile_options(a7zip PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden -fno-exceptions) diff --git a/native/src/main/jni/a7zip/a7zip/BlackHole.cpp b/native/src/main/jni/a7zip/a7zip/BlackHole.cpp new file mode 100644 index 000000000..eadde9670 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/BlackHole.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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. + */ + +#include "BlackHole.h" + +using namespace a7zip; + +BlackHole::BlackHole(): pos(0), size(0) { } + +HRESULT BlackHole::Write(const void*, UInt32 size, UInt32* processedSize) { + if (processedSize != nullptr) { + *processedSize = size; + } + + this->pos += size; + if (this->size < this->pos) { + this->size = this->pos; + } + + return S_OK; +} + +HRESULT BlackHole::Seek(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) { + Int64 newPos = -1; + + switch (seekOrigin) { + case STREAM_SEEK_SET: + newPos = offset; + break; + case STREAM_SEEK_CUR: + newPos = this->pos + offset; + break; + case STREAM_SEEK_END: + newPos = this->size + offset; + break; + default: + return E_INVALIDARG; + } + + if (newPos < 0) { + return STG_E_INVALIDFUNCTION; + } + + *newPosition = this->pos; + + return S_OK; +} + +HRESULT BlackHole::SetSize(UInt64 newSize) { + this->size = newSize; + if (this->pos > this->size) { + this->pos = this->size; + } + + return S_OK; +} diff --git a/native/src/main/jni/a7zip/a7zip/BlackHole.h b/native/src/main/jni/a7zip/a7zip/BlackHole.h new file mode 100644 index 000000000..125470b78 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/BlackHole.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_BLACK_HOLE_H__ +#define __A7ZIP_BLACK_HOLE_H__ + +#include +#include <7zip/IStream.h> + +#include "Log.h" + +namespace a7zip { + +class BlackHole : + public IOutStream, + public CMyUnknownImp +{ + public: + BlackHole(); + + public: + MY_UNKNOWN_IMP2(ISequentialOutStream, IOutStream) + STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); + STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); + STDMETHOD(SetSize)(UInt64 newSize); + + private: + UInt64 pos; + UInt64 size; +}; + +} + +#endif //__A7ZIP_BLACK_HOLE_H__ diff --git a/native/src/main/jni/a7zip/a7zip/InArchive.cpp b/native/src/main/jni/a7zip/a7zip/InArchive.cpp new file mode 100644 index 000000000..03d9b35d6 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/InArchive.cpp @@ -0,0 +1,360 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#include "InArchive.h" + +#include +#include <7zip/ICoder.h> +#include <7zip/IPassword.h> + +#include "BlackHole.h" +#include "Log.h" +#include "Utils.h" + +using namespace a7zip; + +class ArchiveExtractCallback : + public IArchiveExtractCallback, + public ICryptoGetTextPassword, + public CMyUnknownImp +{ + public: + ArchiveExtractCallback(UInt32 index, BSTR password, CMyComPtr& out_stream); + ~ArchiveExtractCallback(); + + public: + MY_UNKNOWN_IMP2(IArchiveExtractCallback, ICryptoGetTextPassword) + + STDMETHOD(SetTotal)(UInt64 total); + STDMETHOD(SetCompleted)(const UInt64 *completeValue); + + STDMETHOD(GetStream)(UInt32 index, ISequentialOutStream** outStream, Int32 askExtractMode); + STDMETHOD(PrepareOperation)(Int32 askExtractMode); + STDMETHOD(SetOperationResult)(Int32 opRes); + + STDMETHOD(CryptoGetTextPassword)(BSTR *password); + + HRESULT GetBetterResult(HRESULT result); + + private: + UInt32 index; + BSTR password; + CMyComPtr out_stream; + bool has_asked_password; +}; + +ArchiveExtractCallback::ArchiveExtractCallback( + UInt32 index, + BSTR password, + CMyComPtr& out_stream +) : + index(index), + password(::SysAllocString(password)), + out_stream(out_stream), + has_asked_password(false) {} + +ArchiveExtractCallback::~ArchiveExtractCallback() { + ::SysFreeString(password); +} + +HRESULT ArchiveExtractCallback::SetTotal(UInt64 total) { + // Ignored + return S_OK; +} + +HRESULT ArchiveExtractCallback::SetCompleted(const UInt64 *completeValue) { + // Ignored + return S_OK; +} + +HRESULT ArchiveExtractCallback::GetStream( + UInt32 index, + ISequentialOutStream** outStream, + Int32 askExtractMode +) { + // If it's not extract mode or the index is different, return a black hole to skip data + if (askExtractMode != NArchive::NExtract::NAskMode::kExtract || this->index != index) { + CMyComPtr black_hole(new BlackHole()); + *outStream = black_hole.Detach(); + return S_OK; + } + + if (out_stream == nullptr) { + return E_NO_OUT_STREAM; + } + + CMyComPtr steam_copy(out_stream); + *outStream = steam_copy.Detach(); + + return S_OK; +} + +HRESULT ArchiveExtractCallback::PrepareOperation(Int32 askExtractMode) { + // Always return S_OK + return S_OK; +} + +HRESULT ArchiveExtractCallback::SetOperationResult(Int32 opRes) { + // Stop extracting action if operation result is not OK + switch (opRes) { + case NArchive::NExtract::NOperationResult::kOK: + return S_OK; + case NArchive::NExtract::NOperationResult::kUnsupportedMethod: + return E_UNSUPPORTED_METHOD; + case NArchive::NExtract::NOperationResult::kDataError: + return E_DATA_ERROR; + case NArchive::NExtract::NOperationResult::kCRCError: + return E_CRC_ERROR; + case NArchive::NExtract::NOperationResult::kUnavailable: + return E_UNAVAILABLE; + case NArchive::NExtract::NOperationResult::kUnexpectedEnd: + return E_UNEXPECTED_END; + case NArchive::NExtract::NOperationResult::kDataAfterEnd: + return E_DATA_AFTER_END; + case NArchive::NExtract::NOperationResult::kIsNotArc: + return E_IS_NOT_ARC; + case NArchive::NExtract::NOperationResult::kHeadersError: + return E_HEADERS_ERROR; + case NArchive::NExtract::NOperationResult::kWrongPassword: + return E_WRONG_PASSWORD; + default: + return E_UNKNOWN_ERROR; + } +} + +HRESULT ArchiveExtractCallback::CryptoGetTextPassword(BSTR* password) { + has_asked_password = true; + *password = ::SysAllocString(this->password); + return this->password != nullptr ? S_OK : E_NO_PASSWORD; +} + +HRESULT ArchiveExtractCallback::GetBetterResult(HRESULT result) { + if (result == S_OK) { + return S_OK; + } else if (has_asked_password) { + if (password != nullptr) { + return E_WRONG_PASSWORD; + } else { + return E_NO_PASSWORD; + } + } else { + return result; + } +} + +InArchive::InArchive( + InArchive* parent, + CMyComPtr& in_archive, + AString& format_name +) : + parent(parent), + in_archive(in_archive), + format_name(format_name) { } + +InArchive::~InArchive() { + this->in_archive->Close(); + if (parent != nullptr) { + delete parent; + parent = nullptr; + } +} + +const AString& InArchive::GetFormatName() { + return this->format_name; +} + +HRESULT InArchive::GetNumberOfEntries(UInt32& number) { + return this->in_archive->GetNumberOfItems(&number); +} + +static PropType VarTypeToPropType(VARTYPE var_enum) { + // TODO VT_ERROR + switch (var_enum) { + case VT_EMPTY: + return PT_EMPTY; + case VT_BOOL: + return PT_BOOL; + case VT_I1: + case VT_I2: + case VT_I4: + case VT_INT: + case VT_UI1: + case VT_UI2: + case VT_UI4: + case VT_UINT: + return PT_INT; + case VT_I8: + case VT_UI8: + case VT_FILETIME: + return PT_LONG; + case VT_BSTR: + return PT_STRING; + default: + return PT_UNKNOWN; + } +} + +#define GET_ARCHIVE_PROPERTY_START(METHOD_NAME, VALUE_TYPE) \ +HRESULT InArchive::METHOD_NAME(PROPID prop_id, VALUE_TYPE value) { \ + NWindows::NCOM::CPropVariant prop; \ + RETURN_SAME_IF_NOT_ZERO(this->in_archive->GetArchiveProperty(prop_id, &prop)); + +#define GET_ARCHIVE_PROPERTY_END \ +} + +#define GET_ENTRY_PROPERTY_START(METHOD_NAME, VALUE_TYPE) \ +HRESULT InArchive::METHOD_NAME(UInt32 index, PROPID prop_id, VALUE_TYPE value) { \ + NWindows::NCOM::CPropVariant prop; \ + RETURN_SAME_IF_NOT_ZERO(this->in_archive->GetProperty(index, prop_id, &prop)); + +#define GET_ENTRY_PROPERTY_END \ +} + +#define GET_PROPERTY_TYPE \ + *value = VarTypeToPropType(prop.vt); \ + return S_OK; + +GET_ARCHIVE_PROPERTY_START(GetArchivePropertyType, PropType*) + GET_PROPERTY_TYPE +GET_ARCHIVE_PROPERTY_END + +GET_ENTRY_PROPERTY_START(GetEntryPropertyType, PropType*) + GET_PROPERTY_TYPE +GET_ENTRY_PROPERTY_END + +#define GET_BOOLEAN_PROPERTY \ + switch (prop.vt) { \ + case VT_BOOL: \ + *value = prop.boolVal != 0; \ + return S_OK; \ + case VT_EMPTY: \ + return E_EMPTY_PROP; \ + default: \ + return E_INCONSISTENT_PROP_TYPE; \ + } + +GET_ARCHIVE_PROPERTY_START(GetArchiveBooleanProperty, bool*) + GET_BOOLEAN_PROPERTY +GET_ARCHIVE_PROPERTY_END + +GET_ENTRY_PROPERTY_START(GetEntryBooleanProperty, bool*) + GET_BOOLEAN_PROPERTY +GET_ENTRY_PROPERTY_END + +#define GET_INT_PROPERTY \ + switch (prop.vt) { \ + case VT_I1: \ + *value = prop.cVal; \ + return S_OK; \ + case VT_I2: \ + *value = prop.iVal; \ + return S_OK; \ + case VT_I4: \ + *value = prop.lVal; \ + return S_OK; \ + case VT_INT: \ + *value = prop.intVal; \ + return S_OK; \ + case VT_UI1: \ + *value = prop.bVal; \ + return S_OK; \ + case VT_UI2: \ + *value = prop.uiVal; \ + return S_OK; \ + case VT_UI4: \ + *value = prop.ulVal; \ + return S_OK; \ + case VT_UINT: \ + *value = prop.uintVal; \ + return S_OK; \ + case VT_EMPTY: \ + return E_EMPTY_PROP; \ + default: \ + return E_INCONSISTENT_PROP_TYPE; \ + } + +GET_ARCHIVE_PROPERTY_START(GetArchiveIntProperty, Int32*) + GET_INT_PROPERTY +GET_ARCHIVE_PROPERTY_END + +GET_ENTRY_PROPERTY_START(GetEntryIntProperty, Int32*) + GET_INT_PROPERTY +GET_ENTRY_PROPERTY_END + +static const UInt64 FILE_TIME_OFFSET = (369 * 365 + 89) * 86400ULL * 10000000ULL; +static const UInt64 FILE_TIME_MULTIPLE = 10000ULL; + +#define GET_LONG_PROPERTY \ + switch (prop.vt) { \ + case VT_I8: \ + *value = prop.hVal.QuadPart; \ + return S_OK; \ + case VT_UI8: \ + *value = prop.uhVal.QuadPart; \ + return S_OK; \ + case VT_FILETIME: \ + *value = (*reinterpret_cast(&prop.filetime) - FILE_TIME_OFFSET) / \ + FILE_TIME_MULTIPLE; \ + return S_OK; \ + case VT_EMPTY: \ + return E_EMPTY_PROP; \ + default: \ + return E_INCONSISTENT_PROP_TYPE; \ + } + +GET_ARCHIVE_PROPERTY_START(GetArchiveLongProperty, Int64*) + GET_LONG_PROPERTY +GET_ARCHIVE_PROPERTY_END + +GET_ENTRY_PROPERTY_START(GetEntryLongProperty, Int64*) + GET_LONG_PROPERTY +GET_ENTRY_PROPERTY_END + +#define GET_STRING_PROPERTY \ + switch (prop.vt) { \ + case VT_BSTR: \ + *value = ::SysAllocString(prop.bstrVal); \ + return S_OK; \ + case VT_EMPTY: \ + return E_EMPTY_PROP; \ + default: \ + return E_INCONSISTENT_PROP_TYPE; \ + } + +GET_ARCHIVE_PROPERTY_START(GetArchiveStringProperty, BSTR*) + GET_STRING_PROPERTY +GET_ARCHIVE_PROPERTY_END + +GET_ENTRY_PROPERTY_START(GetEntryStringProperty, BSTR*) + GET_STRING_PROPERTY +GET_ENTRY_PROPERTY_END + +HRESULT InArchive::GetEntryStream(UInt32 index, ISequentialInStream** stream) { + *stream = nullptr; + CMyComPtr in_archive_get_stream; + in_archive->QueryInterface(IID_IInArchiveGetStream, reinterpret_cast(&in_archive_get_stream)); + if (in_archive_get_stream != nullptr) { + return in_archive_get_stream->GetStream(index, stream); + } else { + return E_NOTIMPL; + } +} + +HRESULT InArchive::ExtractEntry(UInt32 index, BSTR password, CMyComPtr& out_stream) { + CMyComPtr callback(new ArchiveExtractCallback(index, password, out_stream)); + HRESULT result = this->in_archive->Extract(&index, 1, false, callback); + return callback->GetBetterResult(result); +} diff --git a/native/src/main/jni/a7zip/a7zip/InArchive.h b/native/src/main/jni/a7zip/a7zip/InArchive.h new file mode 100644 index 000000000..d157d808b --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/InArchive.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_IN_ARCHIVE_H__ +#define __A7ZIP_IN_ARCHIVE_H__ + +#include +#include +#include +#include <7zip/Archive/IArchive.h> + +#include "PropType.h" + +namespace a7zip { + +class InArchive { + public: + InArchive(InArchive* parent, CMyComPtr& in_archive, AString& format_name); + ~InArchive(); + + public: + const AString& GetFormatName(); + HRESULT GetNumberOfEntries(UInt32& number); + + HRESULT GetArchivePropertyType(PROPID prop_id, PropType* prop_type); + HRESULT GetArchiveBooleanProperty(PROPID prop_id, bool *bool_prop); + HRESULT GetArchiveIntProperty(PROPID prop_id, Int32* int_prop); + HRESULT GetArchiveLongProperty(PROPID prop_id, Int64* long_prop); + HRESULT GetArchiveStringProperty(PROPID prop_id, BSTR* str_prop); + + HRESULT GetEntryPropertyType(UInt32 index, PROPID prop_id, PropType* prop_type); + HRESULT GetEntryBooleanProperty(UInt32 index, PROPID prop_id, bool *bool_prop); + HRESULT GetEntryIntProperty(UInt32 index, PROPID prop_id, Int32* int_prop); + HRESULT GetEntryLongProperty(UInt32 index, PROPID prop_id, Int64* long_prop); + HRESULT GetEntryStringProperty(UInt32 index, PROPID prop_id, BSTR* str_prop); + + HRESULT GetEntryStream(UInt32 index, ISequentialInStream** stream); + + HRESULT ExtractEntry(UInt32 index, BSTR password, CMyComPtr& out_stream); + + private: + InArchive* parent; + CMyComPtr in_archive; + AString format_name; +}; + +} + +#endif //__A7ZIP_IN_ARCHIVE_H__ diff --git a/native/src/main/jni/a7zip/a7zip/JavaEnv.cpp b/native/src/main/jni/a7zip/a7zip/JavaEnv.cpp new file mode 100644 index 000000000..f8feecebf --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaEnv.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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. + */ + +#include "JavaEnv.h" + +using namespace a7zip; + +JavaVM* JavaEnv::jvm = nullptr; + +void JavaEnv::Initialize(JavaVM* jvm) { + JavaEnv::jvm = jvm; +} + +JavaEnv::JavaEnv() { + env = nullptr; + if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) == JNI_EDETACHED) { + should_detach = jvm->AttachCurrentThread(&env, nullptr) == JNI_OK; + } else { + should_detach = false; + } +} + +JavaEnv::~JavaEnv() { + if (jvm != nullptr && should_detach) { + jvm->DetachCurrentThread(); + env = nullptr; + should_detach = false; + } +} + +bool JavaEnv::IsValid() { + return env != nullptr; +} diff --git a/native/src/main/jni/a7zip/a7zip/JavaEnv.h b/native/src/main/jni/a7zip/a7zip/JavaEnv.h new file mode 100644 index 000000000..7553ce333 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaEnv.h @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_JAVA_ENV_H__ +#define __A7ZIP_JAVA_ENV_H__ + +#include + +namespace a7zip { + +class JavaEnv { + + public: + JavaEnv(); + ~JavaEnv(); + bool IsValid(); + JNIEnv* operator->() const { return env; } + explicit operator JNIEnv*() const { return env; } + + private: + JNIEnv* env; + bool should_detach; + + public: + static void Initialize(JavaVM* jvm); + + private: + static JavaVM* jvm; +}; + +} + +#endif //__A7ZIP_JAVA_ENV_H__ diff --git a/native/src/main/jni/a7zip/a7zip/JavaHelper.cpp b/native/src/main/jni/a7zip/a7zip/JavaHelper.cpp new file mode 100644 index 000000000..9b4edc741 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaHelper.cpp @@ -0,0 +1,121 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#include "JavaHelper.h" + +#include + +#include + +#include "Utils.h" + +#define MAX_MSG_SIZE 1024 + +using namespace a7zip; + +static jint ThrowExceptionInternal( + JNIEnv* env, + const char* exception_name, + const char* message, + va_list va_args +) { + char formatted_message[MAX_MSG_SIZE]; + vsnprintf(formatted_message, MAX_MSG_SIZE, message, va_args); + jclass exception_class = env->FindClass(exception_name); + return env->ThrowNew(exception_class, formatted_message); +} + +jint JavaHelper::ThrowException( + JNIEnv* env, + const char* exception_name, + const char* message, + ... +) { + va_list va_args; + va_start(va_args, message); + jint result = ThrowExceptionInternal(env, exception_name, message, va_args); + va_end(va_args); + return result; +} + +static const char* GetMessageForCode(HRESULT code) { + switch (code) { + case E_NOT_INITIALIZED: + return "The module is not initialized"; + case S_OK: + case E_INTERNAL: + return "a7zip is buggy"; + case E_CLASS_NOT_FOUND: + return "Can't find the class"; + case E_METHOD_NOT_FOUND: + return "Can't find the method"; + case E_JAVA_EXCEPTION: + return "Catch a java exception"; + case E_FAILED_CONSTRUCT: + return "Failed to create new class"; + case E_FAILED_REGISTER: + return "Failed to register methods"; + case E_INCONSISTENT_PROP_TYPE: + return "Inconsistent property type"; + case E_EMPTY_PROP: + return "Empty property"; + case E_UNKNOWN_FORMAT: + return "Unknown archive format"; + case E_UNSUPPORTED_EXTRACT_MODE: + return "Unsupported extract mode"; + case E_NO_OUT_STREAM: + return "No out stream"; + case E_UNSUPPORTED_METHOD: + return "Unsupported method"; + case E_DATA_ERROR: + return "Data error"; + case E_DATA_ERROR_ENCRYPTED: + return "Data Error in encrypted file. Wrong password?"; + case E_CRC_ERROR: + return "CRC failed"; + case E_CRC_ERROR_ENCRYPTED: + return "CRC Failed in encrypted file. Wrong password?"; + case E_UNAVAILABLE: + return "Unavailable data"; + case E_UNEXPECTED_END: + return "Unexpected end of data"; + case E_DATA_AFTER_END: + return "There are some data after the end of the payload data"; + case E_IS_NOT_ARC: + return "Is not archive"; + case E_HEADERS_ERROR: + return "Headers Error"; + case E_WRONG_PASSWORD: + return "Wrong password"; + case E_NO_PASSWORD: + return "No password"; + case E_NOTIMPL: + return "Not implemented"; + case E_OUTOFMEMORY: + return "Out of memory"; + case E_UNKNOWN_ERROR: + default: + return "Unknown error."; + } +} + +jint JavaHelper::ThrowException( + JNIEnv* env, + const char* exception_name, + HRESULT code +) { + return ThrowException(env, exception_name, GetMessageForCode(code)); +} diff --git a/native/src/main/jni/a7zip/a7zip/JavaHelper.h b/native/src/main/jni/a7zip/a7zip/JavaHelper.h new file mode 100644 index 000000000..376b77db2 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaHelper.h @@ -0,0 +1,89 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_JAVA_HELPER_H__ +#define __A7ZIP_JAVA_HELPER_H__ + +#include + +#include + +#define CLASS_NAME_ILLEGAL_STATE_EXCEPTION "java/lang/IllegalStateException" +#define CLASS_NAME_IO_EXCEPTION "java/io/IOException" +#define CLASS_NAME_ARCHIVE_EXCEPTION "com/hippo/a7zip/ArchiveException" +#define CLASS_NAME_PASSWORD_EXCEPTION "com/hippo/a7zip/PasswordException" + +#define THROW_EXCEPTION(ENV, EXCEPTION_NAME, ...) \ + do { \ + a7zip::JavaHelper::ThrowException((ENV), (EXCEPTION_NAME), __VA_ARGS__); \ + return; \ + } while (0) + +#define THROW_EXCEPTION_RET(ENV, RET, EXCEPTION_NAME, ...) \ + do { \ + a7zip::JavaHelper::ThrowException((ENV), (EXCEPTION_NAME), __VA_ARGS__); \ + return (RET); \ + } while (0) + +#define THROW_IO_EXCEPTION(ENV, CODE) \ + THROW_EXCEPTION(ENV, CLASS_NAME_IO_EXCEPTION, CODE) + +#define THROW_IO_EXCEPTION_RET(ENV, RET, CODE) \ + THROW_EXCEPTION_RET(ENV, RET, CLASS_NAME_IO_EXCEPTION, CODE) + +#define THROW_ARCHIVE_EXCEPTION(ENV, CODE) \ + do { \ + if ((CODE) == E_NO_PASSWORD || (CODE) == E_WRONG_PASSWORD) { \ + THROW_EXCEPTION(ENV, CLASS_NAME_PASSWORD_EXCEPTION, CODE); \ + } else { \ + THROW_EXCEPTION(ENV, CLASS_NAME_ARCHIVE_EXCEPTION, CODE); \ + } \ + } while (0) + +#define THROW_ARCHIVE_EXCEPTION_RET(ENV, RET, CODE) \ + do { \ + if ((CODE) == E_NO_PASSWORD || (CODE) == E_WRONG_PASSWORD) { \ + THROW_EXCEPTION_RET(ENV, RET, CLASS_NAME_PASSWORD_EXCEPTION, CODE); \ + } else { \ + THROW_EXCEPTION_RET(ENV, RET, CLASS_NAME_ARCHIVE_EXCEPTION, CODE); \ + } \ + } while (0) + +#define CHECK_CLOSED(ENV, NATIVE_PTR) \ + do { \ + if ((NATIVE_PTR) == 0) { \ + THROW_EXCEPTION(ENV, CLASS_NAME_ILLEGAL_STATE_EXCEPTION, "It's closed"); \ + } \ + } while (0) + +#define CHECK_CLOSED_RET(ENV, RET, NATIVE_PTR) \ + do { \ + if ((NATIVE_PTR) == 0) { \ + THROW_EXCEPTION_RET( \ + ENV, RET, CLASS_NAME_ILLEGAL_STATE_EXCEPTION, "It's closed"); \ + } \ + } while (0) + +namespace a7zip { +namespace JavaHelper { + +jint ThrowException(JNIEnv* env, const char* exception_name, const char* message, ...); +jint ThrowException(JNIEnv* env, const char* exception_name, HRESULT code); + +} +} + +#endif //__A7ZIP_JAVA_HELPER_H__ diff --git a/native/src/main/jni/a7zip/a7zip/JavaInArchive.cpp b/native/src/main/jni/a7zip/a7zip/JavaInArchive.cpp new file mode 100644 index 000000000..b93a70270 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaInArchive.cpp @@ -0,0 +1,397 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#include "JavaInArchive.h" + +#include +#include + +#include +#include <7zip/Archive/IArchive.h> + +#include "OpenVolumeCallback.h" +#include "SeekableInputStream.h" +#include "JavaHelper.h" +#include "JavaSeekableInputStream.h" +#include "JavaInputStream.h" +#include "Log.h" +#include "OutputStream.h" +#include "SevenZip.h" +#include "Utils.h" + +#ifdef LOG_TAG +# undef LOG_TAG +#endif //LOG_TAG +#define LOG_TAG "JavaArchive" + +using namespace a7zip; + +static void CopyJStringToBSTR(BSTR bstr, const jchar* jstr, int length) { + for (int i = 0; i < length; i++) { + *bstr++ = *jstr++; + } + *bstr = 0; +} + +static BSTR JStringToBSTR(JNIEnv* env, jstring jstr) { + BSTR bstr = nullptr; + if (jstr != nullptr) { + jsize length = env->GetStringLength(jstr); + const jchar* jchars = env->GetStringChars(jstr, nullptr); + bstr = ::SysAllocStringLen(nullptr, static_cast(length)); + CopyJStringToBSTR(bstr, jchars, length); + env->ReleaseStringChars(jstr, jchars); + } + return bstr; +} + +static jlong NativeOpen( + JNIEnv* env, + jclass, + jobject stream, + jstring password, + jstring filename, + jobject open_volume_callback +) { + CMyComPtr in_stream = nullptr; + HRESULT result = SeekableInputStream::Create(env, stream, in_stream); + if (result != S_OK || in_stream == nullptr) { + // Call java methods before throw exception + if (in_stream != nullptr) { + in_stream.Release(); + } + THROW_ARCHIVE_EXCEPTION_RET(env, 0, result); + } + + BSTR bstr_password = nullptr; + BSTR bstr_filename = nullptr; + CMyComPtr open_volume_callback_wrapper = nullptr; + + if (filename != nullptr && open_volume_callback != nullptr) { + result = OpenVolumeCallback::Create(env, open_volume_callback, open_volume_callback_wrapper); + if (result != S_OK) { + // Call java methods before throw exception + if (in_stream != nullptr) { + in_stream.Release(); + } + THROW_ARCHIVE_EXCEPTION_RET(env, 0, result); + } + bstr_filename = JStringToBSTR(env, filename); + } + bstr_password = JStringToBSTR(env, password); + + InArchive* archive = nullptr; + result = SevenZip::OpenArchive(in_stream, bstr_password, bstr_filename, open_volume_callback_wrapper, &archive); + + ::SysFreeString(bstr_password); + ::SysFreeString(bstr_filename); + + if (result != S_OK || archive == nullptr) { + // Call java methods before throw exception + in_stream.Release(); + delete archive; + THROW_ARCHIVE_EXCEPTION_RET(env, 0, result); + } + + return reinterpret_cast(archive); +} + +static jstring NativeGetFormatName( + JNIEnv* env, + jclass, + jlong native_ptr +) { + CHECK_CLOSED_RET(env, nullptr, native_ptr); + InArchive* archive = reinterpret_cast(native_ptr); + return env->NewStringUTF(archive->GetFormatName()); +} + +static jint NativeGetNumberOfEntries( + JNIEnv* env, + jclass, + jlong native_ptr +) { + CHECK_CLOSED_RET(env, 0, native_ptr); + InArchive* archive = reinterpret_cast(native_ptr); + + UInt32 number = 0; + HRESULT result = archive->GetNumberOfEntries(number); + return result == S_OK ? number : -1; +} + +#define GET_ARCHIVE_PROPERTY_START(METHOD_NAME, RETURN_TYPE) \ +static RETURN_TYPE METHOD_NAME(JNIEnv* env, jclass, jlong native_ptr, jint prop_id) { \ + CHECK_CLOSED_RET(env, 0, native_ptr); \ + InArchive* archive = reinterpret_cast(native_ptr); + +#define GET_ARCHIVE_PROPERTY_END \ +} + +#define GET_ENTRY_PROPERTY_START(METHOD_NAME, RETURN_TYPE) \ +static RETURN_TYPE METHOD_NAME(JNIEnv* env, jclass, jlong native_ptr, jint index, jint prop_id) {\ + CHECK_CLOSED_RET(env, 0, native_ptr); \ + InArchive* archive = reinterpret_cast(native_ptr); + +#define GET_ENTRY_PROPERTY_END \ +} + +#define GET_PROPERTY_TYPE(GETTER) \ + PropType prop_type; \ + HRESULT result = (GETTER); \ + return result == S_OK ? prop_type : PT_UNKNOWN; + +GET_ARCHIVE_PROPERTY_START(NativeGetArchivePropertyType, jint) + GET_PROPERTY_TYPE(archive->GetArchivePropertyType(static_cast(prop_id), &prop_type)) +GET_ARCHIVE_PROPERTY_END + +GET_ENTRY_PROPERTY_START(NativeGetEntryPropertyType, jint) + GET_PROPERTY_TYPE(archive->GetEntryPropertyType(static_cast(index), static_cast(prop_id), &prop_type)) +GET_ENTRY_PROPERTY_END + +#define GET_BOOL_TYPE(GETTER) \ + bool bool_prop; \ + HRESULT result = (GETTER); \ + return result == S_OK ? bool_prop : false; + +GET_ARCHIVE_PROPERTY_START(NativeGetArchiveBooleanProperty, jboolean) + GET_BOOL_TYPE(archive->GetArchiveBooleanProperty(static_cast(prop_id), &bool_prop)) +GET_ARCHIVE_PROPERTY_END + +GET_ENTRY_PROPERTY_START(NativeGetEntryBooleanProperty, jboolean) + GET_BOOL_TYPE(archive->GetEntryBooleanProperty(static_cast(index), static_cast(prop_id), &bool_prop)) +GET_ENTRY_PROPERTY_END + +#define GET_INT_TYPE(GETTER) \ + Int32 int_prop; \ + HRESULT result = (GETTER); \ + return result == S_OK ? int_prop : 0; + +GET_ARCHIVE_PROPERTY_START(NativeGetArchiveIntProperty, jint) + GET_INT_TYPE(archive->GetArchiveIntProperty(static_cast(prop_id), &int_prop)) +GET_ARCHIVE_PROPERTY_END + +GET_ENTRY_PROPERTY_START(NativeGetEntryIntProperty, jint) + GET_INT_TYPE(archive->GetEntryIntProperty(static_cast(index), static_cast(prop_id), &int_prop)) +GET_ENTRY_PROPERTY_END + +#define GET_LONG_TYPE(GETTER) \ + Int64 long_prop; \ + HRESULT result = (GETTER); \ + return result == S_OK ? long_prop : 0; + +GET_ARCHIVE_PROPERTY_START(NativeGetArchiveLongProperty, jlong) + GET_LONG_TYPE(archive->GetArchiveLongProperty(static_cast(prop_id), &long_prop)) +GET_ARCHIVE_PROPERTY_END + +GET_ENTRY_PROPERTY_START(NativeGetEntryLongProperty, jlong) + GET_LONG_TYPE(archive->GetEntryLongProperty(static_cast(index), static_cast(prop_id), &long_prop)) +GET_ENTRY_PROPERTY_END + +static void shrink(BSTR bstr) { + jchar* jstr = reinterpret_cast(bstr); + UINT n = ::SysStringLen(bstr); + for (UINT i = 0; i < n; i++) { + jstr[i] = static_cast(bstr[i]); + } + jstr[n] = 0; +} + +#define GET_STRING_PROPERTY(GETTER) \ + BSTR str_prop = nullptr; \ + HRESULT result = (GETTER); \ + if (result != S_OK || str_prop == nullptr) { \ + if (str_prop != nullptr) ::SysFreeString(str_prop); \ + return nullptr; \ + } \ + shrink(str_prop); \ + jstring jstr = \ + env->NewString(reinterpret_cast(str_prop), ::SysStringLen(str_prop)); \ + ::SysFreeString(str_prop); \ + return jstr; + +GET_ARCHIVE_PROPERTY_START(NativeGetArchiveStringProperty, jstring) + GET_STRING_PROPERTY(archive->GetArchiveStringProperty(static_cast(prop_id), &str_prop)) +GET_ARCHIVE_PROPERTY_END + +GET_ENTRY_PROPERTY_START(NativeGetEntryStringProperty, jstring) + GET_STRING_PROPERTY(archive->GetEntryStringProperty(static_cast(index), static_cast(prop_id), &str_prop)) +GET_ENTRY_PROPERTY_END + +static jobject NativeGetEntryStream( + JNIEnv* env, + jclass, + jlong native_ptr, + jint index +) { + CHECK_CLOSED_RET(env, nullptr, native_ptr); + InArchive* archive = reinterpret_cast(native_ptr); + + CMyComPtr sequential_in_stream = nullptr; + HRESULT result = archive->GetEntryStream(static_cast(index), &sequential_in_stream); + if (result != S_OK || sequential_in_stream == nullptr) { + if (sequential_in_stream != nullptr) { + // Release the stream manually before throw java exception + sequential_in_stream.Release(); + } + THROW_ARCHIVE_EXCEPTION_RET(env, nullptr, result); + } + + // Try to get IInStream + CMyComPtr in_stream = nullptr; + sequential_in_stream.QueryInterface(IID_IInStream, &in_stream); + + if (in_stream != nullptr) { + // It's an IInStream + jobject object = nullptr; + result = JavaSeekableInputStream::NewInstance(env, in_stream, &object); + if (object == nullptr) { + // Release the stream manually before throw java exception + in_stream->Release(); + sequential_in_stream->Release(); + THROW_ARCHIVE_EXCEPTION_RET(env, nullptr, result); + } + return object; + } else { + // It's just an ISequentialInStream + jobject object = nullptr; + result = JavaInputStream::NewInstance(env, sequential_in_stream, &object); + if (object == nullptr) { + // Release the stream manually before throw java exception + sequential_in_stream->Release(); + THROW_ARCHIVE_EXCEPTION_RET(env, nullptr, result); + } + return object; + } +} + +static void NativeExtractEntry( + JNIEnv* env, + jclass, + jlong native_ptr, + jint index, + jstring password, + jobject stream +) { + CHECK_CLOSED(env, native_ptr); + InArchive* archive = reinterpret_cast(native_ptr); + + CMyComPtr out_stream = nullptr; + HRESULT result = OutputStream::Create(env, stream, out_stream); + if (result != S_OK || out_stream == nullptr) { + if (out_stream != nullptr) { + // Call java methods before throw exception + out_stream.Release(); + } else { + // Let java code closes the OutStream + } + THROW_ARCHIVE_EXCEPTION(env, result); + } + + const jchar* j_password = nullptr; + BSTR bstr_password = nullptr; + if (password != nullptr) { + jsize length = env->GetStringLength(password); + j_password = env->GetStringChars(password, nullptr); + bstr_password = ::SysAllocStringLen(nullptr, static_cast(length)); + CopyJStringToBSTR(bstr_password, j_password, length); + } + + result = archive->ExtractEntry(static_cast(index), bstr_password, out_stream); + + if (password != nullptr) { + ::SysFreeString(bstr_password); + env->ReleaseStringChars(password, j_password); + } + + if (result != S_OK) { + // Call java methods before throw exception + out_stream.Release(); + THROW_ARCHIVE_EXCEPTION(env, result); + } +} + +static void NativeClose( + JNIEnv* env, + jclass, + jlong native_ptr +) { + CHECK_CLOSED(env, native_ptr); + InArchive* archive = reinterpret_cast(native_ptr); + delete archive; +} + +static JNINativeMethod archive_methods[] = { + { "nativeOpen", + "(Lcom/hippo/a7zip/SeekableInputStream;Ljava/lang/String;Ljava/lang/String;Lcom/hippo/a7zip/InArchive$OpenVolumeCallback;)J", + reinterpret_cast(NativeOpen) }, + { "nativeGetFormatName", + "(J)Ljava/lang/String;", + reinterpret_cast(NativeGetFormatName) }, + { "nativeGetNumberOfEntries", + "(J)I", + reinterpret_cast(NativeGetNumberOfEntries) }, + { "nativeGetArchivePropertyType", + "(JI)I", + reinterpret_cast(NativeGetArchivePropertyType) }, + { "nativeGetArchiveBooleanProperty", + "(JI)Z", + reinterpret_cast(NativeGetArchiveBooleanProperty) }, + { "nativeGetArchiveIntProperty", + "(JI)I", + reinterpret_cast(NativeGetArchiveIntProperty) }, + { "nativeGetArchiveLongProperty", + "(JI)J", + reinterpret_cast(NativeGetArchiveLongProperty) }, + { "nativeGetArchiveStringProperty", + "(JI)Ljava/lang/String;", + reinterpret_cast(NativeGetArchiveStringProperty) }, + { "nativeGetEntryPropertyType", + "(JII)I", + reinterpret_cast(NativeGetEntryPropertyType) }, + { "nativeGetEntryBooleanProperty", + "(JII)Z", + reinterpret_cast(NativeGetEntryBooleanProperty) }, + { "nativeGetEntryIntProperty", + "(JII)I", + reinterpret_cast(NativeGetEntryIntProperty) }, + { "nativeGetEntryLongProperty", + "(JII)J", + reinterpret_cast(NativeGetEntryLongProperty) }, + { "nativeGetEntryStringProperty", + "(JII)Ljava/lang/String;", + reinterpret_cast(NativeGetEntryStringProperty) }, + { "nativeGetEntryStream", + "(JI)Ljava/io/InputStream;", + reinterpret_cast(NativeGetEntryStream) }, + { "nativeExtractEntry", + "(JILjava/lang/String;Ljava/io/OutputStream;)V", + reinterpret_cast(NativeExtractEntry) }, + { "nativeClose", + "(J)V", + reinterpret_cast(NativeClose) } +}; + +HRESULT JavaInArchive::RegisterMethods(JNIEnv* env) { + jclass clazz = env->FindClass("com/hippo/a7zip/InArchive"); + if (clazz == nullptr) return E_CLASS_NOT_FOUND; + + jint result = env->RegisterNatives(clazz, archive_methods, std::extent::value); + if (result < 0) { + return E_FAILED_REGISTER; + } + + return S_OK; +} diff --git a/native/src/main/jni/a7zip/a7zip/JavaInArchive.h b/native/src/main/jni/a7zip/a7zip/JavaInArchive.h new file mode 100644 index 000000000..9ec81e664 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaInArchive.h @@ -0,0 +1,32 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_JAVA_IN_ARCHIVE_H__ +#define __A7ZIP_JAVA_IN_ARCHIVE_H__ + +#include + +#include + +namespace a7zip { +namespace JavaInArchive { + +HRESULT RegisterMethods(JNIEnv* env); + +} +} + +#endif //__A7ZIP_JAVA_IN_ARCHIVE_H__ diff --git a/native/src/main/jni/a7zip/a7zip/JavaInitA7Zip.cpp b/native/src/main/jni/a7zip/a7zip/JavaInitA7Zip.cpp new file mode 100644 index 000000000..1b56b9137 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaInitA7Zip.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#include + +#include "SeekableInputStream.h" +#include "JavaEnv.h" +#include "JavaInArchive.h" +#include "JavaInitA7Zip.h" +#include "JavaSeekableInputStream.h" +#include "JavaInputStream.h" +#include "OpenVolumeCallback.h" +#include "OutputStream.h" +#include "SevenZip.h" +#include "Utils.h" + +using namespace a7zip; + +#ifdef __cplusplus +extern "C" +{ +#endif +bool a7zip_onLoad(JavaVM *vm) { + JNIEnv *env; + + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + return false; + } + + JavaEnv::Initialize(vm); + RETURN_JNI_ERR_IF_NOT_ZERO(SeekableInputStream::Initialize(env)); + RETURN_JNI_ERR_IF_NOT_ZERO(JavaSeekableInputStream::Initialize(env)); + RETURN_JNI_ERR_IF_NOT_ZERO(JavaInputStream::Initialize(env)); + RETURN_JNI_ERR_IF_NOT_ZERO(OpenVolumeCallback::Initialize(env)); + RETURN_JNI_ERR_IF_NOT_ZERO(OutputStream::Initialize(env)); + RETURN_JNI_ERR_IF_NOT_ZERO(SevenZip::Initialize()); + + RETURN_JNI_ERR_IF_NOT_ZERO(JavaInArchive::RegisterMethods(static_cast(env))); + RETURN_JNI_ERR_IF_NOT_ZERO( + JavaSeekableInputStream::RegisterMethods(static_cast(env))); + RETURN_JNI_ERR_IF_NOT_ZERO(JavaInputStream::RegisterMethods(static_cast(env))); + + return true; +} +#ifdef __cplusplus +} +#endif diff --git a/native/src/main/jni/a7zip/a7zip/JavaInitA7Zip.h b/native/src/main/jni/a7zip/a7zip/JavaInitA7Zip.h new file mode 100644 index 000000000..0572132bb --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaInitA7Zip.h @@ -0,0 +1,11 @@ +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif +bool a7zip_onLoad(JavaVM *vm); +#ifdef __cplusplus +} +#endif diff --git a/native/src/main/jni/a7zip/a7zip/JavaInputStream.cpp b/native/src/main/jni/a7zip/a7zip/JavaInputStream.cpp new file mode 100644 index 000000000..f95623963 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaInputStream.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#include "JavaInputStream.h" + +#include +#include + +#include "JavaHelper.h" +#include "Utils.h" + +using namespace a7zip; + +static jint NativeRead( + JNIEnv* env, + jclass, + jlong native_ptr, + jbyteArray array, + jint offset, + jint length +) { + CHECK_CLOSED_RET(env, 0, native_ptr); + ISequentialInStream* stream = reinterpret_cast(native_ptr); + + jbyte* bytes = env->GetByteArrayElements(array, nullptr); + if (bytes == nullptr) THROW_IO_EXCEPTION_RET(env, 0, E_JAVA_EXCEPTION); + + UInt32 processed_size; + HRESULT result = stream->Read(bytes + offset, static_cast(length), &processed_size); + env->ReleaseByteArrayElements(array, bytes, 0); + if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result); + + if (length != 0 && processed_size == 0) { + // EOF + return -1; + } + + return processed_size; +} + +static void NativeClose( + JNIEnv* env, + jclass, + jlong native_ptr +) { + CHECK_CLOSED(env, native_ptr); + ISequentialInStream* stream = reinterpret_cast(native_ptr); + stream->Release(); +} + +static bool initialized = false; +static jclass class_native_sequential_in_stream = nullptr; +static jmethodID constructor_native_sequential_in_stream = nullptr; + +HRESULT JavaInputStream::Initialize(JNIEnv* env) { + if (initialized) { + return S_OK; + } + + class_native_sequential_in_stream = env->FindClass("com/hippo/a7zip/NativeInputStream"); + if (class_native_sequential_in_stream == nullptr) return E_CLASS_NOT_FOUND; + class_native_sequential_in_stream = static_cast(env->NewGlobalRef(class_native_sequential_in_stream)); + if (class_native_sequential_in_stream == nullptr) return E_OUTOFMEMORY; + + constructor_native_sequential_in_stream = env->GetMethodID(class_native_sequential_in_stream, "", "(J)V"); + if (constructor_native_sequential_in_stream == nullptr) return E_METHOD_NOT_FOUND; + + initialized = true; + return JNI_OK; +} + +static JNINativeMethod stream_methods[] = { + { "nativeRead", + "(J[BII)I", + reinterpret_cast(NativeRead) }, + { "nativeClose", + "(J)V", + reinterpret_cast(NativeClose) } +}; + +HRESULT JavaInputStream::RegisterMethods(JNIEnv* env) { + if (!initialized) { + return E_NOT_INITIALIZED; + } + + jint result = env->RegisterNatives(class_native_sequential_in_stream, stream_methods, std::extent::value); + if (result < 0) { + return E_FAILED_REGISTER; + } + + return S_OK; +} + +HRESULT JavaInputStream::NewInstance(JNIEnv* env, ISequentialInStream* stream, jobject* object) { + *object = nullptr; + + if (!initialized) { + return E_NOT_INITIALIZED; + } + + *object = env->NewObject(class_native_sequential_in_stream, constructor_native_sequential_in_stream, stream); + + if (*object == nullptr) { + // Clear java exception and return + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + // No java exception, but it's still an error + return E_UNKNOWN_ERROR; + } + + // Ignore if any exception is pending + CLEAR_IF_EXCEPTION_PENDING(env); + return S_OK; +} diff --git a/native/src/main/jni/a7zip/a7zip/JavaInputStream.h b/native/src/main/jni/a7zip/a7zip/JavaInputStream.h new file mode 100644 index 000000000..1bece0f82 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaInputStream.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_JAVA_SEQUENTIAL_IN_STREAM_H__ +#define __A7ZIP_JAVA_SEQUENTIAL_IN_STREAM_H__ + +#include + +#include +#include <7zip/IStream.h> + +namespace a7zip { +namespace JavaInputStream { + +HRESULT Initialize(JNIEnv* env); + +HRESULT RegisterMethods(JNIEnv* env); + +HRESULT NewInstance(JNIEnv* env, ISequentialInStream* stream, jobject* object); + +} +} + +#endif //__A7ZIP_JAVA_SEQUENTIAL_IN_STREAM_H__ diff --git a/native/src/main/jni/a7zip/a7zip/JavaSeekableInputStream.cpp b/native/src/main/jni/a7zip/a7zip/JavaSeekableInputStream.cpp new file mode 100644 index 000000000..b39fc00d2 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaSeekableInputStream.cpp @@ -0,0 +1,202 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#include "JavaSeekableInputStream.h" + +#include +#include + +#include "JavaHelper.h" +#include "Utils.h" + +using namespace a7zip; + +static void NativeSeek( + JNIEnv* env, + jclass, + jlong native_ptr, + jlong pos +) { + CHECK_CLOSED(env, native_ptr); + IInStream* stream = reinterpret_cast(native_ptr); + + UInt64 newPosition; // Ignored + HRESULT result = stream->Seek(pos, STREAM_SEEK_SET, &newPosition); + if (result != S_OK) THROW_IO_EXCEPTION(env, result); +} + +static jlong NativeTell( + JNIEnv* env, + jclass, + jlong native_ptr +) { + CHECK_CLOSED_RET(env, 0, native_ptr); + IInStream* stream = reinterpret_cast(native_ptr); + + UInt64 newPosition; + HRESULT result = stream->Seek(0, STREAM_SEEK_CUR, &newPosition); + if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result); + return static_cast(newPosition); +} + +static jlong NativeSize( + JNIEnv* env, + jclass, + jlong native_ptr +) { + CHECK_CLOSED_RET(env, 0, native_ptr); + IInStream* stream = reinterpret_cast(native_ptr); + + // Try to get IStreamGetSize + CMyComPtr stream_get_size; + stream->QueryInterface(IID_IStreamGetSize, reinterpret_cast(&stream_get_size)); + + if (stream_get_size != nullptr) { + // It's an IStreamGetSize + UInt64 size; + HRESULT result = stream_get_size->GetSize(&size); + if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result); + return static_cast(size); + } else { + // It's not an IStreamGetSize + // Backup current position + UInt64 cur; + HRESULT result = stream->Seek(0, STREAM_SEEK_CUR, &cur); + if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result); + + // Seek to the end of steam to get size + UInt64 size; + result = stream->Seek(0, STREAM_SEEK_END, &size); + if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result); + + // Back to previous position + UInt64 ignored; + result = stream->Seek(cur, STREAM_SEEK_SET, &ignored); + if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result); + + return static_cast(size); + } +} + +static jint NativeRead( + JNIEnv* env, + jclass, + jlong native_ptr, + jbyteArray array, + jint offset, + jint length +) { + CHECK_CLOSED_RET(env, 0, native_ptr); + IInStream* stream = reinterpret_cast(native_ptr); + + jbyte* bytes = env->GetByteArrayElements(array, nullptr); + if (bytes == nullptr) THROW_IO_EXCEPTION_RET(env, 0, E_JAVA_EXCEPTION); + + UInt32 processed_size; + HRESULT result = stream->Read(bytes + offset, static_cast(length), &processed_size); + env->ReleaseByteArrayElements(array, bytes, 0); + if (result != S_OK) THROW_IO_EXCEPTION_RET(env, 0, result); + + if (length != 0 && processed_size == 0) { + // EOF + return -1; + } + + return processed_size; +} + +static void NativeClose( + JNIEnv* env, + jclass, + jlong native_ptr +) { + CHECK_CLOSED(env, native_ptr); + IInStream* stream = reinterpret_cast(native_ptr); + stream->Release(); +} + +static bool initialized = false; +static jclass class_native_in_stream = nullptr; +static jmethodID constructor_native_in_stream = nullptr; + +HRESULT JavaSeekableInputStream::Initialize(JNIEnv* env) { + if (initialized) { + return S_OK; + } + + class_native_in_stream = env->FindClass("com/hippo/a7zip/NativeSeekableInputStream"); + if (class_native_in_stream == nullptr) return E_CLASS_NOT_FOUND; + class_native_in_stream = static_cast(env->NewGlobalRef(class_native_in_stream)); + if (class_native_in_stream == nullptr) return E_OUTOFMEMORY; + + constructor_native_in_stream = env->GetMethodID(class_native_in_stream, "", "(J)V"); + if (constructor_native_in_stream == nullptr) return E_METHOD_NOT_FOUND; + + initialized = true; + return JNI_OK; +} + +static JNINativeMethod stream_methods[] = { + { "nativeSeek", + "(JJ)V", + reinterpret_cast(NativeSeek) }, + { "nativeTell", + "(J)J", + reinterpret_cast(NativeTell) }, + { "nativeSize", + "(J)J", + reinterpret_cast(NativeSize) }, + { "nativeRead", + "(J[BII)I", + reinterpret_cast(NativeRead) }, + { "nativeClose", + "(J)V", + reinterpret_cast(NativeClose) } +}; + +HRESULT JavaSeekableInputStream::RegisterMethods(JNIEnv* env) { + if (!initialized) { + return E_NOT_INITIALIZED; + } + + jint result = env->RegisterNatives(class_native_in_stream, stream_methods, std::extent::value); + if (result < 0) { + return E_FAILED_REGISTER; + } + + return S_OK; +} + +HRESULT JavaSeekableInputStream::NewInstance(JNIEnv* env, IInStream* stream, jobject* object) { + *object = nullptr; + + if (!initialized) { + return E_NOT_INITIALIZED; + } + + *object = env->NewObject(class_native_in_stream, constructor_native_in_stream, stream); + + if (*object == nullptr) { + // Clear java exception and return + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + // No java exception, but it's still an error + return E_UNKNOWN_ERROR; + } + + // Ignore if any exception is pending + CLEAR_IF_EXCEPTION_PENDING(env); + return S_OK; +} diff --git a/native/src/main/jni/a7zip/a7zip/JavaSeekableInputStream.h b/native/src/main/jni/a7zip/a7zip/JavaSeekableInputStream.h new file mode 100644 index 000000000..cdfea5014 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/JavaSeekableInputStream.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_JAVA_IN_STREAM_H__ +#define __A7ZIP_JAVA_IN_STREAM_H__ + +#include + +#include +#include <7zip/IStream.h> + +namespace a7zip { +namespace JavaSeekableInputStream { + +HRESULT Initialize(JNIEnv* env); + +HRESULT RegisterMethods(JNIEnv* env); + +HRESULT NewInstance(JNIEnv* env, IInStream* stream, jobject* object); + +} +} + +#endif //__A7ZIP_JAVA_IN_STREAM_H__ diff --git a/native/src/main/jni/a7zip/a7zip/Log.h b/native/src/main/jni/a7zip/a7zip/Log.h new file mode 100644 index 000000000..ef4c84e1e --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/Log.h @@ -0,0 +1,14 @@ +#ifndef __A7ZIP_LOG_H__ +#define __A7ZIP_LOG_H__ + +#include + +#define LOG_TAG "a7zip" +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG ,__VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG ,__VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG ,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__) + +#endif //__A7ZIP_LOG_H__ diff --git a/native/src/main/jni/a7zip/a7zip/OpenVolumeCallback.cpp b/native/src/main/jni/a7zip/a7zip/OpenVolumeCallback.cpp new file mode 100644 index 000000000..d79701d15 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/OpenVolumeCallback.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2020 Hippo Seven + * + * 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. + */ + +#include "OpenVolumeCallback.h" + +#include + +#include + +#include "SeekableInputStream.h" +#include "JavaEnv.h" +#include "Utils.h" +#include "Log.h" + +using namespace a7zip; + +bool OpenVolumeCallback::initialized = false; +jmethodID OpenVolumeCallback::method_open_volume = nullptr; + +OpenVolumeCallback::OpenVolumeCallback(jobject callback) : callback(callback) { } + +OpenVolumeCallback::~OpenVolumeCallback() { + JavaEnv env; + if (!env.IsValid()) return; + + env->DeleteGlobalRef(callback); + callback = nullptr; +} + +HRESULT OpenVolumeCallback::OpenVolume(const wchar_t *name, CMyComPtr& in_stream) { + JavaEnv env; + + // const wchar_t* to jstring + unsigned len = MyStringLen(name); + jchar *buffer = reinterpret_cast(malloc(len * sizeof(jchar))); + if (buffer == nullptr) { + return E_OUTOFMEMORY; + } + for (int i = 0; i < len; i++) { + buffer[i] = static_cast(name[i]); + } + jstring j_name = env->NewString(buffer, len); + free(buffer); + if (j_name == nullptr) { + return E_OUTOFMEMORY; + } + + // Wrap java stream + jobject stream = env->CallObjectMethod(callback, method_open_volume, j_name); + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + RETURN_SAME_IF_NOT_ZERO(SeekableInputStream::Create(static_cast(env), stream, in_stream)); + return S_OK; +} + +HRESULT OpenVolumeCallback::Initialize(JNIEnv* env) { + if (initialized) { + return S_OK; + } + + jclass clazz = env->FindClass("com/hippo/a7zip/InArchive$OpenVolumeCallback"); + if (clazz == nullptr) return E_CLASS_NOT_FOUND; + + method_open_volume = env->GetMethodID(clazz, "openVolume", "(Ljava/lang/String;)Lcom/hippo/a7zip/SeekableInputStream;"); + if (method_open_volume == nullptr) return E_METHOD_NOT_FOUND; + + initialized = true; + return S_OK; +} + +HRESULT OpenVolumeCallback::Create( + JNIEnv* env, + jobject callback, + OpenVolumeCallback** result +) { + if (!initialized) { + return E_NOT_INITIALIZED; + } + + jobject g_callback = env->NewGlobalRef(callback); + if (g_callback == nullptr) { + return E_OUTOFMEMORY; + } + + *result = new OpenVolumeCallback(g_callback); + + return S_OK; +} + +HRESULT OpenVolumeCallback::Create( + JNIEnv* env, + jobject callback, + CMyComPtr& result +) { + OpenVolumeCallback *callback_ptr = nullptr; + RETURN_SAME_IF_NOT_ZERO(Create(env, callback, &callback_ptr)); + result = callback_ptr; + return S_OK; +} diff --git a/native/src/main/jni/a7zip/a7zip/OpenVolumeCallback.h b/native/src/main/jni/a7zip/a7zip/OpenVolumeCallback.h new file mode 100644 index 000000000..00e99e204 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/OpenVolumeCallback.h @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_OPEN_VOLUME_CALLBACK_H__ +#define __A7ZIP_OPEN_VOLUME_CALLBACK_H__ + +#include + +#include +#include <7zip/IStream.h> + +namespace a7zip { + +class OpenVolumeCallback : public CMyUnknownImp +{ + private: + OpenVolumeCallback(jobject callback); + public: + virtual ~OpenVolumeCallback(); + + public: + MY_ADDREF_RELEASE + + HRESULT OpenVolume(const wchar_t *name, CMyComPtr& in_stream); + + private: + jobject callback; + + public: + static HRESULT Initialize(JNIEnv* env); + static HRESULT Create(JNIEnv* env, jobject callback, OpenVolumeCallback** result); + static HRESULT Create(JNIEnv* env, jobject callback, CMyComPtr& result); + + private: + static bool initialized; + static jmethodID method_open_volume; +}; + +} + +#endif //__A7ZIP_OPEN_VOLUME_CALLBACK_H__ diff --git a/native/src/main/jni/a7zip/a7zip/OutputStream.cpp b/native/src/main/jni/a7zip/a7zip/OutputStream.cpp new file mode 100644 index 000000000..dafedb88e --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/OutputStream.cpp @@ -0,0 +1,127 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#include "OutputStream.h" + +#include "JavaEnv.h" +#include "Utils.h" +#include "Log.h" + +#define ARRAY_SIZE DEFAULT_BUFFER_SIZE + +using namespace a7zip; + +bool OutputStream::initialized = false; +jmethodID OutputStream::method_write = nullptr; +jmethodID OutputStream::method_close = nullptr; + +OutputStream::OutputStream( + jobject stream, + jbyteArray array +) : + stream(stream), + array(array) { } + +OutputStream::~OutputStream() { + JavaEnv env; + if (!env.IsValid()) return; + + env->CallVoidMethod(stream, method_close); + CLEAR_IF_EXCEPTION_PENDING(env); + + env->DeleteGlobalRef(stream); + env->DeleteGlobalRef(array); + stream = nullptr; + array = nullptr; +} + +HRESULT OutputStream::Write(const void* data, UInt32 size, UInt32* processedSize) { + if (processedSize != nullptr) { + *processedSize = 0; + } + + if (size == 0) { + return S_OK; + } + + JavaEnv env; + if (!env.IsValid()) return E_JAVA_EXCEPTION; + + // Make size not bigger than ARRAY_SIZE + size = MIN(ARRAY_SIZE, size); + + // Copy data from native buffer to java buffer + env->SetByteArrayRegion(array, 0, size, reinterpret_cast(data)); + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + + // Write data to sink + env->CallVoidMethod(stream, method_write, array, 0, size); + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + + if (processedSize != nullptr) { + *processedSize = size; + } + + return S_OK; +} + +HRESULT OutputStream::Initialize(JNIEnv* env) { + if (initialized) { + return S_OK; + } + + jclass clazz = env->FindClass("java/io/OutputStream"); + if (clazz == nullptr) return E_CLASS_NOT_FOUND; + + method_write = env->GetMethodID(clazz, "write", "([BII)V"); + if (method_write == nullptr) return E_METHOD_NOT_FOUND; + method_close = env->GetMethodID(clazz, "close", "()V"); + if (method_close == nullptr) return E_METHOD_NOT_FOUND; + + initialized = true; + return S_OK; +} + +HRESULT OutputStream::Create( + JNIEnv* env, + jobject stream, + CMyComPtr& out_stream +) { + if (!initialized) { + return E_NOT_INITIALIZED; + } + + jobject g_stream = env->NewGlobalRef(stream); + if (g_stream == nullptr) { + return E_OUTOFMEMORY; + } + + jbyteArray array = env->NewByteArray(ARRAY_SIZE); + if (array == nullptr) { + env->DeleteGlobalRef(g_stream); + return E_FAILED_CONSTRUCT; + } + + jbyteArray g_array = static_cast(env->NewGlobalRef(array)); + if (g_array == nullptr) { + env->DeleteGlobalRef(g_stream); + return E_OUTOFMEMORY; + } + + out_stream = new OutputStream(g_stream, g_array); + + return S_OK; +} diff --git a/native/src/main/jni/a7zip/a7zip/OutputStream.h b/native/src/main/jni/a7zip/a7zip/OutputStream.h new file mode 100644 index 000000000..392e6bf08 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/OutputStream.h @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_OUTPUT_STREAM_OUT_STREAM_H__ +#define __A7ZIP_OUTPUT_STREAM_OUT_STREAM_H__ + +#include + +#include +#include <7zip/IStream.h> + +namespace a7zip { + +class OutputStream : + public ISequentialOutStream, + public CMyUnknownImp +{ + private: + OutputStream(jobject stream, jbyteArray array); + + public: + virtual ~OutputStream(); + + public: + MY_UNKNOWN_IMP + STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); + + private: + jobject stream; + jbyteArray array; + + public: + static HRESULT Initialize(JNIEnv* env); + static HRESULT Create(JNIEnv* env, jobject stream, CMyComPtr& out_stream); + + private: + static bool initialized; + static jmethodID method_write; + static jmethodID method_close; +}; + +} + +#endif //__A7ZIP_OUTPUT_STREAM_OUT_STREAM_H__ diff --git a/native/src/main/jni/a7zip/a7zip/PropType.h b/native/src/main/jni/a7zip/a7zip/PropType.h new file mode 100644 index 000000000..0c527527c --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/PropType.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_PROP_TYPE_H__ +#define __A7ZIP_PROP_TYPE_H__ + +namespace a7zip { + +enum PropTypeEnum { + PT_UNKNOWN, + PT_EMPTY, + PT_BOOL, + PT_INT, + PT_LONG, + PT_FLOAT, + PT_DOUBLE, + PT_STRING, +}; + +typedef unsigned short PropType; + +} + +#endif //__A7ZIP_PROP_TYPE_H__ diff --git a/native/src/main/jni/a7zip/a7zip/SeekableInputStream.cpp b/native/src/main/jni/a7zip/a7zip/SeekableInputStream.cpp new file mode 100644 index 000000000..20b750de0 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/SeekableInputStream.cpp @@ -0,0 +1,196 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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. + */ + +#include +#include "SeekableInputStream.h" + +#include "JavaEnv.h" +#include "Utils.h" +#include "Log.h" + +#define ARRAY_SIZE DEFAULT_BUFFER_SIZE + +using namespace a7zip; + +bool SeekableInputStream::initialized = false; +jmethodID SeekableInputStream::method_read = nullptr; +jmethodID SeekableInputStream::method_seek = nullptr; +jmethodID SeekableInputStream::method_tell = nullptr; +jmethodID SeekableInputStream::method_size = nullptr; +jmethodID SeekableInputStream::method_close = nullptr; + +SeekableInputStream::SeekableInputStream( + jobject stream, + jbyteArray array +) : + stream(stream), + array(array) { } + +SeekableInputStream::~SeekableInputStream() { + JavaEnv env; + if (!env.IsValid()) return; + + env->CallVoidMethod(stream, method_close); + CLEAR_IF_EXCEPTION_PENDING(env); + + env->DeleteGlobalRef(stream); + env->DeleteGlobalRef(array); + stream = nullptr; + array = nullptr; +} + +HRESULT SeekableInputStream::Read(void* data, UInt32 size, UInt32* processedSize) { + if (processedSize != nullptr) { + *processedSize = 0; + } + + if (size == 0) { + return S_OK; + } + + JavaEnv env; + if (!env.IsValid()) return E_JAVA_EXCEPTION; + + // Make size not bigger than ARRAY_SIZE + size = MIN(ARRAY_SIZE, size); + + jint read = env->CallIntMethod(stream, method_read, array, 0, size); + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + + // Check EOF + if (read <= 0) { + return S_OK; + } + + env->GetByteArrayRegion(array, 0, read, static_cast(data)); + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + + if (processedSize != nullptr) { + *processedSize = static_cast(read); + } + + return S_OK; +} + +HRESULT SeekableInputStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) { + JavaEnv env; + if (!env.IsValid()) return E_JAVA_EXCEPTION; + + jlong actual_offset; + + switch (seekOrigin) { + case STREAM_SEEK_SET: { + actual_offset = static_cast(offset); + break; + } + case STREAM_SEEK_CUR: { + jlong position = env->CallLongMethod(stream, method_tell); + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + actual_offset = position + offset; + break; + } + case STREAM_SEEK_END: { + jlong size = env->CallLongMethod(stream, method_size); + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + actual_offset = size + offset; + break; + } + default: { + return E_INVALIDARG; + } + } + + if (actual_offset < 0) { + return E_INVALIDARG; + } + + env->CallVoidMethod(stream, method_seek, actual_offset); + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + + if (newPosition != nullptr) { + *newPosition = static_cast(actual_offset); + } + + return S_OK; +} + +HRESULT SeekableInputStream::GetSize(UInt64* size) { + JavaEnv env; + if (!env.IsValid()) return E_JAVA_EXCEPTION; + + jlong store_size = env->CallLongMethod(stream, method_size); + RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(env); + + if (size != nullptr) { + *size = static_cast(store_size); + } + + return S_OK; +} + +HRESULT SeekableInputStream::Initialize(JNIEnv* env) { + if (initialized) { + return S_OK; + } + + jclass clazz = env->FindClass("com/hippo/a7zip/SeekableInputStream"); + if (clazz == nullptr) return E_CLASS_NOT_FOUND; + + method_read = env->GetMethodID(clazz, "read", "([BII)I"); + if (method_read == nullptr) return E_METHOD_NOT_FOUND; + method_seek = env->GetMethodID(clazz, "seek", "(J)V"); + if (method_seek == nullptr) return E_METHOD_NOT_FOUND; + method_tell = env->GetMethodID(clazz, "tell", "()J"); + if (method_tell == nullptr) return E_METHOD_NOT_FOUND; + method_size = env->GetMethodID(clazz, "size", "()J"); + if (method_size == nullptr) return E_METHOD_NOT_FOUND; + method_close = env->GetMethodID(clazz, "close", "()V"); + if (method_close == nullptr) return E_METHOD_NOT_FOUND; + + initialized = true; + return JNI_OK; +} + +HRESULT SeekableInputStream::Create( + JNIEnv* env, + jobject stream, + CMyComPtr& in_stream +) { + if (!initialized) { + return E_NOT_INITIALIZED; + } + + jobject g_stream = env->NewGlobalRef(stream); + if (g_stream == nullptr) { + return E_OUTOFMEMORY; + } + + jbyteArray array = env->NewByteArray(ARRAY_SIZE); + if (array == nullptr) { + env->DeleteGlobalRef(g_stream); + return E_FAILED_CONSTRUCT; + } + + jbyteArray g_array = static_cast(env->NewGlobalRef(array)); + if (g_array == nullptr) { + env->DeleteGlobalRef(g_stream); + return E_OUTOFMEMORY; + } + + in_stream = new SeekableInputStream(g_stream, g_array); + + return S_OK; +} diff --git a/native/src/main/jni/a7zip/a7zip/SeekableInputStream.h b/native/src/main/jni/a7zip/a7zip/SeekableInputStream.h new file mode 100644 index 000000000..02d007803 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/SeekableInputStream.h @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_IN_STREAM_H__ +#define __A7ZIP_IN_STREAM_H__ + +#include + +#include <7zip/IStream.h> +#include + +namespace a7zip { + +class SeekableInputStream : + public IInStream, + public IStreamGetSize, + public CMyUnknownImp +{ + private: + SeekableInputStream(jobject stream, jbyteArray array); + + public: + virtual ~SeekableInputStream(); + + public: + MY_UNKNOWN_IMP2(IInStream, IStreamGetSize) + + STDMETHOD(Read)(void* data, UInt32 size, UInt32* processedSize); + STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64* newPosition); + + STDMETHOD(GetSize)(UInt64* size); + + private: + jobject stream; + jbyteArray array; + + public: + static HRESULT Initialize(JNIEnv* env); + static HRESULT Create(JNIEnv* env, jobject stream, CMyComPtr& in_stream); + + private: + static bool initialized; + static jmethodID method_read; + static jmethodID method_seek; + static jmethodID method_tell; + static jmethodID method_size; + static jmethodID method_close; +}; + +} + +#endif //__A7ZIP_IN_STREAM_H__ diff --git a/native/src/main/jni/a7zip/a7zip/SevenZip.cpp b/native/src/main/jni/a7zip/a7zip/SevenZip.cpp new file mode 100644 index 000000000..341a06eb8 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/SevenZip.cpp @@ -0,0 +1,567 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#include "SevenZip.h" + +#include + +#include +#include <7zip/Archive/IArchive.h> +#include <7zip/IPassword.h> + +#include "Log.h" +#include "Utils.h" + +#include "OpenVolumeCallback.h" + +#ifdef LOG_TAG +# undef LOG_TAG +# define LOG_TAG "P7Zip" +#endif + +using namespace a7zip; + +extern "C" HRESULT GetNumberOfMethods(UINT32 *numCodecs); +extern "C" HRESULT GetNumberOfFormats(UINT32 *numFormats); +extern "C" HRESULT GetMethodProperty(UInt32 codecIndex, PROPID propID, PROPVARIANT* value); +extern "C" HRESULT GetHandlerProperty2(UInt32 formatIndex, PROPID propID, PROPVARIANT* value); +extern "C" HRESULT CreateObject(const GUID* clsid, const GUID* iid, void** outObject); + +typedef HRESULT (*GetPropertyFunc)(UInt32 index, PROPID propID, PROPVARIANT* value); + +class Method { + public: + bool has_name; + AString name; + bool has_encoder; + GUID encoder; + bool has_decoder; + GUID decoder; +}; + +class Format { + public: + GUID class_id; + bool has_name; + AString name; + UInt32 signature_offset; + CObjectVector signatures; +}; + +class CompressCodecsInfo : + public ICompressCodecsInfo, + public CMyUnknownImp { + public: + MY_UNKNOWN_IMP1(ICompressCodecsInfo) + STDMETHOD(GetNumMethods)(UInt32* numMethods); + STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT* value); + STDMETHOD(CreateDecoder)(UInt32 index, const GUID* interfaceID, void** coder); + STDMETHOD(CreateEncoder)(UInt32 index, const GUID* interfaceID, void** coder); +}; + +static bool initialized = false; +static void* handle = nullptr; +static CObjectVector methods; +static CObjectVector formats; +static CompressCodecsInfo compress_codecs_info; + +HRESULT CompressCodecsInfo::GetNumMethods(UInt32 *numMethods) { + if (numMethods != nullptr) { + *numMethods = methods.Size(); + } + return S_OK; +} + +HRESULT CompressCodecsInfo::GetProperty(UInt32 index, PROPID propID, PROPVARIANT* value) { + Method& method = methods[index]; + + switch (propID) { + case NMethodPropID::kDecoderIsAssigned: { + NWindows::NCOM::CPropVariant propVariant; + propVariant = method.has_decoder; + propVariant.Detach(value); + return S_OK; + } + case NMethodPropID::kEncoderIsAssigned: { + NWindows::NCOM::CPropVariant propVariant; + propVariant = method.has_encoder; + propVariant.Detach(value); + return S_OK; + } + default: { + return GetMethodProperty(index, propID, value); + } + } +} + +HRESULT CompressCodecsInfo::CreateDecoder( + UInt32 index, + const GUID* interfaceID, + void** coder +) { + Method& method = methods[index]; + + if (method.has_decoder) { + return CreateObject(&(method.decoder), interfaceID, coder); + } else { + return S_OK; + } +} + +HRESULT CompressCodecsInfo::CreateEncoder( + UInt32 index, + const GUID* interfaceID, + void** coder +) { + Method& method = methods[index]; + + if (method.has_encoder) { + return CreateObject(&(method.encoder), interfaceID, coder); + } else { + return S_OK; + } +} + +class ArchiveOpenCallback : + public IArchiveOpenCallback, + public ICryptoGetTextPassword, + public CMyUnknownImp { + public: + ArchiveOpenCallback(BSTR password) { + this->password = ::SysAllocString(password); + this->has_asked_password = false; + } + + ~ArchiveOpenCallback() { + ::SysFreeString(password); + } + + public: + MY_UNKNOWN_IMP2(IArchiveOpenCallback, ICryptoGetTextPassword) + INTERFACE_IArchiveOpenCallback({ return S_OK; }); + + STDMETHOD(CryptoGetTextPassword)(BSTR *password) { + has_asked_password = true; + *password = ::SysAllocString(this->password); + return this->password != nullptr ? S_OK : E_NO_PASSWORD; + } + + HRESULT GetBetterResult(HRESULT result) { + if (result == S_OK) { + return S_OK; + } else if (has_asked_password) { + if (password != nullptr) { + return E_WRONG_PASSWORD; + } else { + return E_NO_PASSWORD; + } + } else { + return result; + } + } + + private: + BSTR password; + bool has_asked_password; +}; + +class ArchiveOpenCallback2 : + public ArchiveOpenCallback, + public IArchiveOpenVolumeCallback { + + public: + ArchiveOpenCallback2( + BSTR password, + BSTR filename, + CMyComPtr& callback + ): + ArchiveOpenCallback(password), + filename(filename), + callback(callback) { } + + public: + MY_UNKNOWN_IMP3( + IArchiveOpenCallback, + IArchiveOpenVolumeCallback, + ICryptoGetTextPassword) + + STDMETHOD(GetProperty)(PROPID propID, PROPVARIANT *value) { + NWindows::NCOM::CPropVariant prop; + if (propID == kpidName) { + prop = filename; + } + prop.Detach(value); + return S_OK; + } + + STDMETHOD(GetStream)(const wchar_t *name, IInStream **inStream) { + CMyComPtr stream = nullptr; + HRESULT result = callback->OpenVolume(name, stream); + *inStream = stream.Detach(); + return result != S_OK ? S_FALSE : S_OK; + } + + private: + UString filename; + CMyComPtr callback; +}; + +#define GET_PROP_METHOD(METHOD_NAME, PROP_TYPE, VALUE_TYPE, CONVERTER) \ +HRESULT METHOD_NAME( \ + GetPropertyFunc get_property, \ + UInt32 index, \ + PROPID prop_id, \ + PROP_TYPE& value, \ + bool& is_assigned \ +) { \ + NWindows::NCOM::CPropVariant prop; \ + is_assigned = false; \ + \ + RETURN_SAME_IF_NOT_ZERO(get_property(index, prop_id, &prop)); \ + \ + if (prop.vt == VALUE_TYPE) { \ + is_assigned = true; \ + CONVERTER; \ + } else if (prop.vt != VT_EMPTY) { \ + return E_INCONSISTENT_PROP_TYPE; \ + } \ + \ + return S_OK; \ +} + +GET_PROP_METHOD(GetAString, AString, VT_BSTR, value.SetFromWStr_if_Ascii(prop.bstrVal)); + +GET_PROP_METHOD(GetGUID, GUID, VT_BSTR, value = *reinterpret_cast(prop.bstrVal)); + +GET_PROP_METHOD(GetCByteBuffer, CByteBuffer, VT_BSTR, value.CopyFrom((const Byte *)prop.bstrVal, ::SysStringByteLen(prop.bstrVal))); + +GET_PROP_METHOD(GetUInt32, UInt32, VT_UI4, value = prop.ulVal); + +#undef GET_PROP_METHOD + +static HRESULT LoadMethods() { + UInt32 method_number = 0; + + RETURN_SAME_IF_NOT_ZERO(GetNumberOfMethods(&method_number)); + + for(UInt32 i = 0; i < method_number; i++) { + Method& method = methods.AddNew(); + +# define GET_PROP(METHOD, PROP, VALUE, ASSIGNED) \ + if (METHOD(GetMethodProperty, i, PROP, VALUE, ASSIGNED) != S_OK) { \ + methods.DeleteBack(); \ + continue; \ + } + + // It's ok to assume all characters in method name are in ascii charset + GET_PROP(GetAString, NMethodPropID::kName, method.name, method.has_name); + GET_PROP(GetGUID, NMethodPropID::kDecoder, method.decoder, method.has_decoder); + GET_PROP(GetGUID, NMethodPropID::kEncoder, method.encoder, method.has_encoder); + +# undef GET_PROP + } + + return S_OK; +} + +static void AppendMultiSignature( + CObjectVector& signatures, + const unsigned char* multi_signature, + size_t size +) { + while (size > 0) { + unsigned length = *multi_signature++; + size--; + + if (length > size) { + return; + } + + signatures.AddNew().CopyFrom(multi_signature, length); + + multi_signature += length; + size -= length; + } +} + +static HRESULT LoadFormats() { + UInt32 format_number = 0; + + RETURN_SAME_IF_NOT_ZERO(GetNumberOfFormats(&format_number)); + + for(UInt32 i = 0; i < format_number; i++) { + Format& format = formats.AddNew(); + +# define GET_PROP(METHOD, PROP, VALUE, ASSIGNED) \ + if (METHOD(GetHandlerProperty2, i, PROP, VALUE, ASSIGNED) != S_OK) { \ + formats.DeleteBack(); \ + continue; \ + } + + // ClassID is required + bool has_class_id; + GET_PROP(GetGUID, NArchive::NHandlerPropID::kClassID, format.class_id, has_class_id); + if (!has_class_id) { + formats.DeleteBack(); + continue; + } + + // It's ok to assume all characters in format name are in ascii charset + GET_PROP(GetAString, NArchive::NHandlerPropID::kName, format.name, format.has_name); + + bool has_signature; + CByteBuffer signature; + GET_PROP(GetUInt32, NArchive::NHandlerPropID::kSignatureOffset, format.signature_offset, has_signature); + if (!has_signature) { + format.signature_offset = 0; + } + GET_PROP(GetCByteBuffer, NArchive::NHandlerPropID::kSignature, signature, has_signature); + if (has_signature) { + format.signatures.AddNew().CopyFrom(signature, signature.Size()); + } + GET_PROP(GetCByteBuffer, NArchive::NHandlerPropID::kMultiSignature, signature, has_signature); + if (has_signature) { + AppendMultiSignature(format.signatures, signature, signature.Size()); + } + +# undef GET_PROP + } + + return S_OK; +} + +HRESULT SevenZip::Initialize() { + if (initialized) { + return S_OK; + } + + RETURN_SAME_IF_NOT_ZERO(LoadMethods()); + RETURN_SAME_IF_NOT_ZERO(LoadFormats()); + + initialized = true; + return S_OK; +} + +static HRESULT ReadFully(CMyComPtr& stream, Byte* data, UInt32 size, UInt32* processedSize) { + UInt32 read = 0; + + while (read < size) { + RETURN_SAME_IF_NOT_ZERO(stream->Read(data + read, size - read, processedSize)); + + if (*processedSize == 0) { + // EOF + break; + } + + read += *processedSize; + } + + *processedSize = read; + return S_OK; +} + +static HRESULT OpenInArchive( + GUID& class_id, + CMyComPtr& in_stream, + BSTR password, + BSTR filename, + CMyComPtr& open_volume_callback, + CMyComPtr& in_archive +) { + RETURN_SAME_IF_NOT_ZERO(CreateObject(&class_id, &IID_IInArchive, reinterpret_cast(&in_archive))); + + UInt64 newPosition = 0; + HRESULT result = in_stream->Seek(0, STREAM_SEEK_SET, &newPosition); + if (result != S_OK) { + in_archive->Close(); + in_archive = nullptr; + return result; + } + + UInt64 maxCheckStartPosition = 1 << 22; + + ArchiveOpenCallback* callback_ptr = nullptr; + if (filename != nullptr && open_volume_callback != nullptr) { + callback_ptr = new ArchiveOpenCallback2(password, filename, open_volume_callback); + } else { + callback_ptr = new ArchiveOpenCallback(password); + } + CMyComPtr callback(callback_ptr); + + result = in_archive->Open(in_stream, &maxCheckStartPosition, callback); + result = callback->GetBetterResult(result); + if (result != S_OK) { + in_archive->Close(); + in_archive = nullptr; + return result; + } + + return S_OK; +} + +static HRESULT OpenInArchive( + CMyComPtr& in_stream, + BSTR password, + BSTR filename, + CMyComPtr& open_volume_callback, + CMyComPtr& in_archive, + AString& format_name +) { + bool formats_checked[formats.Size()]; + memset(formats_checked, 0, formats.Size() * sizeof(bool)); + + for (int i = 0; i < formats.Size(); i++) { + Format& format = formats[i]; + + // Skip format without signatures + if (format.signatures.Size() == 0) { + continue; + } + + // Check each signature + for (int j = 0; j < format.signatures.Size(); j++) { + CByteBuffer& signature = format.signatures[j]; + + UInt32 processedSize; + CByteBuffer bytes(signature.Size()); + + UInt64 newPosition; + CONTINUE_IF_NOT_ZERO(in_stream->Seek(format.signature_offset, STREAM_SEEK_SET, &newPosition)); + if (newPosition != format.signature_offset) continue; + CONTINUE_IF_NOT_ZERO(ReadFully(in_stream, bytes, static_cast(signature.Size()), &processedSize)); + if (processedSize != signature.Size() || bytes != signature) continue; + + // The signature matched, try to open it + HRESULT result = OpenInArchive(format.class_id, in_stream, password, filename, open_volume_callback, in_archive); + + // Mark the format + formats_checked[i] = true; + + if (result == S_OK) { + format_name = format.name; + return S_OK; + } + + if (result == E_NO_PASSWORD || result == E_WRONG_PASSWORD) { + // It's a password error, the archive format is confirmed + return result; + } + + // Can't open archive in this format + // Skip this format + break; + } + } + + // Try other unchecked formats + for (int i = 0; i < formats.Size(); i++) { + if (!formats_checked[i]) { + Format& format = formats[i]; + HRESULT result = OpenInArchive(format.class_id, in_stream, password, filename, open_volume_callback, in_archive); + + if (result == S_OK) { + format_name = format.name; + return S_OK; + } + + if (result == E_NO_PASSWORD || result == E_WRONG_PASSWORD) { + // It's a password error, the archive format is confirmed + return result; + } + } + } + + return E_UNKNOWN_FORMAT; +} + +HRESULT SevenZip::OpenArchive( + CMyComPtr& in_stream, + BSTR password, + BSTR filename, + CMyComPtr& open_volume_callback, + InArchive** archive +) { + HRESULT result = S_FALSE; + InArchive* previous_archive = nullptr; + + CMyComPtr arg_in_stream = in_stream; + BSTR arg_password = password; + BSTR arg_filename = filename; + CMyComPtr arg_open_volume_callback = open_volume_callback; + + while (true) { + CMyComPtr in_archive = nullptr; + AString format_name; + + result = OpenInArchive( + arg_in_stream, + arg_password, + arg_filename, + arg_open_volume_callback, + in_archive, + format_name + ); + + if (result != S_OK || in_archive == nullptr) { + if (in_archive != nullptr) { + in_archive->Close(); + in_archive = nullptr; + } + if (result == S_OK) { + result = E_INTERNAL; + } + break; + } + + previous_archive = new InArchive(previous_archive, in_archive, format_name); + + // Break if it has no entry or more than one entry + UInt32 number = 0; + previous_archive->GetNumberOfEntries(number); + if (number != 1) { + break; + } + + // Break if it's not IInArchiveGetStream + CMyComPtr archive_get_stream; + in_archive->QueryInterface(IID_IInArchiveGetStream, (void**)&archive_get_stream); + if (archive_get_stream == nullptr) { + break; + } + + // Break if GetStream returns null + CMyComPtr sequential_in_stream; + archive_get_stream->GetStream(0, &sequential_in_stream); + if (sequential_in_stream == nullptr) { + break; + } + + // Break if the stream is not IInStream + arg_in_stream = nullptr; + sequential_in_stream->QueryInterface(IID_IInStream, (void**)&arg_in_stream); + if (arg_in_stream == nullptr) { + break; + } + + // Clear arguments for the first archive + arg_password = nullptr; + arg_filename = nullptr; + arg_open_volume_callback = nullptr; + } + + *archive = previous_archive; + return *archive != nullptr ? S_OK : result; +} diff --git a/native/src/main/jni/a7zip/a7zip/SevenZip.h b/native/src/main/jni/a7zip/a7zip/SevenZip.h new file mode 100644 index 000000000..283e69500 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/SevenZip.h @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_P7ZIP_H__ +#define __A7ZIP_P7ZIP_H__ + +#include + +#include +#include +#include +#include + +#include <7zip/ICoder.h> + +#include "InArchive.h" +#include "OpenVolumeCallback.h" + +namespace a7zip { +namespace SevenZip { + +HRESULT Initialize(); +HRESULT OpenArchive( + CMyComPtr& stream, + BSTR password, + BSTR filename, + CMyComPtr& callback, + InArchive** archive +); + +} +} + +#endif //__A7ZIP_P7ZIP_H__ diff --git a/native/src/main/jni/a7zip/a7zip/Utils.h b/native/src/main/jni/a7zip/a7zip/Utils.h new file mode 100644 index 000000000..1a3656eb2 --- /dev/null +++ b/native/src/main/jni/a7zip/a7zip/Utils.h @@ -0,0 +1,95 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#ifndef __A7ZIP_UTILS_H__ +#define __A7ZIP_UTILS_H__ + +#define DEFAULT_BUFFER_SIZE (4 * 1024) + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + + +#define E_NOT_INITIALIZED ((HRESULT)0x82210000L) +#define E_INTERNAL ((HRESULT)0x82210001L) +#define E_UNKNOWN_ERROR ((HRESULT)0x82210002L) + +#define E_CLASS_NOT_FOUND ((HRESULT)0x82220000L) +#define E_METHOD_NOT_FOUND ((HRESULT)0x82220001L) +#define E_JAVA_EXCEPTION ((HRESULT)0x82220002L) +#define E_FAILED_CONSTRUCT ((HRESULT)0x82220003L) +#define E_FAILED_REGISTER ((HRESULT)0x82220004L) +#define E_FAILED_UNREGISTER ((HRESULT)0x82220005L) + +#define E_INCONSISTENT_PROP_TYPE ((HRESULT)0x82240000L) +#define E_EMPTY_PROP ((HRESULT)0x82240001L) +#define E_UNKNOWN_FORMAT ((HRESULT)0x82240002L) +#define E_UNSUPPORTED_EXTRACT_MODE ((HRESULT)0x82240003L) +#define E_NO_OUT_STREAM ((HRESULT)0x82240004L) + +#define E_UNSUPPORTED_METHOD ((HRESULT)0x82250000L) +#define E_DATA_ERROR ((HRESULT)0x82250001L) +#define E_DATA_ERROR_ENCRYPTED ((HRESULT)0x82250002L) +#define E_CRC_ERROR ((HRESULT)0x82250003L) +#define E_CRC_ERROR_ENCRYPTED ((HRESULT)0x82250004L) +#define E_UNAVAILABLE ((HRESULT)0x82250005L) +#define E_UNEXPECTED_END ((HRESULT)0x82250006L) +#define E_DATA_AFTER_END ((HRESULT)0x82250007L) +#define E_IS_NOT_ARC ((HRESULT)0x82250008L) +#define E_HEADERS_ERROR ((HRESULT)0x82250009L) +#define E_WRONG_PASSWORD ((HRESULT)0x82250010L) +#define E_NO_PASSWORD ((HRESULT)0x82250011L) + +#define CLEAR_IF_EXCEPTION_PENDING(ENV) \ + do { \ + if ((ENV)->ExceptionCheck()) { \ + (ENV)->ExceptionDescribe(); \ + (ENV)->ExceptionClear(); \ + } \ + } while (0) + +#define RETURN_E_JAVA_EXCEPTION_IF_EXCEPTION_PENDING(ENV) \ + do { \ + if ((ENV)->ExceptionCheck()) { \ + (ENV)->ExceptionDescribe(); \ + (ENV)->ExceptionClear(); \ + return E_JAVA_EXCEPTION; \ + } \ + } while (0) + +#define RETURN_JNI_ERR_IF_NOT_ZERO(VALUE) \ + do { \ + if ((VALUE) != 0) { \ + return JNI_ERR; \ + } \ + } while (0) + +#define RETURN_SAME_IF_NOT_ZERO(VALUE) \ + do { \ + auto __temp__ = (VALUE); \ + if (__temp__ != 0) { \ + return __temp__; \ + } \ + } while (0) + +#define CONTINUE_IF_NOT_ZERO(VALUE) \ + do { \ + if ((VALUE) != 0) { \ + continue; \ + } \ + } while (0) + +#endif //__A7ZIP_UTILS_H__ diff --git a/native/src/main/jni/a7zip/p7zip/CMakeLists.txt b/native/src/main/jni/a7zip/p7zip/CMakeLists.txt new file mode 100644 index 000000000..2d854de35 --- /dev/null +++ b/native/src/main/jni/a7zip/p7zip/CMakeLists.txt @@ -0,0 +1,239 @@ +cmake_minimum_required(VERSION 3.4.1) + +project(p7zip C CXX) + +set(P_SEVEN_ZIP_EXTRACT_LITE_SOURCES + p7zip/CPP/7zip/Archive/7z/7zDecode.cpp + p7zip/CPP/7zip/Archive/7z/7zExtract.cpp + p7zip/CPP/7zip/Archive/7z/7zHandler.cpp + p7zip/CPP/7zip/Archive/7z/7zHeader.cpp + p7zip/CPP/7zip/Archive/7z/7zIn.cpp + p7zip/CPP/7zip/Archive/7z/7zProperties.cpp + p7zip/CPP/7zip/Archive/7z/7zRegister.cpp + p7zip/CPP/7zip/Archive/Common/CoderMixer2.cpp + p7zip/CPP/7zip/Archive/Common/FindSignature.cpp + p7zip/CPP/7zip/Archive/Common/InStreamWithCRC.cpp + p7zip/CPP/7zip/Archive/Common/ItemNameUtils.cpp + p7zip/CPP/7zip/Archive/Common/MultiStream.cpp + p7zip/CPP/7zip/Archive/Common/OutStreamWithCRC.cpp + p7zip/CPP/7zip/Archive/Rar/RarHandler.cpp + p7zip/CPP/7zip/Archive/Rar/Rar5Handler.cpp + p7zip/CPP/7zip/Archive/Zip/ZipHandler.cpp + p7zip/CPP/7zip/Archive/Zip/ZipIn.cpp + p7zip/CPP/7zip/Archive/Zip/ZipItem.cpp + p7zip/CPP/7zip/Archive/Zip/ZipRegister.cpp + p7zip/CPP/7zip/Archive/ArchiveExports.cpp + p7zip/CPP/7zip/Archive/DllExports2.cpp + p7zip/CPP/7zip/Archive/HandlerCont.cpp + p7zip/CPP/7zip/Archive/SplitHandler.cpp + p7zip/CPP/7zip/Archive/XzHandler.cpp + p7zip/CPP/7zip/Common/CWrappers.cpp + p7zip/CPP/7zip/Common/CreateCoder.cpp + p7zip/CPP/7zip/Common/FilterCoder.cpp + p7zip/CPP/7zip/Common/InBuffer.cpp + p7zip/CPP/7zip/Common/LimitedStreams.cpp + p7zip/CPP/7zip/Common/OutBuffer.cpp + p7zip/CPP/7zip/Common/ProgressUtils.cpp + p7zip/CPP/7zip/Common/PropId.cpp + p7zip/CPP/7zip/Common/StreamObjects.cpp + p7zip/CPP/7zip/Common/StreamUtils.cpp + p7zip/CPP/7zip/Compress/Bcj2Coder.cpp + p7zip/CPP/7zip/Compress/Bcj2Register.cpp + p7zip/CPP/7zip/Compress/BcjCoder.cpp + p7zip/CPP/7zip/Compress/BcjRegister.cpp + p7zip/CPP/7zip/Compress/BitlDecoder.cpp + p7zip/CPP/7zip/Compress/BranchMisc.cpp + p7zip/CPP/7zip/Compress/BranchRegister.cpp + p7zip/CPP/7zip/Compress/ByteSwap.cpp + p7zip/CPP/7zip/Compress/BZip2Crc.cpp + p7zip/CPP/7zip/Compress/BZip2Decoder.cpp + p7zip/CPP/7zip/Compress/BZip2Register.cpp + p7zip/CPP/7zip/Compress/CodecExports.cpp + p7zip/CPP/7zip/Compress/CopyCoder.cpp + p7zip/CPP/7zip/Compress/CopyRegister.cpp + p7zip/CPP/7zip/Compress/Deflate64Register.cpp + p7zip/CPP/7zip/Compress/DeflateDecoder.cpp + p7zip/CPP/7zip/Compress/DeflateRegister.cpp + p7zip/CPP/7zip/Compress/DeltaFilter.cpp + p7zip/CPP/7zip/Compress/ImplodeDecoder.cpp + p7zip/CPP/7zip/Compress/ImplodeHuffmanDecoder.cpp + p7zip/CPP/7zip/Compress/Lzma2Decoder.cpp + p7zip/CPP/7zip/Compress/Lzma2Register.cpp + p7zip/CPP/7zip/Compress/LzmaDecoder.cpp + p7zip/CPP/7zip/Compress/LzmaRegister.cpp + p7zip/CPP/7zip/Compress/LzOutWindow.cpp + p7zip/CPP/7zip/Compress/PpmdDecoder.cpp + p7zip/CPP/7zip/Compress/PpmdRegister.cpp + p7zip/CPP/7zip/Compress/PpmdZip.cpp + p7zip/CPP/7zip/Compress/Rar1Decoder.cpp + p7zip/CPP/7zip/Compress/Rar2Decoder.cpp + p7zip/CPP/7zip/Compress/Rar3Decoder.cpp + p7zip/CPP/7zip/Compress/Rar3Vm.cpp + p7zip/CPP/7zip/Compress/Rar5Decoder.cpp + p7zip/CPP/7zip/Compress/RarCodecsRegister.cpp + p7zip/CPP/7zip/Compress/ShrinkDecoder.cpp + p7zip/CPP/7zip/Crypto/7zAes.cpp + p7zip/CPP/7zip/Crypto/7zAesRegister.cpp + p7zip/CPP/7zip/Crypto/HmacSha1.cpp + p7zip/CPP/7zip/Crypto/HmacSha256.cpp + p7zip/CPP/7zip/Crypto/MyAes.cpp + p7zip/CPP/7zip/Crypto/MyAesReg.cpp + p7zip/CPP/7zip/Crypto/Pbkdf2HmacSha1.cpp + p7zip/CPP/7zip/Crypto/RandGen.cpp + p7zip/CPP/7zip/Crypto/Rar20Crypto.cpp + p7zip/CPP/7zip/Crypto/Rar5Aes.cpp + p7zip/CPP/7zip/Crypto/RarAes.cpp + p7zip/CPP/7zip/Crypto/WzAes.cpp + p7zip/CPP/7zip/Crypto/ZipCrypto.cpp + p7zip/CPP/7zip/Crypto/ZipStrong.cpp + p7zip/CPP/Common/CRC.cpp + p7zip/CPP/Common/CrcReg.cpp + p7zip/CPP/Common/IntToString.cpp + p7zip/CPP/Common/MyString.cpp + p7zip/CPP/Common/MyVector.cpp + p7zip/CPP/Common/MyWindows.cpp + p7zip/CPP/Common/Sha1Reg.cpp + p7zip/CPP/Common/Sha256Reg.cpp + p7zip/CPP/Common/StringConvert.cpp + p7zip/CPP/Common/StringToInt.cpp + p7zip/CPP/Common/UTFConvert.cpp + p7zip/CPP/Common/Wildcard.cpp + p7zip/CPP/Common/XzCrc64Reg.cpp + p7zip/CPP/Windows/PropVariant.cpp + p7zip/CPP/Windows/PropVariantUtils.cpp + p7zip/CPP/Windows/System.cpp + p7zip/CPP/Windows/TimeUtils.cpp + p7zip/CPP/myWindows/wine_date_and_time.cpp + p7zip/C/7zCrc.c + p7zip/C/7zCrcOpt.c + p7zip/C/7zStream.c + p7zip/C/Aes.c + p7zip/C/Alloc.c + p7zip/C/Bcj2.c + p7zip/C/Blake2s.c + p7zip/C/Bra.c + p7zip/C/Bra86.c + p7zip/C/BraIA64.c + p7zip/C/CpuArch.c + p7zip/C/Delta.c + p7zip/C/Lzma2Dec.c + p7zip/C/LzmaDec.c + p7zip/C/Ppmd7.c + p7zip/C/Ppmd7Dec.c + p7zip/C/Ppmd8.c + p7zip/C/Ppmd8Dec.c + p7zip/C/Sha1.c + p7zip/C/Sha256.c + p7zip/C/Xz.c + p7zip/C/XzCrc64.c + p7zip/C/XzCrc64Opt.c + p7zip/C/XzDec.c + p7zip/C/XzIn.c +) + +set(P_SEVEN_ZIP_EXTRACT_SOURCES + p7zip/CPP/7zip/Archive/Cab/CabBlockInStream.cpp + p7zip/CPP/7zip/Archive/Cab/CabHandler.cpp + p7zip/CPP/7zip/Archive/Cab/CabHeader.cpp + p7zip/CPP/7zip/Archive/Cab/CabIn.cpp + p7zip/CPP/7zip/Archive/Cab/CabRegister.cpp + p7zip/CPP/7zip/Archive/Chm/ChmHandler.cpp + p7zip/CPP/7zip/Archive/Chm/ChmIn.cpp + p7zip/CPP/7zip/Archive/Common/DummyOutStream.cpp + p7zip/CPP/7zip/Archive/Common/OutStreamWithSha1.cpp + p7zip/CPP/7zip/Archive/Iso/IsoHandler.cpp + p7zip/CPP/7zip/Archive/Iso/IsoHeader.cpp + p7zip/CPP/7zip/Archive/Iso/IsoIn.cpp + p7zip/CPP/7zip/Archive/Iso/IsoRegister.cpp + p7zip/CPP/7zip/Archive/Nsis/NsisDecode.cpp + p7zip/CPP/7zip/Archive/Nsis/NsisHandler.cpp + p7zip/CPP/7zip/Archive/Nsis/NsisIn.cpp + p7zip/CPP/7zip/Archive/Nsis/NsisRegister.cpp + p7zip/CPP/7zip/Archive/Tar/TarHandler.cpp + p7zip/CPP/7zip/Archive/Tar/TarHeader.cpp + p7zip/CPP/7zip/Archive/Tar/TarIn.cpp + p7zip/CPP/7zip/Archive/Tar/TarRegister.cpp + p7zip/CPP/7zip/Archive/Udf/UdfHandler.cpp + p7zip/CPP/7zip/Archive/Udf/UdfIn.cpp + p7zip/CPP/7zip/Archive/Wim/WimHandler.cpp + p7zip/CPP/7zip/Archive/Wim/WimIn.cpp + p7zip/CPP/7zip/Archive/Wim/WimRegister.cpp + p7zip/CPP/7zip/Archive/ApmHandler.cpp + p7zip/CPP/7zip/Archive/ArHandler.cpp + p7zip/CPP/7zip/Archive/ArjHandler.cpp + p7zip/CPP/7zip/Archive/Bz2Handler.cpp + p7zip/CPP/7zip/Archive/ComHandler.cpp + p7zip/CPP/7zip/Archive/CpioHandler.cpp + p7zip/CPP/7zip/Archive/CramfsHandler.cpp + p7zip/CPP/7zip/Archive/DmgHandler.cpp + p7zip/CPP/7zip/Archive/ElfHandler.cpp + p7zip/CPP/7zip/Archive/ExtHandler.cpp + p7zip/CPP/7zip/Archive/FatHandler.cpp + p7zip/CPP/7zip/Archive/FlvHandler.cpp + p7zip/CPP/7zip/Archive/GptHandler.cpp + p7zip/CPP/7zip/Archive/GzHandler.cpp + p7zip/CPP/7zip/Archive/HfsHandler.cpp + p7zip/CPP/7zip/Archive/IhexHandler.cpp + p7zip/CPP/7zip/Archive/LzhHandler.cpp + p7zip/CPP/7zip/Archive/LzmaHandler.cpp + p7zip/CPP/7zip/Archive/MachoHandler.cpp + p7zip/CPP/7zip/Archive/MbrHandler.cpp + p7zip/CPP/7zip/Archive/MslzHandler.cpp + p7zip/CPP/7zip/Archive/MubHandler.cpp + p7zip/CPP/7zip/Archive/NtfsHandler.cpp + p7zip/CPP/7zip/Archive/PeHandler.cpp + p7zip/CPP/7zip/Archive/PpmdHandler.cpp + p7zip/CPP/7zip/Archive/QcowHandler.cpp + p7zip/CPP/7zip/Archive/RpmHandler.cpp + p7zip/CPP/7zip/Archive/SquashfsHandler.cpp + p7zip/CPP/7zip/Archive/SwfHandler.cpp + p7zip/CPP/7zip/Archive/UefiHandler.cpp + p7zip/CPP/7zip/Archive/VdiHandler.cpp + p7zip/CPP/7zip/Archive/VhdHandler.cpp + p7zip/CPP/7zip/Archive/VmdkHandler.cpp + p7zip/CPP/7zip/Archive/XarHandler.cpp + p7zip/CPP/7zip/Archive/ZHandler.cpp + p7zip/CPP/7zip/Compress/BZip2Decoder.cpp + p7zip/CPP/7zip/Compress/BZip2Register.cpp + p7zip/CPP/7zip/Compress/LzhDecoder.cpp + p7zip/CPP/7zip/Compress/LzmsDecoder.cpp + p7zip/CPP/7zip/Compress/LzxDecoder.cpp + p7zip/CPP/7zip/Compress/QuantumDecoder.cpp + p7zip/CPP/7zip/Compress/XpressDecoder.cpp + p7zip/CPP/7zip/Compress/ZDecoder.cpp + p7zip/CPP/7zip/Compress/ZlibDecoder.cpp + p7zip/CPP/Common/MyMap.cpp + p7zip/CPP/Common/MyXml.cpp + p7zip/C/HuffEnc.c + p7zip/C/Sort.c +) + +set(P_SEVEN_ZIP_INCLUDES + p7zip/C + p7zip/CPP + p7zip/CPP/Common + p7zip/CPP/include_windows + p7zip/CPP/myWindows + p7zip/CPP/Windows +) + +set(P_SEVEN_ZIP_EXTRACT_FLAGS + "-DEXTRACT_ONLY -D_7ZIP_ST" +) + +set(P_SEVEN_ZIP_COMMON_FLAGS + "-DANDROID_NDK -DNDEBUG -D_REENTRANT -DENV_UNIX -DEXTERNAL_CODECS -DUNICODE -D_UNICODE -DUNIX_USE_WIN_FILE" +) + + +set(P_SEVEN_ZIP_SOURCES ${P_SEVEN_ZIP_EXTRACT_LITE_SOURCES}) +set(P_SEVEN_ZIP_FLAGS "${P_SEVEN_ZIP_COMMON_FLAGS} ${P_SEVEN_ZIP_EXTRACT_FLAGS}") + +set(P_SEVEN_ZIP_NAME "p7zip") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${P_SEVEN_ZIP_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${P_SEVEN_ZIP_FLAGS}") + +add_library(p7zip SHARED ${P_SEVEN_ZIP_SOURCES}) +target_include_directories(p7zip PUBLIC ${P_SEVEN_ZIP_INCLUDES}) +set_target_properties(p7zip PROPERTIES OUTPUT_NAME ${P_SEVEN_ZIP_NAME}) diff --git a/native/src/main/jni/a7zip/p7zip/p7zip b/native/src/main/jni/a7zip/p7zip/p7zip new file mode 160000 index 000000000..6efdeb2e5 --- /dev/null +++ b/native/src/main/jni/a7zip/p7zip/p7zip @@ -0,0 +1 @@ +Subproject commit 6efdeb2e53ac20dafb9b6e03989bcfcf6cf5bd4a diff --git a/native/src/main/jni/image/CMakeLists.txt b/native/src/main/jni/image/CMakeLists.txt new file mode 100644 index 000000000..a62f823c9 --- /dev/null +++ b/native/src/main/jni/image/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright 2018 Hippo Seven +# +# 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. + +cmake_minimum_required(VERSION 3.4.1) + +add_subdirectory(cpufeatures) +add_subdirectory(image) +add_subdirectory(stream) +add_subdirectory(libjpeg-turbo) +add_subdirectory(libpng) +add_subdirectory(giflib) diff --git a/native/src/main/jni/image/cpufeatures/CMakeLists.txt b/native/src/main/jni/image/cpufeatures/CMakeLists.txt new file mode 100644 index 000000000..a97efb136 --- /dev/null +++ b/native/src/main/jni/image/cpufeatures/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright 2018 Hippo Seven +# +# 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. + +cmake_minimum_required(VERSION 3.4.1) +project(cpufeatures C) + +add_library(cpufeatures STATIC ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c) diff --git a/native/src/main/jni/image/giflib/CMakeLists.txt b/native/src/main/jni/image/giflib/CMakeLists.txt new file mode 100644 index 000000000..3a30c8e2b --- /dev/null +++ b/native/src/main/jni/image/giflib/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright 2018 Hippo Seven +# +# 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. + +cmake_minimum_required(VERSION 3.4.1) +project(giflib C) + +set(GIFLIB_SOURCES + giflib/dgif_lib.c + giflib/gif_hash.c + giflib/gifalloc.c + giflib/openbsd-reallocarray.c +) +set(GIFLIB_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/giflib) + +add_library(giflib STATIC ${GIFLIB_SOURCES}) +target_include_directories(giflib PUBLIC ${GIFLIB_INCLUDES}) diff --git a/native/src/main/jni/image/giflib/giflib b/native/src/main/jni/image/giflib/giflib new file mode 160000 index 000000000..df82ff438 --- /dev/null +++ b/native/src/main/jni/image/giflib/giflib @@ -0,0 +1 @@ +Subproject commit df82ff4380596b1b52e9f8b142e2366276cde164 diff --git a/native/src/main/jni/image/image/Android.mk b/native/src/main/jni/image/image/Android.mk new file mode 100644 index 000000000..b28f846b4 --- /dev/null +++ b/native/src/main/jni/image/image/Android.mk @@ -0,0 +1,60 @@ +# Copyright 2015 Hippo Seven +# +# 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +SUPPORT_FORMAT := plain jpeg png gif + +LOCAL_MODULE := image +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../stream +LOCAL_SRC_FILES := \ + image.c \ + image_utils.c \ + java_wrapper.c +LOCAL_LDLIBS := -llog -ljnigraphics -lGLESv2 +LOCAL_STATIC_LIBRARIES := stream + +ifeq ($(filter plain, $(SUPPORT_FORMAT)), plain) + LOCAL_SRC_FILES += image_plain.c +else + LOCAL_CFLAGS += -DIMAGE_NOT_SUPPORT_PLAIN +endif + +ifeq ($(filter jpeg, $(SUPPORT_FORMAT)), jpeg) + LOCAL_C_INCLUDES += $(LOCAL_PATH)/../libjpeg-turbo + LOCAL_SRC_FILES += image_jpeg.c + LOCAL_STATIC_LIBRARIES += jpeg-turbo +else + LOCAL_CFLAGS += -DIMAGE_NOT_SUPPORT_JPEG +endif + +ifeq ($(filter png, $(SUPPORT_FORMAT)), png) + LOCAL_C_INCLUDES += $(LOCAL_PATH)/../libpng + LOCAL_SRC_FILES += image_png.c + LOCAL_STATIC_LIBRARIES += png +else + LOCAL_CFLAGS += -DIMAGE_NOT_SUPPORT_PNG +endif + +ifeq ($(filter gif, $(SUPPORT_FORMAT)), gif) + LOCAL_C_INCLUDES += $(LOCAL_PATH)/../giflib + LOCAL_SRC_FILES += image_gif.c + LOCAL_STATIC_LIBRARIES += gif +else + LOCAL_CFLAGS += -DIMAGE_NOT_SUPPORT_GIF +endif + +include $(BUILD_SHARED_LIBRARY) diff --git a/native/src/main/jni/image/image/CMakeLists.txt b/native/src/main/jni/image/image/CMakeLists.txt new file mode 100644 index 000000000..0e87ed9e7 --- /dev/null +++ b/native/src/main/jni/image/image/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright 2018 Hippo Seven +# +# 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. + +cmake_minimum_required(VERSION 3.4.1) +project(image C) + +set(IMAGE_SOURCES + image.c + image_utils.c + image_plain.c + image_jpeg.c + image_png.c + image_gif.c + filter/clahe.c + filter/gray.c + java_wrapper.c +) +set(IMAGE_INCLUDES + ${CMAKE_CURRENT_SOURCE_DIR} +) +set(IMAGE_LIBRARIES + log + jnigraphics + GLESv2 + stream + libjpeg-turbo + libpng + giflib +) + +add_library(image STATIC ${IMAGE_SOURCES}) +target_include_directories(image PRIVATE ${IMAGE_INCLUDES}) +target_link_libraries(image PRIVATE ${IMAGE_LIBRARIES}) diff --git a/native/src/main/jni/image/image/config.h b/native/src/main/jni/image/image/config.h new file mode 100644 index 000000000..7268a3d4b --- /dev/null +++ b/native/src/main/jni/image/image/config.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/27/2015. +// + +#ifndef IMAGE_CONFIG_H +#define IMAGE_CONFIG_H + +#ifndef IMAGE_NOT_SUPPORT_PLAIN +#define IMAGE_SUPPORT_PLAIN +#endif +#ifndef IMAGE_NOT_SUPPORT_JPEG +#define IMAGE_SUPPORT_JPEG +#endif +#ifndef IMAGE_NOT_SUPPORT_PNG +#define IMAGE_SUPPORT_PNG +#endif +#ifndef IMAGE_NOT_SUPPORT_GIF +#define IMAGE_SUPPORT_GIF +#endif + +#endif //IMAGE_CONFIG_H diff --git a/native/src/main/jni/image/image/filter/clahe.c b/native/src/main/jni/image/image/filter/clahe.c new file mode 100644 index 000000000..afc0b8f0a --- /dev/null +++ b/native/src/main/jni/image/image/filter/clahe.c @@ -0,0 +1,184 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#include "clahe.h" +#include "pixel.h" +#include "../../utils.h" +#include "../../log.h" + +#define TILE_BLOCK 8 +#define LIMIT_MULTIPLE 4 +#define CL_MAX_LOOP_NUMBER 4 + +static BYTE clamp(int t) { + t = t > 255 ? 255 : t; + t = t < 0 ? 0 : t; + return (BYTE) t; +} + +void IMAGE_clahe(void* data, int width, int height, bool to_gray) { + if (width < TILE_BLOCK || height < TILE_BLOCK) { + return; + } + + PIXEL* pixels = data; + + if (to_gray) { + for (int i = 0; i < width * height; ++i) { + PIXEL* p = pixels + i; + int t1 = (int) (p->c1 * 0.299f + p->c2 * 0.587f + p->c3 * 0.114f); + p->c1 = clamp(t1); + } + } else { + // RGBA to YUVA + for (int i = 0; i < width * height; ++i) { + PIXEL* p = pixels + i; + int t1 = (int) (p->c1 * 0.299f + p->c2 * 0.587f + p->c3 * 0.114f); + int t2 = (int) (p->c1 * -0.169f + p->c2 * -0.331f + p->c3 * 0.5f + 128); + int t3 = (int) (p->c1 * 0.5f + p->c2 * -0.419f + p->c3 * -0.081f + 128); + p->c1 = clamp(t1); + p->c2 = clamp(t2); + p->c3 = clamp(t3); + } + } + + int tile_width = width / TILE_BLOCK; + int tile_height = height / TILE_BLOCK; + int histogram[TILE_BLOCK * TILE_BLOCK][256] = {0}; + float accumulation[TILE_BLOCK * TILE_BLOCK][256] = {0.0f}; + + // Build histogram and accumulation + for (int i = 0; i < TILE_BLOCK; i++) { + for (int j = 0; j < TILE_BLOCK; j++) { + int x_start = tile_width * i; + int x_end = i == TILE_BLOCK - 1 ? width : x_start + tile_width; + int y_start = tile_height * j; + int y_end = j == TILE_BLOCK - 1 ? height : y_start + tile_height; + int tile_size = (x_end - x_start) * (y_end - y_start); + int tile_index = i + TILE_BLOCK * j; + + // Count + for(int x = x_start; x < x_end; x++) { + for(int y = y_start; y < y_end ; y++) { + PIXEL* p = pixels + x + y * width; + histogram[tile_index][p->c1] += 1; + } + } + + // CL + int limit = tile_size * LIMIT_MULTIPLE / 256; + for (int m = 0; m < CL_MAX_LOOP_NUMBER; m++) { + int steal = 0; + for (int n = 0; n < 256; n++) { + if (histogram[tile_index][n] > limit) { + steal += histogram[tile_index][n] - limit; + histogram[tile_index][n] = limit; + } + } + int bonus = steal / 256; + if (bonus == 0 && steal != 0 && m == 0) { + bonus = 1; + } + if (bonus == 0) { + break; + } + for (int n = 0; n < 256; n++) { + histogram[tile_index][n] += bonus; + } + } + + // accumulation + for (int n = 0; n < 256; n++) { + accumulation[tile_index][n] = (float) histogram[tile_index][n] / tile_size; + if (n != 0) { + accumulation[tile_index][n] += accumulation[tile_index][n - 1]; + } + } + } + } + + // Interpolation + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + PIXEL* p = pixels + x + y * width; + // top-left corner + if (x <= tile_width / 2 && y <= tile_height / 2) { + p->c1 = clamp((int) (accumulation[0][p->c1] * 255)); + continue; + } + // top-right corner + if (x > width - tile_width / 2 && y <= tile_height / 2) { + p->c1 = clamp((int) (accumulation[TILE_BLOCK - 1][p->c1] * 255)); + continue; + } + // bottom-left corner + if (x <= tile_width / 2 && y > height - tile_height / 2) { + p->c1 = clamp((int) (accumulation[TILE_BLOCK * (TILE_BLOCK - 1)][p->c1] * 255)); + continue; + } + // bottom-right corner + if (x > width - tile_width / 2 && y > height - tile_height / 2) { + p->c1 = clamp((int) (accumulation[TILE_BLOCK * TILE_BLOCK - 1][p->c1] * 255)); + continue; + } + + int tile_x_index = (x - tile_width / 2) / tile_width; + int tile_y_index = (y - tile_height / 2) / tile_height; + int tile_index1 = tile_x_index + TILE_BLOCK * tile_y_index; + int tile_index2 = tile_index1 + 1; + float u = (float) (x - (tile_x_index * tile_width + tile_width / 2)) / tile_width; + // top side and bottom side + if (y <= tile_height / 2 || y > height - tile_height / 2) { + p->c1 = clamp((int) (255 * (accumulation[tile_index1][p->c1] * (1 - u) + accumulation[tile_index2][p->c1] * u))); + continue; + } + + int tile_index3 = tile_index1 + TILE_BLOCK; + float v = (float) (y - (tile_y_index * tile_height + tile_height / 2)) / tile_height; + // left side and right side + if (x <= tile_width / 2 || x > width - tile_width / 2) { + p->c1 = clamp((int) (255 * (accumulation[tile_index1][p->c1] * (1 - v) + accumulation[tile_index3][p->c1] * v))); + continue; + } + + int tile_index4 = tile_index2 + TILE_BLOCK; + p->c1 = clamp((int) (255 * ( + accumulation[tile_index1][p->c1] * (1 - u) * (1 - v) + + accumulation[tile_index2][p->c1] * u * (1 - v) + + accumulation[tile_index3][p->c1] * (1 - u) * v + + accumulation[tile_index4][p->c1] * u * v))); + } + } + + if (to_gray) { + for (int i = 0; i < width * height; ++i) { + PIXEL* p = pixels + i; + p->c2 = p->c1; + p->c3 = p->c1; + } + } else { + // YUVA to RGBA + for (int i = 0; i < width * height; ++i) { + PIXEL* p = pixels + i; + int t1 = (int) (p->c1 + (p->c2 - 128) * -0.00093f + (p->c3 - 128) * 1.401687f); + int t2 = (int) (p->c1 + (p->c2 - 128) * -0.3437f + (p->c3 - 128) * -0.71417f); + int t3 = (int) (p->c1 + (p->c2 - 128) * 1.77216f + (p->c3 - 128) * 0.00099f); + p->c1 = clamp(t1); + p->c2 = clamp(t2); + p->c3 = clamp(t3); + } + } +} diff --git a/native/src/main/jni/image/image/filter/clahe.h b/native/src/main/jni/image/image/filter/clahe.h new file mode 100644 index 000000000..bacf5bbe4 --- /dev/null +++ b/native/src/main/jni/image/image/filter/clahe.h @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Hippo Seven + * + * 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. + */ + +#ifndef IMAGE_CLAHE_H +#define IMAGE_CLAHE_H + +#include + +void IMAGE_clahe(void* data, int width, int height, bool to_gray); + +#endif //IMAGE_CLAHE_H diff --git a/native/src/main/jni/image/image/filter/gray.c b/native/src/main/jni/image/image/filter/gray.c new file mode 100644 index 000000000..a5f0ae123 --- /dev/null +++ b/native/src/main/jni/image/image/filter/gray.c @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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. + */ + +#include "gray.h" +#include "pixel.h" + +bool IMAGE_is_gray(void* data, int width, int height, int error) { + PIXEL* p = data; + + for (int i = 0, n = width * height; i < n; i++, p++) { + int e1 = p->c1 - p->c2; + int e2 = p->c1 - p->c3; + int e3 = p->c2 - p->c3; + + if (e1 > error || e1 < -error + || e2 > error || e2 < -error + || e3 > error || e3 < -error) { + return false; + } + } + + return true; +} diff --git a/native/src/main/jni/image/image/filter/gray.h b/native/src/main/jni/image/image/filter/gray.h new file mode 100644 index 000000000..c31e6c797 --- /dev/null +++ b/native/src/main/jni/image/image/filter/gray.h @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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. + */ + +#ifndef IMAGE_GRAY_H +#define IMAGE_GRAY_H + +#include + +bool IMAGE_is_gray(void* data, int width, int height, int error); + +#endif //IMAGE_GRAY_H diff --git a/native/src/main/jni/image/image/filter/pixel.h b/native/src/main/jni/image/image/filter/pixel.h new file mode 100644 index 000000000..95154afea --- /dev/null +++ b/native/src/main/jni/image/image/filter/pixel.h @@ -0,0 +1,30 @@ +/* + * Copyright 2019 Hippo Seven + * + * 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. + */ + +#ifndef IMAGE_PIXEL_H +#define IMAGE_PIXEL_H + +typedef unsigned char BYTE; + +typedef struct +{ + BYTE c1; + BYTE c2; + BYTE c3; + BYTE c4; +} PIXEL; + +#endif //IMAGE_PIXEL_H diff --git a/native/src/main/jni/image/image/image.c b/native/src/main/jni/image/image/image.c new file mode 100644 index 000000000..a0966e42f --- /dev/null +++ b/native/src/main/jni/image/image/image.c @@ -0,0 +1,532 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/27/2015. +// + +#include "image.h" +#include "image_plain.h" +#include "image_jpeg.h" +#include "image_png.h" +#include "image_gif.h" +#include "filter/clahe.h" +#include "filter/gray.h" +#include "../log.h" + +static int get_format(JNIEnv* env, InputStream* stream) +{ + unsigned char temp[2]; + int read = read_input_stream(env, stream, temp, 0, 2); + + if (read == 2) { +#ifdef IMAGE_SUPPORT_JPEG + if (temp[0] == IMAGE_JPEG_MAGIC_NUMBER_0 && temp[1] == IMAGE_JPEG_MAGIC_NUMBER_1) { + return IMAGE_FORMAT_JPEG; + } +#endif +#ifdef IMAGE_SUPPORT_PNG + if (temp[0] == IMAGE_PNG_MAGIC_NUMBER_0 && temp[1] == IMAGE_PNG_MAGIC_NUMBER_1) { + return IMAGE_FORMAT_PNG; + } +#endif +#ifdef IMAGE_SUPPORT_GIF + if (temp[0] == IMAGE_GIF_MAGIC_NUMBER_0 && temp[1] == IMAGE_GIF_MAGIC_NUMBER_1) { + return IMAGE_FORMAT_GIF; + } +#endif + LOGE(MSG("Can't recognize the two magic number: %d, %d"), temp[0], temp[1]); + } else { + LOGE(MSG("Can't read two magic number from stream")); + } + + return IMAGE_FORMAT_UNKNOWN; +} + +void* decode(JNIEnv* env, InputStream* stream, bool partially, int* format) +{ + unsigned char magic_numbers[2]; + PatchHeadInputStream* patch_head_input_stream; + + *format = get_format(env, stream); + + switch (*format) { +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + magic_numbers[0] = IMAGE_JPEG_MAGIC_NUMBER_0; + magic_numbers[1] = IMAGE_JPEG_MAGIC_NUMBER_1; + break; +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + magic_numbers[0] = IMAGE_PNG_MAGIC_NUMBER_0; + magic_numbers[1] = IMAGE_PNG_MAGIC_NUMBER_1; + break; +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + magic_numbers[0] = IMAGE_GIF_MAGIC_NUMBER_0; + magic_numbers[1] = IMAGE_GIF_MAGIC_NUMBER_1; + break; +#endif + default: + LOGE(MSG("Can't detect format %d"), *format); + destroy_input_stream(env, &stream); + return NULL; + } + + patch_head_input_stream = create_patch_head_input_stream(stream, magic_numbers, 2); + if (patch_head_input_stream == NULL){ + WTF_OM; + destroy_input_stream(env, &stream); + return NULL; + } + + switch (*format) { +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + return JPEG_decode(env, patch_head_input_stream, partially); +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + return PNG_decode(env, patch_head_input_stream, partially); +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + return GIF_decode(env, patch_head_input_stream, partially); +#endif + default: + LOGE(MSG("Can't detect format %d"), *format); + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } +} + +void* create(unsigned int width, unsigned int height, const void* data) +{ +#ifdef IMAGE_SUPPORT_PLAIN + return PLAIN_create(width, height, data); +#else + return NULL; +#endif +} + +bool complete(JNIEnv* env, void* image, int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + return PLAIN_complete((PLAIN*) image); +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + return JPEG_complete((JPEG*) image); +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + return PNG_complete(env, (PNG*) image); +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + return GIF_complete(env, (GIF*) image); +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + return false; + } +} + +bool is_completed(void* image, int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + return PLAIN_is_completed((PLAIN*) image); +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + return JPEG_is_completed((JPEG*) image); +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + return PNG_is_completed((PNG*) image); +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + return GIF_is_completed((GIF*) image); +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + return false; + } +} + +int get_width(void* image, int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + return PLAIN_get_width((PLAIN*) image); +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + return JPEG_get_width((JPEG*) image); +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + return PNG_get_width((PNG*) image); +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + return GIF_get_width((GIF*) image); +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + return -1; + } +} + +int get_height(void* image, int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + return PLAIN_get_height((PLAIN*) image); +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + return JPEG_get_height((JPEG*) image); +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + return PNG_get_height((PNG*) image); +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + return GIF_get_height((GIF*) image); +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + return -1; + } +} + +int get_byte_count(void* image, int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + return PLAIN_get_byte_count((PLAIN*) image); +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + return JPEG_get_byte_count((JPEG*) image); +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + return PNG_get_byte_count((PNG*) image); +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + return GIF_get_byte_count((GIF*) image); +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + return -1; + } +} + +void render(void* image, int format, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + PLAIN_render((PLAIN*) image, src_x, src_y, + dst, dst_w, dst_h, dst_x, dst_y, + width, height, fill_blank, default_color); + break; +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + JPEG_render((JPEG*) image, src_x, src_y, + dst, dst_w, dst_h, dst_x, dst_y, + width, height, fill_blank, default_color); + break; +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + PNG_render((PNG*) image, src_x, src_y, + dst, dst_w, dst_h, dst_x, dst_y, + width, height, fill_blank, default_color); + break; +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + GIF_render((GIF*) image, src_x, src_y, + dst, dst_w, dst_h, dst_x, dst_y, + width, height, fill_blank, default_color); + break; +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + break; + } +} + +void advance(void* image, int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + PLAIN_advance((PLAIN*) image); + break; +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + JPEG_advance((JPEG*) image); + break; +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + PNG_advance((PNG*) image); + break; +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + GIF_advance((GIF*) image); + break; +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + } +} + +int get_delay(void* image, int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + return PLAIN_get_delay((PLAIN*) image); +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + return JPEG_get_delay((JPEG*) image); +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + return PNG_get_delay((PNG*) image); +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + return GIF_get_delay((GIF*) image); +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + return false; + } +} + +int get_frame_count(void* image, int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + return PLAIN_get_frame_count((PLAIN*) image); +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + return JPEG_get_frame_count((JPEG*) image); +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + return PNG_get_frame_count((PNG*) image); +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + return GIF_get_frame_count((GIF*) image); +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + return false; + } +} + +bool is_opaque(void* image, int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + return PLAIN_is_opaque((PLAIN*) image); +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + return JPEG_is_opaque((JPEG*) image); +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + return PNG_is_opaque((PNG*) image); +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + return GIF_is_opaque((GIF*) image); +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + return false; + } +} + +static void get_image_data(void* image, int format, void** pixel, int* width, int* height) { + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: { + PLAIN* plain = (PLAIN*) image; + *pixel = PLAIN_get_pixels(plain); + *width = PLAIN_get_width(plain); + *height = PLAIN_get_height(plain); + break; + } +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: { + JPEG* jpeg = (JPEG*) image; + *pixel = JPEG_get_pixels(jpeg); + *width = JPEG_get_width(jpeg); + *height = JPEG_get_height(jpeg); + break; + } +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: { + PNG* png = (PNG*) image; + *pixel = PNG_get_pixels(png); + *width = PNG_get_width(png); + *height = PNG_get_height(png); + break; + } +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: { + GIF* gif = (GIF*) image; + *pixel = GIF_get_pixels(gif); + *width = GIF_get_width(gif); + *height = GIF_get_height(gif); + break; + } +#endif + default: + *pixel = NULL; + *width = 0; + *height = 0; + break; + } +} + +bool is_gray(void* image, int format, int error) +{ + void* pixel = NULL; + int width = 0; + int height = 0; + + get_image_data(image, format, &pixel, &width, &height); + + if (pixel == NULL || width == 0 || height == 0) { + return false; + } + + return IMAGE_is_gray(pixel, width, height, error); +} + +void clahe(void* image, int format, bool to_gray) +{ + void* pixel = NULL; + int width = 0; + int height = 0; + + get_image_data(image, format, &pixel, &width, &height); + + if (pixel == NULL || width == 0 || height == 0) { + return; + } + + IMAGE_clahe(pixel, width, height, to_gray); +} + +void recycle(JNIEnv *env, void* image, int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_PLAIN + case IMAGE_FORMAT_PLAIN: + PLAIN_recycle((PLAIN*) image); + break; +#endif +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + JPEG_recycle((JPEG*) image); + break; +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + PNG_recycle(env, (PNG*) image); + break; +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + GIF_recycle(env, (GIF*) image); + break; +#endif + default: + LOGE(MSG("Can't detect format %d"), format); + } +} + +int get_supported_formats(int *formats) +{ + int i = 0; +#ifdef IMAGE_SUPPORT_JPEG + formats[i++] = IMAGE_FORMAT_JPEG; +#endif +#ifdef IMAGE_SUPPORT_PNG + formats[i++] = IMAGE_FORMAT_PNG; +#endif +#ifdef IMAGE_SUPPORT_GIF + formats[i++] = IMAGE_FORMAT_GIF; +#endif + return i; +} + +const char *get_decoder_description(int format) +{ + switch (format) { +#ifdef IMAGE_SUPPORT_JPEG + case IMAGE_FORMAT_JPEG: + return IMAGE_JPEG_DECODER_DESCRIPTION; +#endif +#ifdef IMAGE_SUPPORT_PNG + case IMAGE_FORMAT_PNG: + return IMAGE_PNG_DECODER_DESCRIPTION; +#endif +#ifdef IMAGE_SUPPORT_GIF + case IMAGE_FORMAT_GIF: + return IMAGE_GIF_DECODER_DESCRIPTION; +#endif + default: + return NULL; + } +} diff --git a/native/src/main/jni/image/image/image.h b/native/src/main/jni/image/image/image.h new file mode 100644 index 000000000..2721d1476 --- /dev/null +++ b/native/src/main/jni/image/image/image.h @@ -0,0 +1,65 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/27/2015. +// + +#ifndef IMAGE_IMAGE_H +#define IMAGE_IMAGE_H + +#include + +#include "config.h" +#include "input_stream.h" + +#define IMAGE_FORMAT_UNKNOWN -1 +#ifdef IMAGE_SUPPORT_PLAIN +#define IMAGE_FORMAT_PLAIN 0 +#endif +#ifdef IMAGE_SUPPORT_JPEG +#define IMAGE_FORMAT_JPEG 1 +#endif +#ifdef IMAGE_SUPPORT_PNG +#define IMAGE_FORMAT_PNG 2 +#endif +#ifdef IMAGE_SUPPORT_GIF +#define IMAGE_FORMAT_GIF 3 +#endif + +#define IMAGE_MAX_SUPPORTED_FORMAT_COUNT 3 + +void* decode(JNIEnv* env, InputStream* stream, bool partially, int* format); +void* create(unsigned int width, unsigned int height, const void* data); +bool complete(JNIEnv* env, void* image, int format); +bool is_completed(void* image, int format); +int get_width(void* image, int format); +int get_height(void* image, int format); +int get_byte_count(void* image, int format); +void render(void* image, int format, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color); +void advance(void* image, int format); +int get_delay(void* image, int format); +int get_frame_count(void* image, int format); +bool is_opaque(void* image, int format); +bool is_gray(void* image, int format, int error); +void clahe(void* image, int format, bool to_gray); +void recycle(JNIEnv *env, void* image, int format); +int get_supported_formats(int *); +const char *get_decoder_description(int); + +#endif //IMAGE_IMAGE_H diff --git a/native/src/main/jni/image/image/image_gif.c b/native/src/main/jni/image/image/image_gif.c new file mode 100644 index 000000000..ebaea6369 --- /dev/null +++ b/native/src/main/jni/image/image/image_gif.c @@ -0,0 +1,558 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/27/2015. +// + +#include "config.h" +#ifdef IMAGE_SUPPORT_GIF + +#include +#include + +#include "image_gif.h" +#include "image_utils.h" +#include "java_wrapper.h" +#include "../log.h" +#include "../utils.h" + +static int error_code = 0; + +typedef struct { + unsigned char red; + unsigned char green; + unsigned char blue; + unsigned char alpha; +} RGBA; + +static int custom_read_fun(GifFileType* gif, GifByteType* bytes, int size) { + bool attach; + int read; + PatchHeadInputStream* patch_head_input_stream = gif->UserData; + JNIEnv *env = obtain_env(&attach); + + if (env == NULL) { + LOGE(MSG("Can't get JNIEnv")); + return 0; + } + + read = read_patch_head_input_stream(env, patch_head_input_stream, bytes, 0, (size_t) size); + + if (attach) { + release_env(); + } + + return read; +} + +static void generate_prepare(GIF_FRAME_INFO* frame_info_array, int count) +{ + int disposal = 0xff; + int i; + + for (i = 0; i < count; i++){ + GIF_FRAME_INFO* frame_info = frame_info_array + i; + + switch (disposal) { + case DISPOSAL_UNSPECIFIED: + case DISPOSE_DO_NOT: + frame_info->prepare = IMAGE_GIF_PREPARE_NONE; + break; + case DISPOSE_BACKGROUND: + frame_info->prepare = IMAGE_GIF_PREPARE_BACKGROUND; + break; + case DISPOSE_PREVIOUS: + frame_info->prepare = IMAGE_GIF_PREPARE_USE_BACKUP; + break; + default: + frame_info->prepare = IMAGE_GIF_PREPARE_BACKGROUND; + break; + } + + disposal = frame_info->disposal; + } +} + +static void read_gcb(GifFileType* gif_file, int index, GIF_FRAME_INFO* frame_info) +{ + GraphicsControlBlock gcb; + if (DGifSavedExtensionToGCB(gif_file, index, &gcb) == GIF_OK) { + frame_info->tran = gcb.TransparentColor; + frame_info->delay = gcb.DelayTime * 10; + frame_info->disposal = gcb.DisposalMode; + frame_info->prepare = IMAGE_GIF_PREPARE_UNKNOWN; + } else { + frame_info->tran = -1; + frame_info->delay = 0; + frame_info->disposal = DISPOSE_DO_NOT; + frame_info->prepare = IMAGE_GIF_PREPARE_UNKNOWN; + } +} + +static void fix_gif_file(GifFileType* gif_file) { + if (gif_file->ImageCount == 0) { + return; + } + + SavedImage last_image = gif_file->SavedImages[gif_file->ImageCount - 1]; + // If RasterBits is NULL, discard this frame + if (last_image.RasterBits == NULL) { + gif_file->ImageCount--; + // Free last image + if (last_image.ImageDesc.ColorMap != NULL) { + GifFreeMapObject(last_image.ImageDesc.ColorMap); + last_image.ImageDesc.ColorMap = NULL; + } + GifFreeExtensions(&last_image.ExtensionBlockCount, &last_image.ExtensionBlocks); + } +} + +void* GIF_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially) +{ + GIF* gif = NULL; + GifFileType* gif_file = NULL; + void* buffer = NULL; + void*shown_buffer = NULL; + GIF_FRAME_INFO* frame_info_array = NULL; + int i; + + gif = (GIF*) malloc(sizeof(GIF)); + if (gif == NULL) { + WTF_OM; + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + // Open + gif_file = DGifOpen(patch_head_input_stream, &custom_read_fun, &error_code); + if (gif_file == NULL) { + WTF_OM; + free(gif); + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + // Buffer + buffer = malloc(gif_file->SWidth * gif_file->SHeight * sizeof(RGBA)); + shown_buffer = malloc(gif_file->SWidth * gif_file->SHeight * sizeof(RGBA)); + if (buffer == NULL || shown_buffer == NULL) { + WTF_OM; + free(buffer); + free(shown_buffer); + DGifCloseFile(gif_file, &error_code); + free(gif); + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + if (partially) { + // Glance + if (DGifGlance(gif_file) != GIF_OK) { + LOGE(MSG("GIF error code %d"), error_code); + DGifCloseFile(gif_file, &error_code); + free(buffer); + free(gif); + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + // Frame info + frame_info_array = (GIF_FRAME_INFO*) malloc(sizeof(GIF_FRAME_INFO)); + if (frame_info_array == NULL) { + WTF_OM; + DGifCloseFile(gif_file, &error_code); + free(buffer); + free(gif); + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + // Read gcb + read_gcb(gif_file, 0, frame_info_array); + + gif->partially = true; + gif->patch_head_input_stream = patch_head_input_stream; + } else { + // Slurp + if (DGifSlurp(gif_file) == GIF_ERROR) { + fix_gif_file(gif_file); + } + if (gif_file->ImageCount <= 0) { + LOGE(MSG("No frame")); + DGifCloseFile(gif_file, &error_code); + free(buffer); + free(gif); + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + // Frame info + frame_info_array = (GIF_FRAME_INFO*) malloc(gif_file->ImageCount * sizeof(GIF_FRAME_INFO)); + if (frame_info_array == NULL) { + WTF_OM; + DGifCloseFile(gif_file, &error_code); + free(buffer); + free(gif); + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + // Read gcb + for (i = 0; i < gif_file->ImageCount; i++) { + read_gcb(gif_file, i, frame_info_array + i); + } + + // Generate prepare + generate_prepare(frame_info_array, gif_file->ImageCount); + + // Close input stream + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + + gif->partially = false; + gif->patch_head_input_stream = NULL; + } + + gif->gif_file = gif_file; + gif->frame_info_array = frame_info_array; + gif->buffer = buffer; + gif->buffer_index = -1; + gif->backup = NULL; + gif->shown_buffer = shown_buffer; + + GIF_advance(gif); + + return gif; +} + +bool GIF_complete(JNIEnv* env, GIF* gif) +{ + int i; + + if (!gif->partially) { + return true; + } + + if (gif->gif_file == NULL || gif->patch_head_input_stream == NULL) { + LOGE(MSG("Some stuff is NULL")); + return false; + } + + if (DGifSlurp(gif->gif_file) == GIF_ERROR) { + fix_gif_file(gif->gif_file); + } + + // Close input stream + close_patch_head_input_stream(env, gif->patch_head_input_stream); + destroy_patch_head_input_stream(env, &gif->patch_head_input_stream); + gif->patch_head_input_stream = NULL; + + gif->partially = false; + + gif->frame_info_array = (GIF_FRAME_INFO*) realloc(gif->frame_info_array, + gif->gif_file->ImageCount * sizeof(GIF_FRAME_INFO)); + if (gif->frame_info_array == NULL) { + WTF_OM; + return false; + } + + // Read gcb + for (i = 0; i < gif->gif_file->ImageCount; i++) { + read_gcb(gif->gif_file, i, gif->frame_info_array + i); + } + + // Generate prepare + generate_prepare(gif->frame_info_array, gif->gif_file->ImageCount); + + return true; +} + +bool GIF_is_completed(GIF* gif) +{ + return !gif->partially; +} + +void* GIF_get_pixels(GIF* gif) +{ + if (!gif->partially && GIF_get_frame_count(gif) == 1) { + return gif->buffer; + } else { + return NULL; + } +} + +int GIF_get_width(GIF* gif) +{ + return gif->gif_file->SWidth; +} + +int GIF_get_height(GIF* gif) +{ + return gif->gif_file->SHeight; +} + +int GIF_get_byte_count(GIF* gif) +{ + int i; + SavedImage saved_image; + int size = 0; + // Add buffer size + if (gif->buffer != NULL) { + size += gif->gif_file->SWidth * gif->gif_file->SHeight * 4; + } + // Add backup size + if (gif->backup != NULL) { + size += gif->gif_file->SWidth * gif->gif_file->SHeight * 4; + } + // Add shown buffer size + if (gif->shown_buffer != NULL) { + size += gif->gif_file->SWidth * gif->gif_file->SHeight * 4; + } + // Add frames size + if (gif->gif_file->SavedImages != NULL) { + for (i = 0; i < gif->gif_file->ImageCount; i++) { + saved_image = gif->gif_file->SavedImages[i]; + if (saved_image.RasterBits != NULL) { + size += saved_image.ImageDesc.Width * saved_image.ImageDesc.Height * 1; + } + } + } + return size; +} + +void GIF_render(GIF* gif, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color) +{ + copy_pixels(gif->shown_buffer, gif->gif_file->SWidth, gif->gif_file->SHeight, src_x, src_y, + dst, dst_w, dst_h, dst_x, dst_y, + width, height, fill_blank, default_color); +} + +static void backup(GIF* gif) +{ + if (gif->backup == NULL) { + gif->backup = (unsigned char*) malloc((size_t) (gif->gif_file->SWidth * gif->gif_file->SHeight * 4)); + if (gif->backup == NULL) { + WTF_OM; + return; + } + } + memcpy(gif->backup, gif->buffer, (size_t) (gif->gif_file->SWidth * gif->gif_file->SHeight * 4)); +} + +static void restore(GIF* gif) +{ + if (gif->backup == NULL) { + LOGE(MSG("Backup is NULL")); + } else { + memcpy(gif->buffer, gif->backup, (size_t) (gif->gif_file->SWidth * gif->gif_file->SHeight * 4)); + } +} + +static void switch_buffer_backup(GIF* gif) +{ + unsigned char* temp; + + if (gif->backup == NULL) { + backup(gif); + } else { + temp = gif->buffer; + gif->buffer = gif->backup; + gif->backup = temp; + } +} + +static void clear(RGBA* pixels, int num, RGBA color) { + RGBA* ptr = pixels; + int i; + for (i = 0; i < num; i++) { + *(ptr++) = color; + } +} + +static bool get_color_from_table(const ColorMapObject* cmap, int index, + RGBA* color) { + if (cmap == NULL || index < 0 || index >= cmap->ColorCount) { + return false; + } else { + GifColorType gct = cmap->Colors[index]; + color->red = gct.Red; + color->green = gct.Green; + color->blue = gct.Blue; + color->alpha = 0xff; + return true; + } +} + +static void clear_bg(GifFileType* gif_file, void* pixels) +{ + RGBA color; + if (!get_color_from_table(gif_file->SColorMap, gif_file->SBackGroundColor, &color)) { + color.red = 0x00; + color.green = 0x00; + color.blue = 0x00; + color.alpha = 0x00; + } + clear(pixels, gif_file->SWidth * gif_file->SHeight, color); +} + +static void copy_line(GifByteType* src, RGBA* dst, + const ColorMapObject* cmap, int tran, int len) +{ + for (; len > 0; len--, src++, dst++) { + int index = *src; + if (tran == -1 || index != tran) { + get_color_from_table(cmap, index, dst); + } + } +} + +static void blend(GifFileType* gif_file, int index, void* pixels, int tran) +{ + int width = gif_file->SWidth; + int height = gif_file->SHeight; + SavedImage cur = gif_file->SavedImages[index]; + GifImageDesc desc = cur.ImageDesc; + int copy_width = MIN(width - desc.Left, desc.Width); + int copy_height = MIN(height - desc.Top, desc.Height); + ColorMapObject *cmap = desc.ColorMap; + GifByteType* src = cur.RasterBits; + RGBA* dst = pixels; + GifByteType* src_ptr; + RGBA* dst_ptr; + size_t len; + int i; + + if (cmap == NULL) { + cmap = gif_file->SColorMap; + } + if (cmap != NULL) { + for (i = 0; i < copy_height; i++) { + src_ptr = src + (i * desc.Width); + dst_ptr = dst + ((desc.Top + i) * width + desc.Left); + len = (size_t) copy_width; + copy_line(src_ptr, dst_ptr, cmap, tran, len); + } + } else { + LOGW(MSG("Can't find color map")); + } +} + +void GIF_advance(GIF* gif) +{ + int index; + GIF_FRAME_INFO frame_info; + + index = (gif->buffer_index + 1) % gif->gif_file->ImageCount; + if (index != 0 && gif->partially) { + LOGE(MSG("The png is only decoded partially. Only the first frame can be shown.")); + return; + } + + if (gif->frame_info_array == NULL) { + frame_info.tran = -1; + frame_info.disposal = DISPOSE_DO_NOT; + frame_info.delay = 0; + frame_info.prepare = IMAGE_GIF_PREPARE_BACKGROUND; + } else { + memcpy(&frame_info, gif->frame_info_array + index, sizeof(GIF_FRAME_INFO)); + } + + if (frame_info.disposal == DISPOSE_PREVIOUS && frame_info.prepare == IMAGE_GIF_PREPARE_USE_BACKUP) { + switch_buffer_backup(gif); + } else { + // Backup + if (frame_info.disposal == DISPOSE_PREVIOUS) { + backup(gif); + } + + // Prepare + switch (frame_info.prepare) { + case IMAGE_GIF_PREPARE_NONE: + // Do nothing + break; + default: + case IMAGE_GIF_PREPARE_BACKGROUND: + // Set bg + clear_bg(gif->gif_file, gif->buffer); + break; + case IMAGE_GIF_PREPARE_USE_BACKUP: + restore(gif); + break; + } + } + + blend(gif->gif_file, index, gif->buffer, frame_info.tran); + + // Copy to shown buffer + memcpy(gif->shown_buffer, gif->buffer, (size_t) (gif->gif_file->SWidth * gif->gif_file->SHeight * 4)); + + gif->buffer_index = index; +} + +int GIF_get_delay(GIF* gif) +{ + if (gif->frame_info_array == NULL) { + return 0; + } else { + return gif->frame_info_array[gif->buffer_index].delay; + } +} + +int GIF_get_frame_count(GIF* gif) +{ + return gif->gif_file->ImageCount; +} + +bool GIF_is_opaque(GIF* gif) +{ + return gif->frame_info_array->tran < 0; +} + +void GIF_recycle(JNIEnv* env, GIF* gif) +{ + DGifCloseFile(gif->gif_file, &error_code); + gif->gif_file = NULL; + + free(gif->frame_info_array); + gif->frame_info_array = NULL; + + free(gif->buffer); + gif->buffer = NULL; + + free(gif->backup); + gif->backup = NULL; + + free(gif->shown_buffer); + gif->shown_buffer = NULL; + + if (gif->patch_head_input_stream != NULL) { + close_patch_head_input_stream(env, gif->patch_head_input_stream); + destroy_patch_head_input_stream(env, &gif->patch_head_input_stream); + gif->patch_head_input_stream = NULL; + } +} + +#endif // IMAGE_SUPPORT_GIF diff --git a/native/src/main/jni/image/image/image_gif.h b/native/src/main/jni/image/image/image_gif.h new file mode 100644 index 000000000..c2d28e3eb --- /dev/null +++ b/native/src/main/jni/image/image/image_gif.h @@ -0,0 +1,84 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/27/2015. +// + +#ifndef IMAGE_IMAGE_GIF_H +#define IMAGE_IMAGE_GIF_H + +#include "config.h" +#ifdef IMAGE_SUPPORT_GIF + +#include + +#include "patch_head_input_stream.h" +#include "gif_lib.h" +#include "../utils.h" + +#define IMAGE_GIF_DECODER_DESCRIPTION ("giflib " MAKESTRING(STRINGIZE, GIFLIB_MAJOR) "." \ + MAKESTRING(STRINGIZE, GIFLIB_MINOR) "." \ + MAKESTRING(STRINGIZE, GIFLIB_RELEASE)) + +#define IMAGE_GIF_MAGIC_NUMBER_0 0x47 +#define IMAGE_GIF_MAGIC_NUMBER_1 0x49 + +#define IMAGE_GIF_PREPARE_UNKNOWN 0x00 +#define IMAGE_GIF_PREPARE_NONE 0x01 +#define IMAGE_GIF_PREPARE_BACKGROUND 0x02 +#define IMAGE_GIF_PREPARE_USE_BACKUP 0x03 + +typedef struct +{ + int tran; + int disposal; + int delay; + int prepare; +} GIF_FRAME_INFO; + +typedef struct +{ + GifFileType* gif_file; + GIF_FRAME_INFO* frame_info_array; + void* buffer; + int buffer_index; + void* backup; + bool partially; + // Use extra buffer to avoid blink + void* shown_buffer; + PatchHeadInputStream* patch_head_input_stream; +} GIF; + +void* GIF_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially); +bool GIF_complete(JNIEnv* env, GIF* gif); +bool GIF_is_completed(GIF* gif); +void* GIF_get_pixels(GIF* gif); +int GIF_get_width(GIF* gif); +int GIF_get_height(GIF* gif); +int GIF_get_byte_count(GIF* gif); +void GIF_render(GIF* gif, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color); +void GIF_advance(GIF* gif); +int GIF_get_delay(GIF* gif); +int GIF_get_frame_count(GIF* gif); +bool GIF_is_opaque(GIF* gif); +void GIF_recycle(JNIEnv* env, GIF* gif); + +#endif // IMAGE_SUPPORT_GIF + +#endif // IMAGE_IMAGE_GIF_H diff --git a/native/src/main/jni/image/image/image_jpeg.c b/native/src/main/jni/image/image/image_jpeg.c new file mode 100644 index 000000000..c04a80675 --- /dev/null +++ b/native/src/main/jni/image/image/image_jpeg.c @@ -0,0 +1,210 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 1/3/2016. +// + +#include "config.h" +#ifdef IMAGE_SUPPORT_JPEG + +#include +#include + +#include "image_jpeg.h" +#include "image_utils.h" +#include "java_wrapper.h" +#include "../log.h" + +struct my_error_mgr { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +typedef struct my_error_mgr * my_error_ptr; + +static char emsg[JMSG_LENGTH_MAX]; + +static void my_error_exit(j_common_ptr cinfo) +{ + my_error_ptr myerr = (my_error_ptr) cinfo->err; + (*cinfo->err->format_message)(cinfo, emsg); + longjmp(myerr->setjmp_buffer, 1); +} + +static size_t custom_read(void * custom_stuff, unsigned char * buffer, size_t size) +{ + bool attach; + size_t read; + PatchHeadInputStream* patch_head_input_stream = custom_stuff; + JNIEnv *env = obtain_env(&attach); + + if (env == NULL) { + LOGE(MSG("Can't get JNIEnv")); + return 0; + } + + read = read_patch_head_input_stream(env, patch_head_input_stream, buffer, 0, size); + + if (attach) { + release_env(); + } + + return read; +} + +void* JPEG_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially) +{ + JPEG* jpeg = NULL; + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr; + unsigned char* buffer = NULL; + size_t stride; + unsigned char* line_buffer_array[3]; + int read_lines; + + jpeg = (JPEG*) malloc(sizeof(JPEG)); + if (jpeg == NULL) { + WTF_OM; + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + // Init + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + if (setjmp(jerr.setjmp_buffer)) { + LOGE(MSG("%s"), emsg); + free(jpeg); + free(buffer); + jpeg_destroy_decompress(&cinfo); + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + jpeg_create_decompress(&cinfo); + jpeg_custom_src(&cinfo, &custom_read, patch_head_input_stream); + jpeg_read_header(&cinfo, TRUE); + + // Start decompress + cinfo.out_color_space = JCS_EXT_RGBA; + jpeg_start_decompress(&cinfo); + + stride = cinfo.output_components * cinfo.output_width; + buffer = malloc(stride * cinfo.output_height); + if (buffer == NULL) { + free(jpeg); + jpeg_destroy_decompress(&cinfo); + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + // Copy buffer + line_buffer_array[0] = buffer; + line_buffer_array[1] = line_buffer_array[0] + stride; + line_buffer_array[2] = line_buffer_array[1] + stride; + while (cinfo.output_scanline < cinfo.output_height) { + read_lines = jpeg_read_scanlines(&cinfo, line_buffer_array, 3); + line_buffer_array[0] += stride * read_lines; + line_buffer_array[1] = line_buffer_array[0] + stride; + line_buffer_array[2] = line_buffer_array[1] + stride; + } + + // Finish decompress + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + // Close stream + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + + // Fill jpeg + jpeg->width = cinfo.output_width; + jpeg->height = cinfo.output_height; + jpeg->buffer = buffer; + + return jpeg; +} + +bool JPEG_complete(JPEG* jpeg) +{ + return true; +} + +bool JPEG_is_completed(JPEG* jpeg) +{ + return true; +} + +void* JPEG_get_pixels(JPEG* jpeg) +{ + return jpeg->buffer; +} + +int JPEG_get_width(JPEG* jpeg) +{ + return jpeg->width; +} + +int JPEG_get_height(JPEG* jpeg) +{ + return jpeg->height; +} + +int JPEG_get_byte_count(JPEG* jpeg) +{ + return jpeg->width * jpeg->height * 4; +} + +void JPEG_render(JPEG* jpeg, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color) +{ + copy_pixels(jpeg->buffer, jpeg->width, jpeg->height, src_x, src_y, + dst, dst_w, dst_h, dst_x, dst_y, + width, height, fill_blank, default_color); +} + +void JPEG_advance(JPEG* jpeg) +{ +} + +int JPEG_get_delay(JPEG* jpeg) +{ + return 0; +} + +int JPEG_get_frame_count(JPEG* jpeg) +{ + return 1; +} + +bool JPEG_is_opaque(JPEG* jpeg) +{ + return true; +} + +void JPEG_recycle(JPEG* jpeg) +{ + free(jpeg->buffer); + jpeg->buffer = NULL; + + free(jpeg); +} + +#endif // IMAGE_SUPPORT_JPEG diff --git a/native/src/main/jni/image/image/image_jpeg.h b/native/src/main/jni/image/image/image_jpeg.h new file mode 100644 index 000000000..e2a79d8e9 --- /dev/null +++ b/native/src/main/jni/image/image/image_jpeg.h @@ -0,0 +1,64 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 1/3/2016. +// + +#ifndef IMAGE_IMAGE_JPEG_H +#define IMAGE_IMAGE_JPEG_H + +#include "config.h" +#ifdef IMAGE_SUPPORT_JPEG + +#include +#include + +#include "jpeglib.h" +#include "patch_head_input_stream.h" +#include "../utils.h" + +#define IMAGE_JPEG_DECODER_DESCRIPTION ("libjpeg-turbo " MAKESTRING(STRINGIZE, LIBJPEG_TURBO_VERSION)) + +#define IMAGE_JPEG_MAGIC_NUMBER_0 0xFF +#define IMAGE_JPEG_MAGIC_NUMBER_1 0xD8 + +typedef struct +{ + unsigned int width; + unsigned int height; + void* buffer; +} JPEG; + +void* JPEG_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially); +bool JPEG_complete(JPEG* jpeg); +bool JPEG_is_completed(JPEG* jpeg); +void* JPEG_get_pixels(JPEG* jpeg); +int JPEG_get_width(JPEG* jpeg); +int JPEG_get_height(JPEG* jpeg); +int JPEG_get_byte_count(JPEG* jpeg); +void JPEG_render(JPEG* jpeg, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color); +void JPEG_advance(JPEG* jpeg); +int JPEG_get_delay(JPEG* jpeg); +int JPEG_get_frame_count(JPEG* jpeg); +bool JPEG_is_opaque(JPEG* jpeg); +void JPEG_recycle(JPEG* jpeg); + +#endif // IMAGE_SUPPORT_JPEG + +#endif // IMAGE_IMAGE_JPEG_H diff --git a/native/src/main/jni/image/image/image_plain.c b/native/src/main/jni/image/image/image_plain.c new file mode 100644 index 000000000..3090a93f4 --- /dev/null +++ b/native/src/main/jni/image/image/image_plain.c @@ -0,0 +1,129 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 5/3/2016. +// + +#include "config.h" +#ifdef IMAGE_SUPPORT_PLAIN + +#include +#include + +#include "image_plain.h" +#include "image_utils.h" +#include "../log.h" + +void* PLAIN_create(unsigned int width, unsigned int height, const void* data) +{ + PLAIN* plain = NULL; + void* buffer = NULL; + size_t length; + + plain = (PLAIN*) malloc(sizeof(PLAIN)); + if (plain == NULL) { + WTF_OM; + return NULL; + } + + length = (size_t) (width * height * 4); + + buffer = malloc(length); + if (buffer == NULL) { + WTF_OM; + free(plain); + return NULL; + } + + memcpy(buffer, data, length); + + plain->width = width; + plain->height = height; + plain->buffer = buffer; + + return plain; +} + +bool PLAIN_complete(PLAIN* plain) +{ + return true; +} + +bool PLAIN_is_completed(PLAIN* plain) +{ + return true; +} + +void* PLAIN_get_pixels(PLAIN* plain) +{ + return plain->buffer; +} + +int PLAIN_get_width(PLAIN* plain) +{ + return plain->width; +} + +int PLAIN_get_height(PLAIN* plain) +{ + return plain->height; +} + +int PLAIN_get_byte_count(PLAIN* plain) +{ + return plain->width * plain->height * 4; +} + +void PLAIN_render(PLAIN* plain, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color) +{ + copy_pixels(plain->buffer, plain->width, plain->height, src_x, src_y, + dst, dst_w, dst_h, dst_x, dst_y, + width, height, fill_blank, default_color); +} + +void PLAIN_advance(PLAIN* plain) +{ + +} + +int PLAIN_get_delay(PLAIN* plain) +{ + return 0; +} + +int PLAIN_get_frame_count(PLAIN* plain) +{ + return 1; +} + +bool PLAIN_is_opaque(PLAIN* plain) +{ + // TODO Check plain all alpha + return false; +} + +void PLAIN_recycle(PLAIN* plain) +{ + free(plain->buffer); + plain->buffer = NULL; + + free(plain); +} + +#endif // IMAGE_SUPPORT_PLAIN diff --git a/native/src/main/jni/image/image/image_plain.h b/native/src/main/jni/image/image/image_plain.h new file mode 100644 index 000000000..6aed105e6 --- /dev/null +++ b/native/src/main/jni/image/image/image_plain.h @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 5/3/2016. +// + +#ifndef IMAGE_IMAGE_PLAIN_H +#define IMAGE_IMAGE_PLAIN_H + +#include "config.h" +#ifdef IMAGE_SUPPORT_PLAIN + +#include + +#include "patch_head_input_stream.h" + +typedef struct +{ + unsigned int width; + unsigned int height; + void* buffer; +} PLAIN; + +void* PLAIN_create(unsigned int width, unsigned int height, const void* data); +bool PLAIN_complete(PLAIN* plain); +bool PLAIN_is_completed(PLAIN* plain); +void* PLAIN_get_pixels(PLAIN* plain); +int PLAIN_get_width(PLAIN* plain); +int PLAIN_get_height(PLAIN* plain); +int PLAIN_get_byte_count(PLAIN* plain); +void PLAIN_render(PLAIN* plain, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color); +void PLAIN_advance(PLAIN* plain); +int PLAIN_get_delay(PLAIN* plain); +int PLAIN_get_frame_count(PLAIN* plain); +bool PLAIN_is_opaque(PLAIN* plain); +void PLAIN_recycle(PLAIN* plain); + +#endif // IMAGE_SUPPORT_PLAIN + +#endif // IMAGE_IMAGE_PLAIN_H diff --git a/native/src/main/jni/image/image/image_png.c b/native/src/main/jni/image/image/image_png.c new file mode 100644 index 000000000..4a46e9718 --- /dev/null +++ b/native/src/main/jni/image/image/image_png.c @@ -0,0 +1,642 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/27/2015. +// + +#include "config.h" +#ifdef IMAGE_SUPPORT_PNG + +#include +#include + +#include "../log.h" +#include "../utils.h" +#include "image_png.h" +#include "image_utils.h" +#include "java_wrapper.h" + +static void user_read_fn(png_structp png_ptr, + png_bytep data, png_size_t length) +{ + bool attach; + PatchHeadInputStream* patch_head_input_stream = png_get_io_ptr(png_ptr); + JNIEnv *env = obtain_env(&attach); + + if (env == NULL) { + LOGE(MSG("Can't get JNIEnv")); + } + + read_patch_head_input_stream(env, patch_head_input_stream, data, 0, length); + + if (attach) { + release_env(); + } +} + +static void user_error_fn(png_structp png_ptr, + png_const_charp error_msg) +{ + LOGE(MSG("%s"), error_msg); +} + +static void user_warn_fn(png_structp png_ptr, + png_const_charp error_msg) +{ + LOGW(MSG("%s"), error_msg); +} + +static void free_frame_info_array(PNG_FRAME_INFO* frame_info_array, unsigned int count) +{ + int i; + + if (frame_info_array == NULL) { + return; + } + + for (i = 0; i < count; i++) { + PNG_FRAME_INFO* frame_info = frame_info_array + i; + free(frame_info->buffer); + frame_info->buffer = NULL; + } + free(frame_info_array); +} + +static bool read_image(png_structp png_ptr, unsigned char* buffer, unsigned int width, unsigned int height) +{ + int i; + unsigned char** line_buffer_ptr_array = (unsigned char**) malloc(height * sizeof(unsigned char*)); + if (line_buffer_ptr_array == NULL) { + WTF_OM; + return false; + } + for (i = 0; i < height; i++) { + line_buffer_ptr_array[i] = buffer + (width * i * 4); + } + png_read_image(png_ptr, line_buffer_ptr_array); + free(line_buffer_ptr_array); + return true; +} + +static void read_frame(png_structp png_ptr, png_infop info_ptr, PNG_FRAME_INFO* frame_info) +{ + unsigned int width; + unsigned int height; + unsigned int offset_x; + unsigned int offset_y; + unsigned short delay_num; + unsigned short delay_den; + unsigned char dop; + unsigned char bop; + unsigned int delay; + unsigned char* buffer = NULL; + + png_read_frame_head(png_ptr, info_ptr); + png_get_next_frame_fcTL(png_ptr, info_ptr, &width, &height, &offset_x, &offset_y, &delay_num, &delay_den, &dop, &bop); + + delay = 1000u * delay_num / delay_den; + + frame_info->width = width; + frame_info->height = height; + frame_info->offset_x = offset_x; + frame_info->offset_y = offset_y; + frame_info->delay = delay; + frame_info->dop = dop; + frame_info->bop = bop; + frame_info->pop = IMAGE_PNG_PREPARE_UNKNOWN; + + buffer = (unsigned char*) malloc(width * height * 4); + if (buffer == NULL) { + frame_info->buffer = NULL; + return; + } + + read_image(png_ptr, buffer, width, height); + frame_info->buffer = buffer; +} + +static void generate_pop(PNG_FRAME_INFO* frame_info_array, int count) +{ + unsigned char dop = 0xff; + int i; + + for (i = 0; i < count; i++){ + PNG_FRAME_INFO* frame_info = frame_info_array + i; + + switch (dop) { + case PNG_DISPOSE_OP_NONE: + frame_info->pop = IMAGE_PNG_PREPARE_NONE; + break; + case PNG_DISPOSE_OP_BACKGROUND: + frame_info->pop = IMAGE_PNG_PREPARE_BACKGROUND; + break; + case PNG_DISPOSE_OP_PREVIOUS: + frame_info->pop = IMAGE_PNG_PREPARE_USE_BACKUP; + break; + default: + frame_info->pop = IMAGE_PNG_PREPARE_BACKGROUND; + break; + } + + dop = frame_info->dop; + } +} + +void* PNG_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially) +{ + PNG *png = NULL; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + bool apng; + unsigned int width; + unsigned int height; + int color_type; + int bit_depth; + bool is_opaque; + unsigned char* buffer = NULL; + unsigned int frame_count = 0; + bool hide_first_frame = false; + PNG_FRAME_INFO* frame_info_array = NULL; + int i; + + png = (PNG *) malloc(sizeof(PNG)); + if (png == NULL) { + WTF_OM; + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, &user_error_fn, &user_warn_fn); + if (png_ptr == NULL) { + free(png); + png = NULL; + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + free(png); + png = NULL; + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + LOGE(MSG("Error in png decode")); + free_frame_info_array(frame_info_array, frame_count); + frame_info_array = NULL; + free(buffer); + buffer = NULL; + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + free(png); + png = NULL; + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + // Set custom read function + png_set_read_fn(png_ptr, patch_head_input_stream, &user_read_fn); + + // Get png info + png_read_info(png_ptr, info_ptr); + + // Check apng + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) { + apng = true; + } else { + apng = false; + } + + // PNG info + width = png_get_image_width(png_ptr, info_ptr); + height = png_get_image_height(png_ptr, info_ptr); + color_type = png_get_color_type(png_ptr, info_ptr); + bit_depth = png_get_bit_depth(png_ptr, info_ptr); + + // Create buffer + buffer = (unsigned char*) malloc(width * height * 4); + if (buffer == NULL) { + WTF_OM; + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + free(png); + png = NULL; + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + + if (apng) { + // Get frame count + frame_count = png_get_num_frames(png_ptr, info_ptr); + hide_first_frame = png_get_first_frame_is_hidden(png_ptr, info_ptr); + if (hide_first_frame) { + frame_count--; + } + + // Create frame info array + frame_info_array = (PNG_FRAME_INFO*) calloc(frame_count, sizeof(PNG_FRAME_INFO)); + if (frame_info_array == NULL) { + WTF_OM; + free(buffer); + buffer = NULL; + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + free(png); + png = NULL; + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + return NULL; + } + } + + // Configure to ARGB + png_set_expand(png_ptr); + if (bit_depth == 16) { + png_set_scale_16(png_ptr); + } + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + } + if (!(color_type & PNG_COLOR_MASK_ALPHA)) { + is_opaque = true; + png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER); + } else { + is_opaque = false; + } + + if (apng) { + if (hide_first_frame) { + // Skip first frame + read_image(png_ptr, buffer, width, height); + } + + // Read first frame + read_frame(png_ptr, info_ptr, frame_info_array); + // Fix dop + if (frame_info_array->dop == PNG_DISPOSE_OP_PREVIOUS) { + frame_info_array->dop = PNG_DISPOSE_OP_BACKGROUND; + } + + if (!partially || frame_count == 1) { + // Read all frame + for (i = 1; i < frame_count; read_frame(png_ptr, info_ptr, frame_info_array + i++)); + + // Generate pop + generate_pop(frame_info_array, frame_count); + + // End read + png_read_end(png_ptr, info_ptr); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + // Close input stream + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + + png->partially = false; + png->png_ptr = NULL; + png->info_ptr = NULL; + png->patch_head_input_stream = NULL; + } else { + png->partially = true; + png->png_ptr = png_ptr; + png->info_ptr = info_ptr; + png->patch_head_input_stream = patch_head_input_stream; + } + + // Fill PNG + png->width = width; + png->height = height; + png->is_opaque = is_opaque; + png->buffer = buffer; + png->apng = true; + png->buffer_index = -1; + png->frame_info_array = frame_info_array; + png->frame_count = frame_count; + png->backup = NULL; + + // Render first frame + PNG_advance(png); + } else { + read_image(png_ptr, buffer, width, height); + + // End read + png_read_end(png_ptr, info_ptr); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + // Close input stream + close_patch_head_input_stream(env, patch_head_input_stream); + destroy_patch_head_input_stream(env, &patch_head_input_stream); + + // Fill PNG + png->width = width; + png->height = height; + png->buffer = buffer; + png->apng = false; + png->buffer_index = 0; + png->frame_info_array = NULL; + png->frame_count = 0; + png->backup = NULL; + png->partially = false; + png->png_ptr = NULL; + png->info_ptr = NULL; + png->patch_head_input_stream = NULL; + } + + return png; +} + +bool PNG_complete(JNIEnv *env, PNG* png) +{ + int i; + + if (!png->partially) { + return true; + } + + if (png->png_ptr == NULL || png->info_ptr == NULL || png->patch_head_input_stream == NULL) { + LOGE(MSG("Some stuff is NULL")); + return false; + } + + // Read left frames + for (i = 1; i < png->frame_count; read_frame(png->png_ptr, png->info_ptr, png->frame_info_array + i++)); + + // Generate pop + generate_pop(png->frame_info_array, png->frame_count); + + // End read + png_read_end(png->png_ptr, png->info_ptr); + png_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL); + + // Close input stream + close_patch_head_input_stream(env, png->patch_head_input_stream); + destroy_patch_head_input_stream(env, &png->patch_head_input_stream); + + // Clean up + png->partially = false; + png->png_ptr = NULL; + png->info_ptr = NULL; + png->patch_head_input_stream = NULL; + + return true; +} + +bool PNG_is_completed(PNG* png) +{ + return !png->partially; +} + +void* PNG_get_pixels(PNG* png) +{ + if (!png->partially && PNG_get_frame_count(png) == 1) { + return png->buffer; + } else { + return NULL; + } +} + +int PNG_get_width(PNG* png) +{ + return png->width; +} + +int PNG_get_height(PNG* png) +{ + return png->height; +} + +int PNG_get_byte_count(PNG* png) +{ + int i; + PNG_FRAME_INFO* frame_info; + int size = 0; + // Add buffer size + if (png->buffer != NULL) { + size += png->width * png->height * 4; + } + // Add backup size + if (png->backup != NULL) { + size += png->width * png->height * 4; + } + // Add frames size + if (png->frame_info_array != NULL) { + for (i = 0; i < png->frame_count; i++) { + frame_info = png->frame_info_array + i; + // Add frame buffer size + if (frame_info->buffer != NULL) { + size += frame_info->width * frame_info->height * 4; + } + } + } + return size; +} + +void PNG_render(PNG* png, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color) +{ + copy_pixels(png->buffer, png->width, png->height, src_x, src_y, + dst, dst_w, dst_h, dst_x, dst_y, + width, height, fill_blank, default_color); +} + +static void backup(PNG* png) +{ + if (png->backup == NULL) { + png->backup = (unsigned char*) malloc(png->width * png->height * 4); + if (png->backup == NULL) { + WTF_OM; + return; + } + } + memcpy(png->backup, png->buffer, png->width * png->height * 4); +} + +static void restore(PNG* png) +{ + if (png->backup == NULL) { + LOGE(MSG("Backup is NULL")); + } else { + memcpy(png->buffer, png->backup, png->width * png->height * 4); + } +} + +static void switch_buffer_backup(PNG* png) +{ + unsigned char* temp; + + if (png->backup == NULL) { + backup(png); + } else { + temp = png->buffer; + png->buffer = png->backup; + png->backup = temp; + } +} + +static void blend_over(unsigned char* dp, const unsigned char* sp, size_t len) +{ + unsigned int i; + int u, v, al; + + for (i = 0; i < len; i += 4, sp += 4, dp += 4) { + if (sp[3] == 255) { + memcpy(dp, sp, 4); + } else if (sp[3] != 0) { + if (dp[3] != 0) { + u = sp[3] * 255; + v = (255 - sp[3]) * dp[3]; + al = u + v; + dp[0] = (unsigned char) ((sp[0] * u + dp[0] * v) / al); + dp[1] = (unsigned char) ((sp[1] * u + dp[1] * v) / al); + dp[2] = (unsigned char) ((sp[2] * u + dp[2] * v) / al); + dp[3] = (unsigned char) (al / 255); + } else { + memcpy(dp, sp, 4); + } + } + } +} + +static void blend(unsigned char* src, int src_width, int src_height, int offset_x, int offset_y, + unsigned char* dst, int dst_width, int dst_height, bool blend_op_over) +{ + int i; + unsigned char* src_ptr; + unsigned char* dst_ptr; + size_t len; + int copy_width = MIN(dst_width - offset_x, src_width); + int copy_height = MIN(dst_height - offset_y, src_height); + + for (i = 0; i < copy_height; i++) { + src_ptr = src + (i * src_width * 4); + dst_ptr = dst + (((offset_y + i) * dst_width + offset_x) * 4); + len = (size_t) (copy_width * 4); + + if (blend_op_over) { + blend_over(dst_ptr, src_ptr, len); + } else { + memcpy(dst_ptr, src_ptr, len); + } + } +} + +void PNG_advance(PNG* png) +{ + int index; + + if (!png->apng) { + LOGW(MSG("It is not apng, no need to advance")); + return; + } + + index = (png->buffer_index + 1) % png->frame_count; + if (index != 0 && png->partially) { + LOGE(MSG("The png is only decoded partially. Only the first frame can be shown.")); + return; + } + + PNG_FRAME_INFO* frame_info = png->frame_info_array + index; + + if (frame_info->dop == PNG_DISPOSE_OP_PREVIOUS && frame_info->pop == IMAGE_PNG_PREPARE_USE_BACKUP) { + switch_buffer_backup(png); + } else { + // Backup + if (frame_info->dop == PNG_DISPOSE_OP_PREVIOUS) { + backup(png); + } + + // Prepare + switch (frame_info->pop) { + case IMAGE_PNG_PREPARE_NONE: + // Do nothing + break; + default: + case IMAGE_PNG_PREPARE_BACKGROUND: + // Set transparent + memset(png->buffer, '\0', png->width * png->height * 4); + break; + case IMAGE_PNG_PREPARE_USE_BACKUP: + restore(png); + break; + } + } + + blend(frame_info->buffer, frame_info->width, frame_info->height, frame_info->offset_x, frame_info->offset_y, + png->buffer, png->width, png->height, frame_info->bop == PNG_BLEND_OP_OVER); + + png->buffer_index = index; +} + +int PNG_get_delay(PNG* png) +{ + if (png->apng) { + return png->frame_info_array[png->buffer_index].delay; + } else { + return 0; + } +} + +int PNG_get_frame_count(PNG* png) +{ + if (png->apng) { + return png->frame_count; + } else { + return 1; + } +} + +bool PNG_is_opaque(PNG* png) +{ + return png->is_opaque; +} + +void PNG_recycle(JNIEnv *env, PNG* png) +{ + if (png == NULL) { + return; + } + + free(png->buffer); + png->buffer = NULL; + + free_frame_info_array(png->frame_info_array, png->frame_count); + png->frame_info_array = NULL; + + free(png->backup); + png->backup = NULL; + + if (png->png_ptr != NULL && png->info_ptr != NULL) { + png_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL); + } + png->png_ptr = NULL; + png->info_ptr = NULL; + + if (png->patch_head_input_stream != NULL) { + close_patch_head_input_stream(env, png->patch_head_input_stream); + destroy_patch_head_input_stream(env, &png->patch_head_input_stream); + png->patch_head_input_stream = NULL; + } +} + +#endif // IMAGE_SUPPORT_PNG diff --git a/native/src/main/jni/image/image/image_png.h b/native/src/main/jni/image/image/image_png.h new file mode 100644 index 000000000..02642c230 --- /dev/null +++ b/native/src/main/jni/image/image/image_png.h @@ -0,0 +1,89 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/27/2015. +// + +#ifndef IMAGE_IMAGE_PNG_H +#define IMAGE_IMAGE_PNG_H + +#include "config.h" +#ifdef IMAGE_SUPPORT_PNG + +#include + +#include "png.h" +#include "patch_head_input_stream.h" + +#define IMAGE_PNG_DECODER_DESCRIPTION ("libpng " PNG_LIBPNG_VER_STRING) + +#define IMAGE_PNG_MAGIC_NUMBER_0 0x89 +#define IMAGE_PNG_MAGIC_NUMBER_1 0x50 + +#define IMAGE_PNG_PREPARE_UNKNOWN 0x00 +#define IMAGE_PNG_PREPARE_NONE 0x01 +#define IMAGE_PNG_PREPARE_BACKGROUND 0x02 +#define IMAGE_PNG_PREPARE_USE_BACKUP 0x03 + +typedef struct { + unsigned char* buffer; + unsigned int width; + unsigned int height; + unsigned int offset_x; + unsigned int offset_y; + unsigned int delay; // ms + unsigned char dop; + unsigned char bop; + unsigned char pop; +} PNG_FRAME_INFO; + +typedef struct +{ + unsigned int width; + unsigned int height; + bool is_opaque; + unsigned char* buffer; + bool apng; + int buffer_index; + PNG_FRAME_INFO* frame_info_array; + unsigned int frame_count; + unsigned char* backup; + bool partially; + png_structp png_ptr; + png_infop info_ptr; + PatchHeadInputStream* patch_head_input_stream; +} PNG; + +void* PNG_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially); +bool PNG_complete(JNIEnv* env, PNG* png); +bool PNG_is_completed(PNG* png); +void* PNG_get_pixels(PNG* png); +int PNG_get_width(PNG* png); +int PNG_get_height(PNG* png); +int PNG_get_byte_count(PNG* png); +void PNG_render(PNG* png, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color); +void PNG_advance(PNG* png); +int PNG_get_delay(PNG* png); +int PNG_get_frame_count(PNG* png); +bool PNG_is_opaque(PNG* png); +void PNG_recycle(JNIEnv* env, PNG* png); + +#endif // IMAGE_SUPPORT_PNG + +#endif // IMAGE_IMAGE_PNG_H diff --git a/native/src/main/jni/image/image/image_utils.c b/native/src/main/jni/image/image/image_utils.c new file mode 100644 index 000000000..e88bee1a2 --- /dev/null +++ b/native/src/main/jni/image/image/image_utils.c @@ -0,0 +1,185 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/27/2015. +// + +#include "image_utils.h" + +#include +#include + +#define LITTLE_ENDIAN 0x00 +#define BIG_ENDIAN 0x01 + +static unsigned int endian = ~0U; + +static unsigned int get_endian() { + unsigned int x = 1; + + if (endian == ~0U) { + if (1 == ((char *)&x)[0]) { + endian = LITTLE_ENDIAN; + } else { + endian = BIG_ENDIAN; + } + } + + return endian; +} + +static int convert_color(int origin) { + int result; + unsigned char* orPtr = (unsigned char *) &origin; + unsigned char* rePtr = (unsigned char *) &result; + + if (get_endian() == BIG_ENDIAN) { + rePtr[0] = orPtr[1]; + rePtr[1] = orPtr[2]; + rePtr[2] = orPtr[3]; + rePtr[3] = orPtr[0]; + } else { + rePtr[0] = orPtr[2]; + rePtr[1] = orPtr[1]; + rePtr[2] = orPtr[0]; + rePtr[3] = orPtr[3]; + } + + return result; +} + +static void memset_int(int* dst, int val, size_t size) { + int* maxPtr = dst + size; + int* ptr = dst; + while(ptr < maxPtr) + *ptr++ = val; +} + +bool copy_pixels_internal(const void* src, int src_w, int src_h, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int color) +{ + int left; + int line; + size_t line_stride; + int src_stride; + int src_pos; + int dst_pos; + size_t dst_blank_length; + + // Sanitize + if (src_x < 0) { + width -= src_x; + dst_x -= src_x; + src_x = 0; + } + if (dst_x < 0) { + width -= dst_x; + src_x -= dst_x; + dst_x = 0; + } + if (width <= 0) { + return false; + } + if (src_y < 0) { + height -= src_y; + dst_y -= src_y; + src_y = 0; + } + if (dst_y < 0) { + height -= dst_y; + src_y -= dst_y; + dst_y = 0; + } + if (height <= 0) { + return false; + } + left = src_x + width - src_w; + if (left > 0) { + width -= left; + } + left = dst_x + width - dst_w; + if (left > 0) { + width -= left; + } + if (width <= 0) { + return false; + } + left = src_y + height - src_h; + if (left > 0) { + height -= left; + } + left = dst_y + height - dst_h; + if (left > 0) { + height -= left; + } + if (height <= 0) { + return false; + } + + // Init + line_stride = (size_t) (width * 4); + src_stride = src_w * 4; + src_pos = src_y * src_stride + src_x * 4; + dst_pos = 0; + + dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4; + // Fill start blank if necessary + if (fill_blank) { + memset_int((int *) (dst + dst_pos), color, dst_blank_length / 4); + } + + // First line + dst_pos += dst_blank_length; + memcpy(dst + dst_pos, src + src_pos, line_stride); + dst_pos += line_stride; + src_pos += src_stride; + + // Other lines + dst_blank_length = (size_t) ((dst_w - width) * 4); + for (line = 1; line < height; line++) { + if (fill_blank) { + memset_int((int *) (dst + dst_pos), color, dst_blank_length / 4); + } + dst_pos += dst_blank_length; + memcpy(dst + dst_pos, src + src_pos, line_stride); + dst_pos += line_stride; + src_pos += src_stride; + } + + // Fill left blank if necessary + if (fill_blank) { + memset_int((int *) (dst + dst_pos), color, (size_t) (dst_w * dst_h * 4 - dst_pos) / 4); + } + + return true; +} + +void copy_pixels(const void* src, int src_w, int src_h, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color) +{ + int color = 0; + if (fill_blank) { + color = convert_color(default_color); + } + + if (!copy_pixels_internal(src, src_w, src_h, src_x, src_y, dst, dst_w, dst_h, dst_x, + dst_y, width, height, fill_blank, color) && fill_blank) { + memset_int(dst, color, (size_t) (dst_w * dst_h)); + } +} diff --git a/native/src/main/jni/image/image/image_utils.h b/native/src/main/jni/image/image/image_utils.h new file mode 100644 index 000000000..455ecd51f --- /dev/null +++ b/native/src/main/jni/image/image/image_utils.h @@ -0,0 +1,14 @@ +// +// Created by Hippo on 12/27/2015. +// + +#ifndef IMAGE_IMAGE_UTILS_H +#define IMAGE_IMAGE_UTILS_H + +#include + +void copy_pixels(const void* src, int src_w, int src_h, int src_x, int src_y, + void* dst, int dst_w, int dst_h, int dst_x, int dst_y, + int width, int height, bool fill_blank, int default_color); + +#endif //IMAGE_IMAGE_UTILS_H diff --git a/native/src/main/jni/image/image/java_wrapper.c b/native/src/main/jni/image/image/java_wrapper.c new file mode 100644 index 000000000..ff0e7c1b4 --- /dev/null +++ b/native/src/main/jni/image/image/java_wrapper.c @@ -0,0 +1,304 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/27/2015. +// + +#include +#include +#include + +#include "java_wrapper.h" +#include "input_stream.h" +#include "image.h" +#include "../log.h" + +static JavaVM* jvm; + +static void* tile_buffer; + +JNIEnv *obtain_env(bool *attach) +{ + JNIEnv *env; + switch ((*jvm)->GetEnv(jvm, (void**) &env, JNI_VERSION_1_6)) { + case JNI_EDETACHED: + if ((*jvm)->AttachCurrentThread(jvm, &env, NULL) == JNI_OK) { + *attach = true; + return env; + } else { + return NULL; + } + case JNI_OK: + *attach = false; + return env; + default: + case JNI_EVERSION: + return NULL; + } +} + +void release_env() +{ + (*jvm)->DetachCurrentThread(jvm); +} + +jobject create_image_object(JNIEnv* env, void* ptr, int format, int width, int height) +{ + jclass image_clazz; + jmethodID constructor; + + image_clazz = (*env)->FindClass(env, "com/hippo/image/Image"); + constructor = (*env)->GetMethodID(env, image_clazz, "", "(JIII)V"); + if (constructor == 0) { + LOGE(MSG("Can't find Image object constructor")); + return NULL; + } else { + return (*env)->NewObject(env, image_clazz, constructor, + (jlong) (uintptr_t) ptr, (jint) format, (jint) width, (jint) height); + } +} + +JNIEXPORT jobject JNICALL +Java_com_hippo_image_Image_nativeDecode(JNIEnv* env, + jclass clazz, jobject is, jboolean partially) +{ + InputStream* input_stream; + int format; + void* image; + jobject image_object; + + input_stream = create_input_stream(env, is); + if (input_stream == NULL) { + return NULL; + } + + image = decode(env, input_stream, partially, &format); + if (image == NULL) { + return NULL; + } + + image_object = create_image_object(env, image, format, + get_width(image, format), get_height(image, format)); + if (image_object == NULL) { + recycle(env, image, format); + return NULL; + } else { + return image_object; + } +} + +JNIEXPORT jobject JNICALL +Java_com_hippo_image_Image_nativeCreate(JNIEnv* env, + jclass clazz, jobject bitmap) +{ +#ifdef IMAGE_SUPPORT_PLAIN + AndroidBitmapInfo info; + void *pixels = NULL; + void* image = NULL; + jobject image_object; + + AndroidBitmap_getInfo(env, bitmap, &info); + AndroidBitmap_lockPixels(env, bitmap, &pixels); + if (pixels == NULL) { + LOGE(MSG("Can't lock bitmap pixels")); + return NULL; + } + + image = create(info.width, info.height, pixels); + + AndroidBitmap_unlockPixels(env, bitmap); + + if (image == NULL) { + return NULL; + } + + image_object = create_image_object(env, image, IMAGE_FORMAT_PLAIN, + info.width, info.height); + if (image_object == NULL) { + recycle(env, image, IMAGE_FORMAT_PLAIN); + return NULL; + } else { + return image_object; + } +#else + return NULL; +#endif +} + +JNIEXPORT jboolean JNICALL +Java_com_hippo_image_Image_nativeComplete(JNIEnv* env, + jclass clazz, jlong ptr, jint format) +{ + return (jboolean) complete(env, (void*) (intptr_t) ptr, format); +} + +JNIEXPORT jboolean JNICALL +Java_com_hippo_image_Image_nativeIsCompleted(JNIEnv* env, + jclass clazz, jlong ptr, jint format) +{ + return (jboolean) is_completed((void*) (intptr_t) ptr, format); +} + +JNIEXPORT jint JNICALL +Java_com_hippo_image_Image_nativeGetByteCount(JNIEnv* env, + jclass clazz, jlong ptr, jint format) +{ + return (jint) get_byte_count((void*) (intptr_t) ptr, format); +} + +JNIEXPORT void JNICALL +Java_com_hippo_image_Image_nativeRender(JNIEnv* env, + jclass clazz, jlong ptr, jint format, + jint src_x, jint src_y, jobject dst, jint dst_x, jint dst_y, + jint width, jint height, jboolean fill_blank, jint default_color) +{ + AndroidBitmapInfo info; + void *pixels = NULL; + + AndroidBitmap_getInfo(env, dst, &info); + AndroidBitmap_lockPixels(env, dst, &pixels); + if (pixels == NULL) { + LOGE(MSG("Can't lock bitmap pixels")); + return; + } + + render((void*) (intptr_t) ptr, format, src_x, src_y, + pixels, info.width, info.height, dst_x, dst_y, + width, height, fill_blank, default_color); + + AndroidBitmap_unlockPixels(env, dst); + + return; +} + +JNIEXPORT void JNICALL +Java_com_hippo_image_Image_nativeTexImage(JNIEnv* env, + jclass clazz, jlong ptr, jint format, jboolean init, + jint src_x, jint src_y, jint width, jint height) +{ + // Check tile_buffer NULL + if (NULL == tile_buffer) { + return; + } + // Check render size + if (width * height > IMAGE_TILE_MAX_SIZE) { + return; + } + + render((void*) (intptr_t) ptr, format, src_x, src_y, + tile_buffer, width, height, 0, 0, + width, height, false, 0); + + if (init) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, tile_buffer); + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, + GL_RGBA, GL_UNSIGNED_BYTE, tile_buffer); + } +} + +JNIEXPORT void JNICALL +Java_com_hippo_image_Image_nativeAdvance(JNIEnv* env, + jclass clazz, jlong ptr, jint format) +{ + advance((void*) (intptr_t) ptr, format); +} + +JNIEXPORT jint JNICALL +Java_com_hippo_image_Image_nativeGetDelay(JNIEnv* env, + jclass clazz, jlong ptr, jint format) +{ + return (jint) get_delay((void*) (intptr_t) ptr, format); +} + +JNIEXPORT jint JNICALL +Java_com_hippo_image_Image_nativeFrameCount(JNIEnv* env, + jclass clazz, jlong ptr, jint format) +{ + return (jint) get_frame_count((void*) (intptr_t) ptr, format); +} + +JNIEXPORT jboolean JNICALL +Java_com_hippo_image_Image_nativeIsOpaque(JNIEnv* env, + jclass clazz, jlong ptr, jint format) +{ + return (jboolean) is_opaque((void*) (intptr_t) ptr, format); +} + +JNIEXPORT jboolean JNICALL +Java_com_hippo_image_Image_nativeIsGray(JNIEnv* env, + jclass clazz, jlong ptr, jint format, jint error) +{ + return (jboolean) is_gray((void*) (intptr_t) ptr, format, error); +} + +JNIEXPORT void JNICALL +Java_com_hippo_image_Image_nativeClahe(JNIEnv* env, + jclass clazz, jlong ptr, jint format, jboolean to_gray) +{ + clahe((void*) (intptr_t) ptr, format, to_gray); +} + +JNIEXPORT void JNICALL +Java_com_hippo_image_Image_nativeRecycle(JNIEnv* env, + jclass clazz, jlong ptr, jint format) +{ + recycle(env, (void*) (intptr_t) ptr, format); +} + +JNIEXPORT jintArray JNICALL +Java_com_hippo_image_Image_nativeGetSupportedImageFormats( + JNIEnv *env, jclass clazz) +{ + int formats[IMAGE_MAX_SUPPORTED_FORMAT_COUNT]; + int count = get_supported_formats(formats); + jintArray array = (*env)->NewIntArray(env, count); + if (array == NULL) { + return NULL; + } + (*env)->SetIntArrayRegion(env, array, 0, count, formats); + return array; +} + +JNIEXPORT jstring JNICALL +Java_com_hippo_image_Image_nativeGetDecoderDescription( + JNIEnv *env, jclass clazz, jint format) +{ + const char *description = get_decoder_description(format); + if (description == NULL) { + return NULL; + } else { + return (*env)->NewStringUTF(env, description); + } +} + +bool image_onLoad(JavaVM *vm) +{ + jvm = vm; + + tile_buffer = malloc(IMAGE_TILE_MAX_SIZE * 4); + + return true; +} + +JNIEXPORT void JNICALL +JNI_OnUnload(JavaVM *vm, void *reserved) +{ + free(tile_buffer); + tile_buffer = NULL; +} diff --git a/native/src/main/jni/image/image/java_wrapper.h b/native/src/main/jni/image/image/java_wrapper.h new file mode 100644 index 000000000..f4b24d9ce --- /dev/null +++ b/native/src/main/jni/image/image/java_wrapper.h @@ -0,0 +1,19 @@ +// +// Created by Hippo on 12/28/2015. +// + +#ifndef IMAGE_JAVA_WRAPPER_H +#define IMAGE_JAVA_WRAPPER_H + +#include +#include + +#define IMAGE_TILE_MAX_SIZE (512 * 512) + +bool image_onLoad(JavaVM *vm); + +JNIEnv *obtain_env(bool *attach); + +void release_env(); + +#endif // IMAGE_JAVA_WRAPPER_H diff --git a/native/src/main/jni/image/libjpeg-turbo/CMakeLists.txt b/native/src/main/jni/image/libjpeg-turbo/CMakeLists.txt new file mode 100644 index 000000000..dcbf7ac0b --- /dev/null +++ b/native/src/main/jni/image/libjpeg-turbo/CMakeLists.txt @@ -0,0 +1,181 @@ +# Copyright 2018 Hippo Seven +# +# 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. + +cmake_minimum_required(VERSION 3.4.1) +project(libjpeg-turbo C) + +set(LIBJPEG_TURBO_SOURCES + libjpeg-turbo/jaricom.c + libjpeg-turbo/jcomapi.c + libjpeg-turbo/jdapimin.c + libjpeg-turbo/jdapistd.c + libjpeg-turbo/jdarith.c + libjpeg-turbo/jdatasrc.c + libjpeg-turbo/jdatasrc-tj.c + libjpeg-turbo/jdcoefct.c + libjpeg-turbo/jdcolor.c + libjpeg-turbo/jddctmgr.c + libjpeg-turbo/jdhuff.c + libjpeg-turbo/jdinput.c + libjpeg-turbo/jdmainct.c + libjpeg-turbo/jdmarker.c + libjpeg-turbo/jdmaster.c + libjpeg-turbo/jdmerge.c + libjpeg-turbo/jdphuff.c + libjpeg-turbo/jdpostct.c + libjpeg-turbo/jdsample.c + libjpeg-turbo/jdtrans.c + libjpeg-turbo/jerror.c + libjpeg-turbo/jfdctflt.c + libjpeg-turbo/jfdctfst.c + libjpeg-turbo/jfdctint.c + libjpeg-turbo/jidctflt.c + libjpeg-turbo/jidctfst.c + libjpeg-turbo/jidctint.c + libjpeg-turbo/jidctred.c + libjpeg-turbo/jmemmgr.c + libjpeg-turbo/jmemnobs.c + libjpeg-turbo/jquant1.c + libjpeg-turbo/jquant2.c + libjpeg-turbo/jutils.c +) +set(LIBJPEG_TURBO_INCLUDES + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/libjpeg-turbo +) + +if (${ANDROID_ABI} STREQUAL "armeabi-v7a") + + enable_language(ASM) + + set(LIBJPEG_TURBO_SOURCES + ${LIBJPEG_TURBO_SOURCES} + libjpeg-turbo/simd/arm/jsimd.c + libjpeg-turbo/simd/arm/jsimd_neon.S + ) + +elseif (${ANDROID_ABI} STREQUAL "arm64-v8a") + + enable_language(ASM) + + set(LIBJPEG_TURBO_SOURCES + ${LIBJPEG_TURBO_SOURCES} + libjpeg-turbo/simd/arm64/jsimd.c + libjpeg-turbo/simd/arm64/jsimd_neon.S + ) + +elseif (${ANDROID_ABI} STREQUAL "x86") + + enable_language(ASM_NASM) + + set(LIBJPEG_TURBO_SOURCES + ${LIBJPEG_TURBO_SOURCES} + libjpeg-turbo/simd/i386/jsimd.c + libjpeg-turbo/simd/i386/jsimdcpu.asm + libjpeg-turbo/simd/i386/jfdctflt-3dn.asm + libjpeg-turbo/simd/i386/jidctflt-3dn.asm + libjpeg-turbo/simd/i386/jquant-3dn.asm + libjpeg-turbo/simd/i386/jccolor-mmx.asm + libjpeg-turbo/simd/i386/jcgray-mmx.asm + libjpeg-turbo/simd/i386/jcsample-mmx.asm + libjpeg-turbo/simd/i386/jdcolor-mmx.asm + libjpeg-turbo/simd/i386/jdmerge-mmx.asm + libjpeg-turbo/simd/i386/jdsample-mmx.asm + libjpeg-turbo/simd/i386/jfdctfst-mmx.asm + libjpeg-turbo/simd/i386/jfdctint-mmx.asm + libjpeg-turbo/simd/i386/jidctfst-mmx.asm + libjpeg-turbo/simd/i386/jidctint-mmx.asm + libjpeg-turbo/simd/i386/jidctred-mmx.asm + libjpeg-turbo/simd/i386/jquant-mmx.asm + libjpeg-turbo/simd/i386/jfdctflt-sse.asm + libjpeg-turbo/simd/i386/jidctflt-sse.asm + libjpeg-turbo/simd/i386/jquant-sse.asm + libjpeg-turbo/simd/i386/jccolor-sse2.asm + libjpeg-turbo/simd/i386/jcgray-sse2.asm + libjpeg-turbo/simd/i386/jchuff-sse2.asm + libjpeg-turbo/simd/i386/jcphuff-sse2.asm + libjpeg-turbo/simd/i386/jcsample-sse2.asm + libjpeg-turbo/simd/i386/jdcolor-sse2.asm + libjpeg-turbo/simd/i386/jdmerge-sse2.asm + libjpeg-turbo/simd/i386/jdsample-sse2.asm + libjpeg-turbo/simd/i386/jfdctfst-sse2.asm + libjpeg-turbo/simd/i386/jfdctint-sse2.asm + libjpeg-turbo/simd/i386/jidctflt-sse2.asm + libjpeg-turbo/simd/i386/jidctfst-sse2.asm + libjpeg-turbo/simd/i386/jidctint-sse2.asm + libjpeg-turbo/simd/i386/jidctred-sse2.asm + libjpeg-turbo/simd/i386/jquantf-sse2.asm + libjpeg-turbo/simd/i386/jquanti-sse2.asm + libjpeg-turbo/simd/i386/jccolor-avx2.asm + libjpeg-turbo/simd/i386/jcgray-avx2.asm + libjpeg-turbo/simd/i386/jcsample-avx2.asm + libjpeg-turbo/simd/i386/jdcolor-avx2.asm + libjpeg-turbo/simd/i386/jdmerge-avx2.asm + libjpeg-turbo/simd/i386/jdsample-avx2.asm + libjpeg-turbo/simd/i386/jfdctint-avx2.asm + libjpeg-turbo/simd/i386/jidctint-avx2.asm + libjpeg-turbo/simd/i386/jquanti-avx2.asm + ) + + set(CMAKE_ASM_NASM_FLAGS "-DELF -DPIC -I${CMAKE_CURRENT_SOURCE_DIR}/libjpeg-turbo/simd/nasm") + +elseif (${ANDROID_ABI} STREQUAL "x86_64") + + enable_language(ASM_NASM) + + set(LIBJPEG_TURBO_SOURCES + ${LIBJPEG_TURBO_SOURCES} + libjpeg-turbo/simd/x86_64/jsimd.c + libjpeg-turbo/simd/x86_64/jsimdcpu.asm + libjpeg-turbo/simd/x86_64/jfdctflt-sse.asm + libjpeg-turbo/simd/x86_64/jccolor-sse2.asm + libjpeg-turbo/simd/x86_64/jcgray-sse2.asm + libjpeg-turbo/simd/x86_64/jchuff-sse2.asm + libjpeg-turbo/simd/x86_64/jcphuff-sse2.asm + libjpeg-turbo/simd/x86_64/jcsample-sse2.asm + libjpeg-turbo/simd/x86_64/jdcolor-sse2.asm + libjpeg-turbo/simd/x86_64/jdmerge-sse2.asm + libjpeg-turbo/simd/x86_64/jdsample-sse2.asm + libjpeg-turbo/simd/x86_64/jfdctfst-sse2.asm + libjpeg-turbo/simd/x86_64/jfdctint-sse2.asm + libjpeg-turbo/simd/x86_64/jidctflt-sse2.asm + libjpeg-turbo/simd/x86_64/jidctfst-sse2.asm + libjpeg-turbo/simd/x86_64/jidctint-sse2.asm + libjpeg-turbo/simd/x86_64/jidctred-sse2.asm + libjpeg-turbo/simd/x86_64/jquantf-sse2.asm + libjpeg-turbo/simd/x86_64/jquanti-sse2.asm + libjpeg-turbo/simd/x86_64/jccolor-avx2.asm + libjpeg-turbo/simd/x86_64/jcgray-avx2.asm + libjpeg-turbo/simd/x86_64/jcsample-avx2.asm + libjpeg-turbo/simd/x86_64/jdcolor-avx2.asm + libjpeg-turbo/simd/x86_64/jdmerge-avx2.asm + libjpeg-turbo/simd/x86_64/jdsample-avx2.asm + libjpeg-turbo/simd/x86_64/jfdctint-avx2.asm + libjpeg-turbo/simd/x86_64/jidctint-avx2.asm + libjpeg-turbo/simd/x86_64/jquanti-avx2.asm + ) + + set(CMAKE_ASM_NASM_FLAGS "-D__x86_64__ -DELF -DPIC -I${CMAKE_CURRENT_SOURCE_DIR}/libjpeg-turbo/simd/nasm") + +else () + + set(LIBJPEG_TURBO_SOURCES + ${LIBJPEG_TURBO_SOURCES} + libjpeg-turbo/jsimd_none.c + ) + +endif () + +add_library(libjpeg-turbo STATIC ${LIBJPEG_TURBO_SOURCES}) +target_include_directories(libjpeg-turbo PUBLIC ${LIBJPEG_TURBO_INCLUDES}) diff --git a/native/src/main/jni/image/libjpeg-turbo/jconfig.h b/native/src/main/jni/image/libjpeg-turbo/jconfig.h new file mode 100644 index 000000000..d2e52cf8e --- /dev/null +++ b/native/src/main/jni/image/libjpeg-turbo/jconfig.h @@ -0,0 +1,73 @@ +/* Version ID for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". + */ +#define JPEG_LIB_VERSION 62 + +/* libjpeg-turbo version */ +#define LIBJPEG_TURBO_VERSION 2.0.0 + +/* libjpeg-turbo version in integer form */ +#define LIBJPEG_TURBO_VERSION_NUMBER 2000000 + +/* Support arithmetic encoding */ +#define C_ARITH_CODING_SUPPORTED + +/* Support arithmetic decoding */ +#define D_ARITH_CODING_SUPPORTED + +/* Support in-memory source/destination managers */ +#define MEM_SRCDST_SUPPORTED + +/* Use accelerated SIMD routines. */ +#define WITH_SIMD + +/* + * Define BITS_IN_JSAMPLE as either + * 8 for 8-bit sample values (the usual setting) + * 12 for 12-bit sample values + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + * JPEG standard, and the IJG code does not support anything else! + * We do not support run-time selection of data precision, sorry. + */ + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LOCALE_H + +/* Define to 1 if you have the header file. */ +#define HAVE_STDDEF_H + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H + +/* Define if you need to include to get size_t. */ +#define NEED_SYS_TYPES_H + +/* Define if you have BSD-like bzero and bcopy in rather than + memset/memcpy in . */ +/* #undef NEED_BSD_STRINGS */ + +/* Define to 1 if the system has the type `unsigned char'. */ +#define HAVE_UNSIGNED_CHAR + +/* Define to 1 if the system has the type `unsigned short'. */ +#define HAVE_UNSIGNED_SHORT + +/* Compiler does not support pointers to undefined structures. */ +/* #undef INCOMPLETE_TYPES_BROKEN */ + +/* Define if your (broken) compiler shifts signed values as if they were + unsigned. */ +/* #undef RIGHT_SHIFT_IS_UNSIGNED */ + +/* Define to 1 if type `char' is unsigned and you are not using gcc. */ +#ifndef __CHAR_UNSIGNED__ +/* #undef __CHAR_UNSIGNED__ */ +#endif + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ diff --git a/native/src/main/jni/image/libjpeg-turbo/jconfigint.h b/native/src/main/jni/image/libjpeg-turbo/jconfigint.h new file mode 100644 index 000000000..6a4bd376c --- /dev/null +++ b/native/src/main/jni/image/libjpeg-turbo/jconfigint.h @@ -0,0 +1,31 @@ +/* libjpeg-turbo build number */ +#define BUILD "20180730" + +/* Compiler's inline keyword */ +#undef inline + +/* How to obtain function inlining. */ +#define INLINE __inline__ __attribute__((always_inline)) + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "libjpeg-turbo" + +/* Version number of package */ +#define VERSION "2.0.0" + +/* The size of `size_t', as computed by sizeof. */ +#define SIZEOF_SIZE_T __SIZEOF_SIZE_T__ + +/* Define if your compiler has __builtin_ctzl() and sizeof(unsigned long) == sizeof(size_t). */ +#define HAVE_BUILTIN_CTZL + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_INTRIN_H */ + +#if defined(_MSC_VER) && defined(HAVE_INTRIN_H) +#if (SIZEOF_SIZE_T == 8) +#define HAVE_BITSCANFORWARD64 +#elif (SIZEOF_SIZE_T == 4) +#define HAVE_BITSCANFORWARD +#endif +#endif diff --git a/native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo b/native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo new file mode 160000 index 000000000..8c008059a --- /dev/null +++ b/native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo @@ -0,0 +1 @@ +Subproject commit 8c008059a71a64333ba0850295bf0be77eac647b diff --git a/native/src/main/jni/image/libpng/CMakeLists.txt b/native/src/main/jni/image/libpng/CMakeLists.txt new file mode 100644 index 000000000..77acab786 --- /dev/null +++ b/native/src/main/jni/image/libpng/CMakeLists.txt @@ -0,0 +1,96 @@ +# Copyright 2018 Hippo Seven +# +# 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. + +cmake_minimum_required(VERSION 3.4.1) +project(libpng C) + +set(LIBPNG_SOURCES + libpng/png.c + libpng/pngerror.c + libpng/pngget.c + libpng/pngmem.c + libpng/pngpread.c + libpng/pngread.c + libpng/pngrio.c + libpng/pngrtran.c + libpng/pngrutil.c + libpng/pngset.c + libpng/pngtrans.c +) +set(LIBPNG_INCLUDES + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/libpng +) +set(LIBPNG_DEFINITIONS) +set(LIBPNG_LIBRARIES z) + +if (${ANDROID_ABI} STREQUAL "armeabi-v7a") + + enable_language(ASM) + + set(LIBPNG_SOURCES + ${LIBPNG_SOURCES} + libpng/arm/arm_init.c + libpng/arm/filter_neon.S + ) + set(LIBPNG_INCLUDES + ${LIBPNG_INCLUDES} + ${ANDROID_NDK}/sources/android/cpufeatures + ) + set(LIBPNG_DEFINITIONS + ${LIBPNG_DEFINITIONS} + -DPNG_ARM_NEON_API_SUPPORTED + -DPNG_ARM_NEON_CHECK_SUPPORTED + -DPNG_ARM_NEON_OPT=1 + -DPNG_ARM_NEON_IMPLEMENTATION=2 + -DPNG_ARM_NEON_FILE=\"contrib/arm-neon/android-ndk.c\" + ) + set(LIBPNG_LIBRARIES + ${LIBPNG_LIBRARIES} + cpufeatures + ) + +elseif (${ANDROID_ABI} STREQUAL "arm64-v8a") + + enable_language(ASM) + + set(LIBPNG_SOURCES + ${LIBPNG_SOURCES} + libpng/arm/arm_init.c + libpng/arm/filter_neon_intrinsics.c + libpng/arm/palette_neon_intrinsics.c + ) + set(LIBPNG_INCLUDES + ${LIBPNG_INCLUDES} + ${ANDROID_NDK}/sources/android/cpufeatures + ) + set(LIBPNG_DEFINITIONS + ${LIBPNG_DEFINITIONS} + -DPNG_ARM_NEON_API_SUPPORTED + -DPNG_ARM_NEON_CHECK_SUPPORTED + -DPNG_ARM_NEON_OPT=1 + -DPNG_ARM_NEON_IMPLEMENTATION=1 + -DPNG_ARM_NEON_FILE=\"contrib/arm-neon/android-ndk.c\" + ) + set(LIBPNG_LIBRARIES + ${LIBPNG_LIBRARIES} + cpufeatures + ) + +endif () + +add_library(libpng STATIC ${LIBPNG_SOURCES}) +target_include_directories(libpng PUBLIC ${LIBPNG_INCLUDES}) +target_compile_definitions(libpng PRIVATE ${LIBPNG_DEFINITIONS}) +target_link_libraries(libpng PRIVATE ${LIBPNG_LIBRARIES}) diff --git a/native/src/main/jni/image/libpng/libpng b/native/src/main/jni/image/libpng/libpng new file mode 160000 index 000000000..c81fed471 --- /dev/null +++ b/native/src/main/jni/image/libpng/libpng @@ -0,0 +1 @@ +Subproject commit c81fed4715301b2b05cf2f554c9d1395d3e73a08 diff --git a/native/src/main/jni/image/libpng/pnglibconf.h b/native/src/main/jni/image/libpng/pnglibconf.h new file mode 100644 index 000000000..d4015903d --- /dev/null +++ b/native/src/main/jni/image/libpng/pnglibconf.h @@ -0,0 +1,227 @@ +/* libpng 1.6.34 STANDARD API DEFINITION */ + +/* pnglibconf.h - library build configuration */ + +/* Libpng version 1.6.34 - September 29, 2017 */ + +/* Copyright (c) 1998-2017 Glenn Randers-Pehrson */ + +/* This code is released under the libpng license. */ +/* For conditions of distribution and use, see the disclaimer */ +/* and license in png.h */ + +/* pnglibconf.h */ +/* Machine generated file: DO NOT EDIT */ +/* Derived from: scripts/pnglibconf.dfa */ +#ifndef PNGLCONF_H +#define PNGLCONF_H +/* options */ +#define PNG_16BIT_SUPPORTED +#define PNG_ALIGNED_MEMORY_SUPPORTED +/*#undef PNG_ARM_NEON_API_SUPPORTED*/ +/*#undef PNG_ARM_NEON_CHECK_SUPPORTED*/ +/*#undef PNG_POWERPC_VSX_API_SUPPORTED*/ +/*#undef PNG_POWERPC_VSX_CHECK_SUPPORTED*/ +#define PNG_BENIGN_ERRORS_SUPPORTED +#define PNG_BENIGN_READ_ERRORS_SUPPORTED +/*#undef PNG_BENIGN_WRITE_ERRORS_SUPPORTED*/ +#define PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED +#define PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_COLORSPACE_SUPPORTED +#define PNG_CONSOLE_IO_SUPPORTED +#define PNG_CONVERT_tIME_SUPPORTED +#define PNG_EASY_ACCESS_SUPPORTED +/*#undef PNG_ERROR_NUMBERS_SUPPORTED*/ +#define PNG_ERROR_TEXT_SUPPORTED +#define PNG_FIXED_POINT_SUPPORTED +#define PNG_FLOATING_ARITHMETIC_SUPPORTED +#define PNG_FLOATING_POINT_SUPPORTED +#define PNG_FORMAT_AFIRST_SUPPORTED +#define PNG_FORMAT_BGR_SUPPORTED +#define PNG_GAMMA_SUPPORTED +#define PNG_GET_PALETTE_MAX_SUPPORTED +#define PNG_HANDLE_AS_UNKNOWN_SUPPORTED +#define PNG_INCH_CONVERSIONS_SUPPORTED +#define PNG_INFO_IMAGE_SUPPORTED +#define PNG_IO_STATE_SUPPORTED +#define PNG_MNG_FEATURES_SUPPORTED +#define PNG_POINTER_INDEXING_SUPPORTED +#define PNG_PROGRESSIVE_READ_SUPPORTED +#define PNG_READ_16BIT_SUPPORTED +#define PNG_READ_ALPHA_MODE_SUPPORTED +#define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED +#define PNG_READ_BACKGROUND_SUPPORTED +#define PNG_READ_BGR_SUPPORTED +#define PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_READ_COMPOSITE_NODIV_SUPPORTED +#define PNG_READ_COMPRESSED_TEXT_SUPPORTED +#define PNG_READ_EXPAND_16_SUPPORTED +#define PNG_READ_EXPAND_SUPPORTED +#define PNG_READ_FILLER_SUPPORTED +#define PNG_READ_GAMMA_SUPPORTED +#define PNG_READ_GET_PALETTE_MAX_SUPPORTED +#define PNG_READ_GRAY_TO_RGB_SUPPORTED +#define PNG_READ_INTERLACING_SUPPORTED +#define PNG_READ_INT_FUNCTIONS_SUPPORTED +#define PNG_READ_INVERT_ALPHA_SUPPORTED +#define PNG_READ_INVERT_SUPPORTED +#define PNG_READ_OPT_PLTE_SUPPORTED +#define PNG_READ_PACKSWAP_SUPPORTED +#define PNG_READ_PACK_SUPPORTED +#define PNG_READ_QUANTIZE_SUPPORTED +#define PNG_READ_RGB_TO_GRAY_SUPPORTED +#define PNG_READ_SCALE_16_TO_8_SUPPORTED +#define PNG_READ_SHIFT_SUPPORTED +#define PNG_READ_STRIP_16_TO_8_SUPPORTED +#define PNG_READ_STRIP_ALPHA_SUPPORTED +#define PNG_READ_SUPPORTED +#define PNG_READ_SWAP_ALPHA_SUPPORTED +#define PNG_READ_SWAP_SUPPORTED +#define PNG_READ_TEXT_SUPPORTED +#define PNG_READ_TRANSFORMS_SUPPORTED +#define PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_READ_USER_CHUNKS_SUPPORTED +#define PNG_READ_USER_TRANSFORM_SUPPORTED +#define PNG_READ_bKGD_SUPPORTED +#define PNG_READ_cHRM_SUPPORTED +#define PNG_READ_eXIf_SUPPORTED +#define PNG_READ_gAMA_SUPPORTED +#define PNG_READ_hIST_SUPPORTED +#define PNG_READ_iCCP_SUPPORTED +#define PNG_READ_iTXt_SUPPORTED +#define PNG_READ_oFFs_SUPPORTED +#define PNG_READ_pCAL_SUPPORTED +#define PNG_READ_pHYs_SUPPORTED +#define PNG_READ_sBIT_SUPPORTED +#define PNG_READ_sCAL_SUPPORTED +#define PNG_READ_sPLT_SUPPORTED +#define PNG_READ_sRGB_SUPPORTED +#define PNG_READ_tEXt_SUPPORTED +#define PNG_READ_tIME_SUPPORTED +#define PNG_READ_tRNS_SUPPORTED +#define PNG_READ_zTXt_SUPPORTED +/*#undef PNG_SAVE_INT_32_SUPPORTED*/ +#define PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_SEQUENTIAL_READ_SUPPORTED +#define PNG_SETJMP_SUPPORTED +#define PNG_SET_OPTION_SUPPORTED +#define PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_SET_USER_LIMITS_SUPPORTED +/*#undef PNG_SIMPLIFIED_READ_AFIRST_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_READ_BGR_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_READ_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_WRITE_STDIO_SUPPORTED*/ +/*#undef PNG_SIMPLIFIED_WRITE_SUPPORTED*/ +/*#undef PNG_STDIO_SUPPORTED*/ +#define PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_TEXT_SUPPORTED +#define PNG_TIME_RFC1123_SUPPORTED +#define PNG_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_USER_CHUNKS_SUPPORTED +#define PNG_USER_LIMITS_SUPPORTED +#define PNG_USER_MEM_SUPPORTED +#define PNG_USER_TRANSFORM_INFO_SUPPORTED +#define PNG_USER_TRANSFORM_PTR_SUPPORTED +#define PNG_WARNINGS_SUPPORTED +/* +#define PNG_WRITE_16BIT_SUPPORTED +#define PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED +#define PNG_WRITE_BGR_SUPPORTED +#define PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_WRITE_COMPRESSED_TEXT_SUPPORTED +#define PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED +#define PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +#define PNG_WRITE_FILLER_SUPPORTED +#define PNG_WRITE_FILTER_SUPPORTED +#define PNG_WRITE_FLUSH_SUPPORTED +#define PNG_WRITE_GET_PALETTE_MAX_SUPPORTED +#define PNG_WRITE_INTERLACING_SUPPORTED +#define PNG_WRITE_INT_FUNCTIONS_SUPPORTED +#define PNG_WRITE_INVERT_ALPHA_SUPPORTED +#define PNG_WRITE_INVERT_SUPPORTED +#define PNG_WRITE_OPTIMIZE_CMF_SUPPORTED +#define PNG_WRITE_PACKSWAP_SUPPORTED +#define PNG_WRITE_PACK_SUPPORTED +#define PNG_WRITE_SHIFT_SUPPORTED +#define PNG_WRITE_SUPPORTED +#define PNG_WRITE_SWAP_ALPHA_SUPPORTED +#define PNG_WRITE_SWAP_SUPPORTED +#define PNG_WRITE_TEXT_SUPPORTED +#define PNG_WRITE_TRANSFORMS_SUPPORTED +#define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_WRITE_USER_TRANSFORM_SUPPORTED +#define PNG_WRITE_WEIGHTED_FILTER_SUPPORTED +#define PNG_WRITE_bKGD_SUPPORTED +#define PNG_WRITE_cHRM_SUPPORTED +#define PNG_WRITE_eXIf_SUPPORTED +#define PNG_WRITE_gAMA_SUPPORTED +#define PNG_WRITE_hIST_SUPPORTED +#define PNG_WRITE_iCCP_SUPPORTED +#define PNG_WRITE_iTXt_SUPPORTED +#define PNG_WRITE_oFFs_SUPPORTED +#define PNG_WRITE_pCAL_SUPPORTED +#define PNG_WRITE_pHYs_SUPPORTED +#define PNG_WRITE_sBIT_SUPPORTED +#define PNG_WRITE_sCAL_SUPPORTED +#define PNG_WRITE_sPLT_SUPPORTED +#define PNG_WRITE_sRGB_SUPPORTED +#define PNG_WRITE_tEXt_SUPPORTED +#define PNG_WRITE_tIME_SUPPORTED +#define PNG_WRITE_tRNS_SUPPORTED +#define PNG_WRITE_zTXt_SUPPORTED +*/ +#define PNG_bKGD_SUPPORTED +#define PNG_cHRM_SUPPORTED +#define PNG_eXIf_SUPPORTED +#define PNG_gAMA_SUPPORTED +#define PNG_hIST_SUPPORTED +#define PNG_iCCP_SUPPORTED +#define PNG_iTXt_SUPPORTED +#define PNG_oFFs_SUPPORTED +#define PNG_pCAL_SUPPORTED +#define PNG_pHYs_SUPPORTED +#define PNG_sBIT_SUPPORTED +#define PNG_sCAL_SUPPORTED +#define PNG_sPLT_SUPPORTED +#define PNG_sRGB_SUPPORTED +#define PNG_tEXt_SUPPORTED +#define PNG_tIME_SUPPORTED +#define PNG_tRNS_SUPPORTED +#define PNG_zTXt_SUPPORTED +/* end of options */ +/* apng */ +#define PNG_APNG_SUPPORTED +#define PNG_READ_APNG_SUPPORTED +/*#undef PNG_WRITE_APNG_SUPPORTED*/ +/* end of apng */ +/* settings */ +#define PNG_API_RULE 0 +#define PNG_DEFAULT_READ_MACROS 1 +#define PNG_GAMMA_THRESHOLD_FIXED 5000 +#define PNG_IDAT_READ_SIZE PNG_ZBUF_SIZE +#define PNG_INFLATE_BUF_SIZE 1024 +#define PNG_LINKAGE_API extern +#define PNG_LINKAGE_CALLBACK extern +#define PNG_LINKAGE_DATA extern +#define PNG_LINKAGE_FUNCTION extern +#define PNG_MAX_GAMMA_8 11 +#define PNG_QUANTIZE_BLUE_BITS 5 +#define PNG_QUANTIZE_GREEN_BITS 5 +#define PNG_QUANTIZE_RED_BITS 5 +#define PNG_TEXT_Z_DEFAULT_COMPRESSION (-1) +#define PNG_TEXT_Z_DEFAULT_STRATEGY 0 +#define PNG_USER_CHUNK_CACHE_MAX 1000 +#define PNG_USER_CHUNK_MALLOC_MAX 8000000 +#define PNG_USER_HEIGHT_MAX 1000000 +#define PNG_USER_WIDTH_MAX 1000000 +#define PNG_ZBUF_SIZE 8192 +#define PNG_ZLIB_VERNUM 0 /* unknown */ +#define PNG_Z_DEFAULT_COMPRESSION (-1) +#define PNG_Z_DEFAULT_NOFILTER_STRATEGY 0 +#define PNG_Z_DEFAULT_STRATEGY 1 +#define PNG_sCAL_PRECISION 5 +#define PNG_sRGB_PROFILE_CHECKS 2 +/* end of settings */ +#endif /* PNGLCONF_H */ diff --git a/native/src/main/jni/image/log.h b/native/src/main/jni/image/log.h new file mode 100644 index 000000000..4ca0c4b6e --- /dev/null +++ b/native/src/main/jni/image/log.h @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 10/19/2015. +// + +#ifndef LOG_H +#define LOG_H + +#include + +#include "utils.h" + +#define TAG "Image" +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG ,__VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG ,__VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG ,__VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG ,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG ,__VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG ,__VA_ARGS__) + +#define FILE_LINE MAKESTRING(STRINGIZE, __LINE__ ) +#define MSG(msg) __FILE__ "(" FILE_LINE "): "msg + +#define OUT_OF_MEMORY "Out of memory" + +#define WTF_OM LOGE(MSG(OUT_OF_MEMORY)) + +#endif //LOG_H diff --git a/native/src/main/jni/image/stream/Android.mk b/native/src/main/jni/image/stream/Android.mk new file mode 100644 index 000000000..218ac041d --- /dev/null +++ b/native/src/main/jni/image/stream/Android.mk @@ -0,0 +1,27 @@ +# Copyright 2015 Hippo Seven +# +# 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := stream +LOCAL_SRC_FILES := \ + input_stream.c \ + input_stream_pipe.c \ + output_stream.c \ + output_stream_pipe.c \ + patch_head_input_stream.c + +include $(BUILD_STATIC_LIBRARY) diff --git a/native/src/main/jni/image/stream/CMakeLists.txt b/native/src/main/jni/image/stream/CMakeLists.txt new file mode 100644 index 000000000..ca0c08ee5 --- /dev/null +++ b/native/src/main/jni/image/stream/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright 2018 Hippo Seven +# +# 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. + +cmake_minimum_required(VERSION 3.4.1) +project(stream C) + +set(STREAM_SOURCES + input_stream.c + input_stream_pipe.c + output_stream.c + output_stream_pipe.c + patch_head_input_stream.c +) +set(STREAM_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(stream STATIC ${STREAM_SOURCES}) +target_include_directories(stream PUBLIC ${STREAM_INCLUDES}) diff --git a/native/src/main/jni/image/stream/config.h b/native/src/main/jni/image/stream/config.h new file mode 100644 index 000000000..f9d6a116c --- /dev/null +++ b/native/src/main/jni/image/stream/config.h @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/28/2015. +// + +#ifndef STREAM_CONFIG_H +#define STREAM_CONFIG_H + +#define STREAM_SUPPORT_INPUT +//#define STREAM_SUPPORT_INPUT_PIPE +#define STREAM_SUPPORT_INPUT_PATCH_HEAD +#define STREAM_SUPPORT_OUTPUT +//#define STREAM_SUPPORT_OUTPUT_PIPE + +#endif // STREAM_CONFIG_H diff --git a/native/src/main/jni/image/stream/input_stream.c b/native/src/main/jni/image/stream/input_stream.c new file mode 100644 index 000000000..723259ac5 --- /dev/null +++ b/native/src/main/jni/image/stream/input_stream.c @@ -0,0 +1,117 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 10/19/2015. +// + +#include "config.h" +#ifdef STREAM_SUPPORT_INPUT + +#include + +#include "../log.h" +#include "../utils.h" +#include "input_stream.h" +#include "stream_utils.h" + +InputStream* create_input_stream(JNIEnv* env, jobject is) +{ + jclass streamCls = (*env)->GetObjectClass(env, is); + jmethodID readMID = (*env)->GetMethodID(env, streamCls, "read", "([BII)I"); + jmethodID closeMID = (*env)->GetMethodID(env, streamCls, "close", "()V"); + jbyteArray buffer; + + if (readMID == NULL || closeMID == NULL) { + LOGE(MSG("Can't get read or close method id")); + return NULL; + } + + buffer = (*env)->NewByteArray(env, BUFFER_SIZE); + buffer = (*env)->NewGlobalRef(env, buffer); + if (buffer == NULL) { + LOGE(MSG("Can't create buffer")); + return NULL; + } + + InputStream* inputStream = (InputStream*) malloc(sizeof(InputStream)); + if (inputStream == NULL) { + LOGE(MSG("Out of memory")); + return NULL; + } + + inputStream->is = (*env)->NewGlobalRef(env, is); + inputStream->readMID = readMID; + inputStream->closeMID = closeMID; + inputStream->buffer = buffer; + + return inputStream; +} + +void destroy_input_stream(JNIEnv* env, InputStream** inputStream) +{ + if (inputStream != NULL && *inputStream != NULL) { + (*env)->DeleteGlobalRef(env, (*inputStream)->is); + (*env)->DeleteGlobalRef(env, (*inputStream)->buffer); + free(*inputStream); + *inputStream = NULL; + } +} + +size_t read_input_stream(JNIEnv* env, InputStream* inputStream, unsigned char* buffer, int offset, size_t size) +{ + size_t remainSize = size; + size_t readSize = 0; + int bufferOffset = offset; + int len; + + while (remainSize > 0) { + len = MIN(BUFFER_SIZE, remainSize); + + len = (*env)->CallIntMethod(env, inputStream->is, inputStream->readMID, inputStream->buffer, 0, len); + if ((*env)->ExceptionCheck(env)) { + LOGE(MSG("Catch exception")); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + len = -1; + } + + // end of the stream or catch exception + if (len < 0) { + break; + } + + (*env)->GetByteArrayRegion(env, inputStream->buffer, 0, len, (jbyte *) (buffer + bufferOffset)); + + remainSize -= len; + readSize += len; + bufferOffset += len; + } + + return readSize; +} + +void close_input_stream(JNIEnv* env, InputStream* inputStream) +{ + (*env)->CallVoidMethod(env, inputStream->is, inputStream->closeMID); + if ((*env)->ExceptionCheck(env)) { + LOGE(MSG("Catch exception")); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } +} + +#endif // STREAM_SUPPORT_INPUT diff --git a/native/src/main/jni/image/stream/input_stream.h b/native/src/main/jni/image/stream/input_stream.h new file mode 100644 index 000000000..41378342b --- /dev/null +++ b/native/src/main/jni/image/stream/input_stream.h @@ -0,0 +1,45 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 10/19/2015. +// + +#ifndef STREAM_INPUT_STREAM_H +#define STREAM_INPUT_STREAM_H + +#include "config.h" +#ifdef STREAM_SUPPORT_INPUT + +#include +#include + +typedef struct +{ + jobject is; + jmethodID readMID; + jmethodID closeMID; + jbyteArray buffer; +} InputStream; + +InputStream* create_input_stream(JNIEnv* env, jobject is); +void destroy_input_stream(JNIEnv* env, InputStream** inputStream); +size_t read_input_stream(JNIEnv* env, InputStream* inputStream, unsigned char* buffer, int offset, size_t size); +void close_input_stream(JNIEnv* env, InputStream* inputStream); + +#endif // STREAM_SUPPORT_INPUT + +#endif // STREAM_INPUT_STREAM_H diff --git a/native/src/main/jni/image/stream/input_stream_pipe.c b/native/src/main/jni/image/stream/input_stream_pipe.c new file mode 100644 index 000000000..1552b3f53 --- /dev/null +++ b/native/src/main/jni/image/stream/input_stream_pipe.c @@ -0,0 +1,94 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 10/19/2015. +// + +#include "config.h" +#if defined(STREAM_SUPPORT_INPUT) && defined(STREAM_SUPPORT_INPUT_PIPE) + +#include + +#include "input_stream_pipe.h" +#include "../log.h" + +InputStreamPipe* create_input_stream_pipe(JNIEnv* env, jobject isPipe) +{ + jclass streamPipeCls = (*env)->GetObjectClass(env, isPipe); + jmethodID obtainMID = (*env)->GetMethodID(env, streamPipeCls, "obtain", "()V"); + jmethodID releaseMID = (*env)->GetMethodID(env, streamPipeCls, "release", "()V"); + jmethodID openMID = (*env)->GetMethodID(env, streamPipeCls, "open", "()Ljava/io/InputStream;"); + jmethodID closeMID = (*env)->GetMethodID(env, streamPipeCls, "close", "()V"); + + if (obtainMID == NULL || releaseMID == NULL || openMID == NULL || closeMID == NULL) { + LOGE(MSG("Can't get method id")); + return NULL; + } + + InputStreamPipe* inputStreamPipe = (InputStreamPipe*) malloc(sizeof(InputStreamPipe)); + if (inputStreamPipe == NULL) { + LOGE(MSG("Out of memory")); + return NULL; + } + + inputStreamPipe->isPipe = (*env)->NewGlobalRef(env, isPipe); + inputStreamPipe->obtainMID = obtainMID; + inputStreamPipe->releaseMID = releaseMID; + inputStreamPipe->openMID = openMID; + inputStreamPipe->closeMID = closeMID; + + return inputStreamPipe; +} + +void destroy_input_stream_pipe(JNIEnv* env, InputStreamPipe** inputStreamPipe) +{ + if (inputStreamPipe != NULL && *inputStreamPipe != NULL) { + (*env)->DeleteGlobalRef(env, (*inputStreamPipe)->isPipe); + free(*inputStreamPipe); + *inputStreamPipe = NULL; + } +} + +void obtain_input_stream_pipe(JNIEnv* env, InputStreamPipe* inputStreamPipe) +{ + (*env)->CallVoidMethod(env, inputStreamPipe->isPipe, inputStreamPipe->obtainMID); +} + +void release_input_stream_pipe(JNIEnv* env, InputStreamPipe* inputStreamPipe) +{ + (*env)->CallVoidMethod(env, inputStreamPipe->isPipe, inputStreamPipe->releaseMID); +} + +InputStream* open_input_stream_from_pipe(JNIEnv* env, InputStreamPipe* inputStreamPipe) +{ + jobject is = (*env)->CallObjectMethod(env, inputStreamPipe->isPipe, inputStreamPipe->openMID); + if ((*env)->ExceptionCheck(env)) { + LOGE(MSG("Catch exception")); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return NULL; + } + + return create_input_stream(env, is); +} + +void close_input_stream_from_pipe(JNIEnv* env, InputStreamPipe* inputStreamPipe) +{ + (*env)->CallVoidMethod(env, inputStreamPipe->isPipe, inputStreamPipe->closeMID); +} + +#endif // STREAM_SUPPORT_INPUT && STREAM_SUPPORT_INPUT_PIPE diff --git a/native/src/main/jni/image/stream/input_stream_pipe.h b/native/src/main/jni/image/stream/input_stream_pipe.h new file mode 100644 index 000000000..51e22ced9 --- /dev/null +++ b/native/src/main/jni/image/stream/input_stream_pipe.h @@ -0,0 +1,49 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 10/19/2015. +// + +#ifndef STREAM_INPUT_STREAM_PIPE_H +#define STREAM_INPUT_STREAM_PIPE_H + +#include "config.h" +#if defined(STREAM_SUPPORT_INPUT) && defined(STREAM_SUPPORT_INPUT_PIPE) + +#include + +#include "input_stream.h" + +typedef struct +{ + jobject isPipe; + jmethodID obtainMID; + jmethodID releaseMID; + jmethodID openMID; + jmethodID closeMID; +} InputStreamPipe; + +InputStreamPipe* create_input_stream_pipe(JNIEnv* env, jobject isPipe); +void destroy_input_stream_pipe(JNIEnv* env, InputStreamPipe** inputStreamPipe); +void obtain_input_stream_pipe(JNIEnv* env, InputStreamPipe* inputStreamPipe); +void release_input_stream_pipe(JNIEnv* env, InputStreamPipe* inputStreamPipe); +InputStream* open_input_stream_from_pipe(JNIEnv* env, InputStreamPipe* inputStreamPipe); +void close_input_stream_from_pipe(JNIEnv* env, InputStreamPipe* inputStreamPipe); + +#endif // STREAM_SUPPORT_INPUT && STREAM_SUPPORT_INPUT_PIPE + +#endif // STREAM_INPUT_STREAM_PIPE_H diff --git a/native/src/main/jni/image/stream/output_stream.c b/native/src/main/jni/image/stream/output_stream.c new file mode 100644 index 000000000..7a8a4f77d --- /dev/null +++ b/native/src/main/jni/image/stream/output_stream.c @@ -0,0 +1,116 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 10/19/2015. +// + +#include "config.h" +#ifdef STREAM_SUPPORT_OUTPUT + +#include + +#include "../log.h" +#include "../utils.h" +#include "output_stream.h" +#include "stream_utils.h" + +OutputStream* create_output_stream(JNIEnv* env, jobject os) +{ + jclass streamCls = (*env)->GetObjectClass(env, os); + jmethodID writeMID = (*env)->GetMethodID(env, streamCls, "write", "([BII)V"); + jmethodID closeMID = (*env)->GetMethodID(env, streamCls, "close", "()V"); + jbyteArray buffer; + + if (writeMID == NULL || closeMID == NULL) { + LOGE(MSG("Can't get write or close method id")); + return NULL; + } + + buffer = (*env)->NewByteArray(env, BUFFER_SIZE); + buffer = (*env)->NewGlobalRef(env, buffer); + if (buffer == NULL) { + LOGE(MSG("Can't create buffer")); + return NULL; + } + + OutputStream* outputStream = (OutputStream*) malloc(sizeof(OutputStream)); + if (outputStream == NULL) { + LOGE(MSG("Out of memory")); + return NULL; + } + + outputStream->os = (*env)->NewGlobalRef(env, os); + outputStream->writeMID = writeMID; + outputStream->closeMID = closeMID; + outputStream->buffer = buffer; + + return outputStream; +} + +void destroy_output_stream(JNIEnv* env, OutputStream** outputStream) +{ + if (outputStream != NULL && *outputStream != NULL) { + (*env)->DeleteGlobalRef(env, (*outputStream)->os); + (*env)->DeleteGlobalRef(env, (*outputStream)->buffer); + free(*outputStream); + *outputStream = NULL; + } +} + +size_t write_output_stream(JNIEnv* env, OutputStream* outputStream, const unsigned char* buffer, int offset, size_t size) +{ + size_t remainSize = size; + size_t readSize = 0; + int bufferOffset = offset; + int len; + + while (remainSize > 0) { + len = MIN(BUFFER_SIZE, remainSize); + + // Copy date from buffer to outputStream->buffer + (*env)->SetByteArrayRegion(env, outputStream->buffer, 0, len, (jbyte *) (buffer + bufferOffset)); + + // Invoke OutputStream.write + (*env)->CallVoidMethod(env, outputStream->os, outputStream->writeMID, outputStream->buffer, 0, len); + + // Catch exception + if ((*env)->ExceptionCheck(env)) { + LOGE(MSG("Catch exception")); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + break; + } + + remainSize -= len; + readSize += len; + bufferOffset += len; + } + + return readSize; +} + +void close_output_stream(JNIEnv* env, OutputStream* outputStream) +{ + (*env)->CallVoidMethod(env, outputStream->os, outputStream->closeMID); + if ((*env)->ExceptionCheck(env)) { + LOGE(MSG("Catch exception")); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } +} + +#endif // STREAM_SUPPORT_OUTPUT diff --git a/native/src/main/jni/image/stream/output_stream.h b/native/src/main/jni/image/stream/output_stream.h new file mode 100644 index 000000000..c8b27fc98 --- /dev/null +++ b/native/src/main/jni/image/stream/output_stream.h @@ -0,0 +1,45 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 10/19/2015. +// + +#ifndef STREAM_OUTPUT_STREAM_H +#define STREAM_OUTPUT_STREAM_H + +#include "config.h" +#ifdef STREAM_SUPPORT_OUTPUT + +#include +#include + +typedef struct +{ + jobject os; + jmethodID writeMID; + jmethodID closeMID; + jbyteArray buffer; +} OutputStream; + +OutputStream* create_output_stream(JNIEnv* env, jobject os); +void destroy_output_stream(JNIEnv* env, OutputStream** outputStream); +size_t write_output_stream(JNIEnv* env, OutputStream* outputStream, const unsigned char* buffer, int offset, size_t size); +void close_output_stream(JNIEnv* env, OutputStream* outputStream); + +#endif // STREAM_SUPPORT_OUTPUT + +#endif // STREAM_OUTPUT_STREAM_H diff --git a/native/src/main/jni/image/stream/output_stream_pipe.c b/native/src/main/jni/image/stream/output_stream_pipe.c new file mode 100644 index 000000000..bced2dcca --- /dev/null +++ b/native/src/main/jni/image/stream/output_stream_pipe.c @@ -0,0 +1,94 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 10/19/2015. +// + +#include "config.h" +#if defined(STREAM_SUPPORT_OUTPUT) && defined(STREAM_SUPPORT_OUTPUT_PIPE) + +#include + +#include "output_stream_pipe.h" +#include "../log.h" + +OutputStreamPipe* create_output_stream_pipe(JNIEnv* env, jobject osPipe) +{ + jclass streamPipeCls = (*env)->GetObjectClass(env, osPipe); + jmethodID obtainMID = (*env)->GetMethodID(env, streamPipeCls, "obtain", "()V"); + jmethodID releaseMID = (*env)->GetMethodID(env, streamPipeCls, "release", "()V"); + jmethodID openMID = (*env)->GetMethodID(env, streamPipeCls, "open", "()Ljava/io/OutputStream;"); + jmethodID closeMID = (*env)->GetMethodID(env, streamPipeCls, "close", "()V"); + + if (obtainMID == NULL || releaseMID == NULL || openMID == NULL || closeMID == NULL) { + LOGE(MSG("Can't get method id")); + return NULL; + } + + OutputStreamPipe* outputStreamPipe = (OutputStreamPipe*) malloc(sizeof(OutputStreamPipe)); + if (outputStreamPipe == NULL) { + LOGE(MSG("Out of memory")); + return NULL; + } + + outputStreamPipe->osPipe = (*env)->NewGlobalRef(env, osPipe); + outputStreamPipe->obtainMID = obtainMID; + outputStreamPipe->releaseMID = releaseMID; + outputStreamPipe->openMID = openMID; + outputStreamPipe->closeMID = closeMID; + + return outputStreamPipe; +} + +void destroy_output_stream_pipe(JNIEnv* env, OutputStreamPipe** outputStreamPipe) +{ + if (outputStreamPipe != NULL && *outputStreamPipe != NULL) { + (*env)->DeleteGlobalRef(env, (*outputStreamPipe)->osPipe); + free(*outputStreamPipe); + *outputStreamPipe = NULL; + } +} + +void obtain_output_stream_pipe(JNIEnv* env, OutputStreamPipe* outputStreamPipe) +{ + (*env)->CallVoidMethod(env, outputStreamPipe->osPipe, outputStreamPipe->obtainMID); +} + +void release_output_stream_pipe(JNIEnv* env, OutputStreamPipe* outputStreamPipe) +{ + (*env)->CallVoidMethod(env, outputStreamPipe->osPipe, outputStreamPipe->releaseMID); +} + +OutputStream* open_output_stream_from_pipe(JNIEnv* env, OutputStreamPipe* outputStreamPipe) +{ + jobject os = (*env)->CallObjectMethod(env, outputStreamPipe->osPipe, outputStreamPipe->openMID); + if ((*env)->ExceptionCheck(env)) { + LOGE(MSG("Catch exception")); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return NULL; + } + + return create_output_stream(env, os); +} + +void close_output_stream_from_pipe(JNIEnv* env, OutputStreamPipe* outputStreamPipe) +{ + (*env)->CallVoidMethod(env, outputStreamPipe->osPipe, outputStreamPipe->closeMID); +} + +#endif // STREAM_SUPPORT_OUTPUT && STREAM_SUPPORT_OUTPUT_PIPE diff --git a/native/src/main/jni/image/stream/output_stream_pipe.h b/native/src/main/jni/image/stream/output_stream_pipe.h new file mode 100644 index 000000000..c1f4f2825 --- /dev/null +++ b/native/src/main/jni/image/stream/output_stream_pipe.h @@ -0,0 +1,49 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 10/19/2015. +// + +#ifndef STREAM_OUTPUT_STREAM_PIPE_H +#define STREAM_OUTPUT_STREAM_PIPE_H + +#include "config.h" +#if defined(STREAM_SUPPORT_OUTPUT) && defined(STREAM_SUPPORT_OUTPUT_PIPE) + +#include + +#include "output_stream.h" + +typedef struct +{ + jobject osPipe; + jmethodID obtainMID; + jmethodID releaseMID; + jmethodID openMID; + jmethodID closeMID; +} OutputStreamPipe; + +OutputStreamPipe* create_output_stream_pipe(JNIEnv* env, jobject osPipe); +void destroy_output_stream_pipe(JNIEnv* env, OutputStreamPipe** outputStreamPipe); +void obtain_output_stream_pipe(JNIEnv* env, OutputStreamPipe* outputStreamPipe); +void release_output_stream_pipe(JNIEnv* env, OutputStreamPipe* outputStreamPipe); +OutputStream* open_output_stream_from_pipe(JNIEnv* env, OutputStreamPipe* outputStreamPipe); +void close_output_stream_from_pipe(JNIEnv* env, OutputStreamPipe* outputStreamPipe); + +#endif // STREAM_SUPPORT_OUTPUT && STREAM_SUPPORT_OUTPUT_PIPE + +#endif // STREAM_OUTPUT_STREAM_PIPE_H diff --git a/native/src/main/jni/image/stream/patch_head_input_stream.c b/native/src/main/jni/image/stream/patch_head_input_stream.c new file mode 100644 index 000000000..8ee937fbb --- /dev/null +++ b/native/src/main/jni/image/stream/patch_head_input_stream.c @@ -0,0 +1,131 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 1/1/2016. +// + +#include "config.h" +#if defined(STREAM_SUPPORT_INPUT) && defined(STREAM_SUPPORT_INPUT_PATCH_HEAD) + +#include +#include + +#include "patch_head_input_stream.h" +#include "../log.h" +#include "../utils.h" + +#define READ_BUFFER_SIZE (256 * 1024) // 256KB + +PatchHeadInputStream* create_patch_head_input_stream( + InputStream* input_stream, const unsigned char* patch, unsigned int patch_length) +{ + unsigned char* patch_copy; + + PatchHeadInputStream* patch_head_input_stream = (PatchHeadInputStream*) malloc(sizeof(PatchHeadInputStream)); + if (patch_head_input_stream == NULL) { + WTF_OM; + return NULL; + } + + // Copy patch + patch_copy = (unsigned char*) malloc(patch_length * sizeof(char)); + if (patch_copy == NULL) { + WTF_OM; + free(patch_head_input_stream); + return NULL; + } + memcpy(patch_copy, patch, patch_length); + + patch_head_input_stream->input_stream = input_stream; + patch_head_input_stream->patch = patch_copy; + patch_head_input_stream->patch_length = patch_length; + patch_head_input_stream->read_count = 0; + + return patch_head_input_stream; +} + +size_t read_patch_head_input_stream(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, + unsigned char* buffer, int offset, size_t size) +{ + size_t len = MIN(size, patch_head_input_stream->patch_length - patch_head_input_stream->read_count); + int buffer_offset = offset; + + if (len > 0) { + memcpy(buffer + buffer_offset, patch_head_input_stream->patch + patch_head_input_stream->read_count, len); + patch_head_input_stream->read_count += len; + buffer_offset += len; + } + + if (size > len) { + len += read_input_stream(env, patch_head_input_stream->input_stream, buffer, buffer_offset, size - len); + } + + return len; +} + +void* read_patch_head_input_stream_all(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, size_t* length) +{ + size_t len = 0; + size_t read; + void* buffer = NULL; + + buffer = malloc(READ_BUFFER_SIZE); + if (buffer == NULL) { + WTF_OM; + return NULL; + } + + for (;;) { + // Read from stream + read = read_patch_head_input_stream(env, patch_head_input_stream, buffer, len, READ_BUFFER_SIZE); + len += read; + // Check stream end + if (read < READ_BUFFER_SIZE) { + // Get the end, shrink the buffer + buffer = realloc(buffer, len); + *length = len; + return buffer; + } + // Extent buffer + buffer = realloc(buffer, len + READ_BUFFER_SIZE); + if (buffer == NULL) { + WTF_OM; + return NULL; + } + } +} + +void destroy_patch_head_input_stream(JNIEnv* env, PatchHeadInputStream** patch_head_input_stream) +{ + if (patch_head_input_stream != NULL && *patch_head_input_stream != NULL) { + // Free input stream + destroy_input_stream(env, &(*patch_head_input_stream)->input_stream); + // Free patch + free((*patch_head_input_stream)->patch); + (*patch_head_input_stream)->patch = NULL; + // Free itself + free(*patch_head_input_stream); + *patch_head_input_stream = NULL; + } +} + +void close_patch_head_input_stream(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream) +{ + close_input_stream(env, patch_head_input_stream->input_stream); +} + +#endif // STREAM_SUPPORT_INPUT && STREAM_SUPPORT_INPUT_PATCH_HEAD diff --git a/native/src/main/jni/image/stream/patch_head_input_stream.h b/native/src/main/jni/image/stream/patch_head_input_stream.h new file mode 100644 index 000000000..8e211529a --- /dev/null +++ b/native/src/main/jni/image/stream/patch_head_input_stream.h @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 1/1/2016. +// + +#ifndef STREAM_PATCH_HEAD_INPUT_STREAM_H +#define STREAM_PATCH_HEAD_INPUT_STREAM_H + +#include "config.h" +#if defined(STREAM_SUPPORT_INPUT) && defined(STREAM_SUPPORT_INPUT_PATCH_HEAD) + +#include "input_stream.h" + +typedef struct +{ + InputStream* input_stream; + unsigned char* patch; + unsigned int patch_length; + unsigned int read_count; +} PatchHeadInputStream; + +PatchHeadInputStream* create_patch_head_input_stream( + InputStream* input_stream, const unsigned char* patch, unsigned int patch_length); +void destroy_patch_head_input_stream(JNIEnv* env, PatchHeadInputStream** patch_head_input_stream); +size_t read_patch_head_input_stream(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, + unsigned char* buffer, int offset, size_t size); +void* read_patch_head_input_stream_all(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, size_t* length); +void close_patch_head_input_stream(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream); + +#endif // STREAM_SUPPORT_INPUT && STREAM_SUPPORT_INPUT_PATCH_HEAD + +#endif // STREAM_PATCH_HEAD_INPUT_STREAM_H diff --git a/native/src/main/jni/image/stream/stream_utils.h b/native/src/main/jni/image/stream/stream_utils.h new file mode 100644 index 000000000..c94241759 --- /dev/null +++ b/native/src/main/jni/image/stream/stream_utils.h @@ -0,0 +1,26 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 10/19/2015. +// + +#ifndef STREAM_STREAM_UTILS_H +#define STREAM_STREAM_UTILS_H + +#define BUFFER_SIZE 256 + +#endif // STREAM_STREAM_UTILS_H diff --git a/native/src/main/jni/image/utils.h b/native/src/main/jni/image/utils.h new file mode 100644 index 000000000..fabffb669 --- /dev/null +++ b/native/src/main/jni/image/utils.h @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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. + */ + +// +// Created by Hippo on 12/29/2015. +// + +#ifndef UTILS_H +#define UTILS_H + +#define STRINGIZE(L) #L +#define MAKESTRING(M, L) M(L) + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#endif // UTILS_H diff --git a/native/src/main/jni/integrity/CMakeLists.txt b/native/src/main/jni/integrity/CMakeLists.txt new file mode 100644 index 000000000..e746de300 --- /dev/null +++ b/native/src/main/jni/integrity/CMakeLists.txt @@ -0,0 +1,16 @@ +project(genuine C) + +add_library(genuine STATIC + am-proxy.c + apk-sign-v2.c + common.c + genuine.c + inline.c + openat.c + path.c + plt.c + pm.c) +target_compile_options(genuine PUBLIC + -Oz -Wall -Wextra -Wshadow -Werror -fvisibility=hidden) +target_include_directories(genuine PUBLIC + integrity) diff --git a/native/src/main/jni/integrity/am-proxy.c b/native/src/main/jni/integrity/am-proxy.c new file mode 100644 index 000000000..00626a642 --- /dev/null +++ b/native/src/main/jni/integrity/am-proxy.c @@ -0,0 +1,824 @@ +// +// Created by Thom on 2019/3/15. +// + +#include "common.h" +#include "am-proxy.h" + +static inline void fill_android_app_ActivityManager(char v[]) { + // android/app/ActivityManager + static unsigned int m = 0; + + if (m == 0) { + m = 23; + } else if (m == 29) { + m = 31; + } + + v[0x0] = 'e'; + v[0x1] = 'k'; + v[0x2] = 'b'; + v[0x3] = 'u'; + v[0x4] = 'g'; + v[0x5] = '`'; + v[0x6] = 'n'; + v[0x7] = '$'; + v[0x8] = 'm'; + v[0x9] = '}'; + v[0xa] = '~'; + v[0xb] = ' '; + v[0xc] = 'Q'; + v[0xd] = 'r'; + v[0xe] = 'f'; + v[0xf] = 'z'; + v[0x10] = 'b'; + v[0x11] = '|'; + v[0x12] = 'b'; + v[0x13] = 'y'; + v[0x14] = 'L'; + v[0x15] = 'c'; + v[0x16] = 'm'; + v[0x17] = 'e'; + v[0x18] = 'b'; + v[0x19] = 'c'; + v[0x1a] = 'u'; + for (unsigned int i = 0; i < 0x1b; ++i) { + v[i] ^= ((i + 0x1b) % m); + } + v[0x1b] = '\0'; +} + +static inline void fill_getService(char v[]) { + // getService + static unsigned int m = 0; + + if (m == 0) { + m = 7; + } else if (m == 11) { + m = 13; + } + + v[0x0] = 'd'; + v[0x1] = 'a'; + v[0x2] = 'q'; + v[0x3] = 'U'; + v[0x4] = 'e'; + v[0x5] = 's'; + v[0x6] = 't'; + v[0x7] = 'j'; + v[0x8] = 'g'; + v[0x9] = '`'; + for (unsigned int i = 0; i < 0xa; ++i) { + v[i] ^= ((i + 0xa) % m); + } + v[0xa] = '\0'; +} + +static inline void fill_getService_signature(char v[]) { + // ()Landroid/app/IActivityManager; + static unsigned int m = 0; + + if (m == 0) { + m = 31; + } else if (m == 37) { + m = 41; + } + + v[0x0] = ')'; + v[0x1] = '+'; + v[0x2] = 'O'; + v[0x3] = 'e'; + v[0x4] = 'k'; + v[0x5] = 'b'; + v[0x6] = 'u'; + v[0x7] = 'g'; + v[0x8] = '`'; + v[0x9] = 'n'; + v[0xa] = '$'; + v[0xb] = 'm'; + v[0xc] = '}'; + v[0xd] = '~'; + v[0xe] = ' '; + v[0xf] = 'Y'; + v[0x10] = 'P'; + v[0x11] = 'q'; + v[0x12] = 'g'; + v[0x13] = '}'; + v[0x14] = 'c'; + v[0x15] = '\x7f'; + v[0x16] = 'c'; + v[0x17] = 'a'; + v[0x18] = 'T'; + v[0x19] = '{'; + v[0x1a] = 'u'; + v[0x1b] = '}'; + v[0x1c] = 'z'; + v[0x1d] = '{'; + v[0x1e] = 'r'; + v[0x1f] = ':'; + for (unsigned int i = 0; i < 0x20; ++i) { + v[i] ^= ((i + 0x20) % m); + } + v[0x20] = '\0'; +} + +static inline void fill_android_app_ActivityManagerNative(char v[]) { + // android/app/ActivityManagerNative + static unsigned int m = 0; + + if (m == 0) { + m = 31; + } else if (m == 37) { + m = 41; + } + + v[0x0] = 'c'; + v[0x1] = 'm'; + v[0x2] = '`'; + v[0x3] = 'w'; + v[0x4] = 'i'; + v[0x5] = 'n'; + v[0x6] = 'l'; + v[0x7] = '&'; + v[0x8] = 'k'; + v[0x9] = '{'; + v[0xa] = '|'; + v[0xb] = '"'; + v[0xc] = 'O'; + v[0xd] = 'l'; + v[0xe] = 'd'; + v[0xf] = 'x'; + v[0x10] = 'd'; + v[0x11] = 'z'; + v[0x12] = '`'; + v[0x13] = 'l'; + v[0x14] = '['; + v[0x15] = 'v'; + v[0x16] = 'v'; + v[0x17] = 'x'; + v[0x18] = '}'; + v[0x19] = '~'; + v[0x1a] = 'n'; + v[0x1b] = 'S'; + v[0x1c] = '\x7f'; + v[0x1d] = 't'; + v[0x1e] = 'h'; + v[0x1f] = 't'; + v[0x20] = 'f'; + for (unsigned int i = 0; i < 0x21; ++i) { + v[i] ^= ((i + 0x21) % m); + } + v[0x21] = '\0'; +} + +static inline void fill_getDefault(char v[]) { + // getDefault + static unsigned int m = 0; + + if (m == 0) { + m = 7; + } else if (m == 11) { + m = 13; + } + + v[0x0] = 'd'; + v[0x1] = 'a'; + v[0x2] = 'q'; + v[0x3] = 'B'; + v[0x4] = 'e'; + v[0x5] = 'g'; + v[0x6] = 'c'; + v[0x7] = 'v'; + v[0x8] = 'h'; + v[0x9] = 'q'; + for (unsigned int i = 0; i < 0xa; ++i) { + v[i] ^= ((i + 0xa) % m); + } + v[0xa] = '\0'; +} + +static inline void fill_getDefault_signature(char v[]) { + // ()Landroid/app/IActivityManager; + static unsigned int m = 0; + + if (m == 0) { + m = 31; + } else if (m == 37) { + m = 41; + } + + v[0x0] = ')'; + v[0x1] = '+'; + v[0x2] = 'O'; + v[0x3] = 'e'; + v[0x4] = 'k'; + v[0x5] = 'b'; + v[0x6] = 'u'; + v[0x7] = 'g'; + v[0x8] = '`'; + v[0x9] = 'n'; + v[0xa] = '$'; + v[0xb] = 'm'; + v[0xc] = '}'; + v[0xd] = '~'; + v[0xe] = ' '; + v[0xf] = 'Y'; + v[0x10] = 'P'; + v[0x11] = 'q'; + v[0x12] = 'g'; + v[0x13] = '}'; + v[0x14] = 'c'; + v[0x15] = '\x7f'; + v[0x16] = 'c'; + v[0x17] = 'a'; + v[0x18] = 'T'; + v[0x19] = '{'; + v[0x1a] = 'u'; + v[0x1b] = '}'; + v[0x1c] = 'z'; + v[0x1d] = '{'; + v[0x1e] = 'r'; + v[0x1f] = ':'; + for (unsigned int i = 0; i < 0x20; ++i) { + v[i] ^= ((i + 0x20) % m); + } + v[0x20] = '\0'; +} + +static inline void fill_android_os_BinderProxy(char v[]) { + // android/os/BinderProxy + static unsigned int m = 0; + + if (m == 0) { + m = 19; + } else if (m == 23) { + m = 29; + } + + v[0x0] = 'b'; + v[0x1] = 'j'; + v[0x2] = 'a'; + v[0x3] = 't'; + v[0x4] = 'h'; + v[0x5] = 'a'; + v[0x6] = 'm'; + v[0x7] = '%'; + v[0x8] = 'd'; + v[0x9] = '\x7f'; + v[0xa] = '"'; + v[0xb] = 'L'; + v[0xc] = 'f'; + v[0xd] = '~'; + v[0xe] = 'u'; + v[0xf] = 'w'; + v[0x10] = 'r'; + v[0x11] = 'Q'; + v[0x12] = 'p'; + v[0x13] = 'l'; + v[0x14] = '|'; + v[0x15] = '|'; + for (unsigned int i = 0; i < 0x16; ++i) { + v[i] ^= ((i + 0x16) % m); + } + v[0x16] = '\0'; +} + +static inline void fill_android_os_IInterface(char v[]) { + // android/os/IInterface + static unsigned int m = 0; + + if (m == 0) { + m = 19; + } else if (m == 23) { + m = 29; + } + + v[0x0] = 'c'; + v[0x1] = 'm'; + v[0x2] = '`'; + v[0x3] = 'w'; + v[0x4] = 'i'; + v[0x5] = 'n'; + v[0x6] = 'l'; + v[0x7] = '&'; + v[0x8] = 'e'; + v[0x9] = 'x'; + v[0xa] = '#'; + v[0xb] = 'D'; + v[0xc] = 'G'; + v[0xd] = 'a'; + v[0xe] = 'd'; + v[0xf] = 't'; + v[0x10] = '`'; + v[0x11] = 'f'; + v[0x12] = '`'; + v[0x13] = 'a'; + v[0x14] = 'f'; + for (unsigned int i = 0; i < 0x15; ++i) { + v[i] ^= ((i + 0x15) % m); + } + v[0x15] = '\0'; +} + +static inline void fill_asBinder(char v[]) { + // asBinder + static unsigned int m = 0; + + if (m == 0) { + m = 7; + } else if (m == 11) { + m = 13; + } + + v[0x0] = '`'; + v[0x1] = 'q'; + v[0x2] = 'A'; + v[0x3] = 'm'; + v[0x4] = 'k'; + v[0x5] = 'b'; + v[0x6] = 'e'; + v[0x7] = 's'; + for (unsigned int i = 0; i < 0x8; ++i) { + v[i] ^= ((i + 0x8) % m); + } + v[0x8] = '\0'; +} + +static inline void fill_asBinder_signature(char v[]) { + // ()Landroid/os/IBinder; + static unsigned int m = 0; + + if (m == 0) { + m = 19; + } else if (m == 23) { + m = 29; + } + + v[0x0] = '+'; + v[0x1] = '-'; + v[0x2] = 'I'; + v[0x3] = 'g'; + v[0x4] = 'i'; + v[0x5] = 'l'; + v[0x6] = '{'; + v[0x7] = 'e'; + v[0x8] = 'b'; + v[0x9] = 'h'; + v[0xa] = '"'; + v[0xb] = 'a'; + v[0xc] = '|'; + v[0xd] = '?'; + v[0xe] = 'X'; + v[0xf] = 'P'; + v[0x10] = 'i'; + v[0x11] = 'o'; + v[0x12] = 'f'; + v[0x13] = 'f'; + v[0x14] = 'v'; + v[0x15] = '>'; + for (unsigned int i = 0; i < 0x16; ++i) { + v[i] ^= ((i + 0x16) % m); + } + v[0x16] = '\0'; +} + +static inline void fill_android_os_ServiceManager(char v[]) { + // android/os/ServiceManager + static unsigned int m = 0; + + if (m == 0) { + m = 23; + } else if (m == 29) { + m = 31; + } + + v[0x0] = 'c'; + v[0x1] = 'm'; + v[0x2] = '`'; + v[0x3] = 'w'; + v[0x4] = 'i'; + v[0x5] = 'n'; + v[0x6] = 'l'; + v[0x7] = '&'; + v[0x8] = 'e'; + v[0x9] = 'x'; + v[0xa] = '#'; + v[0xb] = '^'; + v[0xc] = 'k'; + v[0xd] = '}'; + v[0xe] = 'f'; + v[0xf] = 'x'; + v[0x10] = 'q'; + v[0x11] = 'v'; + v[0x12] = 'Y'; + v[0x13] = 't'; + v[0x14] = 'x'; + v[0x15] = 'a'; + v[0x16] = 'f'; + v[0x17] = 'g'; + v[0x18] = 'q'; + for (unsigned int i = 0; i < 0x19; ++i) { + v[i] ^= ((i + 0x19) % m); + } + v[0x19] = '\0'; +} + +static inline void fill_getService_signature_IBinder(char v[]) { + // (Ljava/lang/String;)Landroid/os/IBinder; + static unsigned int m = 0; + + if (m == 0) { + m = 37; + } else if (m == 41) { + m = 43; + } + + v[0x0] = '+'; + v[0x1] = 'H'; + v[0x2] = 'o'; + v[0x3] = 'g'; + v[0x4] = 'q'; + v[0x5] = 'i'; + v[0x6] = '&'; + v[0x7] = 'f'; + v[0x8] = 'j'; + v[0x9] = 'b'; + v[0xa] = 'j'; + v[0xb] = '!'; + v[0xc] = '\\'; + v[0xd] = 'd'; + v[0xe] = 'c'; + v[0xf] = '{'; + v[0x10] = '}'; + v[0x11] = 's'; + v[0x12] = '.'; + v[0x13] = '?'; + v[0x14] = '['; + v[0x15] = 'y'; + v[0x16] = 'w'; + v[0x17] = '~'; + v[0x18] = 'i'; + v[0x19] = 's'; + v[0x1a] = 't'; + v[0x1b] = 'z'; + v[0x1c] = '0'; + v[0x1d] = 'O'; + v[0x1e] = 'R'; + v[0x1f] = '\r'; + v[0x20] = 'j'; + v[0x21] = 'f'; + v[0x22] = 'i'; + v[0x23] = 'o'; + v[0x24] = 'f'; + v[0x25] = 'f'; + v[0x26] = 'v'; + v[0x27] = '>'; + for (unsigned int i = 0; i < 0x28; ++i) { + v[i] ^= ((i + 0x28) % m); + } + v[0x28] = '\0'; +} + +static inline void fill_activity(char v[]) { + // activity + static unsigned int m = 0; + + if (m == 0) { + m = 7; + } else if (m == 11) { + m = 13; + } + + v[0x0] = '`'; + v[0x1] = 'a'; + v[0x2] = 'w'; + v[0x3] = 'm'; + v[0x4] = 's'; + v[0x5] = 'o'; + v[0x6] = 't'; + v[0x7] = 'x'; + for (unsigned int i = 0; i < 0x8; ++i) { + v[i] ^= ((i + 0x8) % m); + } + v[0x8] = '\0'; +} + +static inline void fill_getName(char v[]) { + // getName + static unsigned int m = 0; + + if (m == 0) { + m = 5; + } else if (m == 7) { + m = 11; + } + + v[0x0] = 'e'; + v[0x1] = 'f'; + v[0x2] = 'p'; + v[0x3] = 'N'; + v[0x4] = '`'; + v[0x5] = 'o'; + v[0x6] = 'f'; + for (unsigned int i = 0; i < 0x7; ++i) { + v[i] ^= ((i + 0x7) % m); + } + v[0x7] = '\0'; +} + +static inline void fill_getName_signature(char v[]) { + // ()Ljava/lang/String; + static unsigned int m = 0; + + if (m == 0) { + m = 19; + } else if (m == 23) { + m = 29; + } + + v[0x0] = ')'; + v[0x1] = '+'; + v[0x2] = 'O'; + v[0x3] = 'n'; + v[0x4] = 'd'; + v[0x5] = 'p'; + v[0x6] = 'f'; + v[0x7] = '\''; + v[0x8] = 'e'; + v[0x9] = 'k'; + v[0xa] = 'e'; + v[0xb] = 'k'; + v[0xc] = '"'; + v[0xd] = ']'; + v[0xe] = '{'; + v[0xf] = 'b'; + v[0x10] = 'x'; + v[0x11] = '|'; + v[0x12] = 'g'; + v[0x13] = ':'; + for (unsigned int i = 0; i < 0x14; ++i) { + v[i] ^= ((i + 0x14) % m); + } + v[0x14] = '\0'; +} + +static inline void fill_activity_manager_service_is_s(char v[]) { + // activity manager service is %s + static unsigned int m = 0; + + if (m == 0) { + m = 29; + } else if (m == 31) { + m = 37; + } + + v[0x0] = '`'; + v[0x1] = 'a'; + v[0x2] = 'w'; + v[0x3] = 'm'; + v[0x4] = 's'; + v[0x5] = 'o'; + v[0x6] = 's'; + v[0x7] = 'q'; + v[0x8] = ')'; + v[0x9] = 'g'; + v[0xa] = 'j'; + v[0xb] = 'b'; + v[0xc] = 'l'; + v[0xd] = 'i'; + v[0xe] = 'j'; + v[0xf] = 'b'; + v[0x10] = '1'; + v[0x11] = 'a'; + v[0x12] = 'v'; + v[0x13] = 'f'; + v[0x14] = 'c'; + v[0x15] = '\x7f'; + v[0x16] = 't'; + v[0x17] = '}'; + v[0x18] = '9'; + v[0x19] = 's'; + v[0x1a] = 'h'; + v[0x1b] = '<'; + v[0x1c] = '%'; + v[0x1d] = 'r'; + for (unsigned int i = 0; i < 0x1e; ++i) { + v[i] ^= ((i + 0x1e) % m); + } + v[0x1e] = '\0'; +} + +static inline void showError(JNIEnv *env, jclass object) { + char v1[0x8], v2[0x1f]; + + if (object == NULL) { + fill_activity_manager_service_is_s(v2); // 0x1f + LOGE(v2, NULL); + } else { + fill_getName(v1); // 0x8 + fill_getName_signature(v2); // 0x15 + + jclass clazz = (*env)->GetObjectClass(env, object); + jmethodID method = (*env)->GetMethodID(env, clazz, v1, v2); + jstring string = (*env)->CallObjectMethod(env, object, method); + + const char *name = (*env)->GetStringUTFChars(env, string, NULL); + fill_activity_manager_service_is_s(v2); // 0x1f + LOGE(v2, name); + + (*env)->ReleaseStringUTFChars(env, string, name); + (*env)->DeleteLocalRef(env, string); + (*env)->DeleteLocalRef(env, clazz); + } +} + +bool isAmProxy(JNIEnv *env, int sdk) { + char v1[0xb], v2[0x29]; + bool proxy = false; + jmethodID method; + jclass classActivityManager; + + if (sdk >= 26) { + fill_android_app_ActivityManager(v2); // 0x1b + 1 + classActivityManager = (*env)->FindClass(env, v2); + debug(env, "ActivityManager: %s", classActivityManager); + fill_getService(v1); // 0xa + 1 + fill_getService_signature(v2); // 0x20 + 1 + method = (*env)->GetStaticMethodID(env, classActivityManager, v1, v2); +#ifdef DEBUG + LOGI("ActivityManager.getService: %p", method); +#endif + if (method == NULL) { +#ifdef DEBUG + LOGW("cannot find ActivityManager.getService"); +#endif + (*env)->ExceptionClear(env); + goto clean3; + } + } else { + fill_android_app_ActivityManagerNative(v2); // 0x21 + 1 + classActivityManager = (*env)->FindClass(env, v2); + debug(env, "ActivityManagerNative: %s", classActivityManager); + fill_getDefault(v1); // 0xa + 1 + fill_getDefault_signature(v2); // 0x20 + 1 + method = (*env)->GetStaticMethodID(env, classActivityManager, v1, v2); +#ifdef DEBUG + LOGI("ActivityManagerNative.getDefault: %p", method); +#endif + } + + jobject activityManager = (*env)->CallStaticObjectMethod(env, classActivityManager, method); + if (activityManager == NULL) { +#ifdef DEBUG + LOGW("activity manager is null"); +#endif + if ((*env)->ExceptionCheck(env)) { +#ifdef DEBUG + (*env)->ExceptionDescribe(env); +#endif + (*env)->ExceptionClear(env); + } + goto clean3; + } + debug(env, "activity manager: %s", activityManager); + + jclass binderClass = NULL; + jobject binder = NULL; + + fill_android_os_BinderProxy(v2); // 0x16 + 1 + jclass classBinderProxy = (*env)->FindClass(env, v2); + if (classBinderProxy == NULL) { +#ifdef DEBUG + LOGW("cannot find BinderProxy"); +#endif + (*env)->ExceptionClear(env); + } else { + debug(env, "BinderProxy: %s", classBinderProxy); + } + + fill_android_os_IInterface(v2); // 0x15 + 1 + jclass classIInterface = (*env)->FindClass(env, v2); + debug(env, "IInterface: %s", classIInterface); + + if (!(*env)->IsInstanceOf(env, activityManager, classIInterface)) { +#ifdef DEBUG + LOGW("activity manager is not IInterface"); +#endif + goto clean2; + } + + fill_asBinder(v1); // 0x8 + 1 + fill_asBinder_signature(v2); // 0x16 + 1 + method = (*env)->GetMethodID(env, classIInterface, v1, v2); +#ifdef DEBUG + LOGI("IInterface.asBinder: %p", method); +#endif + binder = (*env)->CallObjectMethod(env, activityManager, method); + if (binder == NULL) { + if ((*env)->ExceptionCheck(env)) { +#ifdef DEBUG + (*env)->ExceptionDescribe(env); +#endif + (*env)->ExceptionClear(env); + } + showError(env, NULL); + proxy = true; + goto clean2; + } + debug(env, "binder: %s", binder); + + binderClass = (*env)->GetObjectClass(env, binder); + debug(env, "binder class: %s", binderClass); + + if (classBinderProxy != NULL && !(*env)->IsSameObject(env, binderClass, classBinderProxy)) { + showError(env, binderClass); + proxy = true; + goto clean2; + } + + fill_android_os_ServiceManager(v2); // 0x19 + 1 + jclass classServiceManager = (*env)->FindClass(env, v2); + if (classServiceManager == NULL) { +#ifdef DEBUG + LOGW("cannot find ServiceManager"); +#endif + (*env)->ExceptionClear(env); + goto clean2; + } + debug(env, "ServiceManager: %s", classServiceManager); + + fill_getService(v1); // 0xa + 1 + fill_getService_signature_IBinder(v2); // 0x28 + 1 + method = (*env)->GetStaticMethodID(env, classServiceManager, v1, v2); + +#ifdef DEBUG + LOGI("ServiceManager.getService: %p", method); +#endif + if (method == NULL) { + (*env)->ExceptionClear(env); + goto clean1; + } + + fill_activity(v2); // 0x8 + 1 + jstring stringActivity = (*env)->NewStringUTF(env, v2); + jobject service = (*env)->CallStaticObjectMethod(env, classServiceManager, method, + stringActivity); + jclass serviceClass = NULL; + + if (service == NULL) { + if ((*env)->ExceptionCheck(env)) { +#ifdef DEBUG + (*env)->ExceptionDescribe(env); +#endif + (*env)->ExceptionClear(env); + } + showError(env, NULL); + proxy = true; + goto clean; + } + debug(env, "service: %s", service); + + serviceClass = (*env)->GetObjectClass(env, service); + debug(env, "service class: %s", serviceClass); + if (classBinderProxy != NULL && !(*env)->IsSameObject(env, serviceClass, classBinderProxy)) { + showError(env, serviceClass); + proxy = true; + goto clean; + } + + bool equals = (*env)->IsSameObject(env, binder, service); +#ifdef DEBUG + LOGI("binder: %p, service: %p, equals: %d", binder, service, equals); +#endif + proxy = !equals; + +clean: + if (serviceClass != NULL) { + (*env)->DeleteLocalRef(env, serviceClass); + } + if (service != NULL) { + (*env)->DeleteLocalRef(env, service); + } + (*env)->DeleteLocalRef(env, stringActivity); + +clean1: + (*env)->DeleteLocalRef(env, classServiceManager); + +clean2: + if (binderClass != NULL) { + (*env)->DeleteLocalRef(env, binderClass); + } + if (binder != NULL) { + (*env)->DeleteLocalRef(env, binder); + } + if (classBinderProxy != NULL) { + (*env)->DeleteLocalRef(env, classBinderProxy); + } + (*env)->DeleteLocalRef(env, classIInterface); + (*env)->DeleteLocalRef(env, activityManager); + +clean3: + (*env)->DeleteLocalRef(env, classActivityManager); + + return proxy; +} diff --git a/native/src/main/jni/integrity/am-proxy.h b/native/src/main/jni/integrity/am-proxy.h new file mode 100644 index 000000000..7eee7a366 --- /dev/null +++ b/native/src/main/jni/integrity/am-proxy.h @@ -0,0 +1,21 @@ +// +// Created by Thom on 2019/3/15. +// + +#ifndef BREVENT_AM_PROXY_H +#define BREVENT_AM_PROXY_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool isAmProxy(JNIEnv *env, int sdk); + +#ifdef __cplusplus +} +#endif + +#endif //BREVENT_AM_PROXY_H diff --git a/native/src/main/jni/integrity/apk-sign-v2.c b/native/src/main/jni/integrity/apk-sign-v2.c new file mode 100644 index 000000000..f1ca4b9d6 --- /dev/null +++ b/native/src/main/jni/integrity/apk-sign-v2.c @@ -0,0 +1,169 @@ +// +// Created by Thom on 2019/3/8. +// + +#include +#include +#include + +#ifdef MAIN +#include +#else + +#include "common.h" +#include "openat.h" + +#endif + +static bool isApkSigBlock42(const char *buffer) { + // APK Sig Block 42 + return *buffer == 'A' + && *++buffer == 'P' + && *++buffer == 'K' + && *++buffer == ' ' + && *++buffer == 'S' + && *++buffer == 'i' + && *++buffer == 'g' + && *++buffer == ' ' + && *++buffer == 'B' + && *++buffer == 'l' + && *++buffer == 'o' + && *++buffer == 'c' + && *++buffer == 'k' + && *++buffer == ' ' + && *++buffer == '4' + && *++buffer == '2'; +} + +int checkSignature(const char *path) { + unsigned char buffer[0x11] = {0}; + uint32_t size4; + uint64_t size8, size_of_block; + +#ifdef DEBUG + LOGI("check signature for %s", path); +#endif + + int sign = -1; + int fd = (int) openAt(AT_FDCWD, path, O_RDONLY); +#ifdef DEBUG_OPENAT + LOGI("openat %s returns %d", path, fd); +#endif + if (fd < 0) { + return sign; + } + + sign = 1; + // https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD) + for (int i = 0;; ++i) { + unsigned short n; + lseek(fd, -i - 2, SEEK_END); + read(fd, &n, 2); + if (n == i) { + lseek(fd, -22, SEEK_CUR); + read(fd, &size4, 4); + if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) { +#ifdef MAIN + if (i > 0) { + printf("warning: comment length is %d\n", i); + } +#endif + break; + } + } + if (i == 0xffff) { +#ifdef MAIN + printf("error: cannot find eocd\n"); +#endif + goto clean; + } + } + + lseek(fd, 12, SEEK_CUR); + // offset + read(fd, &size4, 0x4); + lseek(fd, (off_t) (size4 - 0x18), SEEK_SET); + + read(fd, &size8, 0x8); + read(fd, buffer, 0x10); + if (!isApkSigBlock42((char *) buffer)) { + goto clean; + } + + lseek(fd, (off_t) (size4 - (size8 + 0x8)), SEEK_SET); + read(fd, &size_of_block, 0x8); + if (size_of_block != size8) { + goto clean; + } + + for (;;) { + uint32_t id; + uint32_t offset; + read(fd, &size8, 0x8); // sequence length + if (size8 == size_of_block) { + break; + } + read(fd, &id, 0x4); // id + offset = 4; +#ifdef MAIN + printf("id: 0x%08x\n", id); +#endif + if ((id ^ 0xdeadbeefu) == 0xafa439f5u || (id ^ 0xdeadbeefu) == 0x2efed62f) { + read(fd, &size4, 0x4); // signer-sequence length + read(fd, &size4, 0x4); // signer length + read(fd, &size4, 0x4); // signed data length + offset += 0x4 * 3; + + read(fd, &size4, 0x4); // digests-sequence length + lseek(fd, (off_t) (size4), SEEK_CUR);// skip digests + offset += 0x4 + size4; + + read(fd, &size4, 0x4); // certificates length + read(fd, &size4, 0x4); // certificate length + offset += 0x4 * 2; +#ifdef MAIN + int hash = 1; + signed char c; + for (unsigned i = 0; i < size4; ++i) { + read(fd, &c, 0x1); + hash = 31 * hash + c; + } + offset += size4; + printf(" size: 0x%04x, hash: 0x%08x\n", size4, ((unsigned) hash) ^ 0x14131211u); +#else +#if defined(GENUINE_SIZE) && defined(GENUINE_HASH) + if (size4 == GENUINE_SIZE) { + int hash = 1; + signed char c; + for (unsigned i = 0; i < size4; ++i) { + read(fd, &c, 0x1); + hash = 31 * hash + c; + } + offset += size4; + if ((((unsigned) hash) ^ 0x14131211u) == GENUINE_HASH) { + sign = 0; + break; + } + } +#else + sign = 0; + break; +#endif +#endif + } + lseek(fd, (off_t) (size8 - offset), SEEK_CUR); + } + +clean: + close(fd); + + return sign; +} + +#ifdef MAIN +int main(int argc, char **argv) { + if (argc > 1) { + checkSignature(argv[1]); + } +} +#endif diff --git a/native/src/main/jni/integrity/apk-sign-v2.h b/native/src/main/jni/integrity/apk-sign-v2.h new file mode 100644 index 000000000..653e6dca9 --- /dev/null +++ b/native/src/main/jni/integrity/apk-sign-v2.h @@ -0,0 +1,18 @@ +// +// Created by Thom on 2019/3/8. +// + +#ifndef BREVENT_APK_SIGN_V2_H +#define BREVENT_APK_SIGN_V2_H + +#ifdef __cplusplus +extern "C" { +#endif + +int checkSignature(const char *path); + +#ifdef __cplusplus +} +#endif + +#endif //BREVENT_APK_SIGN_V2_H diff --git a/native/src/main/jni/integrity/common.c b/native/src/main/jni/integrity/common.c new file mode 100644 index 000000000..dd92b5d97 --- /dev/null +++ b/native/src/main/jni/integrity/common.c @@ -0,0 +1,233 @@ +// +// Created by Thom on 2019/3/20. +// + +#include +#include +#include +#include +#include "common.h" +#include "plt.h" + +static volatile int mGenuine; + +static bool onCheckTrue(JNIEnv *env __unused) { +#ifdef DEBUG_NATIVE + has_native_libs(); +#endif +#ifdef DEBUG_GENUINE_MOCK + start_native_activity_async(env); +#endif + return true; +} + +static bool onCheckFalse(JNIEnv *env __unused) { +#if defined(GENUINE_FALSE_CRASH) + return false; +#elif defined(GENUINE_FALSE_NATIVE) + start_native_activity_async(env); +#endif + return true; +} + +static bool onCheckFake(JNIEnv *env __unused) { +#if defined(GENUINE_FAKE_CRASH) + return false; +#elif defined(GENUINE_FAKE_NATIVE) + start_native_activity_async(env); +#endif + return true; +} + +static bool onCheckOverlay(JNIEnv *env __unused) { +#if defined(GENUINE_OVERLAY_CRASH) + return false; +#elif defined(GENUINE_OVERLAY_NATIVE) + start_native_activity_async(env); +#endif + return true; +} + +static bool onCheckOdex(JNIEnv *env __unused) { +#if defined(GENUINE_ODEX_CRASH) + return false; +#elif defined(GENUINE_ODEX_NATIVE) + start_native_activity_async(env); +#endif + return true; +} + +static bool onCheckDex(JNIEnv *env __unused) { +#if defined(GENUINE_DEX_CRASH) + return false; +#elif defined(GENUINE_DEX_NATIVE) + start_native_activity_async(env); +#endif + return true; +} + +static bool onCheckProxy(JNIEnv *env __unused) { +#if defined(GENUINE_PROXY_CRASH) + return false; +#elif defined(GENUINE_PROXY_NATIVE) + start_native_activity_async(env); +#endif + return true; +} + +static bool onCheckError(JNIEnv *env __unused) { +#if defined(GENUINE_ERROR_CRASH) + return false; +#elif defined(GENUINE_ERROR_NATIVE) + start_native_activity_async(env); +#endif + return true; +} + +static bool onCheckFatal(JNIEnv *env __unused) { +#if defined(GENUINE_FATAL_CRASH) + return false; +#elif defined(GENUINE_FATAL_NATIVE) + start_native_activity_async(env); +#endif + return true; +} + +static bool onCheckNoapk(JNIEnv *env __unused) { +#if defined(GENUINE_NOAPK_CRASH) + return false; +#elif defined(GENUINE_NOAPK_NATIVE) + start_native_activity_async(env); +#endif + return true; +} + +bool setGenuine(JNIEnv *env, int genuine) { + mGenuine = genuine; + switch (genuine) { + case CHECK_TRUE: + return onCheckTrue(env); + case CHECK_FALSE: + return onCheckFalse(env); + case CHECK_FAKE: + return onCheckFake(env); + case CHECK_OVERLAY: + return onCheckOverlay(env); + case CHECK_ODEX: + return onCheckOdex(env); + case CHECK_DEX: + return onCheckDex(env); + case CHECK_PROXY: + return onCheckProxy(env); + case CHECK_ERROR: + return onCheckError(env); + case CHECK_FATAL: + return onCheckFatal(env); + case CHECK_NOAPK: + return onCheckNoapk(env); + default: + return true; + } +} + +int getGenuine() { + return mGenuine; +} + +char *getGenuinePackageName() { +#ifdef GET_GENUINE_PACKAGE_NAME + return GET_GENUINE_PACKAGE_NAME(); +#elif defined(GENUINE_NAME) + static unsigned int m = 0; + if (m == 0) { + m = 20; + } else if (m == 23) { + m = 29; + } + char name[] = GENUINE_NAME; + unsigned int length = sizeof(name) - 1; + for (unsigned int i = 0; i < length; ++i) { + name[i] ^= ((i + length) % m); + } + name[length] = '\0'; + return strdup(name); +#else + return NULL; +#endif +} + +__attribute__((__format__ (__printf__, 2, 0))) +void genuine_log_print(int prio, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + __android_log_vprint(prio, TAG, fmt, ap); + va_end(ap); +} + +static inline void fill_ro_build_version_sdk(char v[]) { + // ro.build.version.sdk + static unsigned int m = 0; + + if (m == 0) { + m = 19; + } else if (m == 23) { + m = 29; + } + + v[0x0] = 's'; + v[0x1] = 'm'; + v[0x2] = '-'; + v[0x3] = 'f'; + v[0x4] = 'p'; + v[0x5] = 'o'; + v[0x6] = 'k'; + v[0x7] = 'l'; + v[0x8] = '\''; + v[0x9] = '|'; + v[0xa] = 'n'; + v[0xb] = '~'; + v[0xc] = '~'; + v[0xd] = 'g'; + v[0xe] = '`'; + v[0xf] = '~'; + v[0x10] = '?'; + v[0x11] = 'a'; + v[0x12] = 'd'; + v[0x13] = 'j'; + for (unsigned int i = 0; i < 0x14; ++i) { + v[i] ^= ((i + 0x14) % m); + } + v[0x14] = '\0'; +} + +int getSdk() { + static int sdk = 0; + if (sdk == 0) { + char v1[0x20]; + char prop[PROP_VALUE_MAX] = {0}; + fill_ro_build_version_sdk(v1); + __system_property_get(v1, prop); + sdk = (int) strtol(prop, NULL, 10); + } + return sdk; +} + +#ifdef DEBUG + +void debug(JNIEnv *env, const char *format, jobject object) { + if (object == NULL) { + LOGI(format, NULL); + } else { + jclass objectClass = (*env)->FindClass(env, "java/lang/Object"); + jmethodID toString = (*env)->GetMethodID(env, objectClass, "toString", + "()Ljava/lang/String;"); + jstring string = (jstring) (*env)->CallObjectMethod(env, object, toString); + const char *value = (*env)->GetStringUTFChars(env, string, NULL); + LOGI(format, value); + (*env)->ReleaseStringUTFChars(env, string, value); + (*env)->DeleteLocalRef(env, string); + (*env)->DeleteLocalRef(env, objectClass); + } +} + +#endif diff --git a/native/src/main/jni/integrity/common.h b/native/src/main/jni/integrity/common.h new file mode 100644 index 000000000..20103b894 --- /dev/null +++ b/native/src/main/jni/integrity/common.h @@ -0,0 +1,73 @@ +// +// Created by Thom on 2019/3/20. +// + +#ifndef BREVENT_COMMON_H +#define BREVENT_COMMON_H + +#include +#include +#include + +#if __has_include("genuine.h") +#include "genuine.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + CHECK_TRUE, + CHECK_FALSE, + CHECK_FAKE, + CHECK_OVERLAY, + CHECK_ODEX, + CHECK_DEX, + CHECK_PROXY, + CHECK_ERROR, + CHECK_FATAL, + CHECK_NOAPK, +}; + +#ifndef TAG +#define TAG "Genuine" +#endif + +#ifndef LOGI +#define LOGI(...) (genuine_log_print(ANDROID_LOG_INFO, __VA_ARGS__)) +#endif + +#ifndef LOGW +#define LOGW(...) (genuine_log_print(ANDROID_LOG_WARN, __VA_ARGS__)) +#endif + +#ifndef LOGE +#define LOGE(...) (genuine_log_print(ANDROID_LOG_ERROR, __VA_ARGS__)) +#endif + +void genuine_log_print(int prio, const char *fmt, ...); + +char *getGenuinePackageName(); + +bool setGenuine(JNIEnv *env, int genuine); + +int getGenuine(); + +int getSdk(); + +bool has_native_libs(); + +#ifdef DEBUG + +void debug(JNIEnv *env, const char *format, jobject object); + +#else +#define debug(...) do {} while(0); +#endif + +#ifdef __cplusplus +} +#endif + +#endif //BREVENT_COMMON_H diff --git a/native/src/main/jni/integrity/genuine.c b/native/src/main/jni/integrity/genuine.c new file mode 100644 index 000000000..96ef3a029 --- /dev/null +++ b/native/src/main/jni/integrity/genuine.c @@ -0,0 +1,1102 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plt.h" +#include "inline.h" +#include "apk-sign-v2.h" +#include "am-proxy.h" +#include "pm.h" +#include "common.h" +#include "openat.h" +#include "path.h" + +#ifdef CHECK_MOUNT +#include "mount.h" +#endif + +static int genuine = CHECK_TRUE; + +static int sdk; + +static int uid; + +static inline void fill_maps(char v[]) { + static unsigned int m = 0; + if (m == 0) { + m = 13; + } else if (m == 17) { + m = 19; + } + v[0x0] = '-'; + v[0x1] = 's'; + v[0x2] = 'v'; + v[0x3] = 'j'; + v[0x4] = 'e'; + v[0x5] = '('; + v[0x6] = '{'; + v[0x7] = 'l'; + v[0x8] = 'f'; + v[0x9] = 'm'; + v[0xa] = '#'; + v[0xb] = 'm'; + v[0xc] = '`'; + v[0xd] = 'r'; + v[0xe] = 'p'; + for (unsigned int i = 0; i < 0xf; ++i) { + v[i] ^= ((i + 0xf) % m); + } + v[0xf] = '\0'; +} + +#ifndef NO_CHECK_MAPS +static inline void fill_cannot_open_proc_self_maps(char v[]) { + // cannot open /proc/self/maps + static unsigned int m = 0; + + if (m == 0) { + m = 23; + } else if (m == 29) { + m = 31; + } + + v[0x0] = 'g'; + v[0x1] = 'd'; + v[0x2] = 'h'; + v[0x3] = 'i'; + v[0x4] = 'g'; + v[0x5] = '}'; + v[0x6] = '*'; + v[0x7] = 'd'; + v[0x8] = '|'; + v[0x9] = 'h'; + v[0xa] = '`'; + v[0xb] = '/'; + v[0xc] = '?'; + v[0xd] = 'a'; + v[0xe] = '`'; + v[0xf] = '|'; + v[0x10] = 'w'; + v[0x11] = ':'; + v[0x12] = 'e'; + v[0x13] = 'e'; + v[0x14] = 'm'; + v[0x15] = 'd'; + v[0x16] = ','; + v[0x17] = 'i'; + v[0x18] = 'd'; + v[0x19] = 'v'; + v[0x1a] = 't'; + for (unsigned int i = 0; i < 0x1b; ++i) { + v[i] ^= ((i + 0x1b) % m); + } + v[0x1b] = '\0'; +} + +static inline void fill_r(char v[]) { + static unsigned int m = 0; + + if (m == 0) { + m = 2; + } else if (m == 3) { + m = 5; + } + + v[0x0] = 's'; + for (unsigned int i = 0; i < 0x1; ++i) { + v[i] ^= ((i + 0x1) % m); + } + v[0x1] = '\0'; +} + +static inline void rstrip(char *line) { + char *path = line; + if (line != NULL) { + while (*path && *path != '\r' && *path != '\n') { + ++path; + } + if (*path) { + *path = '\0'; + } + } +} + +static inline bool isapk(const char *str) { + const char *dot = strrchr(str, '.'); + return dot != NULL + && *++dot == 'a' + && *++dot == 'p' + && *++dot == 'k' + && (*++dot == '\0' || *dot == '\r' || *dot == '\n'); +} + +static inline bool isdex(const char *str) { + const char *dot = strrchr(str, '.'); + return dot != NULL + && *++dot == 'd' + && *++dot == 'e' + && *++dot == 'x' + && (*++dot == '\0' || *dot == '\r' || *dot == '\n'); +} + +static inline bool isodex(const char *str) { + const char *dot = strrchr(str, '.'); + return dot != NULL + && *++dot == 'o' + && *++dot == 'd' + && *++dot == 'e' + && *++dot == 'x' + && (*++dot == '\0' || *dot == '\r' || *dot == '\n'); +} + +static inline bool isso(const char *str) { + const char *dot = strrchr(str, '.'); + return dot != NULL + && *++dot == 's' + && *++dot == 'o' + && (*++dot == '\0' || *dot == '\r' || *dot == '\n'); +} + +#ifdef ANTI_ODEX +static inline size_t fill_dex2oat_cmdline(char v[]) { + // dex2oat-cmdline + static unsigned int m = 0; + + if (m == 0) { + m = 13; + } else if (m == 17) { + m = 19; + } + + v[0x0] = 'f'; + v[0x1] = 'f'; + v[0x2] = '|'; + v[0x3] = '7'; + v[0x4] = 'i'; + v[0x5] = 'f'; + v[0x6] = '|'; + v[0x7] = '$'; + v[0x8] = 'i'; + v[0x9] = 'f'; + v[0xa] = 'h'; + v[0xb] = 'l'; + v[0xc] = 'h'; + v[0xd] = 'l'; + v[0xe] = 'f'; + for (unsigned int i = 0; i < 0xf; ++i) { + v[i] ^= ((i + 0xf) % m); + } + v[0xf] = '\0'; + return 0xf; +} + +static inline size_t fill_dex_file(char v[]) { + // --dex-file + static unsigned int m = 0; + + if (m == 0) { + m = 7; + } else if (m == 11) { + m = 13; + } + + v[0x0] = '.'; + v[0x1] = ')'; + v[0x2] = 'a'; + v[0x3] = 'c'; + v[0x4] = 'x'; + v[0x5] = ','; + v[0x6] = 'd'; + v[0x7] = 'j'; + v[0x8] = 'h'; + v[0x9] = '`'; + for (unsigned int i = 0; i < 0xa; ++i) { + v[i] ^= ((i + 0xa) % m); + } + v[0xa] = '\0'; + return 0xa; +} + +static inline int checkOdex(const char *path) { + size_t len; + char *cmdline; + char buffer[0x400], find[64]; + + int ret = 0; + int fd = open(path, (unsigned) O_RDONLY | (unsigned) O_CLOEXEC); + if (fd == -1) { + return 1; + } + + lseek(fd, 0x1000, SEEK_SET); + read(fd, buffer, 0x400); + + cmdline = buffer; + len = fill_dex2oat_cmdline(find) + 1; + for (int i = 0; i < 0x200; ++i, ++cmdline) { + if (memcmp(cmdline, find, len) == 0) { + cmdline += len; + fill_dex_file(find); + if ((ret = (strstr(cmdline, find) != NULL))) { + fill_dex2oat_cmdline(find); + LOGE(find); + LOGE(cmdline); + } + break; + } + } + + close(fd); + + return ret; +} +#endif + +static inline bool isSameFile(const char *path1, const char *path2) { + struct stat stat1, stat2; + if (path1 == NULL || path2 == NULL) { + return false; + } + if (lstat(path1, &stat1)) { + return false; + } + if (lstat(path2, &stat2)) { + return false; + } + return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino; +} + +static inline void fill_ba88(char v[]) { + // ba887869b4a4a6d1e915d383fad97c49 + static unsigned int m = 0; + if (m == 0) { + m = 13; + } else if (m == 17) { + m = 19; + } + v[0x0] = 'f'; + v[0x1] = 'o'; + v[0x2] = '['; + v[0x3] = 'l'; + v[0x4] = 'r'; + v[0x5] = 'b'; + v[0x6] = 'z'; + v[0x7] = 'h'; + v[0x8] = '~'; + v[0x9] = 'n'; + v[0xa] = 'S'; + v[0xb] = 'p'; + v[0xc] = 'i'; + v[0xd] = 'f'; + v[0xe] = 'q'; + for (unsigned int i = 0; i < 0xf; ++i) { + v[i] ^= ((i + 0xf) % m); + } + v[0xf] = '\0'; +} + +static inline bool isSame(const char *path1, const char *path2) { + if (path1[0] == '/') { + return strcmp(path1, path2) == 0; + } else { + return strcmp(path1, strrchr(path2, '/') + 1) == 0; + } +} + +static inline void fill_cannot_find_s(char v[]) { + // cannot find %s + static unsigned int m = 0; + + if (m == 0) { + m = 13; + } else if (m == 17) { + m = 19; + } + + v[0x0] = 'b'; + v[0x1] = 'c'; + v[0x2] = 'm'; + v[0x3] = 'j'; + v[0x4] = 'j'; + v[0x5] = 'r'; + v[0x6] = '\''; + v[0x7] = 'n'; + v[0x8] = '`'; + v[0x9] = 'd'; + v[0xa] = 'o'; + v[0xb] = ','; + v[0xc] = '%'; + v[0xd] = 'r'; + for (unsigned int i = 0; i < 0xe; ++i) { + v[i] ^= ((i + 0xe) % m); + } + v[0xe] = '\0'; +} + +enum { + TYPE_NON, + TYPE_APK, + TYPE_DEX, + TYPE_SO, +}; + +static inline int checkMaps(const char *packageName, const char *packagePath) { + FILE *fp = NULL; + char line[PATH_MAX]; + char maps[0x10]; + int check = genuine; + bool loaded = false; + + fill_maps(maps); + int fd = (int) openAt(AT_FDCWD, maps, O_RDONLY); +#ifdef DEBUG_OPENAT + LOGI("openat %s returns %d", maps, fd); +#endif + if (fd < 0) { + fill_cannot_open_proc_self_maps(line); + LOGE(line); + return CHECK_ERROR; + } + + Symbol symbol; + fill_ba88(line); + memset(&symbol, 0, sizeof(Symbol)); + symbol.check = (PLT_CHECK_PLT_APP | PLT_CHECK_NAME); + symbol.symbol_name = (char *) line; + if (dl_iterate_phdr_symbol(&symbol)) { + LOGE(line); + check = CHECK_ERROR; + goto clean2; + } + +#if __ANDROID_API__ >= 21 || !defined(__arm__) + if (symbol.size == 0) { + LOGE(line); + check = CHECK_ERROR; + goto clean2; + } +#endif + + char mode[0x2]; + fill_r(mode); + + fp = fdopen(fd, mode); + if (fp == NULL) { + fill_cannot_open_proc_self_maps(line); + LOGE(line); + check = CHECK_ERROR; + goto clean3; + } + + while (fgets(line, PATH_MAX - 1, fp) != NULL) { + int type; + char *path = line; + if (strchr(line, '/') == NULL) { + continue; + } + while (*path != '/') { + ++path; + } + rstrip(path); + if (isapk(path)) { + type = TYPE_APK; + } else if (isodex(path) || isdex(path)) { + type = TYPE_DEX; + } else if (isso(path)) { + type = TYPE_SO; + if (symbol.size > 0) { + bool found = false; + for (int i = 0; i < symbol.size; ++i) { + if (symbol.names[i] != NULL) { + if (isSame(symbol.names[i], path)) { + free(symbol.names[i]); + symbol.names[i] = NULL; + } else { + found = true; + } + } + } + if (!found) { + symbol.size = 0; + } + } + } else { + type = TYPE_NON; + } +#ifdef DEBUG_MAPS + LOGI("type: %d, line: %s", type, line); +#endif + if (strstr(path, packageName) != NULL && access(path, F_OK) == 0) { + if (type == TYPE_APK) { +#ifdef DEBUG + LOGI("check %s", path); +#endif + if (isSameFile(path, packagePath)) { + loaded = true; + } else { + LOGW(path); + } + } else if (type == TYPE_DEX) { +#ifdef ANTI_ODEX +#ifdef DEBUG + LOGI("check %s", path); +#endif + if (checkOdex(path)) { + LOGE(path); + check = CHECK_ODEX; + } +#endif + } + } else if (isDataApp(path)) { + if (type == TYPE_DEX) { + LOGE(path); + check = CHECK_DEX; + } else if (type == TYPE_APK) { +#ifdef ANTI_OVERLAY + LOGE(path); + check = CHECK_OVERLAY; +#endif + } + } + } + + fclose(fp); + +clean3: + close(fd); + +clean2: + fill_cannot_find_s(line); + if (!loaded) { + LOGE(line, packagePath); + check = CHECK_ERROR; + } + for (int i = 0; i < symbol.size; ++i) { + if (symbol.names[i] != NULL) { + if (strchr(symbol.names[i], '!') == NULL) { + LOGE(line, symbol.names[i]); + check = CHECK_ERROR; + } + free(symbol.names[i]); + symbol.names[i] = NULL; + } + } + if (symbol.names != NULL) { + free(symbol.names); + symbol.names = NULL; + } + + return check; +} +#endif + +#ifdef CHECK_HOOK +static inline void fill_jniRegisterNativeMethods(char v[]) { + // jniRegisterNativeMethods + static unsigned int m = 0; + + if (m == 0) { + m = 23; + } else if (m == 29) { + m = 31; + } + + v[0x0] = 'k'; + v[0x1] = 'l'; + v[0x2] = 'j'; + v[0x3] = 'V'; + v[0x4] = '`'; + v[0x5] = 'a'; + v[0x6] = 'n'; + v[0x7] = '{'; + v[0x8] = '}'; + v[0x9] = 'o'; + v[0xa] = 'y'; + v[0xb] = 'B'; + v[0xc] = 'l'; + v[0xd] = 'z'; + v[0xe] = 'f'; + v[0xf] = 'f'; + v[0x10] = 't'; + v[0x11] = '_'; + v[0x12] = 'v'; + v[0x13] = '`'; + v[0x14] = '}'; + v[0x15] = 'y'; + v[0x16] = 'd'; + v[0x17] = 'r'; + for (unsigned int i = 0; i < 0x18; ++i) { + v[i] ^= ((i + 0x18) % m); + } + v[0x18] = '\0'; +} + +static inline void fill___openat(char v[]) { + // __openat + static unsigned int m = 0; + + if (m == 0) { + m = 7; + } else if (m == 11) { + m = 13; + } + + v[0x0] = '^'; + v[0x1] = ']'; + v[0x2] = 'l'; + v[0x3] = 't'; + v[0x4] = '`'; + v[0x5] = 'h'; + v[0x6] = 'a'; + v[0x7] = 'u'; + for (unsigned int i = 0; i < 0x8; ++i) { + v[i] ^= ((i + 0x8) % m); + } + v[0x8] = '\0'; +} + +static inline void fill_openat(char v[]) { + // openat + static unsigned int m = 0; + + if (m == 0) { + m = 5; + } else if (m == 7) { + m = 11; + } + + v[0x0] = 'n'; + v[0x1] = 'r'; + v[0x2] = 'f'; + v[0x3] = 'j'; + v[0x4] = 'a'; + v[0x5] = 'u'; + for (unsigned int i = 0; i < 0x6; ++i) { + v[i] ^= ((i + 0x6) % m); + } + v[0x6] = '\0'; +} + +static inline void fill_open(char v[]) { + // open + static unsigned int m = 0; + + if (m == 0) { + m = 3; + } else if (m == 5) { + m = 7; + } + + v[0x0] = 'n'; + v[0x1] = 'r'; + v[0x2] = 'e'; + v[0x3] = 'o'; + for (unsigned int i = 0; i < 0x4; ++i) { + v[i] ^= ((i + 0x4) % m); + } + v[0x4] = '\0'; +} + +#endif + +static inline void fill_sdk_d_genuine_d(char v[]) { + // sdk: %d, genuine: %d + static unsigned int m = 0; + + if (m == 0) { + m = 19; + } else if (m == 23) { + m = 29; + } + + v[0x0] = 'r'; + v[0x1] = 'f'; + v[0x2] = 'h'; + v[0x3] = '>'; + v[0x4] = '%'; + v[0x5] = '#'; + v[0x6] = 'c'; + v[0x7] = '$'; + v[0x8] = ')'; + v[0x9] = 'm'; + v[0xa] = 'n'; + v[0xb] = 'b'; + v[0xc] = 'x'; + v[0xd] = 'g'; + v[0xe] = 'a'; + v[0xf] = 'u'; + v[0x10] = '+'; + v[0x11] = '2'; + v[0x12] = '%'; + v[0x13] = 'e'; + for (unsigned int i = 0; i < 0x14; ++i) { + v[i] ^= ((i + 0x14) % m); + } + v[0x14] = '\0'; +} + +static inline void fill_add_sigcont(char v[]) { + // add sigcont handler + static unsigned int m = 0; + + if (m == 0) { + m = 17; + } else if (m == 19) { + m = 23; + } + + v[0x0] = 'c'; + v[0x1] = 'g'; + v[0x2] = '`'; + v[0x3] = '%'; + v[0x4] = 'u'; + v[0x5] = 'n'; + v[0x6] = 'o'; + v[0x7] = 'j'; + v[0x8] = 'e'; + v[0x9] = 'e'; + v[0xa] = 'x'; + v[0xb] = '-'; + v[0xc] = 'f'; + v[0xd] = 'n'; + v[0xe] = '~'; + v[0xf] = 'd'; + v[0x10] = 'm'; + v[0x11] = 'g'; + v[0x12] = 'q'; + for (unsigned int i = 0; i < 0x13; ++i) { + v[i] ^= ((i + 0x13) % m); + } + v[0x13] = '\0'; +} + +static inline void fill_received_sigcont(char v[]) { + // received sigcont + static unsigned int m = 0; + + if (m == 0) { + m = 13; + } else if (m == 17) { + m = 19; + } + + v[0x0] = 'q'; + v[0x1] = 'a'; + v[0x2] = 'f'; + v[0x3] = 'c'; + v[0x4] = 'n'; + v[0x5] = '~'; + v[0x6] = 'l'; + v[0x7] = 'n'; + v[0x8] = '+'; + v[0x9] = '\x7f'; + v[0xa] = 'i'; + v[0xb] = 'f'; + v[0xc] = 'a'; + v[0xd] = 'l'; + v[0xe] = 'j'; + v[0xf] = 'q'; + for (unsigned int i = 0; i < 0x10; ++i) { + v[i] ^= ((i + 0x10) % m); + } + v[0x10] = '\0'; +} + +#if defined(CHECK_ARM64) && defined(__arm__) +static inline void fill_ro_product_cpu_abi(char v[]) { + // ro.product.cpu.abi + static unsigned int m = 0; + + if (m == 0) { + m = 17; + } else if (m == 19) { + m = 23; + } + + v[0x0] = 's'; + v[0x1] = 'm'; + v[0x2] = '-'; + v[0x3] = 't'; + v[0x4] = 'w'; + v[0x5] = 'i'; + v[0x6] = 'c'; + v[0x7] = '}'; + v[0x8] = 'j'; + v[0x9] = '~'; + v[0xa] = '%'; + v[0xb] = 'o'; + v[0xc] = '}'; + v[0xd] = '{'; + v[0xe] = '!'; + v[0xf] = 'q'; + v[0x10] = 'b'; + v[0x11] = 'h'; + for (unsigned int i = 0; i < 0x12; ++i) { + v[i] ^= ((i + 0x12) % m); + } + v[0x12] = '\0'; +} + +static inline bool isArm64V8a(const char *str) { + return str != NULL + && *str == 'a' + && *++str == 'r' + && *++str == 'm' + && *++str == '6' + && *++str == '4' + && *++str == '-' + && *++str == 'v' + && *++str == '8' + && *++str == 'a'; +} + +static inline void fill_32_64(char v[]) { + // run in 32 on 64 machine + static unsigned int m = 0; + + if (m == 0) { + m = 19; + } else if (m == 23) { + m = 29; + } + + v[0x0] = 'v'; + v[0x1] = 'p'; + v[0x2] = 'h'; + v[0x3] = '\''; + v[0x4] = 'a'; + v[0x5] = 'g'; + v[0x6] = '*'; + v[0x7] = '8'; + v[0x8] = '>'; + v[0x9] = '-'; + v[0xa] = 'a'; + v[0xb] = 'a'; + v[0xc] = '0'; + v[0xd] = '\''; + v[0xe] = '&'; + v[0xf] = ' '; + v[0x10] = 'l'; + v[0x11] = 'c'; + v[0x12] = '`'; + v[0x13] = 'l'; + v[0x14] = 'l'; + v[0x15] = 'h'; + v[0x16] = 'b'; + for (unsigned int i = 0; i < 0x17; ++i) { + v[i] ^= ((i + 0x17) % m); + } + v[0x17] = '\0'; +} +#endif + +static void handler(int sig __unused) { + char v[0x11]; + fill_received_sigcont(v); + LOGI(v); +} + +static inline void fill_invalid_signature_path_s(char v[]) { + // invalid signature, path: %s + static unsigned int m = 0; + + if (m == 0) { + m = 23; + } else if (m == 29) { + m = 31; + } + + v[0x0] = 'm'; + v[0x1] = 'k'; + v[0x2] = 'p'; + v[0x3] = 'f'; + v[0x4] = 'd'; + v[0x5] = '`'; + v[0x6] = 'n'; + v[0x7] = '+'; + v[0x8] = '\x7f'; + v[0x9] = 'd'; + v[0xa] = 'i'; + v[0xb] = 'a'; + v[0xc] = 'q'; + v[0xd] = 'e'; + v[0xe] = 'g'; + v[0xf] = 'a'; + v[0x10] = 'q'; + v[0x11] = '9'; + v[0x12] = '6'; + v[0x13] = 'p'; + v[0x14] = '`'; + v[0x15] = 'v'; + v[0x16] = 'k'; + v[0x17] = '>'; + v[0x18] = '%'; + v[0x19] = '#'; + v[0x1a] = 't'; + for (unsigned int i = 0; i < 0x1b; ++i) { + v[i] ^= ((i + 0x1b) % m); + } + v[0x1b] = '\0'; +} + +#ifdef DEBUG_HOOK_IO + +static void check_hook_function(void *handle, const char *name) { + void *symbol = dlsym(handle, name); +#ifdef DEBUG + void *symbol2 = plt_dlsym(name, NULL); + LOGI("symbol: %s, dlsym: %p, dl_iterate_phdr: %p", name, symbol, symbol2); +#endif + if (symbol != NULL && setRead(symbol) && isInlineHooked(symbol)) { + LOGI("%s is hooked", name); + } +} + +#define HOOK_SYMBOL(x, y) check_hook_function(x, #y) + +static void check_inline_hook_io() { + void *handle = dlopen("libc.so", RTLD_NOW); + if (handle) { + HOOK_SYMBOL(handle, faccessat); + HOOK_SYMBOL(handle, __openat); + HOOK_SYMBOL(handle, fchmodat); + HOOK_SYMBOL(handle, fchownat); + HOOK_SYMBOL(handle, renameat); + HOOK_SYMBOL(handle, fstatat64); + HOOK_SYMBOL(handle, __statfs); + HOOK_SYMBOL(handle, __statfs64); + HOOK_SYMBOL(handle, mkdirat); + HOOK_SYMBOL(handle, mknodat); + HOOK_SYMBOL(handle, truncate); + HOOK_SYMBOL(handle, linkat); + HOOK_SYMBOL(handle, readlinkat); + HOOK_SYMBOL(handle, unlinkat); + HOOK_SYMBOL(handle, symlinkat); + HOOK_SYMBOL(handle, utimensat); + HOOK_SYMBOL(handle, __getcwd); + HOOK_SYMBOL(handle, chdir); + HOOK_SYMBOL(handle, execve); + } + dlclose(handle); +} + +#endif + +static inline void fill_openat_is_hooked(char v[]) { + // openAt is hooked + static unsigned int m = 0; + + if (m == 0) { + m = 13; + } else if (m == 17) { + m = 19; + } + + v[0x0] = 'l'; + v[0x1] = 't'; + v[0x2] = '`'; + v[0x3] = 'h'; + v[0x4] = 'F'; + v[0x5] = '|'; + v[0x6] = ')'; + v[0x7] = 'c'; + v[0x8] = 'x'; + v[0x9] = ','; + v[0xa] = 'h'; + v[0xb] = 'n'; + v[0xc] = 'm'; + v[0xd] = 'h'; + v[0xe] = 'a'; + v[0xf] = 'a'; + for (unsigned int i = 0; i < 0x10; ++i) { + v[i] ^= ((i + 0x10) % m); + } + v[0x10] = '\0'; +} + +bool has_native_libs() { +#if __ANDROID_API__ >= 24 + return true; +#else + Symbol symbol; + char v[0x10]; + fill_ba88(v); + memset(&symbol, 0, sizeof(Symbol)); + symbol.check = (PLT_CHECK_PLT_APP | PLT_CHECK_NAME); + symbol.symbol_name = (char *) v; + if (dl_iterate_phdr_symbol(&symbol)) { +#ifdef DEBUG_NATIVE + LOGW("cannot dl_iterate_phdr_symbol"); +#endif + return false; + } + + bool extractNativeLibs = true; + for (int i = 0; i < symbol.size; ++i) { + if (symbol.names[i] != NULL) { +#ifdef DEBUG_NATIVE + LOGW("%s: %s", v, symbol.names[i]); +#endif + if (extractNativeLibs && strstr(symbol.names[i], "apk!") != NULL) { + extractNativeLibs = false; + } + free(symbol.names[i]); + symbol.names[i] = NULL; + } + } + if (symbol.names != NULL) { + free(symbol.names); + symbol.names = NULL; + } + +#ifdef DEBUG_NATIVE + LOGW("has_native_libs: %s", extractNativeLibs ? "true" : "false"); +#endif + return extractNativeLibs; +#endif +} + +bool checkGenuine(JNIEnv *env) { + char v1[0x20]; + + signal(SIGCONT, handler); + fill_add_sigcont(v1); + LOGI(v1); // 0x14 + + sdk = getSdk(); + + uid = getuid(); + +#ifdef DEBUG_HOOK_SELF + LOGI("+++ whale +++"); + check_inline_hook_whale(); + LOGI("--- whale ----"); +#if defined(__arm__) || defined(__aarch64__) + LOGI("+++ hookzz-b +++"); + check_inline_hook_hookzz_b(); + LOGI("--- hookzz-b ---"); + LOGI("+++ hookzz +++"); + check_inline_hook_hookzz(); + LOGI("--- hookzz ---"); +#endif +#if defined(__arm__) + LOGI("+++ hookzz-b +++"); + check_inline_hook_hookzz_b(); + LOGI("--- hookzz-b ---"); + LOGI("+++ substrate +++"); + check_inline_hook_substrate(); + LOGI("--- substrate ---"); +#endif +#endif + +#ifdef DEBUG + LOGI("JNI_OnLoad start, sdk: %d, uid: %d", sdk, uid); +#endif + +#ifdef DEBUG_HOOK_IO + check_inline_hook_io(); +#endif + +#ifdef DEBUG_MAPS + LOGI("openAt: %p", &openAt); +#endif + if (isInlineHooked(&openAt)) { + fill_openat_is_hooked(v1); + LOGE(v1); + genuine = CHECK_FATAL; + goto done; + } + + char *packageName = getGenuinePackageName(); + char *packagePath = getPath(env, uid, packageName); + if (packageName == NULL) { + genuine = CHECK_TRUE; + goto clean; + } + if (packagePath == NULL) { + fill_cannot_find_s(v1); + LOGE(v1, packageName); + genuine = CHECK_FAKE; + goto clean; + } else { + int sign = checkSignature(packagePath); + if (sign) { + fill_invalid_signature_path_s(v1); + LOGE(v1, packagePath); +#ifndef DEBUG_FAKE + genuine = sign < 0 ? CHECK_NOAPK : CHECK_FAKE; + goto clean; +#endif + } + } + + if (uid < 10000) { + goto clean; + } + +#ifdef CHECK_MOUNT + checkMount(maps); +#endif + +#ifndef NO_CHECK_MAPS +#ifdef DEBUG + LOGI("checkMaps start"); +#endif + genuine = checkMaps(packageName, packagePath); +#endif + +#ifdef CHECK_HOOK +#define CHECK_HOOK_SYMBOL(x) do {\ + if (genuine == CHECK_TRUE) {\ + fill_##x(v1);\ + if (isPltHooked(v1, true)) {\ + genuine = CHECK_FALSE;\ + }\ + }\ + } while(0); + CHECK_HOOK_SYMBOL(open); + CHECK_HOOK_SYMBOL(openat); + CHECK_HOOK_SYMBOL(__openat); + CHECK_HOOK_SYMBOL(jniRegisterNativeMethods); +#endif + +#if defined(CHECK_ARM64) && defined(__arm__) + if (genuine == CHECK_TRUE) { + fill_ro_product_cpu_abi(v1); + __system_property_get(v1, prop); + if (isArm64V8a(prop)) { + fill_32_64(v1); + LOGW(v1); + genuine = CHECK_FALSE; + } + } +#endif + +clean: +#ifdef GENUINE_NAME + free(packageName); +#endif + free(packagePath); +#ifdef DEBUG + LOGI("JNI_OnLoad end, genuine: %d", genuine); +#endif + + if (genuine != CHECK_PROXY && isAmProxy(env, sdk)) { + genuine = CHECK_PROXY; + } + +done: + fill_sdk_d_genuine_d(v1); // 0x15 + LOGI(v1, sdk, genuine); + + return setGenuine(env, genuine); +} diff --git a/native/src/main/jni/integrity/genuine.h b/native/src/main/jni/integrity/genuine.h new file mode 100644 index 000000000..7f846d051 --- /dev/null +++ b/native/src/main/jni/integrity/genuine.h @@ -0,0 +1,45 @@ +#include + +#define GENUINE_NAME {0x62, 0x63, 0x23, 0x69, 0x66, 0x64, 0x79, 0x67, 0x71, 0x2e, 0x6f, 0x67, 0x68, 0x6b, 0x6c, 0x68, 0x71, 0x6d, 0x7b, 0x7e, 0x6e, 0x7e, 0x23, 0x6b, 0x67, 0x66, 0x78, 0x77, 0x64, 0x65, 0x73, 0x0} +#define GENUINE_SIZE 0x02d1 +#define GENUINE_HASH 0x9a99ba0c + +#ifdef NDEBUG +/* genuine false handler */ +#define GENUINE_FALSE_CRASH +// #define GENUINE_FALSE_NATIVE + +/* genuine fake handler */ +#define GENUINE_FAKE_CRASH +// #define GENUINE_FAKE_NATIVE + +/* genuine overlay handler */ +// #define GENUINE_OVERLAY_CRASH +// #define GENUINE_OVERLAY_NATIVE + +/* genuine odex handler */ +// #define GENUINE_ODEX_CRASH +// #define GENUINE_ODEX_NATIVE + +/* genuine dex handler */ +// #define GENUINE_DEX_CRASH +// #define GENUINE_DEX_NATIVE + +/* genuine proxy handler */ +// #define GENUINE_PROXY_CRASH +// #define GENUINE_PROXY_NATIVE + +/* genuine error handler */ +#define GENUINE_ERROR_CRASH +// #define GENUINE_ERROR_NATIVE + +/* genuine fatal handler */ +#define GENUINE_FATAL_CRASH +// #define GENUINE_FATAL_NATIVE + +/* genuine noapk handler */ +#define GENUINE_NOAPK_CRASH +// #define GENUINE_NOAPK_NATIVE +#endif + +bool checkGenuine(JNIEnv *env); diff --git a/native/src/main/jni/integrity/inline.c b/native/src/main/jni/integrity/inline.c new file mode 100644 index 000000000..508a86d82 --- /dev/null +++ b/native/src/main/jni/integrity/inline.c @@ -0,0 +1,333 @@ +// +// Created by Thom on 2019/3/18. +// + +#include +#include +#include +#include +#include +#include "inline.h" +#include "common.h" + +#ifdef DEBUG_HOOK_SELF + +#include +#include +#include +#include "whale/whale.h" + +static long faccessat_replace(int dirfd, const char *pathname, int mode, int flags) { + return syscall(__NR_faccessat, dirfd, pathname, mode, flags); +} + +static long fchownat_replace(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags) { + return syscall(__NR_fchownat, dirfd, pathname, owner, group, flags); +} + +static int futimens_replace(int fd, const struct timespec times[2]) { + return utimensat(fd, NULL, times, 0); +} + +#if defined(__arm__) || defined(__aarch64__) + +void check_inline_hook_hookzz() { + void *backup; + zz_disable_arm_arm64_b_branch(); + + ZzReplace(&fchownat, &fchownat_replace, &backup); + LOGI("hookzz fchownat: %p, replace: %p, hooked: %d", &fchownat, &fchownat_replace, + isInlineHooked(&fchownat)); + + ZzReplace(&faccessat, &faccessat_replace, &backup); + LOGI("hookzz faccessat: %p, repalce: %p, hooked: %d", &faccessat, &faccessat_replace, + isInlineHooked(&faccessat)); + + ZzReplace(&futimens, &futimens_replace, &backup); + LOGI("hookzz futimens: %p, replace: %p, hooked: %d", &futimens, &futimens_replace, + isInlineHooked(&futimens)); +} + +void check_inline_hook_hookzz_b() { + void *backup; + zz_enable_arm_arm64_b_branch(); + + ZzReplace(&fchownat, &fchownat_replace, &backup); + LOGI("hookzz-b fchownat: %p, replace: %p, hooked: %d", &fchownat, &fchownat_replace, + isInlineHooked(&fchownat)); + + ZzReplace(&faccessat, &faccessat_replace, &backup); + LOGI("hookzz-b faccessat: %p, repalce: %p, hooked: %d", &faccessat, &faccessat_replace, + isInlineHooked(&faccessat)); + + ZzReplace(&futimens, &futimens_replace, &backup); + LOGI("hookzz-b futimens: %p, replace: %p, hooked: %d", &futimens, &futimens_replace, + isInlineHooked(&futimens)); +} + +#endif + +void check_inline_hook_whale() { + WInlineHookFunction(&fchownat, &fchownat_replace, NULL); + LOGI("whale fchownat: %p, replace: %p, hooked: %d", &fchownat, &fchownat_replace, + isInlineHooked(&fchownat)); + + WInlineHookFunction(&faccessat, &faccessat_replace, NULL); + LOGI("whale faccessat: %p, repalce: %p, hooked: %d", &faccessat, &faccessat_replace, + isInlineHooked(&faccessat)); + + WInlineHookFunction(&futimens, &futimens_replace, NULL); + LOGI("whale futimens: %p, replace: %p, hooked: %d", &futimens, &futimens_replace, + isInlineHooked(&futimens)); +} + +#if defined(__arm__) || defined(DEBUG_HOOK_IDE) + +#include "substrate/CydiaSubstrate.h" + +void check_inline_hook_substrate() { + MSHookFunction(&fchownat, &fchownat_replace, NULL); + LOGI("substrate fchownat: %p, replace: %p, hooked: %d", &fchownat, &fchownat_replace, + isInlineHooked(&fchownat)); + + MSHookFunction(&faccessat, &faccessat_replace, NULL); + LOGI("substrate faccessat: %p, repalce: %p, hooked: %d", &faccessat, &faccessat_replace, + isInlineHooked(&faccessat)); + + MSHookFunction(&futimens, &futimens_replace, NULL); + LOGI("substrate futimens: %p, replace: %p, hooked: %d", &futimens, &futimens_replace, + isInlineHooked(&futimens)); +} + +#endif + +#endif + +bool isInlineHooked(void *symbol) { + if (symbol == NULL) { + return false; + } +#if defined(__arm__) || defined(DEBUG_HOOK_IDE) + +// https://developer.arm.com/docs/ddi0597/b/base-instructions-alphabetic-order/ldr-literal-load-register-literal +// A1, !(P == 0 && W == 1), we don't check P and W +// cond 010P U0W1 1111 _Rt_ xxxx xxxx xxxx +#define IS_LDR_PC_A1(x) (((x) & 0xfe5ff000u) == 0xe41ff000u) +// T2 +// 1111 1000 U101 1111 | _Rt_ xxxx xxxx xxxx +#define IS_LDR_PC_T2(x) (((x) & 0xf000ff7fu) == 0xf000f85fu) + +// https://developer.arm.com/docs/ddi0597/b/base-instructions-alphabetic-order/b-branch +// A1 +// cond 100 xxxx xxxx xxxx xxxx xxxx xxxx +#define IS_B_A1(x) (((x) & 0xff000000u) == 0xea000000u) +// T2 +// 1110 0xxx xxxx xxxx +#define IS_B_T2(x) (((x) & 0xf800u) == 0xe000u) +// T4 +// 1111 0Sxx xxxx xxxx | 10J1 Jxxx xxxx xxxx +// -- imm10 --- | --- imm11 --- +#define IS_B_T4(x) (((x) & 0xd000f800u) == 0x9000f000u) + +// https://developer.arm.com/docs/ddi0597/b/base-instructions-alphabetic-order/nop-no-operation +// T1, hint should be 0000, we don't check +// 1011 1111 hint 0000 +#define IS_NOP_T1(x) (((x) & 0xff0fu) == 0xbf00u) + +// https://developer.arm.com/docs/ddi0597/b/base-instructions-alphabetic-order/mov-movs-register-move-register +// cydia use `mov r8, r8` for Nop +// T1, Mmmm is Rm, Dddd is Rd +// 0100 0110 DMmm mddd +#define _IS_MOV_T1(x) (((x) & 0xff00u) == 0x4600u) +#define _RM_MOV_T1(x) ((((x) & 0x78u) >> 3u)) +#define _RD_MOV_T1(x) ((((x) & 0x80u) >> 4u) | ((x) & 7u)) +#define IS_MOV_T1_RR(x) (_IS_MOV_T1(x) && _RM_MOV_T1(x) == _RD_MOV_T1(x)) + +// https://developer.arm.com/docs/ddi0597/b/base-instructions-alphabetic-order/bx-branch-and-exchange +// cydia use `bx` +// T1 +// 0100 0111 0Rmm m000 +#define IS_BX_T1(x) (((x) & 0xff87u) == 0x4700u) +#define RM_BX_T1(x) (((x) & 0x0078u) >> 3u) +#define IS_BX_PC_T1(x) ((x) == 0x4778u) + + uintptr_t address = (uintptr_t) symbol; + if ((address & 1U) == 0) { + uint32_t *value32 = (uint32_t *) address; + if (IS_LDR_PC_A1(*value32)) { +#ifdef DEBUG_HOOK + LOGW("(arm ldr pc) symbol: %p, value: %08x", symbol, *value32); +#endif + return true; + } + if (IS_B_A1(*value32)) { +#ifdef DEBUG_HOOK + LOGW("(arm b) symbol: %p, value: %08x", symbol, *value32); +#endif + return true; + } +#ifdef DEBUG + LOGI("(arm) symbol: %p, value: %08x", symbol, *value32); +#endif + } else { + address = address & ~1U; + uint16_t *value16 = (uint16_t *) address; + uint32_t *value32 = (uint32_t *) address; + if (IS_LDR_PC_T2(*value32)) { +#ifdef DEBUG_HOOK + LOGW("(thumb ldr pc) symbol: %p, address: %p, value: %08x", + symbol, address, *value32); +#endif + return true; + } + if (IS_B_T4(*value32)) { +#ifdef DEBUG_HOOK + LOGW("(thumb b) symbol: %p, address: %p, value: %08x", + symbol, address, *value32); +#endif + return true; + } + if (IS_B_T2(*value16)) { +#ifdef DEBUG_HOOK + LOGW("(thumb b) symbol: %p, address: %p, value: %04x", + symbol, address, *value16); +#endif + return true; + } + if (IS_NOP_T1(*value16) || IS_MOV_T1_RR(*value16)) { +#ifdef DEBUG_HOOK + LOGW("(thumb nop) symbol: %p, address: %p, value: %04x", + symbol, address, *value16); +#endif + address += 2; + value16 = (uint16_t *) address; + value32 = (uint32_t *) address; + } + if (IS_LDR_PC_T2(*value32)) { +#ifdef DEBUG_HOOK + LOGW("(thumb ldr pc) symbol: %p, address: %p, value: %08x", + symbol, address, *value32); +#endif + return true; + } + if (IS_BX_PC_T1(*value16) && IS_LDR_PC_A1(*(value32 + 1))) { +#ifdef DEBUG_HOOK + LOGW("(thumb bx + arm ldr pc) symbol: %p, address: %p, value: %08x %08x", + symbol, address, *value32, *(value32 + 1)); +#endif + return true; + } +#ifdef DEBUG + LOGI("(thumb) symbol: %p, address: %p, value: %08x %08x", + symbol, address, *value32, *(value32 + 1)); +#endif + } +#endif +#if defined(__aarch64__) || defined(DEBUG_HOOK_IDE) + +// https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/b-branch +// 0001 01xx xxxx xxxx xxxx xxxx xxxx xxxx +// ------------ imm26 ------------- +#define IS_B(x) (((x) & 0xfc000000u) == 0x14000000u) + +// https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/ldr-literal-load-register-literal +// 0101 1000 xxxx xxxx xxxx xxxx xxxR tttt +// -------- imm19 -------- +#define IS_LDR_X(x) (((x) & 0xff000000u) == 0x58000000u) +#define X_LDR(x) ((x) & 0x1fu) + +// https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/adrp-form-pc-relative-address-to-4kb-page +// 1xx1 0000 xxxx xxxx xxxx xxxx xxxR dddd +// lo -------- immhi -------- +#define IS_ADRP_X(x) (((x) & 0x9f000000u) == 0x90000000u) +#define X_ADRP(x) ((x) & 0x1fu) + +// https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/br-branch-to-register +// 1101 0110 0001 1111 0000 00Rn nnn0 0000 +#define IS_BR_X(x) (((x) & 0xfffffc0f) == 0xd61f0000u) +#define X_BR(x) (((x) & 0x3e0u) >> 0x5u) + +// https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/movz-move-wide-with-zero +// 1op1 0010 1hwx xxxx xxxx xxxx xxxR dddd +// ------ imm16 ------- +// for op, 00 -> MOVN, 10 -> MOVZ, 11 -> MOVK +#define IS_MOV_X(x) (((x) & 0x9f800000u) == 0x92800000u) +#define X_MOV(x) ((x) & 0x1fu) + + uint32_t *value32 = symbol; + if (IS_B(*value32)) { +#ifdef DEBUG_HOOK + LOGW("(arm64 b) symbol: %p, value: %08x", symbol, *value32); +#endif + return true; + } + if (IS_LDR_X(*value32) && IS_BR_X(*(value32 + 1))) { + uint32_t x = X_LDR(*value32); + if (x == X_BR(*(value32 + 1))) { +#ifdef DEBUG_HOOK + LOGW("(arm64 ldr+br x%d) symbol: %p, value: %08x %08x", + x, symbol, *value32, *(value32 + 1)); +#endif + return true; + } + } + if (IS_ADRP_X(*value32) && IS_BR_X(*(value32 + 1))) { + uint32_t x = X_ADRP(*value32); + if (x == X_BR(*(value32 + 1))) { +#ifdef DEBUG_HOOK + LOGW("(arm64 adrp+br x%d) symbol: %p, value: %08x %08x", + x, symbol, *value32, *(value32 + 1)); +#endif + return true; + } + } + if (IS_MOV_X(*value32)) { + uint32_t x = X_MOV(*value32); + for (int i = 1; i <= 4; ++i) { + if (IS_BR_X(*(value32 + i))) { + if (x != X_BR(*(value32 + i))) { + break; + } +#ifdef DEBUG_HOOK + for (int k = 0; k < i; ++k) { + LOGW("(arm64 mov x%d) symbol: %p, value: %08x", + x, symbol + sizeof(uint32_t) * k, *(value32 + k)); + } + LOGW("(arm64 br x%d) symbol: %p, value: %08x", + x, symbol + sizeof(uint32_t) * i, *(value32 + i)); +#endif + return true; + } else if (IS_MOV_X(*(value32 + i))) { + if (x != X_MOV(*(value32 + i))) { + break; + } + } + } + } +#ifdef DEBUG + LOGI("(arm64) symbol: %p, value: %08x %08x", symbol, *value32, *(value32 + 1)); +#endif +#endif + return false; +} + +#ifdef DEBUG_HOOK_IO +bool setRead(void *symbol) { + uintptr_t address = (uintptr_t) symbol; + uintptr_t page_size = (uintptr_t) getpagesize(); + uintptr_t base = address & ~(page_size - 1); + // inline check read at most 20 bytes + uintptr_t end = (address + 20 + page_size - 1) & -page_size; +#ifdef DEBUG + LOGI("set r+x from %p to %p", base, end); +#endif + if (mprotect((void *) base, end - base, PROT_READ | PROT_EXEC)) { +#ifdef DEBUG + LOGW("cannot mprotect: %s", strerror(errno)); +#endif + return false; + } else { + return true; + } +} +#endif diff --git a/native/src/main/jni/integrity/inline.h b/native/src/main/jni/integrity/inline.h new file mode 100644 index 000000000..52f3936bd --- /dev/null +++ b/native/src/main/jni/integrity/inline.h @@ -0,0 +1,45 @@ +// +// Created by Thom on 2019/3/18. +// + +#ifndef BREVENT_INLINE_H +#define BREVENT_INLINE_H + +#include +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DEBUG_HOOK_IO +bool setRead(void *symbol); +#endif + +bool isInlineHooked(void *symbol); + +#ifdef DEBUG_HOOK_SELF +#if defined(__arm__) || defined(__aarch64__) + +#include "hookzz/hookzz.h" + +void check_inline_hook_hookzz(); + +void check_inline_hook_hookzz_b(); + +#endif + +void check_inline_hook_whale(); + +#if defined(__arm__) + +void check_inline_hook_substrate(); + +#endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif //BREVENT_INLINE_H diff --git a/native/src/main/jni/integrity/openat.c b/native/src/main/jni/integrity/openat.c new file mode 100644 index 000000000..e1e467594 --- /dev/null +++ b/native/src/main/jni/integrity/openat.c @@ -0,0 +1,56 @@ +// +// Created by Thom on 2019/3/30. +// + +#include +#include +#include "openat.h" + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +intptr_t openAt(intptr_t fd, const char *path, intptr_t flag) { +#if defined(__arm__) + intptr_t r; + __asm__ volatile( +#ifndef OPTIMIZE_ASM + "mov r0, %1\n\t" + "mov r1, %2\n\t" + "mov r2, %3\n\t" +#endif + + "mov ip, r7\n\t" + ".cfi_register r7, ip\n\t" + "ldr r7, =" STR(__NR_openat) "\n\t" + "svc #0\n\t" + "mov r7, ip\n\t" + ".cfi_restore r7\n\t" + +#ifndef OPTIMIZE_ASM + "mov %0, r0\n\t" +#endif + : "=r" (r) + : "r" (fd), "r" (path), "r" (flag)); + return r; +#elif defined(__aarch64__) + intptr_t r; + __asm__ volatile( +#ifndef OPTIMIZE_ASM + "mov x0, %1\n\t" + "mov x1, %2\n\t" + "mov x2, %3\n\t" +#endif + + "mov x8, " STR(__NR_openat) "\n\t" + "svc #0\n\t" + +#ifndef OPTIMIZE_ASM + "mov %0, x0\n\t" +#endif + : "=r" (r) + : "r" (fd), "r" (path), "r" (flag)); + return r; +#else + return (intptr_t) syscall(__NR_openat, fd, path, flag); +#endif +} diff --git a/native/src/main/jni/integrity/openat.h b/native/src/main/jni/integrity/openat.h new file mode 100644 index 000000000..212f98535 --- /dev/null +++ b/native/src/main/jni/integrity/openat.h @@ -0,0 +1,28 @@ +// +// Created by Thom on 2019/3/30. +// + +#ifndef BREVENT_OPENAT_H +#define BREVENT_OPENAT_H + +#include +#include +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __ANDROID__ + +intptr_t openAt(intptr_t fd, const char *path, intptr_t flag); + +#else +#define openAt openat +#endif + +#ifdef __cplusplus +} +#endif + +#endif //BREVENT_OPENAT_H diff --git a/native/src/main/jni/integrity/path.c b/native/src/main/jni/integrity/path.c new file mode 100644 index 000000000..586feb017 --- /dev/null +++ b/native/src/main/jni/integrity/path.c @@ -0,0 +1,61 @@ +// +// Created by Thom on 2019-05-03. +// + +#include "path.h" +#include + +static inline bool isSystem(const char *str) { + return str != NULL + && *str == '/' + && *++str == 's' + && *++str == 'y' + && *++str == 's' + && *++str == 't' + && *++str == 'e' + && *++str == 'm' + && *++str == '/'; +} + +static inline bool isVendor(const char *str) { + return str != NULL + && *str == '/' + && *++str == 'v' + && *++str == 'e' + && *++str == 'n' + && *++str == 'd' + && *++str == 'o' + && *++str == 'r' + && *++str == '/'; +} + +static inline bool isOem(const char *str) { + return str != NULL + && *str == '/' + && *++str == 'o' + && *++str == 'e' + && *++str == 'm' + && *++str == '/'; +} + +bool isThirdParty(const char *str) { + if (isSystem(str) || isVendor(str) || isOem(str)) { + return false; + } else { + return true; + } +} + +bool isDataApp(const char *str) { + return str != NULL + && *str == '/' + && *++str == 'd' + && *++str == 'a' + && *++str == 't' + && *++str == 'a' + && *++str == '/' + && *++str == 'a' + && *++str == 'p' + && *++str == 'p' + && *++str == '/'; +} \ No newline at end of file diff --git a/native/src/main/jni/integrity/path.h b/native/src/main/jni/integrity/path.h new file mode 100644 index 000000000..d40124ff1 --- /dev/null +++ b/native/src/main/jni/integrity/path.h @@ -0,0 +1,14 @@ +// +// Created by Thom on 2019-05-03. +// + +#ifndef BREVENT_PATH_H +#define BREVENT_PATH_H + +#include + +bool isThirdParty(const char *str); + +bool isDataApp(const char *str); + +#endif //BREVENT_PATH_H diff --git a/native/src/main/jni/integrity/plt.c b/native/src/main/jni/integrity/plt.c new file mode 100644 index 000000000..b1bc6486d --- /dev/null +++ b/native/src/main/jni/integrity/plt.c @@ -0,0 +1,355 @@ +// +// Created by Thom on 2019/2/16. +// + +#include +#include +#include +#include +#include +#include "plt.h" +#include "common.h" +#include "path.h" + +/* + * reference: https://android.googlesource.com/platform/bionic/+/master/linker/linker_soinfo.cpp + */ +static uint32_t gnu_hash(const uint8_t *name) { + uint32_t h = 5381; + + while (*name) { + h += (h << 5) + *name++; + } + return h; +} + +static uint32_t elf_hash(const uint8_t *name) { + uint32_t h = 0, g; + + while (*name) { + h = (h << 4) + *name++; + g = h & 0xf0000000; + h ^= g; + h ^= g >> 24; + } + + return h; +} + +static ElfW(Dyn) *find_dyn_by_tag(ElfW(Dyn) *dyn, ElfW(Sxword) tag) { + while (dyn->d_tag != DT_NULL) { + if (dyn->d_tag == tag) { + return dyn; + } + ++dyn; + } + return NULL; +} + +static inline bool is_global(ElfW(Sym) *sym) { + unsigned char stb = ELF_ST_BIND(sym->st_info); + if (stb == STB_GLOBAL || stb == STB_WEAK) { + return sym->st_shndx != SHN_UNDEF; + } else { + return false; + } +} + +static ElfW(Addr) * +find_symbol(struct dl_phdr_info *info, ElfW(Dyn) *base_addr, const char *symbol) { + ElfW(Dyn) *dyn; + + dyn = find_dyn_by_tag(base_addr, DT_SYMTAB); + ElfW(Sym) *dynsym = (ElfW(Sym) *) (info->dlpi_addr + dyn->d_un.d_ptr); + + dyn = find_dyn_by_tag(base_addr, DT_STRTAB); + char *dynstr = (char *) (info->dlpi_addr + dyn->d_un.d_ptr); + + dyn = find_dyn_by_tag(base_addr, DT_GNU_HASH); + if (dyn != NULL) { + ElfW(Word) *dt_gnu_hash = (ElfW(Word) *) (info->dlpi_addr + dyn->d_un.d_ptr); + size_t gnu_nbucket_ = dt_gnu_hash[0]; + uint32_t gnu_maskwords_ = dt_gnu_hash[2]; + uint32_t gnu_shift2_ = dt_gnu_hash[3]; + ElfW(Addr) *gnu_bloom_filter_ = (ElfW(Addr) *) (dt_gnu_hash + 4); + uint32_t *gnu_bucket_ = (uint32_t *) (gnu_bloom_filter_ + gnu_maskwords_); + uint32_t *gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - dt_gnu_hash[1]; + + --gnu_maskwords_; + + uint32_t hash = gnu_hash((uint8_t *) symbol); + uint32_t h2 = hash >> gnu_shift2_; + + uint32_t bloom_mask_bits = sizeof(ElfW(Addr)) * 8; + uint32_t word_num = (hash / bloom_mask_bits) & gnu_maskwords_; + ElfW(Addr) bloom_word = gnu_bloom_filter_[word_num]; + + if ((1 & (bloom_word >> (hash % bloom_mask_bits)) & + (bloom_word >> (h2 % bloom_mask_bits))) == 0) { + return NULL; + } + + uint32_t n = gnu_bucket_[hash % gnu_nbucket_]; + + if (n == 0) { + return NULL; + } + + do { + ElfW(Sym) *sym = dynsym + n; + if (((gnu_chain_[n] ^ hash) >> 1) == 0 + && is_global(sym) + && strcmp(dynstr + sym->st_name, symbol) == 0) { + ElfW(Addr) *symbol_sym = (ElfW(Addr) *) (info->dlpi_addr + sym->st_value); +#ifdef DEBUG_PLT + LOGI("found %s(gnu+%u) in %s, %p", symbol, n, info->dlpi_name, symbol_sym); +#endif + return symbol_sym; + } + } while ((gnu_chain_[n++] & 1) == 0); + + return NULL; + } + + dyn = find_dyn_by_tag(base_addr, DT_HASH); + if (dyn != NULL) { + ElfW(Word) *dt_hash = (ElfW(Word) *) (info->dlpi_addr + dyn->d_un.d_ptr); + size_t nbucket_ = dt_hash[0]; + uint32_t *bucket_ = dt_hash + 2; + uint32_t *chain_ = bucket_ + nbucket_; + + uint32_t hash = elf_hash((uint8_t *) (symbol)); + for (uint32_t n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) { + ElfW(Sym) *sym = dynsym + n; + if (is_global(sym) && + strcmp(dynstr + sym->st_name, symbol) == 0) { + ElfW(Addr) *symbol_sym = (ElfW(Addr) *) (info->dlpi_addr + sym->st_value); +#ifdef DEBUG_PLT + LOGI("found %s(elf+%u) in %s, %p", symbol, n, info->dlpi_name, symbol_sym); +#endif + return symbol_sym; + } + } + + return NULL; + } + + return NULL; +} + +#if defined(__LP64__) +#define Elf_Rela ElfW(Rela) +#define ELF_R_SYM ELF64_R_SYM +#else +#define Elf_Rela ElfW(Rel) +#define ELF_R_SYM ELF32_R_SYM +#endif + +#ifdef DEBUG_PLT +#if defined(__x86_64__) +#define R_JUMP_SLOT R_X86_64_JUMP_SLOT +#define ELF_R_TYPE ELF64_R_TYPE +#elif defined(__i386__) +#define R_JUMP_SLOT R_386_JMP_SLOT +#define ELF_R_TYPE ELF32_R_TYPE +#elif defined(__arm__) +#define R_JUMP_SLOT R_ARM_JUMP_SLOT +#define ELF_R_TYPE ELF32_R_TYPE +#elif defined(__aarch64__) +#define R_JUMP_SLOT R_AARCH64_JUMP_SLOT +#define ELF_R_TYPE ELF64_R_TYPE +#else +#error unsupported OS +#endif +#endif + +static ElfW(Addr) *find_plt(struct dl_phdr_info *info, ElfW(Dyn) *base_addr, const char *symbol) { + ElfW(Dyn) *dyn = find_dyn_by_tag(base_addr, DT_JMPREL); + if (dyn == NULL) { + return NULL; + } + Elf_Rela *dynplt = (Elf_Rela *) (info->dlpi_addr + dyn->d_un.d_ptr); + + dyn = find_dyn_by_tag(base_addr, DT_SYMTAB); + ElfW(Sym) *dynsym = (ElfW(Sym) *) (info->dlpi_addr + dyn->d_un.d_ptr); + + dyn = find_dyn_by_tag(base_addr, DT_STRTAB); + char *dynstr = (char *) (info->dlpi_addr + dyn->d_un.d_ptr); + + dyn = find_dyn_by_tag(base_addr, DT_PLTRELSZ); + if (dyn == NULL) { + return NULL; + } + size_t count = dyn->d_un.d_val / sizeof(Elf_Rela); + + for (size_t i = 0; i < count; ++i) { + Elf_Rela *plt = dynplt + i; +#ifdef DEBUG_PLT + if (ELF_R_TYPE(plt->r_info) != R_JUMP_SLOT) { + LOGW("invalid type for plt+%zu in %s", i, info->dlpi_name); + continue; + } +#endif + size_t idx = ELF_R_SYM(plt->r_info); + idx = dynsym[idx].st_name; + if (strcmp(dynstr + idx, symbol) == 0) { + ElfW(Addr) *symbol_plt = (ElfW(Addr) *) (info->dlpi_addr + plt->r_offset); +#ifdef DEBUG_PLT + ElfW(Addr) *symbol_plt_value = (ElfW(Addr) *) *symbol_plt; + LOGI("found %s(plt+%zu) in %s, %p -> %p", symbol, i, info->dlpi_name, symbol_plt, + symbol_plt_value); +#endif + return symbol_plt; + } + } + + return NULL; +} + +static inline bool isso(const char *str) { + if (str == NULL) { + return false; + } + const char *dot = strrchr(str, '.'); + return dot != NULL + && *++dot == 's' + && *++dot == 'o' + && (*++dot == '\0' || *dot == '\r' || *dot == '\n'); +} + +static inline bool should_check_plt(Symbol *symbol, struct dl_phdr_info *info) { + const char *path = info->dlpi_name; + if (symbol->check & PLT_CHECK_PLT_ALL) { + return true; + } else if (symbol->check & PLT_CHECK_PLT_APP) { + return *path != '/' || isThirdParty(path); + } else { + return false; + } +} + +static int callback(struct dl_phdr_info *info, __unused size_t size, void *data) { + if (!isso(info->dlpi_name)) { +#ifdef DEBUG_PLT + LOGW("ignore non-so: %s", info->dlpi_name); +#endif + return 0; + } + Symbol *symbol = (Symbol *) data; +#if 0 + LOGI("Name: \"%s\" (%d segments)", info->dlpi_name, info->dlpi_phnum); +#endif + ++symbol->total; + for (ElfW(Half) phdr_idx = 0; phdr_idx < info->dlpi_phnum; ++phdr_idx) { + ElfW(Phdr) phdr = info->dlpi_phdr[phdr_idx]; + if (phdr.p_type != PT_DYNAMIC) { + continue; + } + ElfW(Dyn) *base_addr = (ElfW(Dyn) *) (info->dlpi_addr + phdr.p_vaddr); + ElfW(Addr) *addr; + addr = should_check_plt(symbol, info) ? find_plt(info, base_addr, symbol->symbol_name) : NULL; + if (addr != NULL) { + if (symbol->symbol_plt != NULL) { + ElfW(Addr) *addr_value = (ElfW(Addr) *) *addr; + ElfW(Addr) *symbol_plt_value = (ElfW(Addr) *) *symbol->symbol_plt; + if (addr_value != symbol_plt_value) { +#ifdef DEBUG_PLT + LOGW("%s, plt %p -> %p != %p", symbol->symbol_name, addr, addr_value, + symbol_plt_value); +#endif + return 1; + } + } + symbol->symbol_plt = addr; + if (symbol->check & PLT_CHECK_NAME) { + if (symbol->size == 0) { + symbol->size = 1; + symbol->names = calloc(1, sizeof(char *)); + } else { + ++symbol->size; + symbol->names = realloc(symbol->names, symbol->size * sizeof(char *)); + } +#ifdef DEBUG_PLT + LOGI("[%d]: %s", symbol->size - 1, info->dlpi_name); +#endif + symbol->names[symbol->size - 1] = strdup(info->dlpi_name); + } + } + addr = find_symbol(info, base_addr, symbol->symbol_name); + if (addr != NULL) { + symbol->symbol_sym = addr; + if (symbol->check == PLT_CHECK_SYM_ONE) { + return PLT_CHECK_SYM_ONE; + } + } + if (symbol->symbol_plt != NULL && symbol->symbol_sym != NULL) { + ElfW(Addr) *symbol_plt_value = (ElfW(Addr) *) *symbol->symbol_plt; + // stop if unmatch + if (symbol_plt_value != symbol->symbol_sym) { +#ifdef DEBUG_PLT + LOGW("%s, plt: %p -> %p != %p", symbol->symbol_name, symbol->symbol_plt, + symbol_plt_value, symbol->symbol_sym); +#endif + return 1; + } + } + } + return 0; +} + +void *plt_dlsym(const char *name, size_t *total) { + Symbol symbol; + memset(&symbol, 0, sizeof(Symbol)); + if (total == NULL) { + symbol.check = PLT_CHECK_SYM_ONE; + } + symbol.symbol_name = name; + dl_iterate_phdr_symbol(&symbol); + if (total != NULL) { + *total = symbol.total; + } + return symbol.symbol_sym; +} + +bool isPltHooked(const char *name, bool all) { + Symbol symbol; + memset(&symbol, 0, sizeof(Symbol)); + symbol.check = all ? PLT_CHECK_PLT_ALL : PLT_CHECK_PLT_APP; + symbol.symbol_name = name; + return dl_iterate_phdr_symbol(&symbol) ? true : false; +} + +/** + * symbol->check PLT_CHECK_PLT | PLT_CHECK_NAME + * @param symbol + * @return + */ +int dl_iterate_phdr_symbol(Symbol *symbol) { + int result; +#ifdef DEBUG_PLT + LOGI("start dl_iterate_phdr: %s", symbol->symbol_name); +#endif +#if __ANDROID_API__ >= 21 || !defined(__arm__) + result = dl_iterate_phdr(callback, symbol); +#else + int (*dl_iterate_phdr)(int (*)(struct dl_phdr_info *, size_t, void *), void *); + dl_iterate_phdr = dlsym(RTLD_NEXT, "dl_iterate_phdr"); + if (dl_iterate_phdr != NULL) { + result = dl_iterate_phdr(callback, symbol); + } else { + result = 0; + void *handle = dlopen("libdl.so", RTLD_NOW); + dl_iterate_phdr = dlsym(handle, "dl_iterate_phdr"); + if (dl_iterate_phdr != NULL) { + result = dl_iterate_phdr(callback, symbol); + } else { + LOGW("cannot dlsym dl_iterate_phdr"); + } + dlclose(handle); + } +#endif +#ifdef DEBUG_PLT + LOGI("complete dl_iterate_phdr: %s", symbol->symbol_name); +#endif + return result; +} diff --git a/native/src/main/jni/integrity/plt.h b/native/src/main/jni/integrity/plt.h new file mode 100644 index 000000000..e6c7010b1 --- /dev/null +++ b/native/src/main/jni/integrity/plt.h @@ -0,0 +1,42 @@ +// +// Created by Thom on 2019/2/16. +// + +#ifndef BREVENT_PLT_H +#define BREVENT_PLT_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PLT_CHECK_PLT_APP ((unsigned short) 0x1u) +#define PLT_CHECK_PLT_ALL ((unsigned short) 0x2u) +#define PLT_CHECK_NAME ((unsigned short) 0x4u) +#define PLT_CHECK_SYM_ONE ((unsigned short) 0x8u) + +typedef struct Symbol { + unsigned short check; + unsigned short size; + size_t total; + ElfW(Addr) *symbol_plt; + ElfW(Addr) *symbol_sym; + const char *symbol_name; + char **names; +} Symbol; + +int dl_iterate_phdr_symbol(Symbol *symbol); + +void *plt_dlsym(const char *name, size_t *total); + +bool isPltHooked(const char *name, bool all); + +#ifdef __cplusplus +} +#endif + +#endif //BREVENT_PLT_H diff --git a/native/src/main/jni/integrity/pm.c b/native/src/main/jni/integrity/pm.c new file mode 100644 index 000000000..ec7a39dde --- /dev/null +++ b/native/src/main/jni/integrity/pm.c @@ -0,0 +1,747 @@ +// +// Created by Thom on 2019/3/15. +// + +#include +#include +#include "common.h" +#include "pm.h" + +static inline void fill_android_os_ServiceManager(char v[]) { + // android/os/ServiceManager + static unsigned int m = 0; + + if (m == 0) { + m = 23; + } else if (m == 29) { + m = 31; + } + + v[0x0] = 'c'; + v[0x1] = 'm'; + v[0x2] = '`'; + v[0x3] = 'w'; + v[0x4] = 'i'; + v[0x5] = 'n'; + v[0x6] = 'l'; + v[0x7] = '&'; + v[0x8] = 'e'; + v[0x9] = 'x'; + v[0xa] = '#'; + v[0xb] = '^'; + v[0xc] = 'k'; + v[0xd] = '}'; + v[0xe] = 'f'; + v[0xf] = 'x'; + v[0x10] = 'q'; + v[0x11] = 'v'; + v[0x12] = 'Y'; + v[0x13] = 't'; + v[0x14] = 'x'; + v[0x15] = 'a'; + v[0x16] = 'f'; + v[0x17] = 'g'; + v[0x18] = 'q'; + for (unsigned int i = 0; i < 0x19; ++i) { + v[i] ^= ((i + 0x19) % m); + } + v[0x19] = '\0'; +} + +static inline void fill_getService(char v[]) { + // getService + static unsigned int m = 0; + + if (m == 0) { + m = 7; + } else if (m == 11) { + m = 13; + } + + v[0x0] = 'd'; + v[0x1] = 'a'; + v[0x2] = 'q'; + v[0x3] = 'U'; + v[0x4] = 'e'; + v[0x5] = 's'; + v[0x6] = 't'; + v[0x7] = 'j'; + v[0x8] = 'g'; + v[0x9] = '`'; + for (unsigned int i = 0; i < 0xa; ++i) { + v[i] ^= ((i + 0xa) % m); + } + v[0xa] = '\0'; +} + +static inline void fill_getService_signature(char v[]) { + // (Ljava/lang/String;)Landroid/os/IBinder; + static unsigned int m = 0; + + if (m == 0) { + m = 37; + } else if (m == 41) { + m = 43; + } + + v[0x0] = '+'; + v[0x1] = 'H'; + v[0x2] = 'o'; + v[0x3] = 'g'; + v[0x4] = 'q'; + v[0x5] = 'i'; + v[0x6] = '&'; + v[0x7] = 'f'; + v[0x8] = 'j'; + v[0x9] = 'b'; + v[0xa] = 'j'; + v[0xb] = '!'; + v[0xc] = '\\'; + v[0xd] = 'd'; + v[0xe] = 'c'; + v[0xf] = '{'; + v[0x10] = '}'; + v[0x11] = 's'; + v[0x12] = '.'; + v[0x13] = '?'; + v[0x14] = '['; + v[0x15] = 'y'; + v[0x16] = 'w'; + v[0x17] = '~'; + v[0x18] = 'i'; + v[0x19] = 's'; + v[0x1a] = 't'; + v[0x1b] = 'z'; + v[0x1c] = '0'; + v[0x1d] = 'O'; + v[0x1e] = 'R'; + v[0x1f] = '\r'; + v[0x20] = 'j'; + v[0x21] = 'f'; + v[0x22] = 'i'; + v[0x23] = 'o'; + v[0x24] = 'f'; + v[0x25] = 'f'; + v[0x26] = 'v'; + v[0x27] = '>'; + for (unsigned int i = 0; i < 0x28; ++i) { + v[i] ^= ((i + 0x28) % m); + } + v[0x28] = '\0'; +} + +static inline void fill_package(char v[]) { + // package + static unsigned int m = 0; + + if (m == 0) { + m = 5; + } else if (m == 7) { + m = 11; + } + + v[0x0] = 'r'; + v[0x1] = 'b'; + v[0x2] = 'g'; + v[0x3] = 'k'; + v[0x4] = '`'; + v[0x5] = 'e'; + v[0x6] = 'f'; + for (unsigned int i = 0; i < 0x7; ++i) { + v[i] ^= ((i + 0x7) % m); + } + v[0x7] = '\0'; +} + +static inline void fill_android_content_pm_IPackageManager$Stub(char v[]) { + // android/content/pm/IPackageManager$Stub + static unsigned int m = 0; + + if (m == 0) { + m = 37; + } else if (m == 41) { + m = 43; + } + + v[0x0] = 'c'; + v[0x1] = 'm'; + v[0x2] = '`'; + v[0x3] = 'w'; + v[0x4] = 'i'; + v[0x5] = 'n'; + v[0x6] = 'l'; + v[0x7] = '&'; + v[0x8] = 'i'; + v[0x9] = 'd'; + v[0xa] = 'b'; + v[0xb] = 'y'; + v[0xc] = 'k'; + v[0xd] = 'a'; + v[0xe] = 'd'; + v[0xf] = '>'; + v[0x10] = 'b'; + v[0x11] = '~'; + v[0x12] = ';'; + v[0x13] = '\\'; + v[0x14] = 'F'; + v[0x15] = 'v'; + v[0x16] = '{'; + v[0x17] = 'r'; + v[0x18] = '{'; + v[0x19] = '|'; + v[0x1a] = 'y'; + v[0x1b] = 'P'; + v[0x1c] = '\x7f'; + v[0x1d] = 'q'; + v[0x1e] = 'A'; + v[0x1f] = 'F'; + v[0x20] = 'G'; + v[0x21] = 'Q'; + v[0x22] = '\x00'; + v[0x23] = 'S'; + v[0x24] = 'u'; + v[0x25] = 'w'; + v[0x26] = 'a'; + for (unsigned int i = 0; i < 0x27; ++i) { + v[i] ^= ((i + 0x27) % m); + } + v[0x27] = '\0'; +} + +static inline void fill_asInterface(char v[]) { + // asInterface + static unsigned int m = 0; + + if (m == 0) { + m = 7; + } else if (m == 11) { + m = 13; + } + + v[0x0] = 'e'; + v[0x1] = 'v'; + v[0x2] = 'O'; + v[0x3] = 'n'; + v[0x4] = 'u'; + v[0x5] = 'g'; + v[0x6] = 'q'; + v[0x7] = 'b'; + v[0x8] = 'd'; + v[0x9] = 'e'; + v[0xa] = 'e'; + for (unsigned int i = 0; i < 0xb; ++i) { + v[i] ^= ((i + 0xb) % m); + } + v[0xb] = '\0'; +} + +static inline void fill_asInterface_signature(char v[]) { + // (Landroid/os/IBinder;)Landroid/content/pm/IPackageManager; + static unsigned int m = 0; + + if (m == 0) { + m = 53; + } else if (m == 59) { + m = 61; + } + + v[0x0] = '-'; + v[0x1] = 'J'; + v[0x2] = 'f'; + v[0x3] = 'f'; + v[0x4] = 'm'; + v[0x5] = 'x'; + v[0x6] = 'd'; + v[0x7] = 'e'; + v[0x8] = 'i'; + v[0x9] = '!'; + v[0xa] = '`'; + v[0xb] = 'c'; + v[0xc] = '>'; + v[0xd] = '['; + v[0xe] = 'Q'; + v[0xf] = '}'; + v[0x10] = '{'; + v[0x11] = 'r'; + v[0x12] = 'r'; + v[0x13] = 'j'; + v[0x14] = '"'; + v[0x15] = '3'; + v[0x16] = 'W'; + v[0x17] = '}'; + v[0x18] = 's'; + v[0x19] = 'z'; + v[0x1a] = 'm'; + v[0x1b] = 'O'; + v[0x1c] = 'H'; + v[0x1d] = 'F'; + v[0x1e] = '\x0c'; + v[0x1f] = 'G'; + v[0x20] = 'J'; + v[0x21] = 'H'; + v[0x22] = 'S'; + v[0x23] = 'M'; + v[0x24] = 'G'; + v[0x25] = '^'; + v[0x26] = '\x04'; + v[0x27] = '\\'; + v[0x28] = '@'; + v[0x29] = '\x01'; + v[0x2a] = 'f'; + v[0x2b] = '`'; + v[0x2c] = 'P'; + v[0x2d] = 'Q'; + v[0x2e] = 'X'; + v[0x2f] = 'U'; + v[0x30] = 'g'; + v[0x31] = 'd'; + v[0x32] = 'O'; + v[0x33] = 'b'; + v[0x34] = 'j'; + v[0x35] = 'd'; + v[0x36] = 'a'; + v[0x37] = 'b'; + v[0x38] = 'z'; + v[0x39] = '2'; + for (unsigned int i = 0; i < 0x3a; ++i) { + v[i] ^= ((i + 0x3a) % m); + } + v[0x3a] = '\0'; +} + +static inline void fill_getApplicationInfo(char v[]) { + // getApplicationInfo + static unsigned int m = 0; + + if (m == 0) { + m = 17; + } else if (m == 19) { + m = 23; + } + + v[0x0] = 'f'; + v[0x1] = 'g'; + v[0x2] = 'w'; + v[0x3] = 'E'; + v[0x4] = 'u'; + v[0x5] = 'v'; + v[0x6] = 'k'; + v[0x7] = 'a'; + v[0x8] = 'j'; + v[0x9] = 'k'; + v[0xa] = '\x7f'; + v[0xb] = 'e'; + v[0xc] = 'b'; + v[0xd] = '`'; + v[0xe] = 'F'; + v[0xf] = '~'; + v[0x10] = 'f'; + v[0x11] = 'n'; + for (unsigned int i = 0; i < 0x12; ++i) { + v[i] ^= ((i + 0x12) % m); + } + v[0x12] = '\0'; +} + +static inline void fill_getApplicationInfo_signature(char v[]) { + // (Ljava/lang/String;II)Landroid/content/pm/ApplicationInfo; + static unsigned int m = 0; + + if (m == 0) { + m = 53; + } else if (m == 59) { + m = 61; + } + + v[0x0] = '-'; + v[0x1] = 'J'; + v[0x2] = 'm'; + v[0x3] = 'i'; + v[0x4] = '\x7f'; + v[0x5] = 'k'; + v[0x6] = '$'; + v[0x7] = '`'; + v[0x8] = 'l'; + v[0x9] = '`'; + v[0xa] = 'h'; + v[0xb] = '?'; + v[0xc] = 'B'; + v[0xd] = 'f'; + v[0xe] = 'a'; + v[0xf] = '}'; + v[0x10] = '{'; + v[0x11] = 'q'; + v[0x12] = ','; + v[0x13] = 'Q'; + v[0x14] = 'P'; + v[0x15] = '3'; + v[0x16] = 'W'; + v[0x17] = '}'; + v[0x18] = 's'; + v[0x19] = 'z'; + v[0x1a] = 'm'; + v[0x1b] = 'O'; + v[0x1c] = 'H'; + v[0x1d] = 'F'; + v[0x1e] = '\x0c'; + v[0x1f] = 'G'; + v[0x20] = 'J'; + v[0x21] = 'H'; + v[0x22] = 'S'; + v[0x23] = 'M'; + v[0x24] = 'G'; + v[0x25] = '^'; + v[0x26] = '\x04'; + v[0x27] = '\\'; + v[0x28] = '@'; + v[0x29] = '\x01'; + v[0x2a] = 'n'; + v[0x2b] = '@'; + v[0x2c] = 'A'; + v[0x2d] = '^'; + v[0x2e] = 'Z'; + v[0x2f] = 'W'; + v[0x30] = 'a'; + v[0x31] = 'u'; + v[0x32] = 'k'; + v[0x33] = 'l'; + v[0x34] = 'j'; + v[0x35] = 'L'; + v[0x36] = 'h'; + v[0x37] = 'a'; + v[0x38] = 'g'; + v[0x39] = '2'; + for (unsigned int i = 0; i < 0x3a; ++i) { + v[i] ^= ((i + 0x3a) % m); + } + v[0x3a] = '\0'; +} + +static inline void fill_sourceDir(char v[]) { + // sourceDir + static unsigned int m = 0; + + if (m == 0) { + m = 7; + } else if (m == 11) { + m = 13; + } + + v[0x0] = 'q'; + v[0x1] = 'l'; + v[0x2] = 'q'; + v[0x3] = 'w'; + v[0x4] = 'e'; + v[0x5] = 'e'; + v[0x6] = 'E'; + v[0x7] = 'k'; + v[0x8] = 'q'; + for (unsigned int i = 0; i < 0x9; ++i) { + v[i] ^= ((i + 0x9) % m); + } + v[0x9] = '\0'; +} + +static inline void fill_sourceDir_signature(char v[]) { + // Ljava/lang/String; + static unsigned int m = 0; + + if (m == 0) { + m = 17; + } else if (m == 19) { + m = 23; + } + + v[0x0] = 'M'; + v[0x1] = 'h'; + v[0x2] = 'b'; + v[0x3] = 'r'; + v[0x4] = 'd'; + v[0x5] = ')'; + v[0x6] = 'k'; + v[0x7] = 'i'; + v[0x8] = 'g'; + v[0x9] = 'm'; + v[0xa] = '$'; + v[0xb] = '_'; + v[0xc] = 'y'; + v[0xd] = '|'; + v[0xe] = 'f'; + v[0xf] = '~'; + v[0x10] = 'g'; + v[0x11] = ':'; + for (unsigned int i = 0; i < 0x12; ++i) { + v[i] ^= ((i + 0x12) % m); + } + v[0x12] = '\0'; +} + +static inline void fill_android_os_BinderProxy(char v[]) { + // android/os/BinderProxy + static unsigned int m = 0; + + if (m == 0) { + m = 19; + } else if (m == 23) { + m = 29; + } + + v[0x0] = 'b'; + v[0x1] = 'j'; + v[0x2] = 'a'; + v[0x3] = 't'; + v[0x4] = 'h'; + v[0x5] = 'a'; + v[0x6] = 'm'; + v[0x7] = '%'; + v[0x8] = 'd'; + v[0x9] = '\x7f'; + v[0xa] = '"'; + v[0xb] = 'L'; + v[0xc] = 'f'; + v[0xd] = '~'; + v[0xe] = 'u'; + v[0xf] = 'w'; + v[0x10] = 'r'; + v[0x11] = 'Q'; + v[0x12] = 'p'; + v[0x13] = 'l'; + v[0x14] = '|'; + v[0x15] = '|'; + for (unsigned int i = 0; i < 0x16; ++i) { + v[i] ^= ((i + 0x16) % m); + } + v[0x16] = '\0'; +} + +static inline void fill_invalid_package_manager_path_s(char v[]) { + // invalid package manager, path: %s + static unsigned int m = 0; + + if (m == 0) { + m = 31; + } else if (m == 37) { + m = 41; + } + + v[0x0] = 'k'; + v[0x1] = 'm'; + v[0x2] = 'r'; + v[0x3] = 'd'; + v[0x4] = 'j'; + v[0x5] = 'n'; + v[0x6] = 'l'; + v[0x7] = ')'; + v[0x8] = 'z'; + v[0x9] = 'j'; + v[0xa] = 'o'; + v[0xb] = 'f'; + v[0xc] = 'o'; + v[0xd] = 'h'; + v[0xe] = 'u'; + v[0xf] = '1'; + v[0x10] = '\x7f'; + v[0x11] = 'r'; + v[0x12] = 'z'; + v[0x13] = 't'; + v[0x14] = 'q'; + v[0x15] = 'r'; + v[0x16] = 'j'; + v[0x17] = '5'; + v[0x18] = ':'; + v[0x19] = 'k'; + v[0x1a] = '}'; + v[0x1b] = 'i'; + v[0x1c] = 'v'; + v[0x1d] = ':'; + v[0x1e] = '!'; + v[0x1f] = '\''; + v[0x20] = 'p'; + for (unsigned int i = 0; i < 0x21; ++i) { + v[i] ^= ((i + 0x21) % m); + } + v[0x21] = '\0'; +} + +char *getPath(JNIEnv *env, int uid, const char *packageName) { + char *path = NULL; + char v1[0x80], v2[0x80]; + + if (packageName == NULL) { + return NULL; + } + fill_android_os_ServiceManager(v2); // 0x19 + 1 + jclass classServiceManager = (*env)->FindClass(env, v2); + if (classServiceManager == NULL) { +#ifdef DEBUG + LOGW("cannot find ServiceManager"); +#endif + (*env)->ExceptionClear(env); + return NULL; + } + debug(env, "ServiceManager: %s", classServiceManager); + + fill_getService(v1); // 0xa + 1 + fill_getService_signature(v2); // 0x28 + 1 + jmethodID method = (*env)->GetStaticMethodID(env, classServiceManager, v1, v2); +#ifdef DEBUG + LOGI("ServiceManager.getService: %p", method); +#endif + if (method == NULL) { + (*env)->ExceptionClear(env); + goto cleanClassServiceManager; + } + + fill_package(v2); // 0x8 + 1 + jstring stringPackage = (*env)->NewStringUTF(env, v2); + jobject service = (*env)->CallStaticObjectMethod(env, classServiceManager, method, + stringPackage); + if (service == NULL) { +#ifdef DEBUG + LOGW("cannot find package service"); +#endif + if ((*env)->ExceptionCheck(env)) { +#ifdef DEBUG + (*env)->ExceptionDescribe(env); +#endif + (*env)->ExceptionClear(env); + } + goto cleanStringPackage; + } +#ifdef DEBUG + debug(env, "package service: %s", service); +#endif + + fill_android_content_pm_IPackageManager$Stub(v2); // 0x27 + 1 + jclass classIPackageManager$Stub = (*env)->FindClass(env, v2); + debug(env, "IPackageManager$Stub: %s", classIPackageManager$Stub); + if (classIPackageManager$Stub == NULL) { +#ifdef DEBUG + LOGW("cannot find IPackageManager$Stub"); +#endif + (*env)->ExceptionClear(env); + goto cleanService; + } + + fill_asInterface(v1); + fill_asInterface_signature(v2); + method = (*env)->GetStaticMethodID(env, classIPackageManager$Stub, v1, v2); +#ifdef DEBUG + LOGI("asInterface: %p", method); +#endif + if (method == NULL) { + (*env)->ExceptionClear(env); + goto cleanClassIPackageManager$Stub; + } + + jobject IPackageManager = (*env)->CallStaticObjectMethod(env, classIPackageManager$Stub, method, + service); + if (IPackageManager == NULL) { +#ifdef DEBUG + LOGW("cannot call asInterface"); +#endif + if ((*env)->ExceptionCheck(env)) { +#ifdef DEBUG + (*env)->ExceptionDescribe(env); +#endif + (*env)->ExceptionClear(env); + } + goto cleanClassIPackageManager$Stub; + } + debug(env, "IPackageManager: %s", IPackageManager); + + // Landroid/content/pm/IPackageManager;->getApplicationInfo(Ljava/lang/String;II)Landroid/content/pm/ApplicationInfo; + jclass classIPackageManager = (*env)->GetObjectClass(env, IPackageManager); + debug(env, "classIPackageManager: %s", classIPackageManager); + fill_getApplicationInfo(v1); + fill_getApplicationInfo_signature(v2); + method = (*env)->GetMethodID(env, classIPackageManager, v1, v2); +#ifdef DEBUG + LOGI("getApplicationInfo: %p", method); +#endif + if (method == NULL) { + (*env)->ExceptionClear(env); + goto cleanClassIPackageManager; + } + + jstring stringPackageName = (*env)->NewStringUTF(env, packageName); + int userId = uid / 100000; + jobject applicationInfo = (*env)->CallObjectMethod(env, IPackageManager, method, + stringPackageName, 0, userId); + debug(env, "applicationInfo: %s", applicationInfo); + if (applicationInfo == NULL) { + if ((*env)->ExceptionCheck(env)) { +#ifdef DEBUG + (*env)->ExceptionDescribe(env); +#endif + (*env)->ExceptionClear(env); + } + goto cleanStringPackageName; + } + + jclass classApplicationInfo = (*env)->GetObjectClass(env, applicationInfo); + debug(env, "class applicationInfo: %s", classApplicationInfo); + + fill_sourceDir(v1); + fill_sourceDir_signature(v2); + jfieldID field = (*env)->GetFieldID(env, classApplicationInfo, v1, v2); +#ifdef DEBUG + LOGI("sourceDir: %p", field); +#endif + if (field == NULL) { + (*env)->ExceptionClear(env); + goto cleanClassApplicationInfo; + } + jstring sourceDir = (*env)->GetObjectField(env, applicationInfo, field); + debug(env, "sourceDir: %s", sourceDir); + const char *chars = (*env)->GetStringUTFChars(env, sourceDir, NULL); + + fill_android_os_BinderProxy(v2); // 0x16 + 1 + jclass classBinderProxy = (*env)->FindClass(env, v2); + if (classBinderProxy == NULL) { +#ifdef DEBUG + LOGW("cannot find BinderProxy"); +#endif + (*env)->ExceptionClear(env); + } else { + debug(env, "BinderProxy: %s", classBinderProxy); + } + + jclass serviceClass = (*env)->GetObjectClass(env, service); + debug(env, "service class: %s", serviceClass); + if (classBinderProxy != NULL && !(*env)->IsSameObject(env, serviceClass, classBinderProxy)) { + fill_invalid_package_manager_path_s(v2); + LOGW(v2, chars); + } + path = strdup(chars); + +#ifdef DEBUG + LOGI("path: %s", chars); +#endif + + (*env)->DeleteLocalRef(env, serviceClass); + if (classBinderProxy != NULL) { + (*env)->DeleteLocalRef(env, classBinderProxy); + } + (*env)->ReleaseStringUTFChars(env, sourceDir, chars); + (*env)->DeleteLocalRef(env, sourceDir); +cleanClassApplicationInfo: + (*env)->DeleteLocalRef(env, classApplicationInfo); + (*env)->DeleteLocalRef(env, applicationInfo); +cleanStringPackageName: + (*env)->DeleteLocalRef(env, stringPackageName); +cleanClassIPackageManager: + (*env)->DeleteLocalRef(env, classIPackageManager); + (*env)->DeleteLocalRef(env, IPackageManager); +cleanClassIPackageManager$Stub: + (*env)->DeleteLocalRef(env, classIPackageManager$Stub); +cleanService: + (*env)->DeleteLocalRef(env, service); +cleanStringPackage: + (*env)->DeleteLocalRef(env, stringPackage); +cleanClassServiceManager: + (*env)->DeleteLocalRef(env, classServiceManager); + + return path; +} + diff --git a/native/src/main/jni/integrity/pm.h b/native/src/main/jni/integrity/pm.h new file mode 100644 index 000000000..5e1e4433b --- /dev/null +++ b/native/src/main/jni/integrity/pm.h @@ -0,0 +1,20 @@ +// +// Created by Thom on 2019/3/15. +// + +#ifndef BREVENT_PM_H +#define BREVENT_PM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +char *getPath(JNIEnv *env, int uid, const char *name); + +#ifdef __cplusplus +} +#endif + +#endif //BREVENT_PM_H diff --git a/native/src/main/jni/main.c b/native/src/main/jni/main.c new file mode 100644 index 000000000..c4289cf5b --- /dev/null +++ b/native/src/main/jni/main.c @@ -0,0 +1,26 @@ +#include + +#include "genuine.h" +#include "java_wrapper.h" +#include "JavaInitA7Zip.h" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) +{ + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) (&env), JNI_VERSION_1_6) != JNI_OK) { + return JNI_ERR; + } + + if (!checkGenuine(env)) { + return JNI_ERR; + } + + if (!image_onLoad(vm)) { + return JNI_ERR; + } + if (!a7zip_onLoad(vm)) { + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} diff --git a/settings.gradle b/settings.gradle index 2b0e21a81..ef792ec4f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':daogenerator' +include ':app', ':daogenerator', ':native'