Skip to content

Refactor native modules and migrate to new Zygisk architecture - #550

Merged
JingMatrix merged 24 commits into
masterfrom
refactor
Mar 6, 2026
Merged

Refactor native modules and migrate to new Zygisk architecture#550
JingMatrix merged 24 commits into
masterfrom
refactor

Conversation

@JingMatrix

Copy link
Copy Markdown
Owner

We replace the legacy magisk-loader and core JNI with a rewritten Zygisk module and a centralized native library.

Major changes:

  • Zygisk Module: Replaced magisk-loader with a rewritten Zygisk implementation in Kotlin and C++.
  • Native Migration: Moved ELF parsing, symbol caching, and hooking logic from core/jni to a standalone native module.
  • Anti-detection: Removed enforced parcel descriptors to evade heap-based string detection.
  • Scripting: Simplified Magisk module shell scripts (customize.sh, service.sh) and removed redundant utility functions.
  • Compatibility: Maintained zygisk.json and the lspd directory to ensure a smooth update path and database continuity.

@JingMatrix

Copy link
Copy Markdown
Owner Author

@Dev4Mod, @Stillhard, @ale5000-git, please test this refactor build and have a look at the code documentation I added.
You can preview the docs in the refactor branch, e.g., https://github.com/JingMatrix/LSPosed/blob/refactor/native/README.md, https://github.com/JingMatrix/LSPosed/blob/refactor/zygisk/README.md.

Leave your comments if you spot errors, or current docs / codes are still unclear for you.

@Dev4Mod

Dev4Mod commented Mar 5, 2026

Copy link
Copy Markdown

It failed on my device, and even crashed Zygisk
lspd-logs.zip

@JingMatrix

Copy link
Copy Markdown
Owner Author

It seems to be a symbol resolution problem. Please test the latest CI again and upload logs.
If the problem is not solved, please also upload logs of the debug version of v1.11.0 in GitHub Release for me to compare and locate the issue.

@huynhbaman

This comment was marked as outdated.

@huynhbaman

Copy link
Copy Markdown

Crash zygisk bro

@JingMatrix

Copy link
Copy Markdown
Owner Author

@huynhbaman If you can, please provide logs as described in #123.

@huynhbaman

huynhbaman commented Mar 5, 2026

Copy link
Copy Markdown

@huynhbaman If you can, please provide logs as described in #123.

Screenshot_20260305_220147_KernelSU Next.jpg

@JingMatrix

Copy link
Copy Markdown
Owner Author

@Dev4Mod

Dev4Mod commented Mar 5, 2026

Copy link
Copy Markdown

Vector has now started normally, however there is a problem with my WAEnhancer module which uses XC_InitPackageResources to obtain resources.
See the logs, I also see other errors occurring silently in the parasite:
vector_logs.zip

@huynhbaman

Copy link
Copy Markdown

@Dev4Mod @huynhbaman Please test again the latest CI buid: https://github.com/JingMatrix/LSPosed/actions/runs/22737067570

It already fixed
Screenshot_20260306_051214_KernelSU Next.jpg

@JingMatrix

Copy link
Copy Markdown
Owner Author

@Dev4Mod Please test https://github.com/JingMatrix/LSPosed/actions/runs/22758702203 and upload logs if anything abnormal is observed.

@Dev4Mod

Dev4Mod commented Mar 6, 2026

Copy link
Copy Markdown

It's working fine now, with no error logs in Vector.

Note that the signature of rewriteXmlReferencesNative is changed to
separate sub-projects xposed and core.
Better to change it after database is redesigned
Java string created via NewStringUTF is detectable by searching the JVM heap.

Renaming `LSPosed` to another string, is stupid but can defeat detectors such as
https://github.com/RiRiRawrZ615/Known-Root-Detectors/blob/main/Risk%20Detector_1.6.apk

Of course, there are plenty of robust solutions to overcome this detection point.
This commit is just for fun, in the process of refactoring LSPosed into Vector.
Add more comments and simplify logic
1. Avoid hard-coding transaction codes for maintance convience.
2. No need to enforce descriptor, which only introduce detection point.
However, we should keep the file `zygisk.json` for smooth updating experience.
This part is replaced by the native module
Using bias_ comparison could be buggy
The Android dynamic linker (Bionic) restricts loading libraries from unauthorized paths via LD_PRELOAD. This causes the linker to reject the hook library with a "not accessible for the namespace" fatal error.

To bypass this restriction, we exploit a fallback in Bionic that explicitly skips the namespace accessibility check for files located on tmpfs. We use `memfd_create` to create an anonymous tmpfs-backed file descriptor, copy the library into it using `sendfile`, and pass the new memfd to LD_PRELOAD instead.

Reference: function `load_library` in https://cs.android.com/android/platform/superproject/main/+/main:bionic/linker/linker.cpp
Correct bugs in dynamical construction of XResources methods.
@JingMatrix
JingMatrix merged commit bee5f2e into master Mar 6, 2026
1 check passed
Enovale pushed a commit to Enovale/Vector that referenced this pull request Jul 13, 2026
…atrix#550)

We replace the legacy magisk-loader and core JNI with a rewritten Zygisk module and a centralized native library.

Major changes:

- Zygisk Module: Replaced magisk-loader with a rewritten Zygisk implementation in Kotlin and C++.
- Native Migration: Moved ELF parsing, symbol caching, and hooking logic from core/jni to a standalone native module.
- Anti-detection: Removed enforced parcel descriptors to evade heap-based string detection.
- Scripting: Simplified Magisk module shell scripts (customize.sh, service.sh) and removed redundant utility functions.
- Dex-obfuscation: Refactored obfuscation management to improve code clarity.

Note that `zygisk.json` and the `lspd` directory are kept to ensure a smooth update path and database continuity.
JingMatrix added a commit that referenced this pull request Aug 1, 2026
buildSrc holds two Gradle task classes no build script registers, and neither
output survives: there is no LangList.java and no material_theme_builder.xml, the
locale list comes from BuildConfig.TRANSLATIONS computed in
manager/build.gradle.kts, and the only XML theme left wraps
Theme.DeviceDefault.NoActionBar. Its README documents registering both tasks
against :app, gone since #796. The directory goes, with the format task's include
for its Kotlin.

The version catalog still pins the whole View stack -- Glide, Rikka's rikkax-*
widgets, Fragment, Navigation 2 with safeargs, preference, RecyclerView,
SwipeRefreshLayout, ConstraintLayout, Material Components for Views. No build
script references any of those 26 aliases, the safeargs plugin, or the nav, glide
and appcenter versions. rikkax-parcelablelist stays; :services:manager-service
exposes it for the AIDL's ParcelableListSlice.

hiddenapi/stubs carries 20 files nothing compiles against, mostly LSPosed-era
rather than rewrite fallout. Where the platform declares the same class the stub
is already inert: :daemon compiles PackageManager.VERSION_CODE_HIGHEST and
XSharedPreferences compiles Environment.getDataDirectory(), neither of which the
stub declares, so android.jar wins today. They are compileOnly and never reach a
dex, so lsplant's FindClass on dalvik/system/VMRuntime keeps resolving the
framework class. Four HiddenApiBridge wrappers and the stub members that existed
only to type them go with them.

In :manager the four @serializable models for the Actions runs and artifacts API
became unreachable when eb07955 moved canary sourcing to release prereleases,
and relativeTime, levelLabel, sectionHeaderStyle, SeedScheme.parseHex,
VectorLightColors, VectorDarkColors, ModulesViewModel.selectAll,
LogPaneState.atOldest and GOOD_FIRST_ISSUE_URL have no call site at all.
RelativeTime.kt becomes TimeText.kt, since relative time is what left it.
Seventeen strings go too, translations deleted alongside their source so the next
Crowdin pull does not add them back. Three of them -- store_offline,
settings_window, module_toggle_failed -- read more like missing features than dead
weight, and their translated copy in eighteen languages goes with them.

Daemon-side, CliHandler.isPackageInstalled, Utils.getZoneId, Utils.isLENOVO and
:xposed's trackedApks have no caller; the live tracker is
LoadedApkTracker.activeApks.

magisk-loader goes as well, and that is the one deletion here that ships
behaviour. Before #550 the loader baked the raw githubusercontent URL of
magisk-loader/update/zygisk.json into every module.prop, so Magisk on a pre-v2.0
install polls it and will now get a 404. Effectively everyone is on v2.0 by now,
and zygisk/update.json -- what module.prop has pointed at since -- is untouched.

Nothing else changes behaviour. Release builds already shrink unused resources,
and no deletion touches a de.robv or io.github.libxposed surface, an AIDL
transaction id, a JNI name, a manifest entry or a keep rule.
JingMatrix added a commit that referenced this pull request Aug 1, 2026
The GitHub sign-in came from a `githubClientId` Gradle property set nowhere
-- not in gradle.properties, not in CI -- so every build compiled
GITHUB_CLIENT_ID as "". isConfigured was therefore always false: the card
returned before drawing anything, signIn() short-circuited to Unavailable,
and tokenProvider() always returned null, so no request ever carried an
Authorization header. It bought only the rate limit, 60 requests an hour
anonymous against 5000 signed in, and nothing depends on having it: the
canary screen sources its zips from release assets precisely so that testing
a build needs no account.

Deleted rather than given a client id, because the flow also has nowhere
good to keep a token. Parasitically the manager's SharedPreferences land in
com.android.shell's data directory, whose shared_prefs is world-executable,
so a token would sit outside our own sandbox. That is worth solving before
offering sign-in again, not alongside it.

The rest is what #796 left unreferenced. buildSrc held two Gradle task
classes no build script registers and whose outputs no longer exist -- the
locale list comes from BuildConfig.TRANSLATIONS now. The version catalog
still pinned the whole View stack: 26 aliases, the safeargs plugin, and the
nav, glide and appcenter versions. hiddenapi/stubs carried 20 files nothing
compiles against, with four HiddenApiBridge wrappers and the stub members
that existed only to type them. In :manager the four @serializable models
for the Actions runs and artifacts API became unreachable when eb07955
moved canary sourcing to release prereleases, and nine symbols --
relativeTime, levelLabel, sectionHeaderStyle, SeedScheme.parseHex,
VectorLightColors, VectorDarkColors, ModulesViewModel.selectAll,
LogPaneState.atOldest and GOOD_FIRST_ISSUE_URL -- have no call site at all.
Seventeen strings go with them, translations deleted alongside their source
so the next Crowdin pull does not add them back. Daemon-side,
CliHandler.isPackageInstalled, Utils.getZoneId, Utils.isLENOVO and :xposed's
trackedApks have no caller; the live tracker is LoadedApkTracker.activeApks.

One deletion ships behaviour. Before #550 the loader baked the raw
githubusercontent URL of magisk-loader/update/zygisk.json into every
module.prop, so Magisk on a pre-v2.0 install polls it and will now get a
404. Effectively everyone is on v2.0 by now, and zygisk/update.json -- what
module.prop has pointed at since -- is untouched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants