From 85a1553679397554d34622cc35d1215489f35d5b Mon Sep 17 00:00:00 2001 From: AXiX-official <2879710747@qq.com> Date: Mon, 17 Jun 2024 11:50:42 +0800 Subject: [PATCH] 1.1.1 --- LICENSE | 21 +++ README.md | 36 ++++++ README_EN.md | 33 +++++ app/build.gradle.kts | 26 +++- app/src/main/AndroidManifest.xml | 28 ++-- app/src/main/assets/xposed_init | 2 +- app/src/main/cpp/main.cpp | 71 +++++----- .../axix/assetsideloader/AssetSideLoader.kt | 11 -- .../com/axix/assetsideloader/MainActivity.kt | 12 -- .../java/com/axix/assetsideloader/MainHook.kt | 14 -- .../java/top/axix/assetsideloader/AppInfo.kt | 44 +++++++ .../axix/assetsideloader/AppInfoActivity.kt | 67 ++++++++++ .../top/axix/assetsideloader/AppsAdapter.kt | 39 ++++++ .../axix/assetsideloader/AppsListActivity.kt | 30 +++++ .../axix/assetsideloader/AssetSideLoader.kt | 11 ++ .../top/axix/assetsideloader/MainActivity.kt | 122 ++++++++++++++++++ .../java/top/axix/assetsideloader/MainHook.kt | 81 ++++++++++++ .../assetsideloader/SelectedAppsAdapter.kt | 64 +++++++++ app/src/main/res/layout/activity_app_info.xml | 92 +++++++++++++ .../main/res/layout/activity_apps_list.xml | 19 +++ app/src/main/res/layout/activity_main.xml | 18 ++- app/src/main/res/layout/item_app.xml | 41 ++++++ app/src/main/res/layout/work_app.xml | 42 ++++++ .../ic_launcher.xml | 0 .../ic_launcher_round.xml | 0 build.gradle.kts | 2 +- doc/example.jpg | Bin 0 -> 73838 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 28 files changed, 842 insertions(+), 86 deletions(-) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README_EN.md delete mode 100644 app/src/main/java/com/axix/assetsideloader/AssetSideLoader.kt delete mode 100644 app/src/main/java/com/axix/assetsideloader/MainActivity.kt delete mode 100644 app/src/main/java/com/axix/assetsideloader/MainHook.kt create mode 100644 app/src/main/java/top/axix/assetsideloader/AppInfo.kt create mode 100644 app/src/main/java/top/axix/assetsideloader/AppInfoActivity.kt create mode 100644 app/src/main/java/top/axix/assetsideloader/AppsAdapter.kt create mode 100644 app/src/main/java/top/axix/assetsideloader/AppsListActivity.kt create mode 100644 app/src/main/java/top/axix/assetsideloader/AssetSideLoader.kt create mode 100644 app/src/main/java/top/axix/assetsideloader/MainActivity.kt create mode 100644 app/src/main/java/top/axix/assetsideloader/MainHook.kt create mode 100644 app/src/main/java/top/axix/assetsideloader/SelectedAppsAdapter.kt create mode 100644 app/src/main/res/layout/activity_app_info.xml create mode 100644 app/src/main/res/layout/activity_apps_list.xml create mode 100644 app/src/main/res/layout/item_app.xml create mode 100644 app/src/main/res/layout/work_app.xml rename app/src/main/res/{mipmap-anydpi => mipmap-anydpi-v26}/ic_launcher.xml (100%) rename app/src/main/res/{mipmap-anydpi => mipmap-anydpi-v26}/ic_launcher_round.xml (100%) create mode 100644 doc/example.jpg diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2e76f38 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 CrossCoreNightly + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..545625a --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# AssetSideLoader + +[README_EN.md](README_EN.md) + +## 介绍 + +一个LSPosed模块,通过hook UnityEngine的内部方法实现从自定义路径加载文件,来替换External Storage或者APK中的资源。 + +--- + +## 说明 + +- 需要先手动授予“获取应用列表”权限 +- 请先在LSPosed中激活模块,并且至少选择一个目标应用,之后启动APP +- 启动APP后可以在`SELECT APPS`中选择目标应用,然后点击`DONE`返回 + - 注意,即使在模块中选择了目标应用,也需要在LSPosed中为模块选择相应的目标应用 + - `SELECT APPS`中只会显示`lib`目录下存在`libil2cpp.so`的应用 +- 选择目标应用后,可以在主界面点击对应的应用进入具体设置 + - 如果回到主界面后没有显示对应的应用,请重启APP +- 在具体设置中有三个路径需要手动填写 + - `APK Patch`:APK文件中的资源相对于`/data/app/*/*/base.apk!/`的路径 + - `Data Patch`:外部存储中的资源相对于`/storage/emulated/0/Android/com.example.www/files`的路径 + - `Mod Patch`:自定义的mod文件夹路径,与Data Patch一样是外部存储中的相对路径 + - APP期望在`Mod Patch`下有和`APK Patch`和`Data Patch`中要替换的文件相同的目录结构和文件名 +- 选择好路径后,点击`SAVE`保存设置,点击`DELETE`删除设置,点击SWITCH切换是否启用 +- 确定设定无误后,启动目标应用即可 + +## 示例 + +![example](doc/example.jpg) + +## 致谢 + +- Perfare: [Zygisk-Il2CppDumper](https://github.com/Perfare/Zygisk-Il2CppDumper) +- jmpewsL [Dobby](https://github.com/jmpews/Dobby) +- LSPosed: [LSPosed](https://github.com/LSPosed/LSPosed) \ No newline at end of file diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..3e61e52 --- /dev/null +++ b/README_EN.md @@ -0,0 +1,33 @@ +# AssetSideLoader + +## Introduction + +A LSPosed module that replaces resources in External Storage or APK by hooking UnityEngine's internal methods to load files from a custom path. + +--- +## Description + +- You need to grant "Get App List" permission manually. +- Please activate the module in LSPosed and select at least one target app, then launch the app. +- After launching the app, you can select the target app in `SELECT APPS`, then click `DONE` to return. + - Note that even if you have selected a target app in the module, you still need to select the corresponding target app for the module in LSPosed. + - The `SELECT APPS` will only show applications that have `libil2cpp.so` in the `lib` directory. +- After selecting the target application, you can click the corresponding application in the main interface to enter the specific settings. + - If the corresponding app is not displayed after returning to the main interface, please restart the app. +- There are three paths you need to fill in manually + - `APK Patch`: the path of resources in APK file relative to `/data/app/*/*/base.apk!/`. + - `Data Patch`: path to resources in external storage relative to `/storage/emulated/0/Android/com.example.www/files`. + - `Mod Patch`: the path to the customized mod folder, which is a relative path in the external storage like the Data Patch. + - APP expects the same directory structure and file names under `Mod Patch` as the files to be replaced in `APK Patch` and `Data Patch`. +- After selecting the path, click `SAVE` to save the settings, click `DELETE` to delete the settings, and click SWITCH to enable or disable the settings. +- After making sure the settings are correct, start the target application. + +## Example + +![example](doc/example.jpg) + +## Credits + +- Perfare: [Zygisk-Il2CppDumper](https://github.com/Perfare/Zygisk-Il2CppDumper) +- jmpewsL [Dobby](https://github.com/jmpews/Dobby) +- LSPosed: [LSPosed](https://github.com/LSPosed/LSPosed) \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f7b1dac..9c1801b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,18 +1,19 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") + id("kotlin-parcelize") } android { - namespace = "com.axix.assetsideloader" + namespace = "top.axix.assetsideloader" compileSdk = 34 defaultConfig { - applicationId = "com.axix.assetsideloader" - minSdk = 26 + applicationId = "top.axix.assetsideloader" + minSdk = 24 targetSdk = 34 - versionCode = 106 - versionName = "1.0.6" + versionCode = 111 + versionName = "1.1.1" ndk { } @@ -34,10 +35,23 @@ android { buildFeatures{ prefab = true } + + buildTypes { + debug { + isMinifyEnabled = false + } + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } + + lint { + abortOnError = true + checkReleaseBuilds = false + } + kotlinOptions { jvmTarget = "1.8" } @@ -52,4 +66,6 @@ dependencies { implementation("com.google.android.material:material:1.9.0") testImplementation("junit:junit:4.13.2") implementation("io.github.hexhacking:xdl:2.1.1") + implementation("androidx.recyclerview:recyclerview:1.2.1") + implementation("com.google.code.gson:gson:2.8.9") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d3d2835..8554786 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,9 @@ + xmlns:tools="http://schemas.android.com/tools" + package="top.axix.assetsideloader"> + + + android:theme="@style/Theme.AppCompat.DayNight"> + + android:value="SideLoad Unity Files" /> + android:name="xposedsharedprefs" + android:value="true" /> + + - + + - \ No newline at end of file diff --git a/app/src/main/assets/xposed_init b/app/src/main/assets/xposed_init index 75976d7..17b53f3 100644 --- a/app/src/main/assets/xposed_init +++ b/app/src/main/assets/xposed_init @@ -1 +1 @@ -com.axix.assetsideloader.MainHook \ No newline at end of file +top.axix.assetsideloader.MainHook \ No newline at end of file diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp index 628b225..61fa0d6 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -4,10 +4,12 @@ #include #include #include +#include +#include #include -#include "xdl.h" #include "log.h" #include "utf8.h" +#include "xdl.h" #include "il2cpp-class.h" //------------------------------------------------------------------------------------------------// @@ -33,28 +35,30 @@ void init_il2cpp_api(void *handle) { //------------------------------------------------------------------------------------------------// -void* Handle = nullptr; - -std::string pakageName = ""; +typedef struct { + void* handle; + const char* pakageName; + const char* dataDir; + const char* apkDir; + const char* modDir; +} AppInfo; -std::string modDir = "/storage/emulated/0/Android/data/"; +AppInfo appInfo; //------------------------------------------------------------------------------------------------// void* ReplacePath(std::string path) { - if (path.find("AssetBundles") != std::string::npos) { - std::string modPath = modDir + path.substr(path.find("AssetBundles") + 12); - if (modPath.find(".ys") != std::string::npos) { - //去除.ys后缀 - modPath = modPath.substr(0, modPath.find(".ys")); - } - // 判断文件是否存在 - std::ifstream file(modPath); - if (file) { - LOG_I("Redirect from: %s", path.c_str()); - LOG_I("To: %s", modPath.c_str()); - return il2cpp_string_new(modPath.c_str()); - } + std::string new_path; + if (path.find(appInfo.apkDir) == 0) { + new_path = appInfo.modDir + path.substr(strlen(appInfo.apkDir)); + } else if (path.find(appInfo.dataDir) == 0) { + new_path = appInfo.modDir + path.substr(strlen(appInfo.dataDir)); + } else { + new_path = path; + } + if (std::__fs::filesystem::exists(new_path)) { + LOG_D("Replace path: %s -> %s", path.c_str(), new_path.c_str()); + return il2cpp_string_new(new_path.c_str()); } return il2cpp_string_new(path.c_str()); } @@ -98,16 +102,12 @@ void* Hook_LoadFromFileAsync_Internal(void* path, uint32_t crc, uint64_t offset) void hook_funcs(){ LOG_I("Init Il2cpp api..."); - init_il2cpp_api(Handle); + init_il2cpp_api(appInfo.handle); LOG_I("hooking..."); - modDir += pakageName; - modDir += "/files/mods"; - LOG_I("modDir: %s", modDir.c_str()); - uint64_t LoadFromFile_Internal_addr = (uint64_t)il2cpp_resolve_icall("UnityEngine.AssetBundle::LoadFromFile_Internal(System.String,System.UInt32,System.UInt64)"); if (LoadFromFile_Internal_addr) { - LOG_I("LoadFromFile_Internal_addr: %" PRIx64"", LoadFromFile_Internal_addr); + LOG_D("LoadFromFile_Internal_addr: %" PRIx64"", LoadFromFile_Internal_addr); DobbyHook( (void *)LoadFromFile_Internal_addr, (void *)Hook_LoadFromFile_Internal, @@ -117,7 +117,7 @@ void hook_funcs(){ uint64_t LoadFromFileAsync_Internal_addr = (uint64_t)il2cpp_resolve_icall("UnityEngine.AssetBundle::LoadFromFileAsync_Internal(System.String,System.UInt32,System.UInt64)"); if (LoadFromFileAsync_Internal_addr) { - LOG_I("LoadFromFileAsync_Internal_addr: %" PRIx64"", LoadFromFileAsync_Internal_addr); + LOG_D("LoadFromFileAsync_Internal_addr: %" PRIx64"", LoadFromFileAsync_Internal_addr); DobbyHook( (void *)LoadFromFileAsync_Internal_addr, (void *)Hook_LoadFromFileAsync_Internal, @@ -135,11 +135,11 @@ il2cpp_init_func il2cpp_init_origin = nullptr; int hook_il2cpp_init(const char *domain_name) { int result = il2cpp_init_origin(domain_name); DobbyDestroy((void*)hook_il2cpp_init); - LOG_I("il2cpp_init finished with result: %d", result); + LOG_D("il2cpp_init finished with result: %d", result); if (result == 1){ DobbyDestroy((void*)hook_il2cpp_init); - Handle = xdl_open("libil2cpp.so", 0); - LOG_I("libil2cpp.so handle: %p", Handle); + appInfo.handle = xdl_open("libil2cpp.so", 0); + LOG_D("libil2cpp.so handle: %p", appInfo.handle); hook_funcs(); } return result; @@ -154,7 +154,7 @@ static dlsym_t orig_dlsym = nullptr; void* my_dlsym(void* handle, const char* symbol){ void* addr = orig_dlsym(handle, symbol); if (strcmp(symbol, "il2cpp_init") == 0) { - LOG_I("symbol il2cpp_init found at: %p", addr); + LOG_D("symbol il2cpp_init found at: %p", addr); DobbyDestroy((void*)my_dlsym); DobbyHook( (void*)addr, @@ -169,8 +169,17 @@ void* my_dlsym(void* handle, const char* symbol){ extern "C" JNIEXPORT void JNICALL -Java_com_axix_assetsideloader_AssetSideLoader_InitHook(JNIEnv *env, jobject thiz, jstring str) { - pakageName = env->GetStringUTFChars(str, NULL); +Java_top_axix_assetsideloader_AssetSideLoader_InitHook(JNIEnv *env, jobject thiz, jstring pgn, jstring dataPath, jstring apkPath, jstring modPath) { + LOG_D("Native hook init..."); + appInfo.pakageName = env->GetStringUTFChars(pgn, NULL); + LOG_D("Package name: %s", appInfo.pakageName); + appInfo.dataDir = env->GetStringUTFChars(dataPath, NULL); + LOG_D("Data dir: %s", appInfo.dataDir); + appInfo.apkDir = env->GetStringUTFChars(apkPath, NULL); + LOG_D("Apk dir: %s", appInfo.apkDir); + appInfo.modDir = env->GetStringUTFChars(modPath, NULL); + LOG_D("Mod dir: %s", appInfo.modDir); LOG_D("Hooking dlsym..."); + DobbyHook((void*) dlsym, (void*)my_dlsym, (void**)&orig_dlsym); } \ No newline at end of file diff --git a/app/src/main/java/com/axix/assetsideloader/AssetSideLoader.kt b/app/src/main/java/com/axix/assetsideloader/AssetSideLoader.kt deleted file mode 100644 index 8f28cbc..0000000 --- a/app/src/main/java/com/axix/assetsideloader/AssetSideLoader.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.axix.assetsideloader - -class AssetSideLoader { - external fun InitHook(pakageName: String): Void - - companion object { - init { - System.loadLibrary("AssetSideLoader") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/axix/assetsideloader/MainActivity.kt b/app/src/main/java/com/axix/assetsideloader/MainActivity.kt deleted file mode 100644 index b142f1a..0000000 --- a/app/src/main/java/com/axix/assetsideloader/MainActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.axix.assetsideloader - -import androidx.appcompat.app.AppCompatActivity -import android.os.Bundle -import com.axix.assetsideloader.R - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/axix/assetsideloader/MainHook.kt b/app/src/main/java/com/axix/assetsideloader/MainHook.kt deleted file mode 100644 index 8e5b3fd..0000000 --- a/app/src/main/java/com/axix/assetsideloader/MainHook.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.axix.assetsideloader - -import android.util.Log -import de.robv.android.xposed.IXposedHookLoadPackage -import de.robv.android.xposed.callbacks.XC_LoadPackage - -class MainHook : IXposedHookLoadPackage { - override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { - if (lpparam.packageName == "com.bilibili.azurlane") { - Log.i("AssetSideLoader", "Target application: ${lpparam.packageName} launched") - AssetSideLoader().InitHook("com.bilibili.azurlane") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/top/axix/assetsideloader/AppInfo.kt b/app/src/main/java/top/axix/assetsideloader/AppInfo.kt new file mode 100644 index 0000000..f714b02 --- /dev/null +++ b/app/src/main/java/top/axix/assetsideloader/AppInfo.kt @@ -0,0 +1,44 @@ +package top.axix.assetsideloader + +import android.content.Context +import android.content.pm.ApplicationInfo +import com.google.gson.Gson +import java.io.Serializable + +data class AppInfo( + val appName: String, + val packageName: String, + val apkPath: String, + var dataPath: String, + var apkPatch: String, + var dataPatch: String, + var modPatch: String, + var isEnabled: Boolean, + var isSelected: Boolean +) : Serializable { + constructor(context: Context, appInfo: ApplicationInfo) : this( + appName = appInfo.loadLabel(context.packageManager).toString(), + packageName = appInfo.packageName, + apkPath = appInfo.sourceDir + "!/", + dataPath = "/storage/emulated/0/Android/data/" + appInfo.packageName + "/files", + apkPatch = "", + dataPatch = "", + modPatch = "", + isEnabled = false, + isSelected = false + ) + + fun toJson(): String { + return Gson().toJson(this) + } + + companion object { + fun fromJson(json: String): AppInfo { + return Gson().fromJson(json, AppInfo::class.java) + } + } +} + +fun applicationInfoToAppInfo(context: Context, appInfos: List): List { + return appInfos.map { AppInfo(context, it) } +} \ No newline at end of file diff --git a/app/src/main/java/top/axix/assetsideloader/AppInfoActivity.kt b/app/src/main/java/top/axix/assetsideloader/AppInfoActivity.kt new file mode 100644 index 0000000..34e08e0 --- /dev/null +++ b/app/src/main/java/top/axix/assetsideloader/AppInfoActivity.kt @@ -0,0 +1,67 @@ +package top.axix.assetsideloader + +import android.os.Bundle +import android.widget.Button +import android.widget.EditText +import android.widget.Switch +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import top.axix.assetsideloader.Global.appInfoData + +class AppInfoActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_app_info) + + val position = intent.getIntExtra("position", -1) + + if (position == -1){ + finish() + } + + val appName = findViewById(R.id.app_name) + val appPackageName = findViewById(R.id.app_pakage_name) + val apkPath = findViewById(R.id.apk_path) + val apkPatch = findViewById(R.id.apk_patch) + var dataPath = findViewById(R.id.data_path) + var dataPatch = findViewById(R.id.data_patch) + var modPatch = findViewById(R.id.mod_patch) + val enableSwitch = findViewById(R.id.enable_switch) + val saveButton = findViewById