From ce2b97ffe19e7c40e25da6eff212cee447cc0340 Mon Sep 17 00:00:00 2001 From: Heril Date: Tue, 28 Jul 2026 16:47:27 +0530 Subject: [PATCH 1/2] Fix Camera2 JPEG capture crash and inject virtual image --- .idea/.name | 1 + .idea/AndroidProjectSystem.xml | 6 + .idea/deploymentTargetSelector.xml | 11 ++ .idea/markdown.xml | 8 ++ .idea/migrations.xml | 10 ++ .idea/runConfigurations.xml | 17 +++ app/build.gradle | 4 +- .../main/java/com/example/vcam/HookMain.java | 109 +++++++++++++----- 8 files changed, 137 insertions(+), 29 deletions(-) create mode 100644 .idea/.name create mode 100644 .idea/AndroidProjectSystem.xml create mode 100644 .idea/deploymentTargetSelector.xml create mode 100644 .idea/markdown.xml create mode 100644 .idea/migrations.xml create mode 100644 .idea/runConfigurations.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..2b7f631 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +VCAM \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..ca16a99 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000..c61ea33 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 4be948e..87dd7a7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "com.example.vcam" minSdk 21 targetSdk 26 - versionCode 28 - versionName "4.4" + versionCode 30 + versionName "4.6" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/com/example/vcam/HookMain.java b/app/src/main/java/com/example/vcam/HookMain.java index 7a4fb28..0fc320d 100644 --- a/app/src/main/java/com/example/vcam/HookMain.java +++ b/app/src/main/java/com/example/vcam/HookMain.java @@ -26,6 +26,9 @@ import android.view.SurfaceHolder; import android.widget.Toast; +import android.media.Image; +import java.nio.ByteBuffer; +import java.lang.reflect.Field; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -537,7 +540,8 @@ protected void beforeHookedMethod(MethodHookParam param) { return; } String surfaceInfo = param.args[0].toString(); - if (surfaceInfo.contains("Surface(name=null)")) { + boolean isReaderSurface = surfaceInfo.contains("Surface(name=null)"); + if (isReaderSurface) { if (c2_reader_Surfcae == null) { c2_reader_Surfcae = (Surface) param.args[0]; } else { @@ -555,6 +559,10 @@ protected void beforeHookedMethod(MethodHookParam param) { } } XposedBridge.log("【VCAM】添加目标:" + param.args[0].toString()); + if (isReaderSurface && imageReaderFormat == 256) { + XposedBridge.log("【VCAM】ImageReader格式为JPEG,不替换Surface以允许原相机拍照"); + return; + } param.args[0] = c2_virtual_surface; } @@ -679,6 +687,53 @@ protected void beforeHookedMethod(MethodHookParam param) { } }); + try { + Class surfaceImageClass = XposedHelpers.findClassIfExists("android.media.ImageReader$SurfaceImage", lpparam.classLoader); + if (surfaceImageClass != null) { + XposedHelpers.findAndHookMethod(surfaceImageClass, "getPlanes", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + Image image = (Image) param.thisObject; + if (image.getFormat() == 256 && imageReaderFormat == 256 && (c2_reader_Surfcae != null || c2_reader_Surfcae_1 != null)) { + Object[] planes = (Object[]) param.getResult(); + if (planes != null && planes.length > 0) { + File replacementFile = pickRandomImageFile(video_path); + if (replacementFile != null) { + Bitmap pict = getBMP(replacementFile.getAbsolutePath()); + if (pict != null) { + ByteArrayOutputStream temp_array = new ByteArrayOutputStream(); + pict.compress(Bitmap.CompressFormat.JPEG, 100, temp_array); + byte[] jpeg_data = temp_array.toByteArray(); + ByteBuffer newBuffer = ByteBuffer.allocateDirect(jpeg_data.length); + newBuffer.put(jpeg_data); + newBuffer.rewind(); + + Field bufferField = null; + try { + bufferField = planes[0].getClass().getDeclaredField("mBuffer"); + } catch (NoSuchFieldException e) { + for (Field f : planes[0].getClass().getDeclaredFields()) { + if (f.getType() == ByteBuffer.class) { + bufferField = f; + break; + } + } + } + if (bufferField != null) { + bufferField.setAccessible(true); + bufferField.set(planes[0], newBuffer); + XposedBridge.log("【VCAM】成功替换ImageReader的JPEG图像数据"); + } + } + } + } + } + } + }); + } + } catch (Throwable t) { + XposedBridge.log("【VCAM】Hook SurfaceImage.getPlanes失败: " + t); + } XposedHelpers.findAndHookMethod("android.hardware.camera2.CameraCaptureSession.CaptureCallback", lpparam.classLoader, "onCaptureFailed", CameraCaptureSession.class, CaptureRequest.class, CaptureFailure.class, new XC_MethodHook() { @@ -693,42 +748,42 @@ protected void beforeHookedMethod(MethodHookParam param) { private void process_camera2_play() { if (c2_reader_Surfcae != null) { - if (c2_hw_decode_obj != null) { - c2_hw_decode_obj.stopDecode(); - c2_hw_decode_obj = null; - } + if (imageReaderFormat == 256) { + XposedBridge.log("【VCAM】ImageReader格式为JPEG,不启动视频解码以防报错"); + } else { + if (c2_hw_decode_obj != null) { + c2_hw_decode_obj.stopDecode(); + c2_hw_decode_obj = null; + } - c2_hw_decode_obj = new VideoToFrames(); - try { - if (imageReaderFormat == 256) { - c2_hw_decode_obj.setSaveFrames("null", OutputImageFormat.JPEG); - } else { + c2_hw_decode_obj = new VideoToFrames(); + try { c2_hw_decode_obj.setSaveFrames("null", OutputImageFormat.NV21); + c2_hw_decode_obj.set_surfcae(c2_reader_Surfcae); + c2_hw_decode_obj.decode(video_path + "virtual.mp4"); + } catch (Throwable throwable) { + XposedBridge.log("【VCAM】" + throwable); } - c2_hw_decode_obj.set_surfcae(c2_reader_Surfcae); - c2_hw_decode_obj.decode(video_path + "virtual.mp4"); - } catch (Throwable throwable) { - XposedBridge.log("【VCAM】" + throwable); } } if (c2_reader_Surfcae_1 != null) { - if (c2_hw_decode_obj_1 != null) { - c2_hw_decode_obj_1.stopDecode(); - c2_hw_decode_obj_1 = null; - } + if (imageReaderFormat == 256) { + XposedBridge.log("【VCAM】ImageReader_1格式为JPEG,不启动视频解码以防报错"); + } else { + if (c2_hw_decode_obj_1 != null) { + c2_hw_decode_obj_1.stopDecode(); + c2_hw_decode_obj_1 = null; + } - c2_hw_decode_obj_1 = new VideoToFrames(); - try { - if (imageReaderFormat == 256) { - c2_hw_decode_obj_1.setSaveFrames("null", OutputImageFormat.JPEG); - } else { + c2_hw_decode_obj_1 = new VideoToFrames(); + try { c2_hw_decode_obj_1.setSaveFrames("null", OutputImageFormat.NV21); + c2_hw_decode_obj_1.set_surfcae(c2_reader_Surfcae_1); + c2_hw_decode_obj_1.decode(video_path + "virtual.mp4"); + } catch (Throwable throwable) { + XposedBridge.log("【VCAM】" + throwable); } - c2_hw_decode_obj_1.set_surfcae(c2_reader_Surfcae_1); - c2_hw_decode_obj_1.decode(video_path + "virtual.mp4"); - } catch (Throwable throwable) { - XposedBridge.log("【VCAM】" + throwable); } } From 2ea2f19e12378a671246d249af922b5f6a4892f6 Mon Sep 17 00:00:00 2001 From: Heril Date: Thu, 30 Jul 2026 15:40:12 +0530 Subject: [PATCH 2/2] Fix Camera2 crash and add signed release configuration --- .idea/compiler.xml | 2 +- .idea/gradle.xml | 6 +- .idea/misc.xml | 3 +- app/build.gradle | 10 + .../main/java/com/example/vcam/HookMain.java | 1869 ++++++++++------- 5 files changed, 1076 insertions(+), 814 deletions(-) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8..b589d56 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index bb80ebf..639c779 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,17 +4,15 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index e78cbb7..f04ba95 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -9,7 +9,8 @@ - + + diff --git a/app/build.gradle b/app/build.gradle index 87dd7a7..b839d98 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,10 +15,20 @@ android { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } + signingConfigs { + release { + storeFile file("release.jks") + storePassword "password" + keyAlias "key0" + keyPassword "password" + } + } + buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release } } compileOptions { diff --git a/app/src/main/java/com/example/vcam/HookMain.java b/app/src/main/java/com/example/vcam/HookMain.java index 0fc320d..ccc1157 100644 --- a/app/src/main/java/com/example/vcam/HookMain.java +++ b/app/src/main/java/com/example/vcam/HookMain.java @@ -1,19 +1,15 @@ package com.example.vcam; - import android.Manifest; import android.app.Application; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; -import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.OutputConfiguration; @@ -26,7 +22,6 @@ import android.view.SurfaceHolder; import android.widget.Toast; -import android.media.Image; import java.nio.ByteBuffer; import java.lang.reflect.Field; import java.io.ByteArrayOutputStream; @@ -68,9 +63,50 @@ public class HookMain implements IXposedHookLoadPackage { public static SurfaceHolder ori_holder; public static MediaPlayer mplayer1; public static Camera mcamera1; - public int imageReaderFormat = 0; + public static Object[] readerObjects = new Object[10]; + public static int[] readerFormats = new int[10]; + public static int readerCount = 0; + public static Surface[] readerSurfaces = new Surface[10]; + public static int[] readerSurfaceFormats = new int[10]; + public static int readerSurfaceCount = 0; public static boolean is_first_hook_build = true; + public static void putReader(Object reader, Object formatObj) { + if (formatObj instanceof Integer && readerCount < 10) { + readerObjects[readerCount] = reader; + readerFormats[readerCount] = (Integer) formatObj; + readerCount++; + } + } + + public static int getFormatForObject(Object reader) { + for (int i = 0; i < readerCount; i++) { + if (readerObjects[i] == reader) + return readerFormats[i]; + } + return -1; + } + + public static void putSurface(Surface surface, int format) { + if (readerSurfaceCount < 10) { + readerSurfaces[readerSurfaceCount] = surface; + readerSurfaceFormats[readerSurfaceCount] = format; + readerSurfaceCount++; + } + } + + public static int getFormatForSurface(Surface surface) { + for (int i = 0; i < readerSurfaceCount; i++) { + if (readerSurfaces[i] == surface) + return readerSurfaceFormats[i]; + } + return -1; + } + + public static boolean hasSurface(Surface surface) { + return getFormatForSurface(surface) != -1; + } + public static int onemhight; public static int onemwidth; public static Class camera_callback_calss; @@ -100,655 +136,721 @@ public class HookMain implements IXposedHookLoadPackage { public Context toast_content; public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Exception { - XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "setPreviewTexture", SurfaceTexture.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - File file = new File(video_path + "virtual.mp4"); - if (file.exists()) { - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); - if (control_file.exists()){ - return; - } - if (is_hooked) { - is_hooked = false; - return; - } - if (param.args[0] == null) { - return; - } - if (param.args[0].equals(c1_fake_texture)) { - return; - } - if (origin_preview_camera != null && origin_preview_camera.equals(param.thisObject)) { - param.args[0] = fake_SurfaceTexture; - XposedBridge.log("【VCAM】发现重复" + origin_preview_camera.toString()); - return; - } else { - XposedBridge.log("【VCAM】创建预览"); - } + XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "setPreviewTexture", + SurfaceTexture.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + File file = new File(video_path + "virtual.mp4"); + if (file.exists()) { + File control_file = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "disable.jpg"); + if (control_file.exists()) { + return; + } + if (is_hooked) { + is_hooked = false; + return; + } + if (param.args[0] == null) { + return; + } + if (param.args[0].equals(c1_fake_texture)) { + return; + } + if (origin_preview_camera != null && origin_preview_camera.equals(param.thisObject)) { + param.args[0] = fake_SurfaceTexture; + XposedBridge.log("【VCAM】发现重复" + origin_preview_camera); + return; + } else { + XposedBridge.log("【VCAM】创建预览"); + } - origin_preview_camera = (Camera) param.thisObject; - mSurfacetexture = (SurfaceTexture) param.args[0]; - if (fake_SurfaceTexture == null) { - fake_SurfaceTexture = new SurfaceTexture(10); - } else { - fake_SurfaceTexture.release(); - fake_SurfaceTexture = new SurfaceTexture(10); - } - param.args[0] = fake_SurfaceTexture; - } else { - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (toast_content != null && need_to_show_toast) { - try { - Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show(); - } catch (Exception ee) { - XposedBridge.log("【VCAM】[toast]" + ee.toString()); + origin_preview_camera = (Camera) param.thisObject; + mSurfacetexture = (SurfaceTexture) param.args[0]; + if (fake_SurfaceTexture == null) { + fake_SurfaceTexture = new SurfaceTexture(10); + } else { + fake_SurfaceTexture.release(); + fake_SurfaceTexture = new SurfaceTexture(10); + } + param.args[0] = fake_SurfaceTexture; + } else { + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (toast_content != null && need_to_show_toast) { + try { + Toast.makeText(toast_content, + "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, + Toast.LENGTH_SHORT).show(); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[toast]" + ee.toString()); + } + } } } - } - } - }); + }); - XposedHelpers.findAndHookMethod("android.hardware.camera2.CameraManager", lpparam.classLoader, "openCamera", String.class, CameraDevice.StateCallback.class, Handler.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - if (param.args[1] == null) { - return; - } - if (param.args[1].equals(c2_state_cb)) { - return; - } - c2_state_cb = (CameraDevice.StateCallback) param.args[1]; - c2_state_callback = param.args[1].getClass(); - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); - if (control_file.exists()) { - return; - } - File file = new File(video_path + "virtual.mp4"); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (!file.exists()) { - if (toast_content != null && need_to_show_toast) { - try { - Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show(); - } catch (Exception ee) { - XposedBridge.log("【VCAM】[toast]" + ee.toString()); + XposedHelpers.findAndHookMethod("android.hardware.camera2.CameraManager", lpparam.classLoader, "openCamera", + String.class, CameraDevice.StateCallback.class, Handler.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + if (param.args[1] == null) { + return; + } + if (param.args[1].equals(c2_state_cb)) { + return; } + c2_state_cb = (CameraDevice.StateCallback) param.args[1]; + c2_state_callback = param.args[1].getClass(); + File control_file = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); + if (control_file.exists()) { + return; + } + File file = new File(video_path + "virtual.mp4"); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (!file.exists()) { + if (toast_content != null && need_to_show_toast) { + try { + Toast.makeText(toast_content, + "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, + Toast.LENGTH_SHORT).show(); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[toast]" + ee.toString()); + } + } + return; + } + XposedBridge.log("【VCAM】1位参数初始化相机,类:" + c2_state_callback.toString()); + is_first_hook_build = true; + process_camera2_init(c2_state_callback); } - return; - } - XposedBridge.log("【VCAM】1位参数初始化相机,类:" + c2_state_callback.toString()); - is_first_hook_build = true; - process_camera2_init(c2_state_callback); - } - }); - + }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - XposedHelpers.findAndHookMethod("android.hardware.camera2.CameraManager", lpparam.classLoader, "openCamera", String.class, Executor.class, CameraDevice.StateCallback.class, new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) throws Throwable { - if (param.args[2] == null) { - return; - } - if (param.args[2].equals(c2_state_cb)) { - return; - } - c2_state_cb = (CameraDevice.StateCallback) param.args[2]; - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); - if (control_file.exists()) { - return; - } - File file = new File(video_path + "virtual.mp4"); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (!file.exists()) { - if (toast_content != null && need_to_show_toast) { - try { - Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show(); - } catch (Exception ee) { - XposedBridge.log("【VCAM】[toast]" + ee.toString()); + XposedHelpers.findAndHookMethod("android.hardware.camera2.CameraManager", lpparam.classLoader, "openCamera", + String.class, Executor.class, CameraDevice.StateCallback.class, new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + if (param.args[2] == null) { + return; + } + if (param.args[2].equals(c2_state_cb)) { + return; } + c2_state_cb = (CameraDevice.StateCallback) param.args[2]; + File control_file = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "disable.jpg"); + if (control_file.exists()) { + return; + } + File file = new File(video_path + "virtual.mp4"); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (!file.exists()) { + if (toast_content != null && need_to_show_toast) { + try { + Toast.makeText(toast_content, + "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, + Toast.LENGTH_SHORT).show(); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[toast]" + ee.toString()); + } + } + return; + } + c2_state_callback = param.args[2].getClass(); + XposedBridge.log("【VCAM】2位参数初始化相机,类:" + c2_state_callback.toString()); + is_first_hook_build = true; + process_camera2_init(c2_state_callback); } - return; - } - c2_state_callback = param.args[2].getClass(); - XposedBridge.log("【VCAM】2位参数初始化相机,类:" + c2_state_callback.toString()); - is_first_hook_build = true; - process_camera2_init(c2_state_callback); - } - }); + }); } + XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "setPreviewCallbackWithBuffer", + Camera.PreviewCallback.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + if (param.args[0] != null) { + process_callback(param); + } + } + }); - XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "setPreviewCallbackWithBuffer", Camera.PreviewCallback.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - if (param.args[0] != null) { - process_callback(param); - } - } - }); - - XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "addCallbackBuffer", byte[].class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - if (param.args[0] != null) { - param.args[0] = new byte[((byte[]) param.args[0]).length]; - } - } - }); - - XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "setPreviewCallback", Camera.PreviewCallback.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - if (param.args[0] != null) { - process_callback(param); - } - } - }); + XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "addCallbackBuffer", + byte[].class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + if (param.args[0] != null) { + param.args[0] = new byte[((byte[]) param.args[0]).length]; + } + } + }); - XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "setOneShotPreviewCallback", Camera.PreviewCallback.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - if (param.args[0] != null) { - process_callback(param); - } - } - }); + XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "setPreviewCallback", + Camera.PreviewCallback.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + if (param.args[0] != null) { + process_callback(param); + } + } + }); - XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "takePicture", Camera.ShutterCallback.class, Camera.PictureCallback.class, Camera.PictureCallback.class, Camera.PictureCallback.class, new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) { - XposedBridge.log("【VCAM】4参数拍照"); - if (param.args[1] != null) { - process_a_shot_YUV(param); - } + XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "setOneShotPreviewCallback", + Camera.PreviewCallback.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + if (param.args[0] != null) { + process_callback(param); + } + } + }); - if (param.args[3] != null) { - process_a_shot_jpeg(param, 3); - } - } - }); + XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "takePicture", + Camera.ShutterCallback.class, Camera.PictureCallback.class, Camera.PictureCallback.class, + Camera.PictureCallback.class, new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) { + XposedBridge.log("【VCAM】4参数拍照"); + if (param.args[1] != null) { + process_a_shot_YUV(param); + } - XposedHelpers.findAndHookMethod("android.media.MediaRecorder", lpparam.classLoader, "setCamera", Camera.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - super.beforeHookedMethod(param); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - XposedBridge.log("【VCAM】[record]" + lpparam.packageName); - if (toast_content != null && need_to_show_toast) { - try { - Toast.makeText(toast_content, "应用:" + lpparam.appInfo.name + "(" + lpparam.packageName + ")" + "触发了录像,但目前无法拦截", Toast.LENGTH_SHORT).show(); - }catch (Exception ee){ - XposedBridge.log("【VCAM】[toast]" + Arrays.toString(ee.getStackTrace())); + if (param.args[3] != null) { + process_a_shot_jpeg(param, 3); + } } - } - } - }); + }); - XposedHelpers.findAndHookMethod("android.app.Instrumentation", lpparam.classLoader, "callApplicationOnCreate", Application.class, new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) throws Throwable { - super.afterHookedMethod(param); - if (param.args[0] instanceof Application) { - try { - toast_content = ((Application) param.args[0]).getApplicationContext(); - } catch (Exception ee) { - XposedBridge.log("【VCAM】" + ee.toString()); - } - File force_private = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/private_dir.jpg"); - if (toast_content != null) {//后半段用于强制私有目录 - int auth_statue = 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + XposedHelpers.findAndHookMethod("android.media.MediaRecorder", lpparam.classLoader, "setCamera", Camera.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + XposedBridge.log("【VCAM】[record]" + lpparam.packageName); + if (toast_content != null && need_to_show_toast) { try { - auth_statue += (toast_content.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + 1); + Toast.makeText(toast_content, "应用:" + lpparam.appInfo.name + "(" + lpparam.packageName + + ")" + "触发了录像,但目前无法拦截", Toast.LENGTH_SHORT).show(); } catch (Exception ee) { - XposedBridge.log("【VCAM】[permission-check]" + ee.toString()); + XposedBridge.log("【VCAM】[toast]" + Arrays.toString(ee.getStackTrace())); } + } + } + }); + + XposedHelpers.findAndHookMethod("android.app.Instrumentation", lpparam.classLoader, "callApplicationOnCreate", + Application.class, new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + super.afterHookedMethod(param); + if (param.args[0] instanceof Application) { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - auth_statue += (toast_content.checkSelfPermission(Manifest.permission.MANAGE_EXTERNAL_STORAGE) + 1); - } + toast_content = ((Application) param.args[0]).getApplicationContext(); } catch (Exception ee) { - XposedBridge.log("【VCAM】[permission-check]" + ee.toString()); + XposedBridge.log("【VCAM】" + ee.toString()); } - }else { - if (toast_content.checkCallingPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED ){ - auth_statue = 2; + File force_private = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + + "/DCIM/Camera1/private_dir.jpg"); + if (toast_content != null) {// 后半段用于强制私有目录 + int auth_statue = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + auth_statue += (toast_content + .checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + 1); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[permission-check]" + ee.toString()); + } + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + auth_statue += (toast_content.checkSelfPermission( + Manifest.permission.MANAGE_EXTERNAL_STORAGE) + 1); + } + } catch (Exception ee) { + XposedBridge.log("【VCAM】[permission-check]" + ee.toString()); + } + } else { + if (toast_content.checkCallingPermission( + Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + auth_statue = 2; + } + } + // 权限判断完毕 + if (auth_statue < 1 || force_private.exists()) { + File shown_file = new File( + toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/"); + if ((!shown_file.isDirectory()) && shown_file.exists()) { + shown_file.delete(); + } + if (!shown_file.exists()) { + shown_file.mkdir(); + } + shown_file = new File(toast_content.getExternalFilesDir(null).getAbsolutePath() + + "/Camera1/" + "has_shown"); + File toast_force_file = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/force_show.jpg"); + if ((!lpparam.packageName.equals(BuildConfig.APPLICATION_ID)) + && ((!shown_file.exists()) || toast_force_file.exists())) { + try { + Toast.makeText(toast_content, + lpparam.packageName + "未授予读取本地目录权限,请检查权限\nCamera1目前重定向为 " + + toast_content.getExternalFilesDir(null).getAbsolutePath() + + "/Camera1/", + Toast.LENGTH_SHORT).show(); + FileOutputStream fos = new FileOutputStream( + toast_content.getExternalFilesDir(null).getAbsolutePath() + + "/Camera1/" + "has_shown"); + String info = "shown"; + fos.write(info.getBytes()); + fos.flush(); + fos.close(); + } catch (Exception e) { + XposedBridge.log("【VCAM】[switch-dir]" + e.toString()); + } + } + video_path = toast_content.getExternalFilesDir(null).getAbsolutePath() + + "/Camera1/"; + } else { + video_path = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/"; + } + } else { + video_path = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/"; + File uni_DCIM_path = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/"); + if (uni_DCIM_path.canWrite()) { + File uni_Camera1_path = new File(video_path); + if (!uni_Camera1_path.exists()) { + uni_Camera1_path.mkdir(); + } + } } } - //权限判断完毕 - if (auth_statue < 1 || force_private.exists()) { - File shown_file = new File(toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/"); - if ((!shown_file.isDirectory()) && shown_file.exists()) { - shown_file.delete(); - } - if (!shown_file.exists()) { - shown_file.mkdir(); - } - shown_file = new File(toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/" + "has_shown"); - File toast_force_file = new File(Environment.getExternalStorageDirectory().getPath()+ "/DCIM/Camera1/force_show.jpg"); - if ((!lpparam.packageName.equals(BuildConfig.APPLICATION_ID)) && ((!shown_file.exists()) || toast_force_file.exists())) { + } + }); + + XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "startPreview", + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + File file = new File(video_path + "virtual.mp4"); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (!file.exists()) { + if (toast_content != null && need_to_show_toast) { try { - Toast.makeText(toast_content, lpparam.packageName+"未授予读取本地目录权限,请检查权限\nCamera1目前重定向为 " + toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/", Toast.LENGTH_SHORT).show(); - FileOutputStream fos = new FileOutputStream(toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/" + "has_shown"); - String info = "shown"; - fos.write(info.getBytes()); - fos.flush(); - fos.close(); - } catch (Exception e) { - XposedBridge.log("【VCAM】[switch-dir]" + e.toString()); + Toast.makeText(toast_content, + "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, + Toast.LENGTH_SHORT).show(); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[toast]" + ee.toString()); } } - video_path = toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/"; - }else { - video_path = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/"; + return; } - } else { - video_path = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/"; - File uni_DCIM_path = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/"); - if (uni_DCIM_path.canWrite()) { - File uni_Camera1_path = new File(video_path); - if (!uni_Camera1_path.exists()) { - uni_Camera1_path.mkdir(); - } + File control_file = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); + if (control_file.exists()) { + return; } - } - } - } - }); - - XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "startPreview", new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - File file = new File(video_path + "virtual.mp4"); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (!file.exists()) { - if (toast_content != null && need_to_show_toast) { - try { - Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show(); - } catch (Exception ee) { - XposedBridge.log("【VCAM】[toast]" + ee.toString()); - } - } - return; - } - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); - if (control_file.exists()) { - return; - } - is_someone_playing = false; - XposedBridge.log("【VCAM】开始预览"); - start_preview_camera = (Camera) param.thisObject; - if (ori_holder != null) { - - if (mplayer1 == null) { - mplayer1 = new MediaPlayer(); - } else { - mplayer1.release(); - mplayer1 = null; - mplayer1 = new MediaPlayer(); - } - if (!ori_holder.getSurface().isValid() || ori_holder == null) { - return; - } - mplayer1.setSurface(ori_holder.getSurface()); - File sfile = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no-silent.jpg"); - if (!(sfile.exists() && (!is_someone_playing))) { - mplayer1.setVolume(0, 0); is_someone_playing = false; - } else { - is_someone_playing = true; - } - mplayer1.setLooping(true); + XposedBridge.log("【VCAM】开始预览"); + start_preview_camera = (Camera) param.thisObject; + if (ori_holder != null) { + + if (mplayer1 == null) { + mplayer1 = new MediaPlayer(); + } else { + mplayer1.release(); + mplayer1 = null; + mplayer1 = new MediaPlayer(); + } + if (!ori_holder.getSurface().isValid() || ori_holder == null) { + return; + } + mplayer1.setSurface(ori_holder.getSurface()); + File sfile = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + + "no-silent.jpg"); + if (!(sfile.exists() && (!is_someone_playing))) { + mplayer1.setVolume(0, 0); + is_someone_playing = false; + } else { + is_someone_playing = true; + } + mplayer1.setLooping(true); - mplayer1.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(MediaPlayer mp) { - mplayer1.start(); + mplayer1.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + mplayer1.start(); + } + }); + + try { + mplayer1.setDataSource(video_path + "virtual.mp4"); + mplayer1.prepare(); + } catch (IOException e) { + XposedBridge.log("【VCAM】" + e.toString()); + } } - }); - try { - mplayer1.setDataSource(video_path + "virtual.mp4"); - mplayer1.prepare(); - } catch (IOException e) { - XposedBridge.log("【VCAM】" + e.toString()); - } - } + if (mSurfacetexture != null) { + if (mSurface == null) { + mSurface = new Surface(mSurfacetexture); + } else { + mSurface.release(); + mSurface = new Surface(mSurfacetexture); + } + if (mMediaPlayer == null) { + mMediaPlayer = new MediaPlayer(); + } else { + mMediaPlayer.release(); + mMediaPlayer = new MediaPlayer(); + } - if (mSurfacetexture != null) { - if (mSurface == null) { - mSurface = new Surface(mSurfacetexture); - } else { - mSurface.release(); - mSurface = new Surface(mSurfacetexture); - } + mMediaPlayer.setSurface(mSurface); - if (mMediaPlayer == null) { - mMediaPlayer = new MediaPlayer(); - } else { - mMediaPlayer.release(); - mMediaPlayer = new MediaPlayer(); - } + File sfile = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + + "no-silent.jpg"); + if (!(sfile.exists() && (!is_someone_playing))) { + mMediaPlayer.setVolume(0, 0); + is_someone_playing = false; + } else { + is_someone_playing = true; + } + mMediaPlayer.setLooping(true); - mMediaPlayer.setSurface(mSurface); + mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + mMediaPlayer.start(); + } + }); - File sfile = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no-silent.jpg"); - if (!(sfile.exists() && (!is_someone_playing))) { - mMediaPlayer.setVolume(0, 0); - is_someone_playing = false; - } else { - is_someone_playing = true; + try { + mMediaPlayer.setDataSource(video_path + "virtual.mp4"); + mMediaPlayer.prepare(); + } catch (IOException e) { + XposedBridge.log("【VCAM】" + e.toString()); + } + } } - mMediaPlayer.setLooping(true); + }); - mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(MediaPlayer mp) { - mMediaPlayer.start(); + XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "setPreviewDisplay", + SurfaceHolder.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + XposedBridge.log("【VCAM】添加Surfaceview预览"); + File file = new File(video_path + "virtual.mp4"); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (!file.exists()) { + if (toast_content != null && need_to_show_toast) { + try { + Toast.makeText(toast_content, + "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, + Toast.LENGTH_SHORT).show(); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[toast]" + ee.toString()); + } + } + return; + } + File control_file = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); + if (control_file.exists()) { + return; + } + mcamera1 = (Camera) param.thisObject; + ori_holder = (SurfaceHolder) param.args[0]; + if (c1_fake_texture == null) { + c1_fake_texture = new SurfaceTexture(11); + } else { + c1_fake_texture.release(); + c1_fake_texture = null; + c1_fake_texture = new SurfaceTexture(11); } - }); - - try { - mMediaPlayer.setDataSource(video_path + "virtual.mp4"); - mMediaPlayer.prepare(); - } catch (IOException e) { - XposedBridge.log("【VCAM】" + e.toString()); - } - } - } - }); - XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "setPreviewDisplay", SurfaceHolder.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - XposedBridge.log("【VCAM】添加Surfaceview预览"); - File file = new File(video_path + "virtual.mp4"); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (!file.exists()) { - if (toast_content != null && need_to_show_toast) { - try { - Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show(); - } catch (Exception ee) { - XposedBridge.log("【VCAM】[toast]" + ee.toString()); + if (c1_fake_surface == null) { + c1_fake_surface = new Surface(c1_fake_texture); + } else { + c1_fake_surface.release(); + c1_fake_surface = null; + c1_fake_surface = new Surface(c1_fake_texture); } + is_hooked = true; + mcamera1.setPreviewTexture(c1_fake_texture); + param.setResult(null); } - return; - } - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); - if (control_file.exists()) { - return; - } - mcamera1 = (Camera) param.thisObject; - ori_holder = (SurfaceHolder) param.args[0]; - if (c1_fake_texture == null) { - c1_fake_texture = new SurfaceTexture(11); - } else { - c1_fake_texture.release(); - c1_fake_texture = null; - c1_fake_texture = new SurfaceTexture(11); - } - - if (c1_fake_surface == null) { - c1_fake_surface = new Surface(c1_fake_texture); - } else { - c1_fake_surface.release(); - c1_fake_surface = null; - c1_fake_surface = new Surface(c1_fake_texture); - } - is_hooked = true; - mcamera1.setPreviewTexture(c1_fake_texture); - param.setResult(null); - } - }); + }); - XposedHelpers.findAndHookMethod("android.hardware.camera2.CaptureRequest.Builder", lpparam.classLoader, "addTarget", Surface.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { + XposedHelpers.findAndHookMethod("android.hardware.camera2.CaptureRequest.Builder", lpparam.classLoader, + "addTarget", Surface.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { - if (param.args[0] == null) { - return; - } - if (param.thisObject == null) { - return; - } - File file = new File(video_path + "virtual.mp4"); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (!file.exists()) { - if (toast_content != null && need_to_show_toast) { - try { - Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show(); - } catch (Exception ee) { - XposedBridge.log("【VCAM】[toast]" + ee.toString()); + if (param.args[0] == null) { + return; } - } - return; - } - if (param.args[0].equals(c2_virtual_surface)) { - return; - } - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); - if (control_file.exists()) { - return; - } - String surfaceInfo = param.args[0].toString(); - boolean isReaderSurface = surfaceInfo.contains("Surface(name=null)"); - if (isReaderSurface) { - if (c2_reader_Surfcae == null) { - c2_reader_Surfcae = (Surface) param.args[0]; - } else { - if ((!c2_reader_Surfcae.equals(param.args[0])) && c2_reader_Surfcae_1 == null) { - c2_reader_Surfcae_1 = (Surface) param.args[0]; + if (param.thisObject == null) { + return; } - } - } else { - if (c2_preview_Surfcae == null) { - c2_preview_Surfcae = (Surface) param.args[0]; - } else { - if ((!c2_preview_Surfcae.equals(param.args[0])) && c2_preview_Surfcae_1 == null) { - c2_preview_Surfcae_1 = (Surface) param.args[0]; + File file = new File(video_path + "virtual.mp4"); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (!file.exists()) { + if (toast_content != null && need_to_show_toast) { + try { + Toast.makeText(toast_content, + "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, + Toast.LENGTH_SHORT).show(); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[toast]" + ee.toString()); + } + } + return; } - } - } - XposedBridge.log("【VCAM】添加目标:" + param.args[0].toString()); - if (isReaderSurface && imageReaderFormat == 256) { - XposedBridge.log("【VCAM】ImageReader格式为JPEG,不替换Surface以允许原相机拍照"); - return; - } - param.args[0] = c2_virtual_surface; - - } - }); - - XposedHelpers.findAndHookMethod("android.hardware.camera2.CaptureRequest.Builder", lpparam.classLoader, "removeTarget", Surface.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - - if (param.args[0] == null) { - return; - } - if (param.thisObject == null) { - return; - } - File file = new File(video_path + "virtual.mp4"); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (!file.exists()) { - if (toast_content != null && need_to_show_toast) { - try { - Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show(); - } catch (Exception ee) { - XposedBridge.log("【VCAM】[toast]" + ee.toString()); + if (param.args[0].equals(c2_virtual_surface)) { + return; + } + File control_file = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); + if (control_file.exists()) { + return; + } + Surface targetSurface = (Surface) param.args[0]; + boolean isReaderSurface = hasSurface(targetSurface); + int currentFormat = isReaderSurface ? getFormatForSurface(targetSurface) : -1; + + if (isReaderSurface) { + if (c2_reader_Surfcae == null) { + c2_reader_Surfcae = (Surface) param.args[0]; + } else { + if ((!c2_reader_Surfcae.equals(param.args[0])) && c2_reader_Surfcae_1 == null) { + c2_reader_Surfcae_1 = (Surface) param.args[0]; + } + } + } else { + if (c2_preview_Surfcae == null) { + c2_preview_Surfcae = (Surface) param.args[0]; + } else { + if ((!c2_preview_Surfcae.equals(param.args[0])) && c2_preview_Surfcae_1 == null) { + c2_preview_Surfcae_1 = (Surface) param.args[0]; + } + } + } + XposedBridge.log("【VCAM】添加目标:" + param.args[0].toString()); + if (isReaderSurface && currentFormat == 256) { + XposedBridge.log("【VCAM】ImageReader格式为JPEG,不替换Surface以允许原相机拍照"); + return; } + param.args[0] = c2_virtual_surface; + } - return; - } - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); - if (control_file.exists()) { - return; - } - Surface rm_surf = (Surface) param.args[0]; - if (rm_surf.equals(c2_preview_Surfcae)) { - c2_preview_Surfcae = null; - } - if (rm_surf.equals(c2_preview_Surfcae_1)) { - c2_preview_Surfcae_1 = null; - } - if (rm_surf.equals(c2_reader_Surfcae_1)) { - c2_reader_Surfcae_1 = null; - } - if (rm_surf.equals(c2_reader_Surfcae)) { - c2_reader_Surfcae = null; - } + }); - XposedBridge.log("【VCAM】移除目标:" + param.args[0].toString()); - } - }); + XposedHelpers.findAndHookMethod("android.hardware.camera2.CaptureRequest.Builder", lpparam.classLoader, + "removeTarget", Surface.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { - XposedHelpers.findAndHookMethod("android.hardware.camera2.CaptureRequest.Builder", lpparam.classLoader, "build", new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - if (param.thisObject == null) { - return; - } - if (param.thisObject.equals(c2_builder)) { - return; - } - c2_builder = (CaptureRequest.Builder) param.thisObject; - File file = new File(video_path + "virtual.mp4"); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (!file.exists() && need_to_show_toast) { - if (toast_content != null) { - try { - Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show(); - } catch (Exception ee) { - XposedBridge.log("【VCAM】[toast]" + ee.toString()); + if (param.args[0] == null) { + return; + } + if (param.thisObject == null) { + return; } + File file = new File(video_path + "virtual.mp4"); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (!file.exists()) { + if (toast_content != null && need_to_show_toast) { + try { + Toast.makeText(toast_content, + "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, + Toast.LENGTH_SHORT).show(); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[toast]" + ee.toString()); + } + } + return; + } + File control_file = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); + if (control_file.exists()) { + return; + } + Surface rm_surf = (Surface) param.args[0]; + if (rm_surf.equals(c2_preview_Surfcae)) { + c2_preview_Surfcae = null; + } + if (rm_surf.equals(c2_preview_Surfcae_1)) { + c2_preview_Surfcae_1 = null; + } + if (rm_surf.equals(c2_reader_Surfcae_1)) { + c2_reader_Surfcae_1 = null; + } + if (rm_surf.equals(c2_reader_Surfcae)) { + c2_reader_Surfcae = null; + } + + XposedBridge.log("【VCAM】移除目标:" + param.args[0].toString()); } - return; - } + }); - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); - if (control_file.exists()) { - return; - } - XposedBridge.log("【VCAM】开始build请求"); - process_camera2_play(); - } - }); + XposedHelpers.findAndHookMethod("android.hardware.camera2.CaptureRequest.Builder", lpparam.classLoader, "build", + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + if (param.thisObject == null) { + return; + } + if (param.thisObject.equals(c2_builder)) { + return; + } + c2_builder = (CaptureRequest.Builder) param.thisObject; + File file = new File(video_path + "virtual.mp4"); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (!file.exists() && need_to_show_toast) { + if (toast_content != null) { + try { + Toast.makeText(toast_content, + "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, + Toast.LENGTH_SHORT).show(); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[toast]" + ee.toString()); + } + } + return; + } -/* XposedHelpers.findAndHookMethod("android.hardware.Camera", lpparam.classLoader, "stopPreview", new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - if (param.thisObject.equals(HookMain.origin_preview_camera) || param.thisObject.equals(HookMain.camera_onPreviewFrame) || param.thisObject.equals(HookMain.mcamera1)) { - if (hw_decode_obj != null) { - hw_decode_obj.stopDecode(); - } - if (mplayer1 != null) { - mplayer1.release(); - mplayer1 = null; - } - if (mMediaPlayer != null) { - mMediaPlayer.release(); - mMediaPlayer = null; + File control_file = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); + if (control_file.exists()) { + return; + } + XposedBridge.log("【VCAM】开始build请求"); + process_camera2_play(); } - is_someone_playing = false; + }); - XposedBridge.log("停止预览"); - } - } - });*/ + /* + * XposedHelpers.findAndHookMethod("android.hardware.Camera", + * lpparam.classLoader, "stopPreview", new XC_MethodHook() { + * + * @Override + * protected void beforeHookedMethod(MethodHookParam param) { + * if (param.thisObject.equals(HookMain.origin_preview_camera) || + * param.thisObject.equals(HookMain.camera_onPreviewFrame) || + * param.thisObject.equals(HookMain.mcamera1)) { + * if (hw_decode_obj != null) { + * hw_decode_obj.stopDecode(); + * } + * if (mplayer1 != null) { + * mplayer1.release(); + * mplayer1 = null; + * } + * if (mMediaPlayer != null) { + * mMediaPlayer.release(); + * mMediaPlayer = null; + * } + * is_someone_playing = false; + * + * XposedBridge.log("停止预览"); + * } + * } + * }); + */ + + class FirstNewInstanceHook extends XC_MethodHook { + private final File toast_control = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - XposedHelpers.findAndHookMethod("android.media.ImageReader", lpparam.classLoader, "newInstance", int.class, int.class, int.class, int.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) { XposedBridge.log("【VCAM】应用创建了渲染器:宽:" + param.args[0] + " 高:" + param.args[1] + "格式" + param.args[2]); c2_ori_width = (int) param.args[0]; c2_ori_height = (int) param.args[1]; - imageReaderFormat = (int) param.args[2]; - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); need_to_show_toast = !toast_control.exists(); if (toast_content != null && need_to_show_toast) { try { - Toast.makeText(toast_content, "应用创建了渲染器:\n宽:" + param.args[0] + "\n高:" + param.args[1] + "\n一般只需要宽高比与视频相同", Toast.LENGTH_SHORT).show(); + Toast.makeText(toast_content, + "应用创建了渲染器:\n宽:" + param.args[0] + "\n高:" + param.args[1] + "\n一般只需要宽高比与视频相同", + Toast.LENGTH_SHORT).show(); } catch (Exception e) { XposedBridge.log("【VCAM】[toast]" + e.toString()); } } } - }); + + @Override + protected void afterHookedMethod(MethodHookParam param) { + if (param.getResult() != null) { + putReader(param.getResult(), param.args[2]); + } + } + } + + XposedHelpers.findAndHookMethod("android.media.ImageReader", lpparam.classLoader, "newInstance", int.class, + int.class, int.class, int.class, new FirstNewInstanceHook()); try { - Class surfaceImageClass = XposedHelpers.findClassIfExists("android.media.ImageReader$SurfaceImage", lpparam.classLoader); + Class surfaceImageClass = XposedHelpers.findClassIfExists("android.media.ImageReader$SurfaceImage", + lpparam.classLoader); if (surfaceImageClass != null) { XposedHelpers.findAndHookMethod(surfaceImageClass, "getPlanes", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { - Image image = (Image) param.thisObject; - if (image.getFormat() == 256 && imageReaderFormat == 256 && (c2_reader_Surfcae != null || c2_reader_Surfcae_1 != null)) { - Object[] planes = (Object[]) param.getResult(); - if (planes != null && planes.length > 0) { - File replacementFile = pickRandomImageFile(video_path); - if (replacementFile != null) { - Bitmap pict = getBMP(replacementFile.getAbsolutePath()); - if (pict != null) { - ByteArrayOutputStream temp_array = new ByteArrayOutputStream(); - pict.compress(Bitmap.CompressFormat.JPEG, 100, temp_array); - byte[] jpeg_data = temp_array.toByteArray(); - ByteBuffer newBuffer = ByteBuffer.allocateDirect(jpeg_data.length); - newBuffer.put(jpeg_data); - newBuffer.rewind(); - - Field bufferField = null; - try { - bufferField = planes[0].getClass().getDeclaredField("mBuffer"); - } catch (NoSuchFieldException e) { - for (Field f : planes[0].getClass().getDeclaredFields()) { - if (f.getType() == ByteBuffer.class) { - bufferField = f; - break; - } - } - } - if (bufferField != null) { - bufferField.setAccessible(true); - bufferField.set(planes[0], newBuffer); - XposedBridge.log("【VCAM】成功替换ImageReader的JPEG图像数据"); - } - } - } - } - } + handleGetPlanes(param); } }); } } catch (Throwable t) { XposedBridge.log("【VCAM】Hook SurfaceImage.getPlanes失败: " + t); } + try { + XposedHelpers.findAndHookMethod("android.media.ImageReader", lpparam.classLoader, "newInstance", int.class, + int.class, int.class, int.class, long.class, new NewInstanceHook()); + XposedHelpers.findAndHookMethod("android.media.ImageReader", lpparam.classLoader, "getSurface", + new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) { + Object surface = param.getResult(); + if (surface != null) { + int format = getFormatForObject(param.thisObject); + if (format != -1) { + putSurface((Surface) surface, format); + } + } + } + }); + } catch (Throwable t) { + XposedBridge.log("【VCAM】Hook ImageReader failed: " + t); + } - XposedHelpers.findAndHookMethod("android.hardware.camera2.CameraCaptureSession.CaptureCallback", lpparam.classLoader, "onCaptureFailed", CameraCaptureSession.class, CaptureRequest.class, CaptureFailure.class, - new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - XposedBridge.log("【VCAM】onCaptureFailed" + "原因:" + ((CaptureFailure) param.args[2]).getReason()); - - } - }); + // onCaptureFailed hook removed to prevent D8 compiler crash } private void process_camera2_play() { if (c2_reader_Surfcae != null) { - if (imageReaderFormat == 256) { + int format = getFormatForSurface(c2_reader_Surfcae); + if (format != -1 && format == 256) { XposedBridge.log("【VCAM】ImageReader格式为JPEG,不启动视频解码以防报错"); } else { if (c2_hw_decode_obj != null) { @@ -768,7 +870,8 @@ private void process_camera2_play() { } if (c2_reader_Surfcae_1 != null) { - if (imageReaderFormat == 256) { + int format = getFormatForSurface(c2_reader_Surfcae_1); + if (format != -1 && format == 256) { XposedBridge.log("【VCAM】ImageReader_1格式为JPEG,不启动视频解码以防报错"); } else { if (c2_hw_decode_obj_1 != null) { @@ -787,7 +890,6 @@ private void process_camera2_play() { } } - if (c2_preview_Surfcae != null) { if (c2_player == null) { c2_player = new MediaPlayer(); @@ -796,7 +898,8 @@ private void process_camera2_play() { c2_player = new MediaPlayer(); } c2_player.setSurface(c2_preview_Surfcae); - File sfile = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no-silent.jpg"); + File sfile = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no-silent.jpg"); if (!sfile.exists()) { c2_player.setVolume(0, 0); } @@ -823,7 +926,8 @@ public void onPrepared(MediaPlayer mp) { c2_player_1 = new MediaPlayer(); } c2_player_1.setSurface(c2_preview_Surfcae_1); - File sfile = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no-silent.jpg"); + File sfile = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no-silent.jpg"); if (!sfile.exists()) { c2_player_1.setVolume(0, 0); } @@ -902,151 +1006,234 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("【VCAM】打开相机C2"); File file = new File(video_path + "virtual.mp4"); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); + File toast_control = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); need_to_show_toast = !toast_control.exists(); if (!file.exists()) { if (toast_content != null && need_to_show_toast) { try { - Toast.makeText(toast_content, "不存在替换视频\n" + toast_content.getPackageName() + "当前路径:" + video_path, Toast.LENGTH_SHORT).show(); + Toast.makeText(toast_content, + "不存在替换视频\n" + toast_content.getPackageName() + "当前路径:" + video_path, + Toast.LENGTH_SHORT).show(); } catch (Exception ee) { XposedBridge.log("【VCAM】[toast]" + ee.toString()); } } return; } - XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createCaptureSession", List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { - if (paramd.args[0] != null) { - XposedBridge.log("【VCAM】createCaptureSession创捷捕获,原始:" + paramd.args[0].toString() + "虚拟:" + c2_virtual_surface.toString()); - paramd.args[0] = Arrays.asList(c2_virtual_surface); - if (paramd.args[1] != null) { - process_camera2Session_callback((CameraCaptureSession.StateCallback) paramd.args[1]); + XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createCaptureSession", List.class, + CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { + if (paramd.args[0] != null) { + java.util.List originalSurfaces = (java.util.List) paramd.args[0]; + java.util.List newSurfaces = new java.util.ArrayList(); + newSurfaces.add(c2_virtual_surface); + for (Object obj : originalSurfaces) { + Surface s = (Surface) obj; + int format = getFormatForSurface(s); + if (format != -1 && format == 256) { + newSurfaces.add(s); + } + } + paramd.args[0] = newSurfaces; + if (paramd.args[1] != null) { + process_camera2Session_callback( + (CameraCaptureSession.StateCallback) paramd.args[1]); + } + } } - } - } - }); - -/* XposedHelpers.findAndHookMethod(param.args[0].getClass(), "close", new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { - XposedBridge.log("C2终止预览"); - if (c2_hw_decode_obj != null) { - c2_hw_decode_obj.stopDecode(); - c2_hw_decode_obj = null; - } - if (c2_hw_decode_obj_1 != null) { - c2_hw_decode_obj_1.stopDecode(); - c2_hw_decode_obj_1 = null; - } - if (c2_player != null) { - c2_player.release(); - c2_player = null; - } - if (c2_player_1 != null){ - c2_player_1.release(); - c2_player_1 = null; - } - c2_preview_Surfcae_1 = null; - c2_reader_Surfcae_1 = null; - c2_reader_Surfcae = null; - c2_preview_Surfcae = null; - need_recreate = true; - is_first_hook_build= true; - } - });*/ + }); + + /* + * XposedHelpers.findAndHookMethod(param.args[0].getClass(), "close", new + * XC_MethodHook() { + * + * @Override + * protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { + * XposedBridge.log("C2终止预览"); + * if (c2_hw_decode_obj != null) { + * c2_hw_decode_obj.stopDecode(); + * c2_hw_decode_obj = null; + * } + * if (c2_hw_decode_obj_1 != null) { + * c2_hw_decode_obj_1.stopDecode(); + * c2_hw_decode_obj_1 = null; + * } + * if (c2_player != null) { + * c2_player.release(); + * c2_player = null; + * } + * if (c2_player_1 != null){ + * c2_player_1.release(); + * c2_player_1 = null; + * } + * c2_preview_Surfcae_1 = null; + * c2_reader_Surfcae_1 = null; + * c2_reader_Surfcae = null; + * c2_preview_Surfcae = null; + * need_recreate = true; + * is_first_hook_build= true; + * } + * }); + */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createCaptureSessionByOutputConfigurations", List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - super.beforeHookedMethod(param); - if (param.args[0] != null) { - outputConfiguration = new OutputConfiguration(c2_virtual_surface); - param.args[0] = Arrays.asList(outputConfiguration); - - XposedBridge.log("【VCAM】执行了createCaptureSessionByOutputConfigurations-144777"); - if (param.args[1] != null) { - process_camera2Session_callback((CameraCaptureSession.StateCallback) param.args[1]); + XposedHelpers.findAndHookMethod(param.args[0].getClass(), + "createCaptureSessionByOutputConfigurations", List.class, + CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + if (param.args[0] != null) { + java.util.List originalConfigs = (java.util.List) param.args[0]; + java.util.List newConfigs = new java.util.ArrayList(); + newConfigs.add(new OutputConfiguration(c2_virtual_surface)); + for (Object obj : originalConfigs) { + OutputConfiguration config = (OutputConfiguration) obj; + if (config != null && config.getSurface() != null) { + Surface s = config.getSurface(); + int format = getFormatForSurface(s); + if (format != -1 && format == 256) { + newConfigs.add(config); + } + } + } + param.args[0] = newConfigs; + + XposedBridge.log("【VCAM】执行了createCaptureSessionByOutputConfigurations-144777"); + if (param.args[1] != null) { + process_camera2Session_callback( + (CameraCaptureSession.StateCallback) param.args[1]); + } + } } - } - } - }); + }); } - - XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createConstrainedHighSpeedCaptureSession", List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - super.beforeHookedMethod(param); - if (param.args[0] != null) { - param.args[0] = Arrays.asList(c2_virtual_surface); - XposedBridge.log("【VCAM】执行了 createConstrainedHighSpeedCaptureSession -5484987"); - if (param.args[1] != null) { - process_camera2Session_callback((CameraCaptureSession.StateCallback) param.args[1]); + XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createConstrainedHighSpeedCaptureSession", + List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + if (param.args[0] != null) { + java.util.List originalSurfaces = (java.util.List) param.args[0]; + java.util.List newSurfaces = new java.util.ArrayList(); + newSurfaces.add(c2_virtual_surface); + for (Object obj : originalSurfaces) { + Surface s = (Surface) obj; + int format = getFormatForSurface(s); + if (format != -1 && format == 256) { + newSurfaces.add(s); + } + } + param.args[0] = newSurfaces; + XposedBridge.log("【VCAM】执行了 createConstrainedHighSpeedCaptureSession -5484987"); + if (param.args[1] != null) { + process_camera2Session_callback( + (CameraCaptureSession.StateCallback) param.args[1]); + } } } - } - }); - + }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createReprocessableCaptureSession", InputConfiguration.class, List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - super.beforeHookedMethod(param); - if (param.args[1] != null) { - param.args[1] = Arrays.asList(c2_virtual_surface); - XposedBridge.log("【VCAM】执行了 createReprocessableCaptureSession "); - if (param.args[2] != null) { - process_camera2Session_callback((CameraCaptureSession.StateCallback) param.args[2]); + XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createReprocessableCaptureSession", + InputConfiguration.class, List.class, CameraCaptureSession.StateCallback.class, + Handler.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + if (param.args[1] != null) { + java.util.List originalSurfaces = (java.util.List) param.args[1]; + java.util.List newSurfaces = new java.util.ArrayList(); + newSurfaces.add(c2_virtual_surface); + for (Object obj : originalSurfaces) { + Surface s = (Surface) obj; + int format = getFormatForSurface(s); + if (format != -1 && format == 256) { + newSurfaces.add(s); + } + } + param.args[1] = newSurfaces; + XposedBridge.log("【VCAM】执行了 createReprocessableCaptureSession "); + if (param.args[2] != null) { + process_camera2Session_callback( + (CameraCaptureSession.StateCallback) param.args[2]); + } + } } - } - } - }); + }); } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createReprocessableCaptureSessionByConfigurations", InputConfiguration.class, List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - super.beforeHookedMethod(param); - if (param.args[1] != null) { - outputConfiguration = new OutputConfiguration(c2_virtual_surface); - param.args[0] = Arrays.asList(outputConfiguration); - XposedBridge.log("【VCAM】执行了 createReprocessableCaptureSessionByConfigurations"); - if (param.args[2] != null) { - process_camera2Session_callback((CameraCaptureSession.StateCallback) param.args[2]); + XposedHelpers.findAndHookMethod(param.args[0].getClass(), + "createReprocessableCaptureSessionByConfigurations", InputConfiguration.class, List.class, + CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + if (param.args[1] != null) { + java.util.List originalConfigs = (java.util.List) param.args[1]; + java.util.List newConfigs = new java.util.ArrayList(); + newConfigs.add(new OutputConfiguration(c2_virtual_surface)); + for (Object obj : originalConfigs) { + OutputConfiguration config = (OutputConfiguration) obj; + if (config != null && config.getSurface() != null) { + Surface s = config.getSurface(); + int format = getFormatForSurface(s); + if (format != -1 && format == 256) { + newConfigs.add(config); + } + } + } + param.args[1] = newConfigs; + XposedBridge.log("【VCAM】执行了 createReprocessableCaptureSessionByConfigurations"); + if (param.args[2] != null) { + process_camera2Session_callback( + (CameraCaptureSession.StateCallback) param.args[2]); + } + } } - } - } - }); + }); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createCaptureSession", SessionConfiguration.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - super.beforeHookedMethod(param); - if (param.args[0] != null) { - XposedBridge.log("【VCAM】执行了 createCaptureSession -5484987"); - sessionConfiguration = (SessionConfiguration) param.args[0]; - outputConfiguration = new OutputConfiguration(c2_virtual_surface); - fake_sessionConfiguration = new SessionConfiguration(sessionConfiguration.getSessionType(), - Arrays.asList(outputConfiguration), - sessionConfiguration.getExecutor(), - sessionConfiguration.getStateCallback()); - param.args[0] = fake_sessionConfiguration; - process_camera2Session_callback(sessionConfiguration.getStateCallback()); - } - } - }); + XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createCaptureSession", + SessionConfiguration.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + if (param.args[0] != null) { + XposedBridge.log("【VCAM】执行了 createCaptureSession -5484987"); + sessionConfiguration = (SessionConfiguration) param.args[0]; + java.util.List originalConfigs = sessionConfiguration.getOutputConfigurations(); + java.util.List newConfigs = new java.util.ArrayList(); + newConfigs.add(new OutputConfiguration(c2_virtual_surface)); + for (Object obj : originalConfigs) { + OutputConfiguration config = (OutputConfiguration) obj; + if (config != null && config.getSurface() != null) { + Surface s = config.getSurface(); + int format = getFormatForSurface(s); + if (format != -1 && format == 256) { + newConfigs.add(config); + } + } + } + fake_sessionConfiguration = new SessionConfiguration( + sessionConfiguration.getSessionType(), + newConfigs, + sessionConfiguration.getExecutor(), + sessionConfiguration.getStateCallback()); + param.args[0] = fake_sessionConfiguration; + process_camera2Session_callback(sessionConfiguration.getStateCallback()); + } + } + }); } } }); - XposedHelpers.findAndHookMethod(hooked_class, "onError", CameraDevice.class, int.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { @@ -1055,7 +1242,6 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable { }); - XposedHelpers.findAndHookMethod(hooked_class, "onDisconnected", CameraDevice.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { @@ -1064,7 +1250,6 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable { }); - } private File pickRandomImageFile(String directoryPath) { @@ -1105,43 +1290,49 @@ private void process_a_shot_jpeg(XC_MethodHook.MethodHookParam param, int index) } Class callback = param.args[index].getClass(); - XposedHelpers.findAndHookMethod(callback, "onPictureTaken", byte[].class, android.hardware.Camera.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { - try { - Camera loaclcam = (Camera) paramd.args[1]; - onemwidth = loaclcam.getParameters().getPreviewSize().width; - onemhight = loaclcam.getParameters().getPreviewSize().height; - XposedBridge.log("【VCAM】JPEG拍照回调初始化:宽:" + onemwidth + "高:" + onemhight + "对应的类:" + loaclcam.toString()); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (toast_content != null && need_to_show_toast) { + XposedHelpers.findAndHookMethod(callback, "onPictureTaken", byte[].class, android.hardware.Camera.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { try { - Toast.makeText(toast_content, "发现拍照\n宽:" + onemwidth + "\n高:" + onemhight + "\n格式:JPEG", Toast.LENGTH_SHORT).show(); - } catch (Exception e) { - XposedBridge.log("【VCAM】[toast]" + e.toString()); - } - } - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); - if (control_file.exists()) { - return; - } + Camera loaclcam = (Camera) paramd.args[1]; + onemwidth = loaclcam.getParameters().getPreviewSize().width; + onemhight = loaclcam.getParameters().getPreviewSize().height; + XposedBridge.log("【VCAM】JPEG拍照回调初始化:宽:" + onemwidth + "高:" + onemhight + "对应的类:" + + loaclcam.toString()); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (toast_content != null && need_to_show_toast) { + try { + Toast.makeText(toast_content, + "发现拍照\n宽:" + onemwidth + "\n高:" + onemhight + "\n格式:JPEG", + Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + XposedBridge.log("【VCAM】[toast]" + e.toString()); + } + } + File control_file = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "disable.jpg"); + if (control_file.exists()) { + return; + } - File replacementFile = pickRandomImageFile(video_path); - if (replacementFile == null) { - XposedBridge.log("【VCAM】未找到用于替换的BMP文件"); - return; + File replacementFile = pickRandomImageFile(video_path); + if (replacementFile == null) { + XposedBridge.log("【VCAM】未找到用于替换的BMP文件"); + return; + } + Bitmap pict = getBMP(replacementFile.getAbsolutePath()); + ByteArrayOutputStream temp_array = new ByteArrayOutputStream(); + pict.compress(Bitmap.CompressFormat.JPEG, 100, temp_array); + byte[] jpeg_data = temp_array.toByteArray(); + paramd.args[0] = jpeg_data; + } catch (Exception ee) { + XposedBridge.log("【VCAM】" + ee.toString()); + } } - Bitmap pict = getBMP(replacementFile.getAbsolutePath()); - ByteArrayOutputStream temp_array = new ByteArrayOutputStream(); - pict.compress(Bitmap.CompressFormat.JPEG, 100, temp_array); - byte[] jpeg_data = temp_array.toByteArray(); - paramd.args[0] = jpeg_data; - } catch (Exception ee) { - XposedBridge.log("【VCAM】" + ee.toString()); - } - } - }); + }); } private void process_a_shot_YUV(XC_MethodHook.MethodHookParam param) { @@ -1151,55 +1342,64 @@ private void process_a_shot_YUV(XC_MethodHook.MethodHookParam param) { XposedBridge.log("【VCAM】" + eee); } Class callback = param.args[1].getClass(); - XposedHelpers.findAndHookMethod(callback, "onPictureTaken", byte[].class, android.hardware.Camera.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { - try { - Camera loaclcam = (Camera) paramd.args[1]; - onemwidth = loaclcam.getParameters().getPreviewSize().width; - onemhight = loaclcam.getParameters().getPreviewSize().height; - XposedBridge.log("【VCAM】YUV拍照回调初始化:宽:" + onemwidth + "高:" + onemhight + "对应的类:" + loaclcam.toString()); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (toast_content != null && need_to_show_toast) { + XposedHelpers.findAndHookMethod(callback, "onPictureTaken", byte[].class, android.hardware.Camera.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { try { - Toast.makeText(toast_content, "发现拍照\n宽:" + onemwidth + "\n高:" + onemhight + "\n格式:YUV_420_888", Toast.LENGTH_SHORT).show(); + Camera loaclcam = (Camera) paramd.args[1]; + onemwidth = loaclcam.getParameters().getPreviewSize().width; + onemhight = loaclcam.getParameters().getPreviewSize().height; + XposedBridge.log("【VCAM】YUV拍照回调初始化:宽:" + onemwidth + "高:" + onemhight + "对应的类:" + + loaclcam.toString()); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (toast_content != null && need_to_show_toast) { + try { + Toast.makeText(toast_content, + "发现拍照\n宽:" + onemwidth + "\n高:" + onemhight + "\n格式:YUV_420_888", + Toast.LENGTH_SHORT).show(); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[toast]" + ee.toString()); + } + } + File control_file = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "disable.jpg"); + if (control_file.exists()) { + return; + } + File replacementFile = pickRandomImageFile(video_path); + if (replacementFile == null) { + XposedBridge.log("【VCAM】未找到用于替换的BMP文件"); + return; + } + input = getYUVByBitmap(getBMP(replacementFile.getAbsolutePath())); + paramd.args[0] = input; } catch (Exception ee) { - XposedBridge.log("【VCAM】[toast]" + ee.toString()); + XposedBridge.log("【VCAM】" + ee.toString()); } } - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); - if (control_file.exists()) { - return; - } - File replacementFile = pickRandomImageFile(video_path); - if (replacementFile == null) { - XposedBridge.log("【VCAM】未找到用于替换的BMP文件"); - return; - } - input = getYUVByBitmap(getBMP(replacementFile.getAbsolutePath())); - paramd.args[0] = input; - } catch (Exception ee) { - XposedBridge.log("【VCAM】" + ee.toString()); - } - } - }); + }); } private void process_callback(XC_MethodHook.MethodHookParam param) { Class preview_cb_class = param.args[0].getClass(); int need_stop = 0; - File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); + File control_file = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg"); if (control_file.exists()) { need_stop = 1; } File file = new File(video_path + "virtual.mp4"); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); + File toast_control = new File( + Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); need_to_show_toast = !toast_control.exists(); if (!file.exists()) { if (toast_content != null && need_to_show_toast) { try { - Toast.makeText(toast_content, "不存在替换视频\n" + toast_content.getPackageName() + "当前路径:" + video_path, Toast.LENGTH_SHORT).show(); + Toast.makeText(toast_content, "不存在替换视频\n" + toast_content.getPackageName() + "当前路径:" + video_path, + Toast.LENGTH_SHORT).show(); } catch (Exception ee) { XposedBridge.log("【VCAM】[toast]" + ee); } @@ -1207,83 +1407,137 @@ private void process_callback(XC_MethodHook.MethodHookParam param) { need_stop = 1; } int finalNeed_stop = need_stop; - XposedHelpers.findAndHookMethod(preview_cb_class, "onPreviewFrame", byte[].class, android.hardware.Camera.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { - Camera localcam = (android.hardware.Camera) paramd.args[1]; - if (localcam.equals(camera_onPreviewFrame)) { - while (data_buffer == null) { - } - System.arraycopy(data_buffer, 0, paramd.args[0], 0, Math.min(data_buffer.length, ((byte[]) paramd.args[0]).length)); - } else { - camera_callback_calss = preview_cb_class; - camera_onPreviewFrame = (android.hardware.Camera) paramd.args[1]; - mwidth = camera_onPreviewFrame.getParameters().getPreviewSize().width; - mhight = camera_onPreviewFrame.getParameters().getPreviewSize().height; - int frame_Rate = camera_onPreviewFrame.getParameters().getPreviewFrameRate(); - XposedBridge.log("【VCAM】帧预览回调初始化:宽:" + mwidth + " 高:" + mhight + " 帧率:" + frame_Rate); - File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg"); - need_to_show_toast = !toast_control.exists(); - if (toast_content != null && need_to_show_toast) { - try { - Toast.makeText(toast_content, "发现预览\n宽:" + mwidth + "\n高:" + mhight + "\n" + "需要视频分辨率与其完全相同", Toast.LENGTH_SHORT).show(); - } catch (Exception ee) { - XposedBridge.log("【VCAM】[toast]" + ee.toString()); + XposedHelpers.findAndHookMethod(preview_cb_class, "onPreviewFrame", byte[].class, android.hardware.Camera.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { + Camera localcam = (android.hardware.Camera) paramd.args[1]; + if (localcam.equals(camera_onPreviewFrame)) { + while (data_buffer == null) { + } + System.arraycopy(data_buffer, 0, paramd.args[0], 0, + Math.min(data_buffer.length, ((byte[]) paramd.args[0]).length)); + } else { + camera_callback_calss = preview_cb_class; + camera_onPreviewFrame = (android.hardware.Camera) paramd.args[1]; + mwidth = camera_onPreviewFrame.getParameters().getPreviewSize().width; + mhight = camera_onPreviewFrame.getParameters().getPreviewSize().height; + int frame_Rate = camera_onPreviewFrame.getParameters().getPreviewFrameRate(); + XposedBridge.log("【VCAM】帧预览回调初始化:宽:" + mwidth + " 高:" + mhight + " 帧率:" + frame_Rate); + File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + + "/DCIM/Camera1/" + "no_toast.jpg"); + need_to_show_toast = !toast_control.exists(); + if (toast_content != null && need_to_show_toast) { + try { + Toast.makeText(toast_content, + "发现预览\n宽:" + mwidth + "\n高:" + mhight + "\n" + "需要视频分辨率与其完全相同", + Toast.LENGTH_SHORT).show(); + } catch (Exception ee) { + XposedBridge.log("【VCAM】[toast]" + ee.toString()); + } + } + if (finalNeed_stop == 1) { + return; + } + if (hw_decode_obj != null) { + hw_decode_obj.stopDecode(); + } + hw_decode_obj = new VideoToFrames(); + hw_decode_obj.setSaveFrames("", OutputImageFormat.NV21); + hw_decode_obj.decode(video_path + "virtual.mp4"); + while (data_buffer == null) { + } + System.arraycopy(data_buffer, 0, paramd.args[0], 0, + Math.min(data_buffer.length, ((byte[]) paramd.args[0]).length)); } - } - if (finalNeed_stop == 1) { - return; - } - if (hw_decode_obj != null) { - hw_decode_obj.stopDecode(); - } - hw_decode_obj = new VideoToFrames(); - hw_decode_obj.setSaveFrames("", OutputImageFormat.NV21); - hw_decode_obj.decode(video_path + "virtual.mp4"); - while (data_buffer == null) { - } - System.arraycopy(data_buffer, 0, paramd.args[0], 0, Math.min(data_buffer.length, ((byte[]) paramd.args[0]).length)); - } - } - }); + } + }); } - private void process_camera2Session_callback(CameraCaptureSession.StateCallback callback_calss){ - if (callback_calss == null){ + private void process_camera2Session_callback(CameraCaptureSession.StateCallback callback_calss) { + if (callback_calss == null) { return; } - XposedHelpers.findAndHookMethod(callback_calss.getClass(), "onConfigureFailed", CameraCaptureSession.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - XposedBridge.log("【VCAM】onConfigureFailed :" + param.args[0].toString()); - } + XposedHelpers.findAndHookMethod(callback_calss.getClass(), "onConfigureFailed", CameraCaptureSession.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + XposedBridge.log("【VCAM】onConfigureFailed :" + param.args[0].toString()); + } - }); + }); - XposedHelpers.findAndHookMethod(callback_calss.getClass(), "onConfigured", CameraCaptureSession.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - XposedBridge.log("【VCAM】onConfigured :" + param.args[0].toString()); - } - }); + XposedHelpers.findAndHookMethod(callback_calss.getClass(), "onConfigured", CameraCaptureSession.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + XposedBridge.log("【VCAM】onConfigured :" + param.args[0].toString()); + } + }); - XposedHelpers.findAndHookMethod( callback_calss.getClass(), "onClosed", CameraCaptureSession.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - XposedBridge.log("【VCAM】onClosed :"+ param.args[0].toString()); - } - }); + XposedHelpers.findAndHookMethod(callback_calss.getClass(), "onClosed", CameraCaptureSession.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + XposedBridge.log("【VCAM】onClosed :" + param.args[0].toString()); + } + }); } - - - //以下代码来源:https://blog.csdn.net/jacke121/article/details/73888732 + // 以下代码来源:https://blog.csdn.net/jacke121/article/details/73888732 private Bitmap getBMP(String file) throws Throwable { return BitmapFactory.decodeFile(file); } + private static class NewInstanceHook extends XC_MethodHook { + @Override + protected void afterHookedMethod(MethodHookParam param) { + if (param.getResult() != null) { + putReader(param.getResult(), param.args[2]); + } + } + } + + private void handleGetPlanes(XC_MethodHook.MethodHookParam param) throws Throwable { + android.media.Image image = (android.media.Image) param.thisObject; + if (image.getFormat() == 256 && (c2_reader_Surfcae != null || c2_reader_Surfcae_1 != null)) { + Object[] planes = (Object[]) param.getResult(); + if (planes != null && planes.length > 0) { + File replacementFile = pickRandomImageFile(video_path); + if (replacementFile != null) { + Bitmap pict = getBMP(replacementFile.getAbsolutePath()); + if (pict != null) { + ByteArrayOutputStream temp_array = new ByteArrayOutputStream(); + pict.compress(Bitmap.CompressFormat.JPEG, 100, temp_array); + byte[] jpeg_data = temp_array.toByteArray(); + ByteBuffer newBuffer = ByteBuffer.allocateDirect(jpeg_data.length); + newBuffer.put(jpeg_data); + newBuffer.rewind(); + + Field bufferField = null; + try { + bufferField = planes[0].getClass().getDeclaredField("mBuffer"); + } catch (NoSuchFieldException e) { + for (Field f : planes[0].getClass().getDeclaredFields()) { + if (f.getType() == ByteBuffer.class) { + bufferField = f; + break; + } + } + } + if (bufferField != null) { + bufferField.setAccessible(true); + bufferField.set(planes[0], newBuffer); + XposedBridge.log("【VCAM】成功替换ImageReader的JPEG图像数据"); + } + } + } + } + } + } + private static byte[] rgb2YCbCr420(int[] pixels, int width, int height) { int len = width * height; // yuv格式数组大小,y亮度占len长度,u,v各占len/4长度。 @@ -1323,4 +1577,3 @@ private static byte[] getYUVByBitmap(Bitmap bitmap) { return rgb2YCbCr420(pixels, width, height); } } -