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