Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7fd88c9
Migrate build to Gradle 9.6.1 / AGP 9 and update dependencies (#792)
JingMatrix Jul 25, 2026
73571c7
Fix daemon crash from the Android 17 IServiceConnection change (#784)
Leaf-lsgtky Jul 25, 2026
04093fd
Fix libxposed API 101 conformance (#794)
JingMatrix Jul 27, 2026
7b4688f
Fix injection and manager launch on Android 10 and below (#793)
JingMatrix Jul 27, 2026
eb07955
Publish canary builds as prereleases anyone can download
JingMatrix Jul 27, 2026
29dd7f7
Restore the INET group patch on the manager's original gid array (#795)
JingMatrix Jul 27, 2026
c147582
Replace the manager with one written in Compose (#796)
JingMatrix Jul 29, 2026
3d8090f
Stop the parasitic manager crashing on its first network request (#802)
JingMatrix Jul 29, 2026
b2dfaac
Fix the long-press sheet's dropped actions and mismatched rows (#814)
JingMatrix Jul 30, 2026
61c89db
Fix the CI artifacts, the update endpoint and the translations (#811)
JingMatrix Jul 30, 2026
93d6647
Evict a pre-cached LoadedApk before binding the parasitic manager (#748)
yunsmall Jul 30, 2026
676d8e2
Load a legacy module into its own process again (#819)
JingMatrix Jul 30, 2026
4455239
Write static final fields again on Android 17 (#821)
JingMatrix Jul 30, 2026
b1e44f2
Give the manager ways in again, and name builds by where they came fr…
JingMatrix Jul 30, 2026
ba32ffb
Put the commit first in the build stamp, and compare only that (#822)
JingMatrix Jul 31, 2026
fbcff2f
Refuse to hook Object.getClass (#807)
JingMatrix Jul 31, 2026
f3448c8
Fix store installs, update offers and in-app browsing (#825)
JingMatrix Jul 31, 2026
5ff67a8
Stage a module's native libraries where system_server may map them (#…
JingMatrix Jul 31, 2026
ebb438b
Fix module scope writes and the daemon notifications the manager drop…
JingMatrix Aug 1, 2026
541a3aa
Fix source matching and locale placeholders in the Crowdin config (#839)
JingMatrix Aug 1, 2026
28daecf
Survive a failed injection, and quieten the scope logs (#826)
JingMatrix Aug 1, 2026
a8b02c7
Keep the stack traces we log, and read them as traces
JingMatrix Aug 1, 2026
b5bce6a
Let the parasitic manager load its DEX on GrapheneOS (#805)
JingMatrix Aug 1, 2026
cd6860d
Rearrange, hide, or float the navigation panels (#813)
JingMatrix Aug 1, 2026
45dc8d8
Remove the sign-in and the code the Compose rewrite left behind (#812)
JingMatrix Aug 1, 2026
f565542
Update the dependencies, and fix what a build was complaining about (…
JingMatrix Aug 2, 2026
b09a978
Release Vector v2.1
JingMatrix Aug 2, 2026
73eba3a
Keep XResources out of the classes R8 shares (#851)
JingMatrix Aug 3, 2026
92f3e60
Stop leaving JNI exceptions pending across the native boundary (#853)
JingMatrix Aug 3, 2026
8d636c4
Redraw the icon, route the board, and check the installed copy (#849)
JingMatrix Aug 3, 2026
abae837
Implement libxposed API 102 and make the multi-user scope model consi…
HSSkyBoy Aug 3, 2026
717095a
Fix canary build links with a rebuilt UI (#860)
JingMatrix Aug 3, 2026
de6e250
Move the manager AIDL out of org.lsposed.lspd (#861)
JingMatrix Aug 4, 2026
88f8e1f
Release Vector v2.2
JingMatrix Aug 4, 2026
481c139
Keep what saved instance state is restored through (#875)
JingMatrix Aug 5, 2026
f2ef0b2
Fix the scope targets no filter can reveal and the routes that lead n…
JingMatrix Aug 5, 2026
077131d
Bypass the notification flags on the Android 15 builds that carry the…
JingMatrix Aug 6, 2026
1c1b829
Keep the bars docked to the window out of the navigation bar (#885)
JingMatrix Aug 7, 2026
4fcea0e
Fix the scope request prompts and their answers (#890)
JingMatrix Aug 7, 2026
e8bec6b
Stop holding every module app at foreground priority (#893)
JingMatrix Aug 7, 2026
ee1bd3a
Fix the status badge, the shortcut offer and the feed's first check (…
JingMatrix Aug 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 4 additions & 4 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ body:
- type: markdown
attributes:
value: |
Thanks for reporting issues of LSPosed!
Thanks for reporting issues of Vector!

To make it easier for us to help you, please read all pinned issues and provide the following details.

感谢给 LSPosed 汇报问题!
感谢给 Vector 汇报问题!
为了使我们更好地帮助你,请务必阅读所有已置顶的 Issues 并提供以下细节。
为了防止重复汇报,标题请务必使用英文。
- type: textarea
Expand Down Expand Up @@ -54,7 +54,7 @@ body:
required: true
- type: input
attributes:
label: LSPosed version/LSPosed 版本
label: Vector version/Vector 版本
description: Don't use 'latest'. Specify actual version with 4 digits, otherwise your issue will be closed./不要填用“最新版”。给出四位版本号,不然 issue 会被关闭。
validations:
required: true
Expand All @@ -69,7 +69,7 @@ body:
attributes:
label: Version requirement/版本要求
options:
- label: I am using the latest debug build from [GitHub Actions](https://github.com/JingMatrix/LSPosed/actions?query=branch%3Amaster)./我正在使用 [GitHub Actions](https://github.com/JingMatrix/LSPosed/actions?query=branch%3Amaster) 中最新的调试版本。
- label: I am using the latest debug build from [GitHub Actions](https://github.com/JingMatrix/Vector/actions?query=branch%3Amaster)./我正在使用 [GitHub Actions](https://github.com/JingMatrix/Vector/actions?query=branch%3Amaster) 中最新的调试版本。
required: true
- type: textarea
attributes:
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Ask a question/提问
url: https://github.com/JingMatrix/LSPosed/discussions/new?category=Q-A
url: https://github.com/JingMatrix/Vector/discussions/new?category=Q-A
about: Please ask and answer questions here./如果有任何疑问请在这里提问
202 changes: 184 additions & 18 deletions .github/workflows/core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,79 @@ on:

jobs:
build:
# A "Release Vector …" commit is cut straight to a `v*` tag, and the tag build below is what
# publishes it — as the stable, `--latest` release. Skip the master-push run for that one commit
# so the very same code does not also go out as a canary prerelease: a release must reach users
# once, as the stable release, never also as a canary. The tag build is a separate push event
# (ref refs/tags/v*, not refs/heads/master) and is unaffected, so the release still ships.
if: ${{ !(github.event_name == 'push' && github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'Release Vector')) }}
runs-on: ubuntu-latest
# Needed to publish the canary prerelease below. Everything else here only reads.
permissions:
contents: write
env:
# Where this build's code came from, for the version string the manager and module.prop show.
# Both are the head of the pull request rather than GitHub's defaults, which on a pull request
# describe the run instead of the code: GITHUB_REPOSITORY is this repository even when the
# branch came from a fork, and the checked-out HEAD is an ephemeral merge commit that exists
# nowhere and cannot be looked up by anyone who reads it off a device. Both head.* values are
# null outside a pull request, where the defaults are already right.
VECTOR_BUILD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
VECTOR_BUILD_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
CCACHE_NOHASHDIR: "true"
CCACHE_HARDLINK: "true"
CCACHE_BASEDIR: "${{ github.workspace }}"

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive
fetch-depth: 0

# Before anything is built, because it needs nothing but Python and a translator's mistake
# should not wait twenty minutes to surface. Crowdin owns the content of these files, so what
# this catches is a bad merge or a line pasted into the wrong language -- both of which have
# happened, and neither of which any other check in this repository looks for.
- name: Check translations
run: |
python3 manager/tools/check_translations.py manager/src/main/res
python3 manager/tools/check_translations.py daemon/src/main/res

# A missing secret used to be skipped over in silence, and the build carried on to publish a
# canary or a tag signed with the debug key — which the daemon's InstallerVerifier rejects,
# and which nobody notices until someone tries to install the manager. On this repository the
# secret is always meant to be there, so its absence stops the run here. A fork has no access
# to it and cannot be given one, so a fork builds unsigned, as does any branch that is not
# master: neither publishes anything.
- name: Write key
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/master' ) || github.ref_type == 'tag' }}
env:
KEY_STORE: ${{ secrets.KEY_STORE }}
KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
ALIAS: ${{ secrets.ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
IS_UPSTREAM: ${{ github.repository_owner == 'JingMatrix' }}
run: |
if [ ! -z "${{ secrets.KEY_STORE }}" ]; then
echo androidStorePassword='${{ secrets.KEY_STORE_PASSWORD }}' >> gradle.properties
echo androidKeyAlias='${{ secrets.ALIAS }}' >> gradle.properties
echo androidKeyPassword='${{ secrets.KEY_PASSWORD }}' >> gradle.properties
echo androidStoreFile='key.jks' >> gradle.properties
echo ${{ secrets.KEY_STORE }} | base64 --decode > key.jks
set -euo pipefail
if [ -z "$KEY_STORE" ]; then
if [ "$IS_UPSTREAM" = "true" ]; then
echo "::error::KEY_STORE is empty on ${{ github.ref }}. Refusing to publish an unsigned build."
exit 1
fi
echo "No signing secret on this fork; building unsigned."
exit 0
fi
# Through the environment rather than interpolated into the script: a password holding a
# quote would otherwise be pasted into a shell word and mangled, or worse, executed.
{
echo "androidStorePassword=$KEY_STORE_PASSWORD"
echo "androidKeyAlias=$ALIAS"
echo "androidKeyPassword=$KEY_PASSWORD"
echo "androidStoreFile=key.jks"
} >> gradle.properties
printf '%s' "$KEY_STORE" | base64 --decode > key.jks

- name: Setup Java
uses: actions/setup-java@v5
Expand All @@ -42,22 +91,21 @@ jobs:
java-version: 21

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6

- name: Configure Gradle properties
run: |
echo 'android.native.buildOutput=verbose' >> ~/.gradle/gradle.properties
echo 'org.gradle.parallel=true' >> ~/.gradle/gradle.properties
echo 'org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC' >> ~/.gradle/gradle.properties
echo 'android.native.buildOutput=verbose' >> ~/.gradle/gradle.properties

- name: Setup ninja
uses: seanmiddleditch/gha-setup-ninja@v6
with:
version: 1.12.1
version: 1.13.2

- name: Setup ccache
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: |
~/.ccache
Expand All @@ -67,7 +115,7 @@ jobs:
${{ runner.os }}-ccache-

- name: Setup Android SDK
uses: android-actions/setup-android@v3
uses: android-actions/setup-android@v4

- name: Remove Android's cmake
shell: bash
Expand All @@ -83,32 +131,150 @@ jobs:
run: |
zygiskReleaseName=`ls zygisk/release/Vector-v*-Release.zip | awk -F '(/|.zip)' '{print $3}'` && echo "zygiskReleaseName=$zygiskReleaseName" >> $GITHUB_OUTPUT
zygiskDebugName=`ls zygisk/release/Vector-v*-Debug.zip | awk -F '(/|.zip)' '{print $3}'` && echo "zygiskDebugName=$zygiskDebugName" >> $GITHUB_OUTPUT
versionCode=`echo "$zygiskDebugName" | awk -F '-' '{print $3}'` && echo "versionCode=$versionCode" >> $GITHUB_OUTPUT
versionName=`echo "$zygiskDebugName" | awk -F '-' '{print $2}'` && echo "versionName=$versionName" >> $GITHUB_OUTPUT
unzip zygisk/release/Vector-v*-Release.zip -d Vector-Release
unzip zygisk/release/Vector-v*-Debug.zip -d Vector-Debug

- name: Upload zygisk release
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ${{ steps.prepareArtifact.outputs.zygiskReleaseName }}
path: "./Vector-Release/*"

- name: Upload zygisk debug
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ${{ steps.prepareArtifact.outputs.zygiskDebugName }}
path: "./Vector-Debug/*"

# One entry per module that minifies. `app` was the old manager and has not existed since
# #796, so the manager's mapping — the one an obfuscated stack trace from a user actually
# needs — has been missing from every run since.
- name: Upload mappings
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: mappings
path: |
zygisk/build/outputs/mapping
app/build/outputs/mapping
manager/build/outputs/mapping
daemon/build/outputs/mapping

# DEBUG_SYMBOLS_PATH is set from `layout.buildDirectory` inside the root `subprojects` block,
# so each module writes its own build/symbols and nothing ever lands in the root one.
- name: Upload symbols
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: symbols
path: build/symbols
path: |
zygisk/build/symbols
daemon/build/symbols
dex2oat/build/symbols

# --- stable release --------------------------------------------------------------------
#
# A pushed `v*` tag is a stable release, not a canary. It is published here from the same
# signed zips this build produced, so cutting a release is `git tag && git push` with nothing
# hand-uploaded afterwards — the hand-assembly is exactly how v2.0's assets came to disagree
# with zygisk/update.json (#811), and an automated attach cannot drift from what was built.
#
# Marked `--latest`, unlike the canaries below: this is what `releases/latest`, and therefore
# the manager's update check, resolves to. The body is the module's own zygisk/changelog.md
# under a GitHub H1 the release page shows. The changelog itself carries no title line — the
# manager renders it beneath its own heading, so a banner there would only read twice.
- name: Publish release
if: ${{ success() && github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }}
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
# `%s` arguments, never the format string, so a `%` anywhere in the changelog is literal.
notes=$(printf '# 🎉 Vector %s 🎉\n\n%s\n' "${TAG#v}" "$(cat zygisk/changelog.md)")

# Recreated rather than edited so a re-run replaces the assets instead of appending a
# second copy. No `--cleanup-tag`: the release is rebuilt, the pushed release tag is kept.
gh release delete "$TAG" --yes 2>/dev/null || true
gh release create "$TAG" \
--latest \
--title "Vector ${TAG}" \
--notes "$notes" \
zygisk/release/Vector-v*-Release.zip \
zygisk/release/Vector-v*-Debug.zip

# --- canary distribution ---------------------------------------------------------------
#
# The zips above are also attached to a prerelease, because an Actions artifact cannot be
# downloaded without a GitHub account — `GET /actions/artifacts/<id>/zip` answers 401 to an
# anonymous caller, while a release asset answers 206. Testing a canary is the lowest-friction
# way for an ordinary user to help, so it must not require handing an OAuth grant to anyone,
# and it must work for the many users who cannot reach GitHub's login page at all.
#
# Marked prerelease so `releases/latest` — which is what update checks read — keeps pointing
# at the last stable tag.
- name: Publish canary prerelease
if: >-
success() &&
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.ref == 'refs/heads/master'))
env:
GH_TOKEN: ${{ github.token }}
VERSION_CODE: ${{ steps.prepareArtifact.outputs.versionCode }}
VERSION_NAME: ${{ steps.prepareArtifact.outputs.versionName }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}
run: |
set -euo pipefail
tag="canary-${VERSION_CODE}"
subject=$(git log -1 --pretty=%s)
short=$(git rev-parse --short HEAD)

notes=$(cat <<EOF
**${subject}**

Built from [\`${short}\`](${COMMIT_URL}) by [run ${{ github.run_id }}](${RUN_URL}).

This is a canary: the current state of \`master\`, tested by CI and nothing else. It is
published here rather than left as a workflow artifact so that it can be downloaded
without a GitHub account.

| Build | Use it when |
| :-- | :-- |
| \`Vector-${VERSION_NAME}-${VERSION_CODE}-Debug.zip\` | You are chasing a bug. Far more logging, and the build maintainers ask for in reports. |
| \`Vector-${VERSION_NAME}-${VERSION_CODE}-Release.zip\` | You just want the newest code. |

Only the five most recent canaries are kept.
EOF
)

# Recreated rather than edited: re-running the workflow for the same commit should
# replace that build, not append a second copy of every asset to it.
gh release delete "$tag" --yes --cleanup-tag 2>/dev/null || true
gh release create "$tag" \
--prerelease \
--target "${{ github.sha }}" \
--title "Vector ${VERSION_NAME} canary ${VERSION_CODE}" \
--notes "$notes" \
zygisk/release/Vector-v*-Release.zip \
zygisk/release/Vector-v*-Debug.zip

# Five is what a tester needs: enough to bisect a regression across a few days, few enough
# that the releases page is still mostly releases. Sorted by version code, which is the commit
# count and therefore monotonic, rather than by date, which reruns and reverts can disorder.
- name: Keep only the five most recent canaries
if: >-
success() &&
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.ref == 'refs/heads/master'))
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
# `grep` exits 1 when nothing matches, which under `pipefail` would fail the step on a
# repository that has no canaries yet. Collected first, then acted on.
tags=$(gh release list --limit 100 --json tagName --jq '.[].tagName' | grep '^canary-' || true)
[ -n "$tags" ] || exit 0
echo "$tags" | sort -t- -k2 -n -r | tail -n +6 | while read -r old; do
echo "Removing $old"
gh release delete "$old" --yes --cleanup-tag
done
11 changes: 8 additions & 3 deletions .github/workflows/crowdin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@ on:
push:
branches: [ master ]
paths:
- app/src/main/res/values/strings.xml
# Named rather than globbed, to match crowdin.yml. The trigger's matching rules and the
# CLI's are defined independently — notably on whether `*` may match nothing — and a
# trigger that quietly never fires is the worse failure.
- manager/src/main/res/values/strings.xml
- manager/src/main/res/values/strings_logs.xml
- manager/src/main/res/values/strings_store.xml
- daemon/src/main/res/values/strings.xml

jobs:
synchronize-with-crowdin:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@main
uses: actions/checkout@v7

- name: crowdin action
uses: crowdin/github-action@master
uses: crowdin/github-action@v2
with:
upload_translations: true
download_translations: false
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
.cache
*.iml
.gradle
# Where the Kotlin plugin keeps its per-build session marker; a build leaves one behind.
.kotlin
/local.properties
/.idea
.DS_Store
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ This project is made possible by the following open-source contributions:
* [XposedBridge](https://github.com/rovo89/XposedBridge): The standard Xposed APIs.
* [Dobby](https://github.com/JingMatrix/Dobby): Inline hooking implementation.
* [LSPosed](https://github.com/LSPosed/LSPosed): Upstream source.
* [EdXposed](https://github.com/ElderDrivers/EdXposed): Upstream source, before LSPosed.
* [xz-embedded](https://github.com/tukaani-project/xz-embedded): Library decompression utilities.

<details>
Expand Down
1 change: 0 additions & 1 deletion app/.gitignore

This file was deleted.

Loading