Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
402 changes: 201 additions & 201 deletions LICENSE

Large diffs are not rendered by default.

154 changes: 77 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,77 @@
# NativeModMenu

### High-Performance In-Memory Dex Loader for Android

`NativeModMenu` is a sophisticated reimplementation of the LGL Android Mod Menu. It leverages `InMemoryDexClassLoader` to load the Java UI layer directly from system memory, eliminating the need for physical `.dex` files on the device storage.

---

## Technical Overview

This implementation focuses on stealth and efficiency by embedding the `FloatingModMenu.dex` as a HEX-encoded string within the native binary. At runtime, the JNI layer performs a dynamic bootstrap to initialize the menu without leaving any traces in the application's assets or data folders.

### Key Architectural Differences

* **Zero Disk Footprint:** No external dex file is shipped; the menu exists only in memory.
* **Volatile Loading:** Utilizes `dalvik.system.InMemoryDexClassLoader` for runtime execution.
* **Native-Led Initialization:** The entire menu lifecycle is managed via JNI native bindings.
* **Reduced Attack Surface:** Eliminates file-based detection vectors by avoiding asset extraction.
* **Native Compatibility:** Fully compatible with existing LGL Java menu logic.

---

## Core Logic Flow

1. **Data Embedding:** The `FloatingModMenu.dex` is converted to a HEX string and compiled into the C++ source.
2. **Buffer Reconstruction:** At runtime, the HEX string is decoded into a `ByteBuffer`.
3. **Class Loading:** The `InMemoryDexClassLoader` interprets the buffer and loads the `uk.lgl.modmenu.FloatingModMenu` class.
4. **JNI Registration:** Native functions are dynamically bound to the Java class methods.
5. **Execution:** The `binJava()` entry point triggers `FloatingModMenu.antik(Context)` to render the UI.

---

## JNI Native Interface

The following methods are registered dynamically to bridge the Native and Java layers:

| Method | Functionality |
| --- | --- |
| `Icon()` | Returns the primary menu icon |
| `IconWebViewData()` | Manages WebView icon resources |
| `getFeatureList()` | Retrieves the feature configuration array |
| `settingsList()` | Retrieves the menu settings configuration |
| `Changes(...)` | Main event handler for feature toggles |
| `setTitleText(...)` | Customizes the UI title appearance |
| `setHeadingText(...)` | Customizes the UI heading appearance |

---

## Implementation Details

### Requirements

* **Android Version:** 8.0 (API 26) or higher
* **Architecture:** ARMv7, ARM64-v8a
* **Environment:** Native JNI-based injection

### Entry Point

```cpp
// Executed post JNI_OnLoad
void binJava();

```

The `binJava` function is responsible for retrieving the `Application` context via `ActivityThread` and initiating the memory-loading sequence.

---

## Legal Notice

This project is developed strictly for **educational and research purposes**. Modifying the runtime behavior of third-party applications may violate their Terms of Service. The developers assume no liability for misuse.

## Acknowledgments

* **Original Architecture:** LGL Android Mod Menu
* **Lead Developer:** AntikMods
* **Memory Loading Logic:** NepMods
# NativeModMenu
### High-Performance In-Memory Dex Loader for Android
`NativeModMenu` is a sophisticated reimplementation of the LGL Android Mod Menu. It leverages `InMemoryDexClassLoader` to load the Java UI layer directly from system memory, eliminating the need for physical `.dex` files on the device storage.
---
## Technical Overview
This implementation focuses on stealth and efficiency by embedding the `FloatingModMenu.dex` as a HEX-encoded string within the native binary. At runtime, the JNI layer performs a dynamic bootstrap to initialize the menu without leaving any traces in the application's assets or data folders.
### Key Architectural Differences
* **Zero Disk Footprint:** No external dex file is shipped; the menu exists only in memory.
* **Volatile Loading:** Utilizes `dalvik.system.InMemoryDexClassLoader` for runtime execution.
* **Native-Led Initialization:** The entire menu lifecycle is managed via JNI native bindings.
* **Reduced Attack Surface:** Eliminates file-based detection vectors by avoiding asset extraction.
* **Native Compatibility:** Fully compatible with existing LGL Java menu logic.
---
## Core Logic Flow
1. **Data Embedding:** The `FloatingModMenu.dex` is converted to a HEX string and compiled into the C++ source.
2. **Buffer Reconstruction:** At runtime, the HEX string is decoded into a `ByteBuffer`.
3. **Class Loading:** The `InMemoryDexClassLoader` interprets the buffer and loads the `uk.lgl.modmenu.FloatingModMenu` class.
4. **JNI Registration:** Native functions are dynamically bound to the Java class methods.
5. **Execution:** The `binJava()` entry point triggers `FloatingModMenu.antik(Context)` to render the UI.
---
## JNI Native Interface
The following methods are registered dynamically to bridge the Native and Java layers:
| Method | Functionality |
| --- | --- |
| `Icon()` | Returns the primary menu icon |
| `IconWebViewData()` | Manages WebView icon resources |
| `getFeatureList()` | Retrieves the feature configuration array |
| `settingsList()` | Retrieves the menu settings configuration |
| `Changes(...)` | Main event handler for feature toggles |
| `setTitleText(...)` | Customizes the UI title appearance |
| `setHeadingText(...)` | Customizes the UI heading appearance |
---
## Implementation Details
### Requirements
* **Android Version:** 8.0 (API 26) or higher
* **Architecture:** ARMv7, ARM64-v8a
* **Environment:** Native JNI-based injection
### Entry Point
```cpp
// Executed post JNI_OnLoad
void binJava();
```
The `binJava` function is responsible for retrieving the `Application` context via `ActivityThread` and initiating the memory-loading sequence.
---
## Legal Notice
This project is developed strictly for **educational and research purposes**. Modifying the runtime behavior of third-party applications may violate their Terms of Service. The developers assume no liability for misuse.
## Acknowledgments
* **Original Architecture:** LGL Android Mod Menu
* **Lead Developer:** AntikMods
* **Memory Loading Logic:** NepMods
179 changes: 90 additions & 89 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,90 +1,91 @@
import com.android.build.gradle.tasks.ExternalNativeBuildTask
import java.io.FileInputStream

plugins {
alias(libs.plugins.android.application)
}

class JavaDex {
public static String xxd_p(String filePath) {
StringBuilder hexString = new StringBuilder()
File file = new File(filePath)
try (FileInputStream fis = new FileInputStream(file)) {
byte[] data = fis.readAllBytes()
for (byte b : data) {
hexString.append(String.format("%02X", b))
}
}
return hexString.toString()
}
}

android {
namespace 'uk.lgl'
compileSdk 36

defaultConfig {
applicationId "uk.lgl"
minSdk 28
targetSdk 36
versionCode 1
versionName "1.0"

ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
}

multiDexEnabled false
}

buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

externalNativeBuild {
ndkBuild {
path file('src/main/jni/Android.mk')
}
}

android.applicationVariants.all { variant ->
def variantName = variant.name.capitalize()
def taskName = "Dex2CppAnd8Up${variantName}"

def customTask = tasks.register(taskName) {
outputs.upToDateWhen { false }

def mergeDexTask = tasks.findByName("mergeDex${variantName}")
if (mergeDexTask) {
dependsOn mergeDexTask
}

doLast {
def dexDir = file("${project.buildDir}/intermediates/dex/${variant.name}/mergeDex${variantName}")
def dexFiles = fileTree(dir: dexDir, include: "**/classes.dex")

if (dexFiles.isEmpty()) {
throw new GradleException("classes.dex not found for ${variant.name} in ${dexDir}")
}

def dexFile = dexFiles.first()
String hexString = JavaDex.xxd_p(dexFile.absolutePath)
def headerFile = file("${project.projectDir}/src/main/jni/JavaGPP/Interface/OreoOrMore.h")

headerFile.parentFile.mkdirs()
headerFile.write("#define OreoOrMore \"${hexString}\"\n")
println ">>> Success: Generated header for ${variantName}"
}
}

tasks.configureEach { task ->
if (task.name.startsWith("buildNdkBuild${variantName}") ||
task.name.startsWith("externalNativeBuild${variantName}")) {
task.dependsOn customTask
}
}
}
import com.android.build.gradle.tasks.ExternalNativeBuildTask
import java.io.FileInputStream

plugins {
alias(libs.plugins.android.application)
}

class JavaDex {
public static String xxd_p(String filePath) {
StringBuilder hexString = new StringBuilder()
File file = new File(filePath)
try (FileInputStream fis = new FileInputStream(file)) {
byte[] data = fis.readAllBytes()
for (byte b : data) {
hexString.append(String.format("%02X", b))
}
}
return hexString.toString()
}
}

android {
namespace 'com.android.support'
compileSdk 36

defaultConfig {
applicationId "com.android.support"
minSdk 28
targetSdk 36
versionCode 1
versionName "1.0"

ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
}

multiDexEnabled false
}

buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

externalNativeBuild {
ndkBuild {
path file('src/main/jni/Android.mk')
}
ndkVersion = '28.2.13676358'
}

android.applicationVariants.all { variant ->
def variantName = variant.name.capitalize()
def taskName = "Dex2CppAnd8Up${variantName}"

def customTask = tasks.register(taskName) {
outputs.upToDateWhen { false }

def mergeDexTask = tasks.findByName("mergeDex${variantName}")
if (mergeDexTask) {
dependsOn mergeDexTask
}

doLast {
def dexDir = file("${project.buildDir}/intermediates/dex/${variant.name}/mergeDex${variantName}")
def dexFiles = fileTree(dir: dexDir, include: "**/classes.dex")

if (dexFiles.isEmpty()) {
throw new GradleException("classes.dex not found for ${variant.name} in ${dexDir}")
}

def dexFile = dexFiles.first()
String hexString = JavaDex.xxd_p(dexFile.absolutePath)
def headerFile = file("${project.projectDir}/src/main/jni/Includes/Dex.h")

headerFile.parentFile.mkdirs()
headerFile.write("#define hexdex \"${hexString}\"\n")
println ">>> Success: Generated header for ${variantName}"
}
}

tasks.configureEach { task ->
if (task.name.startsWith("buildNdkBuild${variantName}") ||
task.name.startsWith("externalNativeBuild${variantName}")) {
task.dependsOn customTask
}
}
}
}
40 changes: 20 additions & 20 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Loading
Loading