From 39fa1693fb42ca0a75b9a46e97fe6e867764e678 Mon Sep 17 00:00:00 2001 From: Cross2pro Date: Tue, 11 Nov 2025 20:40:41 +0800 Subject: [PATCH 1/4] Add Android build workflow --- .github/workflows/android-build.yml | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/android-build.yml diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml new file mode 100644 index 0000000..2ae5da7 --- /dev/null +++ b/.github/workflows/android-build.yml @@ -0,0 +1,30 @@ +name: Android Build + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '11' + cache: gradle + + - name: Build with Gradle + run: ./gradlew --no-daemon clean assembleRelease + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: android-build + path: | + app/build/outputs/apk/release/app-release.apk + app/build/outputs/** From 9742f95efeac7124bf052784d5ae5cc39787f530 Mon Sep 17 00:00:00 2001 From: Cross2pro Date: Tue, 11 Nov 2025 20:41:14 +0800 Subject: [PATCH 2/4] Add random BMP selection for capture replacements --- .../main/java/com/example/vcam/HookMain.java | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/vcam/HookMain.java b/app/src/main/java/com/example/vcam/HookMain.java index e2d13d8..7a4fb28 100644 --- a/app/src/main/java/com/example/vcam/HookMain.java +++ b/app/src/main/java/com/example/vcam/HookMain.java @@ -30,9 +30,11 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.ThreadLocalRandom; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; @@ -1010,6 +1012,35 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable { } + private File pickRandomImageFile(String directoryPath) { + File directory = new File(directoryPath); + if (!directory.exists() || !directory.isDirectory()) { + XposedBridge.log("【VCAM】图像目录不存在:" + directoryPath); + return null; + } + + File[] files = directory.listFiles(); + if (files == null) { + XposedBridge.log("【VCAM】无法列出目录:" + directoryPath); + return null; + } + + List bmpFiles = new ArrayList<>(); + for (File file : files) { + if (file != null && file.isFile() && file.getName().toLowerCase().endsWith(".bmp")) { + bmpFiles.add(file); + } + } + + if (bmpFiles.isEmpty()) { + XposedBridge.log("【VCAM】目录中没有可用的BMP文件:" + directoryPath); + return null; + } + + int randomIndex = ThreadLocalRandom.current().nextInt(bmpFiles.size()); + return bmpFiles.get(randomIndex); + } + private void process_a_shot_jpeg(XC_MethodHook.MethodHookParam param, int index) { try { XposedBridge.log("【VCAM】第二个jpeg:" + param.args[index].toString()); @@ -1041,7 +1072,12 @@ protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { return; } - Bitmap pict = getBMP(video_path + "1000.bmp"); + 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(); @@ -1081,7 +1117,12 @@ protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable { if (control_file.exists()) { return; } - input = getYUVByBitmap(getBMP(video_path + "1000.bmp")); + 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()); From 8d4b0f4c621be33df139fb85fdd68ecf61c1a450 Mon Sep 17 00:00:00 2001 From: Cross2pro Date: Tue, 11 Nov 2025 20:51:08 +0800 Subject: [PATCH 3/4] Ensure Gradle wrapper is executable in workflow --- .github/workflows/android-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 2ae5da7..417cfa1 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -18,6 +18,9 @@ jobs: java-version: '11' cache: gradle + - name: Make Gradle wrapper executable + run: chmod +x gradlew + - name: Build with Gradle run: ./gradlew --no-daemon clean assembleRelease From 5787628461383b1aaf833a275ed9e45bc5303d93 Mon Sep 17 00:00:00 2001 From: Cross2pro Date: Tue, 11 Nov 2025 21:06:33 +0800 Subject: [PATCH 4/4] Sign release APK and publish to GitHub releases --- .github/workflows/android-build.yml | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/android-build.yml diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml new file mode 100644 index 0000000..f55e207 --- /dev/null +++ b/.github/workflows/android-build.yml @@ -0,0 +1,65 @@ +name: Android Build + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '11' + cache: gradle + + - name: Make Gradle wrapper executable + run: chmod +x gradlew + + - name: Decode release keystore + run: | + if [ -z "$ANDROID_KEYSTORE_BASE64" ]; then + echo 'ANDROID_KEYSTORE_BASE64 secret is not configured.' >&2 + exit 1 + fi + echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > release.keystore + env: + ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + + - name: Build signed release with Gradle + env: + ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/release.keystore + ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + run: | + for var in ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do + if [ -z "${!var}" ]; then + echo "$var secret is not configured." >&2 + exit 1 + fi + done + ./gradlew --no-daemon clean assembleRelease \ + -Pandroid.injected.signing.store.file="$ANDROID_KEYSTORE_PATH" \ + -Pandroid.injected.signing.store.password="$ANDROID_KEYSTORE_PASSWORD" \ + -Pandroid.injected.signing.key.alias="$ANDROID_KEY_ALIAS" \ + -Pandroid.injected.signing.key.password="$ANDROID_KEY_PASSWORD" + + - name: Upload signed APK artifact + uses: actions/upload-artifact@v4 + with: + name: app-release-signed + path: app/build/outputs/apk/release/app-release.apk + + - name: Publish signed APK to GitHub Release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + files: app/build/outputs/apk/release/app-release.apk