├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── keystore │ └── androidkey.jks ├── proguard-rules.pro └── src │ ├── androidTest │ └── kotlin │ │ └── top │ │ └── laoxin │ │ └── modmanager │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── aidl │ │ └── top │ │ │ └── laoxin │ │ │ └── modmanager │ │ │ ├── data │ │ │ └── bean │ │ │ │ ├── GameInfoBean.aidl │ │ │ │ └── ModBean.aidl │ │ │ └── service │ │ │ └── IFileExplorerService.aidl │ ├── ic_launcher-playstore.png │ ├── kotlin │ │ └── top │ │ │ └── laoxin │ │ │ └── modmanager │ │ │ ├── App.kt │ │ │ ├── activity │ │ │ ├── main │ │ │ │ └── MainActivity.kt │ │ │ ├── start │ │ │ │ └── StartActivity.kt │ │ │ └── userAgreement │ │ │ │ └── UserAgreementActivity.kt │ │ │ ├── constant │ │ │ ├── FileType.kt │ │ │ ├── GameInfoConstant.kt │ │ │ ├── OSVersion.kt │ │ │ ├── PathType.kt │ │ │ ├── RequestCode.kt │ │ │ ├── ResultCode.kt │ │ │ ├── ScanModPath.kt │ │ │ ├── SpecialGame.kt │ │ │ └── UserPreferencesKeys.kt │ │ │ ├── data │ │ │ ├── bean │ │ │ │ ├── AntiHarmonyBean.kt │ │ │ │ ├── BackupBean.kt │ │ │ │ ├── DownloadGameConfigBean.kt │ │ │ │ ├── GameInfoBean.kt │ │ │ │ ├── GithubBean.kt │ │ │ │ ├── InfoBean.kt │ │ │ │ ├── ModBean.kt │ │ │ │ ├── ModBeanTemp.kt │ │ │ │ ├── ScanFileBean.kt │ │ │ │ └── ThanksBean.kt │ │ │ ├── network │ │ │ │ ├── AppConfig.kt │ │ │ │ ├── GithubApiService.kt │ │ │ │ └── ModManagerApiService.kt │ │ │ └── repository │ │ │ │ ├── Converters.kt │ │ │ │ ├── ModManagerDatabase.kt │ │ │ │ ├── UserPreferencesRepository.kt │ │ │ │ ├── VersionRepository.kt │ │ │ │ ├── antiharmony │ │ │ │ ├── AntiHarmonyDao.kt │ │ │ │ ├── AntiHarmonyRepository.kt │ │ │ │ └── OfflineAntiHarmonyRepository.kt │ │ │ │ ├── backup │ │ │ │ ├── BackupDao.kt │ │ │ │ ├── BackupRepository.kt │ │ │ │ └── OfflineBackupRepository.kt │ │ │ │ ├── mod │ │ │ │ ├── ModDao.kt │ │ │ │ ├── ModRepository.kt │ │ │ │ └── OfflineModsRepository.kt │ │ │ │ └── scanfile │ │ │ │ ├── OfflineScanFileRepository.kt │ │ │ │ ├── ScanFileDao.kt │ │ │ │ └── ScanFileRepository.kt │ │ │ ├── di │ │ │ ├── AppModule.kt │ │ │ ├── FileToolsModule.kt │ │ │ ├── RepositoryModule.kt │ │ │ └── SpecialGameToolsModule.kt │ │ │ ├── domain │ │ │ └── usercase │ │ │ │ ├── app │ │ │ │ ├── CheckPermissionUserCase.kt │ │ │ │ ├── CheckUpdateUserCase.kt │ │ │ │ ├── GetInformationUserCase.kt │ │ │ │ └── UpdateLogToolUserCase.kt │ │ │ │ ├── console │ │ │ │ ├── SaveSelectModDirectoryUserCase.kt │ │ │ │ └── SwitchAntiHarmonyUserCase.kt │ │ │ │ ├── gameinfo │ │ │ │ ├── CheckGameConfigUserCase.kt │ │ │ │ ├── LoadGameConfigUserCase.kt │ │ │ │ └── UpdateGameInfoUserCase.kt │ │ │ │ ├── mod │ │ │ │ ├── CheckCanFlashModsUserCase.kt │ │ │ │ ├── CheckCanSwitchModsUserCase.kt │ │ │ │ ├── CheckModPasswordUserCase.kt │ │ │ │ ├── DeleteModUserCase.kt │ │ │ │ ├── DeleteSelectedModsUserCase.kt │ │ │ │ ├── DisableModUserCase.kt │ │ │ │ ├── EnableModUserCase.kt │ │ │ │ ├── EnsureGameModPathUserCase.kt │ │ │ │ ├── FlashModDetailUserCase.kt │ │ │ │ ├── FlashModImageUserCase.kt │ │ │ │ ├── FlashModsUserCase.kt │ │ │ │ └── ReadModReadmeFileUserCase.kt │ │ │ │ ├── repository │ │ │ │ ├── AntiHarmonyRepositoryUserCase.kt │ │ │ │ └── ModRepositoryUserCase.kt │ │ │ │ ├── setting │ │ │ │ ├── DeleteBackupUserCase.kt │ │ │ │ ├── DeleteCacheUserCase.kt │ │ │ │ ├── DeleteTempUserCase.kt │ │ │ │ ├── DownloadGameConfigUserCase.kt │ │ │ │ ├── FlashGameConfigUserCase.kt │ │ │ │ └── SelectGameUserCase.kt │ │ │ │ └── userpreference │ │ │ │ ├── GetUserPreferenceUseCase.kt │ │ │ │ └── SaveUserPreferenceUseCase.kt │ │ │ ├── exception │ │ │ └── ModManagerException.kt │ │ │ ├── listener │ │ │ └── ProgressUpdateListener.kt │ │ │ ├── observer │ │ │ ├── FlashModsObserver.kt │ │ │ ├── FlashModsObserverManager.kt │ │ │ └── FlashObserverInterface.kt │ │ │ ├── tools │ │ │ ├── AppInfoTools.kt │ │ │ ├── ArchiveUtil.kt │ │ │ ├── LogTools.kt │ │ │ ├── MD5Tools.kt │ │ │ ├── PermissionTools.kt │ │ │ ├── ToastUtils.kt │ │ │ ├── filetools │ │ │ │ ├── BaseFileTools.kt │ │ │ │ ├── FileToolsManager.kt │ │ │ │ └── impl │ │ │ │ │ ├── DocumentFileTools.kt │ │ │ │ │ ├── FileTools.kt │ │ │ │ │ └── ShizukuFileTools.kt │ │ │ ├── manager │ │ │ │ ├── AppPathsManager.kt │ │ │ │ └── GameInfoManager.kt │ │ │ └── specialGameTools │ │ │ │ ├── ArknightsTools.kt │ │ │ │ ├── BaseSpecialGameTools.kt │ │ │ │ ├── ProjectSnowTools.kt │ │ │ │ └── SpecialGameToolsManager.kt │ │ │ ├── ui │ │ │ ├── state │ │ │ │ ├── ConsoleUiState.kt │ │ │ │ ├── ModUiState.kt │ │ │ │ ├── SettingUiState.kt │ │ │ │ └── UserPreferencesState.kt │ │ │ ├── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ ├── view │ │ │ │ ├── Console.kt │ │ │ │ ├── ModManagerApp.kt │ │ │ │ ├── UserAgreement.kt │ │ │ │ ├── commen │ │ │ │ │ ├── DialogCompose.kt │ │ │ │ │ ├── DialogComposeForUpdate.kt │ │ │ │ │ └── PermissionCompose.kt │ │ │ │ ├── modView │ │ │ │ │ ├── AllMod.kt │ │ │ │ │ ├── Mod.kt │ │ │ │ │ ├── ModBrowser.kt │ │ │ │ │ ├── ModDetail.kt │ │ │ │ │ ├── ModList.kt │ │ │ │ │ └── ModTopBar.kt │ │ │ │ ├── settingView │ │ │ │ │ ├── License.kt │ │ │ │ │ ├── Setting.kt │ │ │ │ │ └── SettingTopBar.kt │ │ │ │ └── startView │ │ │ │ │ └── StartContent.kt │ │ │ └── viewmodel │ │ │ │ ├── ConsoleViewModel.kt │ │ │ │ ├── ModViewModel.kt │ │ │ │ ├── SettingViewModel.kt │ │ │ │ └── VersionViewModel.kt │ │ │ └── userService │ │ │ ├── gamestart │ │ │ └── ProjectSnowStartService.kt │ │ │ └── shizuku │ │ │ ├── FileExplorerService.kt │ │ │ └── FileExplorerServiceManager.kt │ └── res │ │ ├── drawable │ │ ├── alipay_icon.png │ │ ├── app_icon.png │ │ ├── back_icon.png │ │ ├── book_icon.png │ │ ├── discord_icon.png │ │ ├── folder_icon.png │ │ ├── folder_mod_icon.png │ │ ├── github_icon.png │ │ ├── notification_icon.png │ │ ├── qq_icon.png │ │ ├── shizuku_icon.png │ │ ├── start.webp │ │ ├── start_1.png │ │ ├── start_2.png │ │ ├── thank_icon.png │ │ ├── update_icon.png │ │ └── zip_mod_icon.png │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── values-en │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── kotlin │ └── top │ └── laoxin │ └── modmanager │ ├── ArchiveUtil.kt │ ├── ExampleUnitTest.kt │ ├── ak.json │ ├── hot_update_list.json │ ├── persistent_res_list.json │ ├── test.json │ ├── test1.json │ └── 碧蓝航线示例配置.json ├── build.gradle.kts ├── gameConfig ├── api │ ├── gameConfig.json │ ├── information.json │ └── thanks.json ├── com.RoamingStar.BlueArchive.bilibili.json ├── com.RoamingStar.BlueArchive.json ├── com.YoStarEN.Arknights.json ├── com.YostarJP.BlueArchive.json ├── com.bilibili.azurlane.json ├── com.bilibili.blhx.m4399.json ├── com.dragonli.projectsnow.bilibili.json ├── com.dragonli.projectsnow.lhm.json ├── com.gamebeans.gp.jczx.json ├── com.neowizgames.game.browndust2.json ├── com.nexon.bluearchive.json ├── com.seasun.snowbreak.google.json ├── jp.co.nextninja.dimensions.json └── 配置文件使用说明.txt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image └── readme │ ├── 1715962256872.png │ ├── 1715962345763.png │ ├── 1715962369620.png │ ├── 1715962378369.png │ ├── 1715962396435.png │ ├── 1715962416212.png │ ├── 1715963940732.png │ ├── 1715964083599.png │ ├── 1715964191813.png │ ├── 1718686384539.png │ └── 1718686686335.png ├── renovate.json └── settings.gradle.kts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - '*' 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup Java 17 | uses: actions/setup-java@v4 18 | with: 19 | distribution: temurin 20 | java-version: 21 21 | 22 | - name: Grant execute permission for gradlew 23 | run: chmod +x ./gradlew 24 | 25 | - uses: actions/cache@v4 26 | with: 27 | path: | 28 | ~/.gradle/caches 29 | ~/.gradle/wrapper 30 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 31 | restore-keys: | 32 | ${{ runner.os }}-gradle- 33 | 34 | - name: assembleRelease with Gradle 35 | run: ./gradlew assembleRelease 36 | 37 | - name: Upload ARM64 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: arm64-v8a 41 | path: app/build/outputs/apk/release/app-arm64-v8a-release.apk 42 | 43 | - name: Upload ARM32 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: armeabi-v7a 47 | path: app/build/outputs/apk/release/app-armeabi-v7a-release.apk 48 | 49 | - name: Upload x86 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: x86 53 | path: app/build/outputs/apk/release/app-x86-release.apk 54 | 55 | - name: Upload x86_64 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: x86_64 59 | path: app/build/outputs/apk/release/app-x86_64-release.apk 60 | 61 | - name: Upload universal 62 | uses: actions/upload-artifact@v4 63 | with: 64 | name: universal 65 | path: app/build/outputs/apk/release/app-universal-release.apk 66 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup Java 17 | uses: actions/setup-java@v4 18 | with: 19 | distribution: temurin 20 | java-version: 21 21 | 22 | - name: Grant execute permission for gradlew 23 | run: chmod +x ./gradlew 24 | 25 | - uses: actions/cache@v4 26 | with: 27 | path: | 28 | ~/.gradle/caches 29 | ~/.gradle/wrapper 30 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 31 | restore-keys: | 32 | ${{ runner.os }}-gradle- 33 | 34 | - name: Assemble Release with Gradle 35 | run: ./gradlew assembleRelease 36 | 37 | - name: List APK files before renaming 38 | run: ls -R app/build/outputs/apk/ 39 | 40 | - name: Rename APK files 41 | run: | 42 | cd app/build/outputs/apk/release 43 | tag_name="${GITHUB_REF_NAME}" 44 | for apk in app-*-release.apk; do 45 | # Extract ABI from the filename 46 | abi="${apk#app-}" 47 | abi="${abi%-release.apk}" 48 | # Create new filename 49 | new_name="ModManager-${tag_name}-${abi}.apk" 50 | mv "$apk" "$new_name" 51 | done 52 | env: 53 | GITHUB_REF_NAME: ${{ github.ref_name }} 54 | 55 | - name: List APK files after renaming 56 | run: ls -R app/build/outputs/apk/release/ 57 | 58 | - name: Create Release 59 | uses: softprops/action-gh-release@v2.2.2 60 | if: startsWith(github.ref, 'refs/tags/') 61 | with: 62 | tag_name: ${{ github.ref_name }} 63 | name: Release ${{ github.ref_name }} 64 | body: ${{ github.event.head_commit.message }} 65 | draft: false 66 | prerelease: false 67 | files: | 68 | app/build/outputs/apk/release/*.apk 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties 17 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/keystore/androidkey.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/keystore/androidkey.jks -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -allowaccessmodification 2 | -repackageclasses '' 3 | 4 | -keepclassmembers class * implements android.os.Parcelable { 5 | public static final ** CREATOR; 6 | } 7 | 8 | -keepclassmembers enum * { 9 | public static **[] values(); 10 | public static ** valueOf(java.lang.String); 11 | } 12 | 13 | -keepclasseswithmembernames,includedescriptorclasses class * { 14 | native ; 15 | } 16 | 17 | -dontwarn org.slf4j.impl.StaticLoggerBinder 18 | -dontwarn org.conscrypt.** 19 | -dontwarn androidx.window.** 20 | -dontwarn com.squareup.okhttp.** 21 | 22 | -keep class net.sf.sevenzipjbinding.** { *; } 23 | -keep class top.laoxin.** { *; } 24 | -keep class okhttp3.** { *; } 25 | 26 | -keep class com.google.gson.** { *; } 27 | -keepclassmembers class * { 28 | @com.google.gson.annotations.* ; 29 | } 30 | 31 | -keep class dagger.** { *; } 32 | -keep interface dagger.** { *; } 33 | -keep class androidx.lifecycle.** { *; } 34 | 35 | -keep class dagger.hilt.** { *; } 36 | 37 | -keep class * extends androidx.lifecycle.ViewModel { *; } 38 | 39 | -keep class **.Hilt_* { *; } 40 | -keep class **.HiltInjector { *; } 41 | 42 | -keep class javax.inject.** { *; } 43 | -keep class com.google.inject.** { *; } 44 | 45 | -keep @dagger.hilt.android.HiltAndroidApp class * -------------------------------------------------------------------------------- /app/src/androidTest/kotlin/top/laoxin/modmanager/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.rule.GrantPermissionRule 5 | import org.junit.Rule 6 | import org.junit.runner.RunWith 7 | 8 | /** 9 | * Instrumented test, which will execute on an Android device. 10 | * 11 | * See [testing documentation](http://d.android.com/tools/testing). 12 | */ 13 | @RunWith(AndroidJUnit4::class) 14 | class ExampleInstrumentedTest { 15 | @get:Rule 16 | var permissionRule: GrantPermissionRule = GrantPermissionRule.grant( 17 | android.Manifest.permission.READ_EXTERNAL_STORAGE, 18 | android.Manifest.permission.WRITE_EXTERNAL_STORAGE, 19 | android.Manifest.permission.MANAGE_EXTERNAL_STORAGE 20 | ) 21 | // @Test 22 | // fun useAppContext() { 23 | // 24 | // // Context of the app under test. 25 | // val appContext = InstrumentationRegistry.getInstrumentation().targetContext 26 | // assertEquals("top.laoxin.modmanager", appContext.packageName) 27 | // try { 28 | // val file = File(ModTools.ROOT_PATH + "/Download/Telegram/测.rar") 29 | // // SevenZip.initSevenZipFromPlatformJAR() 30 | // val randomAccessFile: RandomAccessFile = RandomAccessFile(file, "r") 31 | // val inStream: RandomAccessFileInStream = RandomAccessFileInStream(randomAccessFile) 32 | // val callback: ArchiveOpenCallback = ArchiveOpenCallback() 33 | // val inArchive: IInArchive = 34 | // SevenZip.openInArchive(ArchiveFormat.RAR5, inStream, callback) 35 | // 36 | // val format: ArchiveFormat = inArchive.getArchiveFormat() 37 | // Log.i("测试", "Archive format: " + format.getMethodName()) 38 | // 39 | // val itemCount: Int = inArchive.getNumberOfItems() 40 | // Log.i("测试", "Items in archive: $itemCount") 41 | // for (i in 0 until itemCount) { 42 | // Log.i( 43 | // "测试", 44 | // ("File " + i + ": " + inArchive.getStringProperty( 45 | // i, 46 | // PropID.PATH 47 | // )).toString() + " : " + inArchive.getStringProperty(i, PropID.SIZE) 48 | // ) 49 | // } 50 | // 51 | // inArchive.close() 52 | // inStream.close() 53 | // } catch (e: FileNotFoundException) { 54 | // Log.e("测试", e.message!!) 55 | // } catch (e: SevenZipException) { 56 | // //Log.e("测试", e.message) 57 | // } catch (e: IOException) { 58 | // Log.e("测试", e.message!!) 59 | // } 60 | // } 61 | // 62 | // private class ArchiveOpenCallback : IArchiveOpenCallback { 63 | // override fun setTotal(files: Long, bytes: Long) { 64 | // Log.i("测试", "Archive open, total work: $files files, $bytes bytes") 65 | // } 66 | // 67 | // override fun setCompleted(files: Long, bytes: Long) { 68 | // Log.i("测试", "Archive open, completed: $files files, $bytes bytes") 69 | // } 70 | // } 71 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 57 | 58 | 64 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/src/main/aidl/top/laoxin/modmanager/data/bean/GameInfoBean.aidl: -------------------------------------------------------------------------------- 1 | // ModBean.aidl 2 | package top.laoxin.modmanager.data.bean; 3 | import top.laoxin.modmanager.data.bean.GameInfoBean; 4 | 5 | // Declare any non-default types here with import statements 6 | 7 | parcelable GameInfoBean; -------------------------------------------------------------------------------- /app/src/main/aidl/top/laoxin/modmanager/data/bean/ModBean.aidl: -------------------------------------------------------------------------------- 1 | // ModBean.aidl 2 | package top.laoxin.modmanager.data.bean; 3 | import top.laoxin.modmanager.data.bean.ModBean; 4 | 5 | // Declare any non-default types here with import statements 6 | 7 | parcelable ModBean; -------------------------------------------------------------------------------- /app/src/main/aidl/top/laoxin/modmanager/service/IFileExplorerService.aidl: -------------------------------------------------------------------------------- 1 | // IFileExplorerService.aidl 2 | package top.laoxin.modmanager.service; 3 | import top.laoxin.modmanager.data.bean.ModBean; 4 | import top.laoxin.modmanager.data.bean.GameInfoBean; 5 | // Declare any non-default types here with import statements 6 | 7 | interface IFileExplorerService { 8 | List getFilesNames(String path); 9 | boolean deleteFile(String path); 10 | boolean copyFile(String srcPath, String destPath); 11 | boolean writeFile(String srcPath,String name, String content); 12 | boolean fileExists(String path); 13 | boolean chmod(String path); 14 | boolean unZipFile(String zipPath,String unzipPath,String filename, String password); 15 | boolean scanMods(String sacnPath, in GameInfoBean gameInfo); 16 | boolean moveFile(String srcPath, String destPath); 17 | boolean isFile(String path); 18 | // 文件是否发生变化 19 | long isFileChanged(String path); 20 | boolean changDictionaryName (String path, String newName); 21 | boolean createDictionary (String path); 22 | // listfile 23 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/App.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager 2 | 3 | import android.app.Application 4 | import android.app.NotificationChannel 5 | import android.app.NotificationManager 6 | import android.os.Build 7 | import android.util.Log 8 | import androidx.core.app.NotificationManagerCompat 9 | import dagger.hilt.android.HiltAndroidApp 10 | import top.laoxin.modmanager.constant.OSVersion 11 | import top.laoxin.modmanager.tools.LogTools 12 | import java.io.File 13 | 14 | 15 | @HiltAndroidApp 16 | class App : Application() { 17 | 18 | companion object { 19 | var osVersion: OSVersion = OSVersion.OS_5 20 | var isHuawei: Boolean = false 21 | 22 | @Volatile 23 | private var instance: App? = null 24 | 25 | fun get(): App = instance ?: synchronized(this) { 26 | instance ?: throw IllegalStateException("Application not initialized!") 27 | } 28 | } 29 | 30 | override fun onCreate() { 31 | super.onCreate() 32 | synchronized(App::class) { 33 | instance = this 34 | } 35 | 36 | initializeOsVersion() 37 | createTestFile() 38 | setupNotificationChannel() 39 | setupGlobalExceptionHandler() 40 | } 41 | 42 | // 初始化操作系统版本 43 | private fun initializeOsVersion() { 44 | val sdkVersion = Build.VERSION.SDK_INT 45 | osVersion = when { 46 | sdkVersion >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> OSVersion.OS_14 47 | sdkVersion >= Build.VERSION_CODES.TIRAMISU -> OSVersion.OS_13 48 | sdkVersion >= Build.VERSION_CODES.R -> OSVersion.OS_11 49 | sdkVersion >= Build.VERSION_CODES.M -> OSVersion.OS_6 50 | else -> OSVersion.OS_5 51 | } 52 | 53 | // Check for Huawei/HarmonyOS 4 specific conditions 54 | val phoneBrand = Build.MANUFACTURER.lowercase() 55 | isHuawei = phoneBrand.contains("harmony") || phoneBrand.contains("oce") 56 | || phoneBrand.contains("huawei") 57 | 58 | if (isHuawei) { 59 | osVersion = OSVersion.OS_14 60 | } 61 | 62 | Log.d("App", "Detected OS Version: $osVersion") 63 | } 64 | 65 | // 创建测试文件 66 | private fun createTestFile() { 67 | try { 68 | val filesDir = getExternalFilesDir(null)?.parent ?: return 69 | val testFile = File(filesDir, "test.txt") 70 | 71 | if (!testFile.exists()) { 72 | testFile.createNewFile() 73 | testFile.writeBytes("Hello world!".toByteArray()) 74 | } 75 | } catch (e: Exception) { 76 | Log.e("App", "Failed to create test file", e) 77 | } 78 | } 79 | 80 | // 设置通知渠道 81 | private fun setupNotificationChannel() { 82 | val channelId = getString(R.string.channel_id) 83 | val channelName = getString(R.string.channel_name) 84 | 85 | NotificationManagerCompat.from(this).apply { 86 | createNotificationChannel( 87 | NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT) 88 | ) 89 | } 90 | } 91 | 92 | // 设置全局异常处理 93 | private fun setupGlobalExceptionHandler() { 94 | Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> 95 | handleGlobalException(thread, throwable) 96 | } 97 | } 98 | 99 | // 处理全局异常 100 | private fun handleGlobalException(thread: Thread, throwable: Throwable) { 101 | Log.e("GlobalException", "Uncaught exception in thread ${thread.name}", throwable) 102 | LogTools.logRecord("Uncaught exception in thread ${thread.name}: ${throwable.message}") 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/activity/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.activity.main 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.activity.enableEdgeToEdge 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Surface 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.toArgb 13 | import androidx.core.view.WindowCompat 14 | import androidx.core.view.WindowInsetsControllerCompat 15 | import dagger.hilt.android.AndroidEntryPoint 16 | import rikka.shizuku.Shizuku 17 | import top.laoxin.modmanager.tools.PermissionTools 18 | import top.laoxin.modmanager.ui.theme.ModManagerTheme 19 | import top.laoxin.modmanager.ui.view.ModManagerApp 20 | 21 | @AndroidEntryPoint 22 | class MainActivity() : ComponentActivity() { 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | 27 | setupWindow() 28 | setupShizuku() 29 | enableEdgeToEdge() 30 | setContent { 31 | ModManagerTheme { 32 | ConfigureSystemBars() 33 | Surface(Modifier.fillMaxSize()) { 34 | ModManagerApp() 35 | } 36 | } 37 | } 38 | } 39 | 40 | override fun onDestroy() { 41 | super.onDestroy() 42 | cleanupShizuku() 43 | } 44 | 45 | // 设置窗口 46 | private fun setupWindow() { 47 | WindowCompat.setDecorFitsSystemWindows(window, false) 48 | window.setBackgroundDrawableResource(android.R.color.transparent) 49 | } 50 | 51 | // 添加 Shizuku 监听器 52 | private fun setupShizuku() { 53 | Shizuku.addRequestPermissionResultListener(PermissionTools.REQUEST_PERMISSION_RESULT_LISTENER) 54 | } 55 | 56 | // 移除 Shizuku 监听器 57 | private fun cleanupShizuku() { 58 | if (PermissionTools.isShizukuAvailable) { 59 | Shizuku.removeRequestPermissionResultListener(PermissionTools.REQUEST_PERMISSION_RESULT_LISTENER) 60 | } 61 | } 62 | 63 | // 设置状态栏和导航栏 64 | @Composable 65 | private fun ConfigureSystemBars() { 66 | WindowInsetsControllerCompat(window, window.decorView).apply { 67 | isAppearanceLightNavigationBars = true 68 | @Suppress("DEPRECATION") 69 | window.navigationBarColor = MaterialTheme.colorScheme.surfaceContainer.toArgb() 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/activity/userAgreement/UserAgreementActivity.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.activity.userAgreement 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.LocalActivity 6 | import androidx.activity.compose.setContent 7 | import androidx.activity.enableEdgeToEdge 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.graphics.toArgb 11 | import androidx.core.view.WindowCompat 12 | import androidx.core.view.WindowInsetsControllerCompat 13 | import top.laoxin.modmanager.ui.theme.ModManagerTheme 14 | import top.laoxin.modmanager.ui.view.UserAgreement 15 | 16 | class UserAgreementActivity : ComponentActivity() { 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setupWindowConfiguration() 20 | setContent { 21 | ModManagerTheme { 22 | ConfigureSystemBars() 23 | UserAgreement() 24 | } 25 | } 26 | } 27 | 28 | // 设置全屏模式,使内容可以扩展到状态栏和导航栏区域 29 | private fun setupWindowConfiguration() { 30 | WindowCompat.setDecorFitsSystemWindows(window, false) 31 | window.setBackgroundDrawableResource(android.R.color.transparent) 32 | enableEdgeToEdge() 33 | } 34 | 35 | // 设置导航栏背景颜色和图标亮度 36 | @Composable 37 | private fun ConfigureSystemBars() { 38 | val activity = LocalActivity.current 39 | activity?.let { WindowInsetsControllerCompat(it.window, activity.window.decorView) }.apply { 40 | this?.isAppearanceLightNavigationBars = true 41 | if (activity != null) { 42 | @Suppress("DEPRECATION") 43 | activity.window.navigationBarColor = 44 | MaterialTheme.colorScheme.surfaceContainer.toArgb() 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/constant/FileType.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.constant 2 | 3 | enum class FileType { 4 | ZIP, 5 | RAR, 6 | _7z, 7 | UNKNOWN, 8 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/constant/GameInfoConstant.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.constant 2 | 3 | import android.os.Environment 4 | import top.laoxin.modmanager.data.bean.GameInfoBean 5 | 6 | 7 | object GameInfoConstant { 8 | val ROOT_PATH = Environment.getExternalStorageDirectory().path 9 | val CROSSCORE = GameInfoBean( 10 | "交错战线", 11 | "官服", 12 | "com.megagame.crosscore", 13 | ROOT_PATH + "/Android/data/com.megagame.crosscore/", 14 | "crosscore/", 15 | ROOT_PATH + "/Android/data/com.megagame.crosscore/files/internation.txt", 16 | "1", 17 | mutableListOf( 18 | ROOT_PATH + "/Android/data/com.megagame.crosscore/files/Custom/", 19 | ROOT_PATH + "/Android/data/com.megagame.crosscore/files/videos/login/", 20 | ROOT_PATH + "/Android/data/com.megagame.crosscore/files/sounds/cv/", 21 | ROOT_PATH + "/Android/data/com.megagame.crosscore/files/sounds/cv/cv_skin/", 22 | ROOT_PATH + "/Android/data/com.megagame.crosscore/files/sounds/cv/bgms/", 23 | ROOT_PATH + "/Android/data/com.megagame.crosscore/files/sounds/cv/picture/", 24 | ROOT_PATH + "/Android/data/com.megagame.crosscore/files/sounds/cv/fight/effect", 25 | 26 | ), 27 | "", 28 | mutableListOf( 29 | "游戏模型", 30 | "登录页面", 31 | "角色语言", 32 | "皮肤语音", 33 | "背景音乐", 34 | "图册语音", 35 | "战斗音效", 36 | ), 37 | false 38 | ) 39 | val CROSSCOREB = GameInfoBean( 40 | "交错战线", 41 | "B服", 42 | "com.megagame.crosscore.bilibili", 43 | ROOT_PATH + "/Android/data/com.megagame.crosscore.bilibili/", 44 | "crosscore/", 45 | ROOT_PATH + "/Android/data/com.megagame.crosscore.bilibili/files/internation.txt", 46 | "1", 47 | mutableListOf( 48 | ROOT_PATH + "/Android/data/com.megagame.crosscore.bilibili/files/Custom/", 49 | ROOT_PATH + "/Android/data/com.megagame.crosscore.bilibili/files/videos/login/", 50 | ROOT_PATH + "/Android/data/com.megagame.crosscore.bilibili/files/sounds/cv/", 51 | ROOT_PATH + "/Android/data/com.megagame.crosscore.bilibili/files/sounds/cv/cv_skin/", 52 | ROOT_PATH + "/Android/data/com.megagame.crosscore.bilibili/files/sounds/bgms/", 53 | ROOT_PATH + "/Android/data/com.megagame.crosscore.bilibili/files/sounds/cv/picture/", 54 | ROOT_PATH + "/Android/data/com.megagame.crosscore.bilibili/files/sounds/cv/fight/effect", 55 | ), 56 | "", 57 | mutableListOf( 58 | "游戏模型", 59 | "登录页面", 60 | "角色语言", 61 | "皮肤语音", 62 | "背景音乐", 63 | "图册语音", 64 | "战斗音效", 65 | ), 66 | false 67 | ) 68 | 69 | val NO_GAME = GameInfoBean( 70 | "", 71 | "", 72 | "", 73 | "", 74 | "", 75 | "", 76 | "", 77 | mutableListOf(), 78 | "", 79 | mutableListOf(), 80 | true 81 | ) 82 | val gameInfoList = mutableListOf(NO_GAME, CROSSCORE, CROSSCOREB) 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/constant/OSVersion.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.constant 2 | 3 | enum class OSVersion { 4 | OS_5, 5 | OS_6, 6 | OS_11, 7 | OS_13, 8 | OS_14 9 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/constant/PathType.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.constant 2 | 3 | import androidx.annotation.IntDef 4 | 5 | 6 | interface PathType { 7 | @IntDef(*[FILE, DOCUMENT, PACKAGE_NAME, SHIZUKU]) 8 | annotation class PathType1 9 | companion object { 10 | /** 11 | * 通过File接口,访问一般路径 12 | */ 13 | const val FILE = 0 14 | 15 | /** 16 | * 通过Document API 访问特殊路径 17 | */ 18 | const val DOCUMENT = 1 19 | 20 | /** 21 | * 安卓13及以上,直接用包名展示data、obb下的目录(因为data、obb不能直接授权了,只能对子目录授权) 22 | */ 23 | const val PACKAGE_NAME = 2 24 | 25 | /** 26 | * 通过Shizuku授权访问特殊路径 27 | */ 28 | const val SHIZUKU = 3 29 | 30 | /** 31 | * 无权限 32 | */ 33 | const val NULL = -1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/constant/RequestCode.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.constant 2 | 3 | import androidx.annotation.IntDef 4 | 5 | 6 | 7 | interface RequestCode { 8 | @IntDef(*[STORAGE, DOCUMENT, SHIZUKU]) 9 | annotation class RequestCode 10 | companion object { 11 | const val STORAGE = 0 12 | const val DOCUMENT = 1 13 | const val SHIZUKU = 2 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/constant/ResultCode.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.constant 2 | 3 | import androidx.annotation.IntDef 4 | 5 | interface ResultCode { 6 | @IntDef(*[NO_PERMISSION, FAIL,SUCCESS,NOT_SUPPORT,NO_MY_APP_PERMISSION,NO_SELECTED_GAME, MOD_NEED_PASSWORD, NO_EXECUTE, HAVE_ENABLE_MODS]) 7 | annotation class ResultCode 8 | companion object { 9 | 10 | 11 | // 权限不足 12 | const val NO_PERMISSION = -1 13 | // 失败 14 | const val FAIL = -2 15 | // 不支持 16 | const val NOT_SUPPORT = -3 17 | // 无本APP文件目录权限 18 | const val NO_MY_APP_PERMISSION = -4 19 | // 未选择游戏 20 | const val NO_SELECTED_GAME = -5 21 | // mod需要密码 22 | const val MOD_NEED_PASSWORD = -6 23 | // 不执行 24 | const val NO_EXECUTE = -7 25 | // 存在开启的mods 26 | const val HAVE_ENABLE_MODS = -8 27 | // 游戏已更新 28 | const val GAME_UPDATE = -9 29 | 30 | // 成功 31 | const val SUCCESS = 0 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/constant/ScanModPath.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.constant 2 | 3 | import android.os.Environment 4 | 5 | 6 | object ScanModPath { 7 | val ROOT_PATH = Environment.getExternalStorageDirectory().path 8 | val MOD_PATH_QQ = ROOT_PATH + "/Android/data/com.tencent.mobileqq/Tencent/QQfile_recv/" 9 | val MOD_PATH_DOWNLOAD = ROOT_PATH + "/Download/" 10 | val ANDROID_DATA = ROOT_PATH + "/Android/data/" 11 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/constant/SpecialGame.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.constant 2 | 3 | object SpecialGame { 4 | val specialGameList = listOf( 5 | "arknights", 6 | "com.mrfz", 7 | "projectsnow", 8 | "snowbreak" 9 | ) 10 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/constant/UserPreferencesKeys.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.constant 2 | 3 | object UserPreferencesKeys { 4 | const val SELECTED_GAME = "SELECTED_GAME" 5 | const val SCAN_QQ_DIRECTORY = "SCAN_QQ_DIRECTORY" 6 | const val SELECTED_DIRECTORY = "SELECTED_DIRECTORY" 7 | const val SCAN_DOWNLOAD = "SCAN_DOWNLOAD" 8 | const val OPEN_PERMISSION_REQUEST_DIALOG = "OPEN_PERMISSION_REQUEST_DIALOG" 9 | const val SCAN_DIRECTORY_MODS = "SCAN_DIRECTORY_MODS" 10 | const val DELETE_UNZIP_DIRECTORY = "DELETE_UNZIP_DIRECTORY" 11 | const val SHOW_CATEGORY_VIEW = "SHOW_CATEGORY_VIEW" 12 | const val USER_TIPS = "USER_TIPS" 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/bean/AntiHarmonyBean.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.bean 2 | 3 | import androidx.room.Entity 4 | import androidx.room.Index 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "antiHarmony", indices = [Index(value = ["gamePackageName"])]) 8 | data class AntiHarmonyBean( 9 | @PrimaryKey(autoGenerate = true) 10 | val id: Int = 0, 11 | val gamePackageName: String, 12 | val isEnable: Boolean, 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/bean/BackupBean.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.bean 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | 7 | /** 8 | * 备份实体类 9 | */ 10 | @Entity(tableName = "backups") 11 | data class BackupBean( 12 | @PrimaryKey(autoGenerate = true) 13 | val id: Int = 0, 14 | val filename: String?, 15 | val gamePath: String?, 16 | val gameFilePath: String?, 17 | val backupPath: String?, 18 | val gamePackageName: String?, 19 | val modName: String?, 20 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/bean/DownloadGameConfigBean.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.bean 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class DownloadGameConfigBean( 7 | val gameName: String, 8 | val packageName: String, 9 | val serviceName: String, 10 | val downloadUrl: String, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/bean/GameInfoBean.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.bean 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class GameInfoBean( 9 | val gameName: String, 10 | val serviceName: String, 11 | val packageName: String, 12 | val gamePath: String, 13 | val modSavePath: String = "", 14 | val antiHarmonyFile: String = "", 15 | val antiHarmonyContent: String = "", 16 | val gameFilePath: List, 17 | val version: String, 18 | val modType: List, 19 | val isGameFileRepeat: Boolean = true, 20 | val enableBackup: Boolean = true, 21 | val tips: String = "" 22 | 23 | ) : Parcelable { 24 | constructor(parcel: Parcel) : this( 25 | parcel.readString()!!, 26 | parcel.readString()!!, 27 | parcel.readString()!!, 28 | parcel.readString()!!, 29 | parcel.readString()!!, 30 | parcel.readString()!!, 31 | parcel.readString()!!, 32 | parcel.createStringArrayList()!!, 33 | parcel.readString()!!, 34 | parcel.createStringArrayList()!!, 35 | parcel.readByte() != 0.toByte(), 36 | parcel.readByte() != 0.toByte(), 37 | parcel.readString()!! 38 | 39 | ) 40 | 41 | override fun writeToParcel(parcel: Parcel, flags: Int) { 42 | parcel.writeString(gameName) 43 | parcel.writeString(serviceName) 44 | parcel.writeString(packageName) 45 | parcel.writeString(gamePath) 46 | parcel.writeString(modSavePath) 47 | parcel.writeString(antiHarmonyFile) 48 | parcel.writeString(antiHarmonyContent) 49 | parcel.writeStringList(gameFilePath) 50 | parcel.writeString(version) 51 | parcel.writeStringList(modType) 52 | parcel.writeByte(if (isGameFileRepeat) 1 else 0) 53 | parcel.writeByte(if (enableBackup) 1 else 0) 54 | parcel.writeString(tips) 55 | 56 | } 57 | 58 | override fun describeContents(): Int { 59 | return 0 60 | } 61 | 62 | companion object CREATOR : Parcelable.Creator { 63 | override fun createFromParcel(parcel: Parcel): GameInfoBean { 64 | return GameInfoBean(parcel) 65 | } 66 | 67 | override fun newArray(size: Int): Array { 68 | return arrayOfNulls(size) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/bean/GithubBean.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.bean 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import top.laoxin.modmanager.data.network.AppConfig 5 | 6 | data class GithubBean( 7 | @SerializedName("tag_name") val version: String, 8 | @SerializedName("body") val info: String, 9 | @SerializedName("assets") val assets: List 10 | ) { 11 | fun getDownloadLink(): String { 12 | return assets.find { AppConfig.matchVariant(it.downloadLink) }?.downloadLink 13 | ?: assets[0].downloadLink 14 | } 15 | 16 | fun getDownloadLinkUniversal(): String { 17 | return assets.find { it.downloadLink.contains("universal") }?.downloadLink 18 | ?: assets[0].downloadLink 19 | } 20 | } 21 | 22 | data class GitHubAssets(@SerializedName("browser_download_url") val downloadLink: String) 23 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/bean/InfoBean.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.bean 2 | 3 | data class InfoBean( 4 | var version: Double, 5 | var msg: String, 6 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/bean/ModBeanTemp.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.bean 2 | 3 | data class ModBeanTemp( 4 | val name: String, 5 | val modFiles: MutableList, 6 | var readmePath: String?, 7 | var fileReadmePath: String?, 8 | var iconPath: String?, 9 | var images: MutableList, 10 | val isEncrypted: Boolean, 11 | val gamePackageName: String, 12 | val modType: String, 13 | val virtualPaths: String, 14 | val gameModPath: String, 15 | val modPath: String, 16 | val isZip: Boolean = true, 17 | ) 18 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/bean/ScanFileBean.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.bean 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity(tableName = "scanFiles") 7 | data class ScanFileBean ( 8 | @PrimaryKey(autoGenerate = true) val id: Int = 0, 9 | val path: String, 10 | val name: String, 11 | val modifyTime: Long, 12 | val size: Long 13 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/bean/ThanksBean.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.bean 2 | 3 | data class ThanksBean( 4 | val name: String, 5 | val job: String, 6 | val link: String 7 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/network/AppConfig.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.network 2 | 3 | import android.os.Build 4 | 5 | object AppConfig { 6 | 7 | private val abi = Build.SUPPORTED_ABIS[0].takeIf { 8 | it in setOf("arm64-v8a", "x86_64", "armeabi-v7a", "x86") 9 | } ?: "universal" 10 | 11 | fun matchVariant(name: String) = name.contains(abi) 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/network/GithubApiService.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.network 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import retrofit2.Retrofit 6 | import retrofit2.converter.gson.GsonConverterFactory 7 | import retrofit2.http.GET 8 | import top.laoxin.modmanager.data.bean.GithubBean 9 | 10 | private const val GITHUB_URL = 11 | "https://api.github.com" 12 | 13 | val gsonForGithub: Gson = GsonBuilder().create() 14 | 15 | private val retrofit = Retrofit.Builder() 16 | .addConverterFactory(GsonConverterFactory.create(gsonForGithub)) 17 | .baseUrl(GITHUB_URL) 18 | .build() 19 | 20 | interface GithubApiService { 21 | // 获取最新版本 22 | @GET("repos/laoxinH/crosscore-mod-manager/releases/latest") 23 | suspend fun getLatestRelease(): GithubBean 24 | 25 | } 26 | 27 | object GithubApi { 28 | val retrofitService: GithubApiService by lazy { 29 | retrofit.create(GithubApiService::class.java) 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/network/ModManagerApiService.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.network 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import retrofit2.Retrofit 6 | import retrofit2.converter.gson.GsonConverterFactory 7 | import retrofit2.http.GET 8 | import retrofit2.http.Path 9 | import top.laoxin.modmanager.data.bean.DownloadGameConfigBean 10 | import top.laoxin.modmanager.data.bean.GameInfoBean 11 | import top.laoxin.modmanager.data.bean.InfoBean 12 | import top.laoxin.modmanager.data.bean.ThanksBean 13 | 14 | private const val API_URL_JSDELIVR = 15 | "https://cdn.jsdelivr.net" 16 | 17 | val gsonForApi: Gson = GsonBuilder().create() 18 | 19 | private val retrofit = Retrofit.Builder() 20 | .addConverterFactory(GsonConverterFactory.create(gsonForApi)) 21 | .baseUrl(API_URL_JSDELIVR) 22 | .build() 23 | 24 | interface ModManagerApiService { 25 | // 获取游戏配置列表 26 | @GET("gh/laoxinH/crosscore-mod-manager@main/gameConfig/api/gameConfig.json") 27 | suspend fun getGameConfigs(): List 28 | 29 | // 下载游戏配置 30 | @GET("gh/laoxinH/crosscore-mod-manager@main/gameConfig/{name}.json") 31 | suspend fun downloadGameConfig(@Path("name") name: String): GameInfoBean 32 | 33 | // 获取感谢名单 34 | @GET("gh/laoxinH/crosscore-mod-manager@main/gameConfig/api/thanks.json") 35 | suspend fun getThanksList(): List 36 | 37 | // 获取最新信息 38 | @GET("gh/laoxinH/crosscore-mod-manager@main/gameConfig/api/information.json") 39 | suspend fun getInfo(): InfoBean 40 | 41 | } 42 | 43 | object ModManagerApi { 44 | val retrofitService: ModManagerApiService by lazy { 45 | retrofit.create(ModManagerApiService::class.java) 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/Converters.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository 2 | 3 | import androidx.room.TypeConverter 4 | import com.google.gson.Gson 5 | import com.google.gson.reflect.TypeToken 6 | 7 | class Converters { 8 | 9 | @TypeConverter 10 | fun fromString(value: String): List { 11 | val listType = object : TypeToken>() {}.type 12 | return Gson().fromJson(value, listType) 13 | } 14 | 15 | @TypeConverter 16 | fun fromList(list: List): String { 17 | val gson = Gson() 18 | return gson.toJson(list) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/ModManagerDatabase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import androidx.room.TypeConverters 8 | import androidx.room.migration.Migration 9 | import androidx.sqlite.db.SupportSQLiteDatabase 10 | import top.laoxin.modmanager.data.bean.AntiHarmonyBean 11 | import top.laoxin.modmanager.data.bean.BackupBean 12 | import top.laoxin.modmanager.data.bean.ModBean 13 | import top.laoxin.modmanager.data.bean.ScanFileBean 14 | import top.laoxin.modmanager.data.repository.antiharmony.AntiHarmonyDao 15 | import top.laoxin.modmanager.data.repository.backup.BackupDao 16 | import top.laoxin.modmanager.data.repository.mod.ModDao 17 | import top.laoxin.modmanager.data.repository.scanfile.ScanFileDao 18 | 19 | @Database( 20 | entities = [ModBean::class, BackupBean::class, AntiHarmonyBean::class, ScanFileBean::class], 21 | version = 5, 22 | exportSchema = false 23 | ) 24 | 25 | @TypeConverters(Converters::class) 26 | abstract class ModManagerDatabase : RoomDatabase() { 27 | 28 | abstract fun modDao(): ModDao 29 | abstract fun backupDao(): BackupDao 30 | abstract fun antiHarmonyDao(): AntiHarmonyDao 31 | abstract fun scanFileDao(): ScanFileDao 32 | 33 | 34 | companion object { 35 | private val MIGRATION_2_3 = object : Migration(2, 3) { 36 | override fun migrate(database: SupportSQLiteDatabase) { 37 | // 在这里添加创建新表的SQL语句 38 | database.execSQL( 39 | "CREATE TABLE antiHarmony (\n" + 40 | " id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" + 41 | " gamePackageName TEXT NOT NULL,\n" + 42 | " isEnable INTEGER NOT NULL\n" + 43 | ")" 44 | ) 45 | database.execSQL("CREATE INDEX index_antiHarmony_gamePackageName ON antiHarmony(gamePackageName)") 46 | } 47 | } 48 | 49 | // 数据库迁移3-4 50 | private val MIGRATION_3_4 = object : Migration(3, 4) { 51 | override fun migrate(database: SupportSQLiteDatabase) { 52 | // mod表添加字段val virtualPaths: String?, 53 | database.execSQL("ALTER TABLE mods ADD COLUMN virtualPaths TEXT") 54 | // 向所有virtualPaths字段插入""值 55 | database.execSQL("UPDATE mods SET virtualPaths = ''") 56 | } 57 | } 58 | 59 | // 数据库迁移4-5 60 | private val MIGRATION_4_5 = object : Migration(4, 5) { 61 | override fun migrate(database: SupportSQLiteDatabase) { 62 | // Perform the necessary schema changes 63 | database.execSQL("CREATE TABLE IF NOT EXISTS `scanFiles` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `path` TEXT NOT NULL, `name` TEXT NOT NULL, `modifyTime` INTEGER NOT NULL, `size` INTEGER NOT NULL)") 64 | } 65 | } 66 | 67 | @Volatile 68 | private var Instance: ModManagerDatabase? = null 69 | fun getDatabase(context: Context): ModManagerDatabase { 70 | return Instance ?: synchronized(this) { 71 | Room.databaseBuilder(context, ModManagerDatabase::class.java, "mod_database") 72 | .addMigrations(MIGRATION_2_3) 73 | .addMigrations(MIGRATION_3_4) 74 | .addMigrations(MIGRATION_4_5) 75 | //.allowMainThreadQueries() // 允许在主线程查询数据 76 | .build() 77 | .also { Instance = it } 78 | } 79 | } 80 | } 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/UserPreferencesRepository.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import androidx.datastore.core.DataStore 6 | import androidx.datastore.preferences.core.Preferences 7 | import androidx.datastore.preferences.core.booleanPreferencesKey 8 | import androidx.datastore.preferences.core.edit 9 | import androidx.datastore.preferences.core.emptyPreferences 10 | import androidx.datastore.preferences.core.intPreferencesKey 11 | import androidx.datastore.preferences.core.stringPreferencesKey 12 | import androidx.datastore.preferences.preferencesDataStore 13 | import dagger.hilt.android.qualifiers.ApplicationContext 14 | import kotlinx.coroutines.flow.Flow 15 | import kotlinx.coroutines.flow.catch 16 | import kotlinx.coroutines.flow.flowOf 17 | import kotlinx.coroutines.flow.map 18 | import top.laoxin.modmanager.constant.UserPreferencesKeys 19 | import java.io.IOException 20 | import javax.inject.Inject 21 | import javax.inject.Singleton 22 | 23 | private const val PREFERENCE_NAME = "mod_manager_preferences" 24 | private val Context.dataStore: DataStore by preferencesDataStore( 25 | name = PREFERENCE_NAME 26 | ) 27 | 28 | @Singleton 29 | class UserPreferencesRepository @Inject constructor( 30 | @param:ApplicationContext private val context: Context 31 | ) { 32 | companion object { 33 | val PREFERENCES_KEYS = mapOf( 34 | UserPreferencesKeys.SELECTED_GAME to intPreferencesKey(UserPreferencesKeys.SELECTED_GAME), 35 | UserPreferencesKeys.SCAN_QQ_DIRECTORY to booleanPreferencesKey(UserPreferencesKeys.SCAN_QQ_DIRECTORY), 36 | UserPreferencesKeys.SELECTED_DIRECTORY to stringPreferencesKey(UserPreferencesKeys.SELECTED_DIRECTORY), 37 | UserPreferencesKeys.SCAN_DOWNLOAD to booleanPreferencesKey(UserPreferencesKeys.SCAN_DOWNLOAD), 38 | UserPreferencesKeys.OPEN_PERMISSION_REQUEST_DIALOG to booleanPreferencesKey( 39 | UserPreferencesKeys.OPEN_PERMISSION_REQUEST_DIALOG 40 | ), 41 | UserPreferencesKeys.SCAN_DIRECTORY_MODS to booleanPreferencesKey(UserPreferencesKeys.SCAN_DIRECTORY_MODS), 42 | UserPreferencesKeys.DELETE_UNZIP_DIRECTORY to booleanPreferencesKey(UserPreferencesKeys.DELETE_UNZIP_DIRECTORY), 43 | UserPreferencesKeys.SHOW_CATEGORY_VIEW to booleanPreferencesKey(UserPreferencesKeys.SHOW_CATEGORY_VIEW), 44 | UserPreferencesKeys.USER_TIPS to booleanPreferencesKey(UserPreferencesKeys.USER_TIPS) 45 | ) 46 | const val TAG = "UserPreferencesRepo" 47 | } 48 | 49 | suspend fun savePreference(key: String, value: T) { 50 | val preferenceKey = PREFERENCES_KEYS[key] 51 | if (preferenceKey != null) { 52 | context.dataStore.edit { preferences -> 53 | @Suppress("UNCHECKED_CAST") 54 | preferences[preferenceKey as Preferences.Key] = value 55 | } 56 | } 57 | } 58 | 59 | fun getPreferenceFlow(key: String, defaultValue: T): Flow { 60 | 61 | val preferenceKey = PREFERENCES_KEYS[key] 62 | return if (preferenceKey != null) { 63 | context.dataStore.data.catch { 64 | if (it is IOException) { 65 | Log.e(TAG, "Error reading preferences.", it) 66 | emit(emptyPreferences()) 67 | } else { 68 | throw it 69 | } 70 | }.map { preferences -> 71 | @Suppress("UNCHECKED_CAST") 72 | preferences[preferenceKey as Preferences.Key] ?: defaultValue 73 | } 74 | } else { 75 | flowOf(defaultValue) 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/VersionRepository.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.core.edit 7 | import androidx.datastore.preferences.core.stringPreferencesKey 8 | import androidx.datastore.preferences.preferencesDataStore 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import kotlinx.coroutines.flow.first 11 | import top.laoxin.modmanager.App 12 | import top.laoxin.modmanager.BuildConfig 13 | import top.laoxin.modmanager.R 14 | import javax.inject.Inject 15 | import javax.inject.Singleton 16 | 17 | private val Context.dataStore: DataStore by preferencesDataStore(name = "version_data_store") 18 | 19 | @Singleton 20 | class VersionRepository @Inject constructor( 21 | @param:ApplicationContext private val context: Context 22 | ) { 23 | private val versionDataStore = context.dataStore 24 | private val versionKey = stringPreferencesKey("version") 25 | private val versionInfoKey = stringPreferencesKey("version_info") 26 | private val versionUrlKey = stringPreferencesKey("version_url") 27 | private val universalUrlKey = stringPreferencesKey("universal_url") 28 | 29 | // 存储版本号 30 | suspend fun saveVersion(version: String) { 31 | versionDataStore.edit { preferences -> 32 | preferences[versionKey] = version 33 | } 34 | } 35 | 36 | // 获取版本号 37 | suspend fun getVersion(): String { 38 | val preferences = versionDataStore.data.first() 39 | return preferences[versionKey] ?: BuildConfig.VERSION_NAME 40 | } 41 | 42 | // 存储版本信息 43 | suspend fun saveVersionInfo(versionInfo: String) { 44 | versionDataStore.edit { preferences -> 45 | preferences[versionInfoKey] = versionInfo 46 | } 47 | } 48 | 49 | // 获取版本信息 50 | suspend fun getVersionInfo(): String { 51 | val preferences = versionDataStore.data.first() 52 | return preferences[versionInfoKey] ?: "" 53 | } 54 | 55 | // 存储版本下载地址 56 | suspend fun saveVersionUrl(versionUrl: String) { 57 | versionDataStore.edit { preferences -> 58 | preferences[versionUrlKey] = versionUrl 59 | } 60 | } 61 | 62 | // 获取版本下载地址 63 | suspend fun getVersionUrl(): String { 64 | val preferences = versionDataStore.data.first() 65 | return preferences[versionUrlKey] ?: App.get() 66 | .getString(R.string.github_url_releases_latest) 67 | } 68 | 69 | // 存储通用下载地址 70 | suspend fun saveUniversalUrl(universalUrl: String) { 71 | versionDataStore.edit { preferences -> 72 | preferences[universalUrlKey] = universalUrl 73 | } 74 | } 75 | 76 | // 获取通用下载地址 77 | suspend fun getUniversalUrl(): String { 78 | val preferences = versionDataStore.data.first() 79 | return preferences[universalUrlKey] ?: App.get() 80 | .getString(R.string.github_url_releases_latest) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/antiharmony/AntiHarmonyDao.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.antiharmony 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import kotlinx.coroutines.flow.Flow 7 | import top.laoxin.modmanager.data.bean.AntiHarmonyBean 8 | 9 | @Dao 10 | interface AntiHarmonyDao { 11 | // 插入数据 12 | @Insert(onConflict = OnConflictStrategy.IGNORE) // 如果插入的数据已经存在,则忽略 13 | suspend fun insert(antiHarmonyBean: AntiHarmonyBean) 14 | 15 | // 通过gamePackageName更新数据 16 | @androidx.room.Query("UPDATE antiHarmony SET isEnable = :isEnable WHERE gamePackageName = :gamePackageName") 17 | suspend fun updateByGamePackageName(gamePackageName: String, isEnable: Boolean) 18 | 19 | // 通过gamePackageName读取 20 | @androidx.room.Query("SELECT * from antiHarmony WHERE gamePackageName = :gamePackageName") 21 | fun getByGamePackageName(gamePackageName: String): Flow 22 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/antiharmony/AntiHarmonyRepository.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.antiharmony 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.bean.AntiHarmonyBean 5 | 6 | interface AntiHarmonyRepository { 7 | // 通过gamePackageName更新数据 8 | suspend fun updateByGamePackageName(gamePackageName: String, isEnable: Boolean) 9 | 10 | // 通过gamePackageName读取 11 | fun getByGamePackageName(gamePackageName: String): Flow 12 | 13 | // 插入数据 14 | suspend fun insert(antiHarmonyBean: AntiHarmonyBean) 15 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/antiharmony/OfflineAntiHarmonyRepository.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.antiharmony 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.bean.AntiHarmonyBean 5 | import top.laoxin.modmanager.data.repository.ModManagerDatabase 6 | import javax.inject.Inject 7 | 8 | class OfflineAntiHarmonyRepository @Inject constructor(private val database: ModManagerDatabase) : 9 | AntiHarmonyRepository { 10 | private val antiHarmonyDao = database.antiHarmonyDao() 11 | override suspend fun updateByGamePackageName(gamePackageName: String, isEnable: Boolean) { 12 | antiHarmonyDao.updateByGamePackageName(gamePackageName, isEnable) 13 | } 14 | 15 | override fun getByGamePackageName(gamePackageName: String): Flow { 16 | return antiHarmonyDao.getByGamePackageName(gamePackageName) 17 | } 18 | 19 | override suspend fun insert(antiHarmonyBean: AntiHarmonyBean) { 20 | antiHarmonyDao.insert(antiHarmonyBean) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/backup/BackupDao.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.backup 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import kotlinx.coroutines.flow.Flow 7 | import top.laoxin.modmanager.data.bean.BackupBean 8 | 9 | 10 | @Dao 11 | interface BackupDao { 12 | 13 | // 插入数据 14 | @Insert(onConflict = OnConflictStrategy.IGNORE) // 如果插入的数据已经存在,则忽略 15 | suspend fun insert(backupBean: BackupBean) 16 | 17 | // 获取所有数据 18 | @androidx.room.Query("SELECT * from backups") 19 | fun getAll(): List 20 | 21 | @androidx.room.Query("SELECT * from backups WHERE modName = :modPath") 22 | fun getByModPath(modPath: String): Flow> 23 | 24 | @Insert(onConflict = OnConflictStrategy.IGNORE) 25 | suspend fun insertAll(backups: List) 26 | 27 | @androidx.room.Query("DELETE FROM backups") 28 | fun deleteAll() 29 | 30 | 31 | //通过gamePackageName删除 32 | @androidx.room.Query("DELETE FROM backups WHERE gamePackageName = :gamePackageName") 33 | fun deleteByGamePackageName(gamePackageName: String) 34 | 35 | 36 | // 通过modName和gamePackageName查询backups 37 | @androidx.room.Query("SELECT * from backups WHERE modName = :modName AND gamePackageName = :gamePackageName") 38 | fun getByModNameAndGamePackageName( 39 | modName: String, 40 | gamePackageName: String 41 | ): Flow> 42 | 43 | 44 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/backup/BackupRepository.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.backup 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.bean.BackupBean 5 | 6 | interface BackupRepository { 7 | 8 | // 插入数据 9 | suspend fun insert(backupBean: BackupBean) 10 | 11 | 12 | // 通过modPath查询 13 | suspend fun getByModPath(modPath: String): Flow> 14 | 15 | // 插入所有数据 16 | suspend fun insertAll(backups: List) 17 | 18 | fun deleteAllBackups() 19 | 20 | // 通过包名删除mods 21 | suspend fun deleteByGamePackageName(gamePackageName: String) 22 | 23 | // 通过modName和gamePackageName查询backups 24 | fun getByModNameAndGamePackageName( 25 | modName: String, 26 | gamePackageName: String 27 | ): Flow> 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/backup/OfflineBackupRepository.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.backup 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.bean.BackupBean 5 | import top.laoxin.modmanager.data.repository.ModManagerDatabase 6 | import javax.inject.Inject 7 | 8 | class OfflineBackupRepository @Inject constructor(private val database: ModManagerDatabase): 9 | BackupRepository { 10 | 11 | private val backupDao = database.backupDao() 12 | 13 | override suspend fun getByModPath(modPath: String): Flow> { 14 | return backupDao.getByModPath(modPath) 15 | } 16 | 17 | override suspend fun insertAll(backups: List) { 18 | backupDao.insertAll(backups) 19 | } 20 | 21 | override fun deleteAllBackups() { 22 | backupDao.deleteAll() 23 | } 24 | 25 | override suspend fun deleteByGamePackageName(gamePackageName: String) { 26 | backupDao.deleteByGamePackageName(gamePackageName) 27 | } 28 | 29 | override fun getByModNameAndGamePackageName( 30 | modName: String, 31 | gamePackageName: String 32 | ): Flow> { 33 | return backupDao.getByModNameAndGamePackageName(modName, gamePackageName) 34 | } 35 | 36 | 37 | override suspend fun insert(backupBean: BackupBean) { 38 | backupDao.insert(backupBean) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/mod/ModDao.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.mod 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Delete 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import androidx.room.Update 9 | import kotlinx.coroutines.flow.Flow 10 | import top.laoxin.modmanager.data.bean.ModBean 11 | 12 | @Dao 13 | interface ModDao { 14 | 15 | // 插入数据 16 | @Insert(onConflict = OnConflictStrategy.IGNORE) // 如果插入的数据已经存在,则忽略 17 | suspend fun insert(modBean: ModBean) 18 | 19 | // 更新数据 20 | @Update 21 | suspend fun update(modBean: ModBean) 22 | 23 | // 删除数据 24 | @Delete 25 | suspend fun delete(modBean: ModBean) 26 | 27 | // 通过id查询数据 28 | @Query("SELECT * from mods WHERE id = :id") 29 | suspend fun getModById(id: Int): ModBean 30 | 31 | // 查询所有数据 32 | @Query("SELECT * from mods ORDER BY date DESC") 33 | fun getAll(): Flow> 34 | 35 | // 通过gamePackageName和名字模糊查询 36 | @Query("SELECT * from mods WHERE gamePackageName = :gamePackageName AND name LIKE '%' || :name || '%'") 37 | fun getModsByGamePackageNameAndName(gamePackageName: String, name: String): Flow> 38 | 39 | // 通过List插入数据 40 | @Insert(onConflict = OnConflictStrategy.IGNORE) 41 | suspend fun insertAll(mods: List) 42 | 43 | 44 | //通过gamePackageName 查询关闭的mod 45 | @Query("SELECT * from mods WHERE gamePackageName = :gamePackageName AND isEnable = 0 ORDER BY id DESC") 46 | fun getDisableModsByGamePackageName(gamePackageName: String): Flow> 47 | 48 | 49 | // 通过list批量删除mods 50 | @Delete 51 | suspend fun deleteMods(mods: List) 52 | 53 | 54 | // 通过List更新 55 | @Update 56 | suspend fun updateMods(mods: List) 57 | 58 | // 通过gamePackageName查询mods 59 | @Query("SELECT * from mods WHERE gamePackageName = :gamePackageName ORDER BY id DESC") 60 | fun getModsByGamePackageName(gamePackageName: String): Flow> 61 | 62 | // 通过gamePackageName查询mods数量 63 | @Query("SELECT COUNT(*) FROM mods WHERE gamePackageName = :gamePackageName") 64 | fun getModsCountByGamePackageName(gamePackageName: String): Flow 65 | 66 | // 通过gamePackageName查询已开启的mods 67 | @Query("SELECT * from mods WHERE gamePackageName = :gamePackageName AND isEnable = 1 ORDER BY id DESC") 68 | fun getEnableModsByGamePackageName(gamePackageName: String): Flow> 69 | 70 | // 通过path和gamePackageName查询mods 71 | @Query("SELECT * from mods WHERE path = :path AND gamePackageName = :gamePackageName ORDER BY id DESC") 72 | fun getModsByPathAndGamePackageName(path: String, gamePackageName: String): Flow> 73 | 74 | 75 | //通过包名查询已开启的mods数量 76 | @Query("SELECT COUNT(*) FROM mods WHERE gamePackageName = :gamePackageName AND isEnable = 1") 77 | fun getModsEnableCountByGamePackageName(gamePackageName: String): Flow 78 | 79 | // 用过ids查询mods 80 | @Query("SELECT * from mods WHERE id IN (:ids)") 81 | fun getModsByIds(ids: List): Flow> 82 | 83 | // 通过modpath查询mod数量` 84 | @Query("SELECT COUNT(*) FROM mods WHERE path = :path") 85 | fun getModsCountByPath(path: String): Flow 86 | 87 | // 通过modpath 模糊查询mods 88 | @Query("SELECT * from mods WHERE path LIKE '%' || :path || '%'") 89 | fun getModsByPath(path: String): Flow> 90 | 91 | // 删除数据库中未启用的mod 92 | @Query("DELETE FROM mods WHERE isEnable = 0 AND gamePackageName = :gamePackageName") 93 | fun deleteDisableMods(gamePackageName: String) 94 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/mod/ModRepository.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.mod 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.bean.ModBean 5 | 6 | interface ModRepository { 7 | 8 | // 插入数据 9 | suspend fun insertMod(mod: ModBean) 10 | 11 | // 更新数据 12 | suspend fun updateMod(mod: ModBean) 13 | 14 | // 删除数据 15 | suspend fun deleteMod(mod: ModBean) 16 | 17 | // 通过id查询数据 18 | suspend fun getModById(id: Int): ModBean 19 | 20 | // 获取所有数据 21 | fun getAllIModsStream(): Flow> 22 | 23 | // 通过gamePackageName和名字模糊查询 24 | fun search(name: String, gamePackageName: String): Flow> 25 | 26 | // 通过List插入数据 27 | suspend fun insertAll(mods: List) 28 | 29 | //通过gamePackageName 查询关闭的mod 30 | fun getDisableMods(gamePackageName: String): Flow> 31 | 32 | // 通过list批量删除mods 33 | suspend fun deleteAll(mods: List) 34 | 35 | // 通过List更新 36 | suspend fun updateAll(mods: List) 37 | 38 | // // 通过gamePackageName查询mods 39 | fun getModsByGamePackageName(gamePackageName: String): Flow> 40 | 41 | // // 通过gamePackageName查询mods数量 42 | fun getModsCountByGamePackageName(gamePackageName: String): Flow 43 | 44 | // 通过gamePackageName查询已开启的mods 45 | fun getEnableMods(gamePackageName: String): Flow> 46 | 47 | 48 | // 通过path和gamePackageName查询mods 49 | fun getModsByPathAndGamePackageName(path: String, gamePackageName: String): Flow> 50 | 51 | //通过包名查询已开启的mods数量 52 | fun getEnableModsCountByGamePackageName(gamePackageName: String): Flow 53 | 54 | // 通过ids查询mods 55 | fun getModsByIds(ids: List): Flow> 56 | 57 | 58 | // 通过path查询mod数量 59 | fun getModsCountByPath(path: String): Flow 60 | 61 | // 通过path查询mods 62 | fun getModsByPath(path: String): Flow> 63 | 64 | // 删除数据库中未启用的mod 65 | fun deleteDisableMods(gamePackageName: String) 66 | 67 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/mod/OfflineModsRepository.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.mod 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.bean.ModBean 5 | import top.laoxin.modmanager.data.repository.ModManagerDatabase 6 | import javax.inject.Inject 7 | 8 | class OfflineModsRepository @Inject constructor(private val database: ModManagerDatabase) : 9 | ModRepository { 10 | private val modDao = database.modDao() 11 | override fun getAllIModsStream(): Flow> { 12 | return modDao.getAll() 13 | } 14 | 15 | override fun search(name: String, gamePackageName: String): Flow> { 16 | return modDao.getModsByGamePackageNameAndName(gamePackageName, name) 17 | } 18 | 19 | 20 | override suspend fun insertMod(mod: ModBean) { 21 | modDao.insert(mod) 22 | } 23 | 24 | override suspend fun deleteMod(mod: ModBean) { 25 | modDao.delete(mod) 26 | } 27 | 28 | override suspend fun updateMod(mod: ModBean) { 29 | modDao.update(mod) 30 | } 31 | 32 | override suspend fun getModById(id: Int): ModBean { 33 | return modDao.getModById(id) 34 | } 35 | 36 | 37 | override suspend fun insertAll(mods: List) { 38 | modDao.insertAll(mods) 39 | } 40 | 41 | override fun getDisableMods(gamePackageName: String): Flow> { 42 | return modDao.getDisableModsByGamePackageName(gamePackageName) 43 | } 44 | 45 | override suspend fun deleteAll(mods: List) { 46 | modDao.deleteMods(mods) 47 | } 48 | 49 | 50 | override suspend fun updateAll(mods: List) { 51 | modDao.updateMods(mods) 52 | } 53 | 54 | override fun getModsByGamePackageName(gamePackageName: String): Flow> { 55 | return modDao.getModsByGamePackageName(gamePackageName) 56 | } 57 | 58 | override fun getModsCountByGamePackageName(gamePackageName: String): Flow { 59 | return modDao.getModsCountByGamePackageName(gamePackageName) 60 | } 61 | 62 | override fun getEnableMods(gamePackageName: String): Flow> { 63 | return modDao.getEnableModsByGamePackageName(gamePackageName) 64 | } 65 | 66 | 67 | override fun getModsByPathAndGamePackageName( 68 | path: String, 69 | gamePackageName: String 70 | ): Flow> { 71 | return modDao.getModsByPathAndGamePackageName(path, gamePackageName) 72 | } 73 | 74 | override fun getEnableModsCountByGamePackageName(gamePackageName: String): Flow { 75 | return modDao.getModsEnableCountByGamePackageName(gamePackageName) 76 | } 77 | 78 | override fun getModsByIds(ids: List): Flow> { 79 | return modDao.getModsByIds(ids) 80 | } 81 | 82 | override fun getModsCountByPath(path: String): Flow { 83 | return modDao.getModsCountByPath(path) 84 | } 85 | 86 | override fun getModsByPath(path: String): Flow> { 87 | return modDao.getModsByPath(path) 88 | } 89 | 90 | override fun deleteDisableMods(gamePackageName: String) { 91 | modDao.deleteDisableMods(gamePackageName) 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/scanfile/OfflineScanFileRepository.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.scanfile 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.bean.ScanFileBean 5 | import top.laoxin.modmanager.data.repository.ModManagerDatabase 6 | import javax.inject.Inject 7 | import javax.inject.Singleton 8 | 9 | @Singleton 10 | class OfflineScanFileRepository @Inject constructor(private val database: ModManagerDatabase) : 11 | ScanFileRepository { 12 | private val scanFileDao = database.scanFileDao() 13 | override suspend fun insert(scanFile: ScanFileBean) { 14 | scanFileDao.insert(scanFile) 15 | } 16 | 17 | override fun getAll(): Flow> { 18 | return scanFileDao.getAll() 19 | } 20 | 21 | override fun getByPath(path: String): Flow> { 22 | return scanFileDao.getByPath(path) 23 | } 24 | 25 | override suspend fun insertAll(scanFiles: List) { 26 | scanFileDao.insertAll(scanFiles) 27 | } 28 | 29 | override fun deleteAll() { 30 | scanFileDao.deleteAll() 31 | } 32 | 33 | override fun delete(it: ScanFileBean) { 34 | scanFileDao.delete(it) 35 | } 36 | 37 | override fun update(scanFile: ScanFileBean) { 38 | scanFileDao.update(scanFile) 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/scanfile/ScanFileDao.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.scanfile 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import kotlinx.coroutines.flow.Flow 7 | import top.laoxin.modmanager.data.bean.ScanFileBean 8 | 9 | 10 | @Dao 11 | interface ScanFileDao { 12 | // 插入一条数据` 13 | @Insert(onConflict = OnConflictStrategy.REPLACE) // 如果插入的数据已经存在,则替换 14 | suspend fun insert(scanFile: ScanFileBean) 15 | 16 | // 获取所有数据 17 | @androidx.room.Query("SELECT * from scanFiles") 18 | fun getAll(): Flow> 19 | 20 | // 通过path查询 21 | @androidx.room.Query("SELECT * from scanFiles WHERE path = :path") 22 | fun getByPath(path: String): Flow> 23 | 24 | // 插入多条数据 25 | @Insert(onConflict = OnConflictStrategy.REPLACE) 26 | suspend fun insertAll(scanFiles: List) 27 | 28 | // 删除所有数据 29 | @androidx.room.Query("DELETE FROM scanFiles") 30 | fun deleteAll() 31 | 32 | // 删除一条数据 33 | @androidx.room.Delete 34 | fun delete(it: ScanFileBean) 35 | 36 | // 更新一条数据 37 | @androidx.room.Update 38 | fun update(scanFile: ScanFileBean) 39 | 40 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/data/repository/scanfile/ScanFileRepository.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.data.repository.scanfile 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.bean.ScanFileBean 5 | 6 | interface ScanFileRepository { 7 | 8 | // 插入一条数据 9 | suspend fun insert(scanFile: ScanFileBean) 10 | 11 | // 获取所有数据 12 | fun getAll(): Flow> 13 | 14 | // 通过path查询 15 | fun getByPath(path: String): Flow> 16 | 17 | // 插入多条数据 18 | suspend fun insertAll(scanFiles: List) 19 | 20 | // 删除所有数据 21 | fun deleteAll() 22 | 23 | fun delete(it: ScanFileBean) 24 | 25 | // 更新一条数据 26 | fun update(scanFile: ScanFileBean) 27 | 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.di 2 | 3 | import android.content.Context 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import dagger.hilt.components.SingletonComponent 9 | import top.laoxin.modmanager.data.repository.ModManagerDatabase 10 | import top.laoxin.modmanager.tools.PermissionTools 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | object AppModule { 16 | 17 | @Provides 18 | @Singleton 19 | fun provideContext(@ApplicationContext context: Context): Context { 20 | return context 21 | } 22 | 23 | @Provides 24 | @Singleton 25 | fun provideModManagerDatabase(@ApplicationContext context: Context): ModManagerDatabase { 26 | return ModManagerDatabase.getDatabase(context) 27 | } 28 | 29 | @Provides 30 | @Singleton 31 | fun providePermissionTools(): PermissionTools { 32 | return PermissionTools 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/di/FileToolsModule.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.di 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import top.laoxin.modmanager.tools.filetools.BaseFileTools 8 | import top.laoxin.modmanager.tools.filetools.impl.DocumentFileTools 9 | import top.laoxin.modmanager.tools.filetools.impl.FileTools 10 | import top.laoxin.modmanager.tools.filetools.impl.ShizukuFileTools 11 | import javax.inject.Qualifier 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | abstract class FileToolsModule { 15 | 16 | @Qualifier 17 | @Retention(AnnotationRetention.BINARY) 18 | annotation class FileToolsImpl 19 | 20 | @Qualifier 21 | @Retention(AnnotationRetention.BINARY) 22 | annotation class ShizukuFileToolsImpl 23 | 24 | @Qualifier 25 | @Retention(AnnotationRetention.BINARY) 26 | annotation class DocumentFileToolsImpl 27 | @Binds 28 | @FileToolsImpl 29 | abstract fun bindFileTools(fileTools: FileTools): BaseFileTools 30 | 31 | @Binds 32 | @ShizukuFileToolsImpl 33 | abstract fun bindShizukuFileTools(shizukuFileTools: ShizukuFileTools): BaseFileTools 34 | 35 | @Binds 36 | @DocumentFileToolsImpl 37 | abstract fun bindDocumentFileTools(documentFileTools: DocumentFileTools): BaseFileTools 38 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.di 2 | 3 | // app/src/main/kotlin/top/laoxin/modmanager/di/RepositoryModule.kt 4 | 5 | 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import top.laoxin.modmanager.data.repository.antiharmony.AntiHarmonyRepository 11 | import top.laoxin.modmanager.data.repository.antiharmony.OfflineAntiHarmonyRepository 12 | import top.laoxin.modmanager.data.repository.backup.BackupRepository 13 | import top.laoxin.modmanager.data.repository.backup.OfflineBackupRepository 14 | import top.laoxin.modmanager.data.repository.mod.ModRepository 15 | import top.laoxin.modmanager.data.repository.mod.OfflineModsRepository 16 | import top.laoxin.modmanager.data.repository.scanfile.OfflineScanFileRepository 17 | import top.laoxin.modmanager.data.repository.scanfile.ScanFileRepository 18 | import javax.inject.Singleton 19 | 20 | @Module 21 | @InstallIn(SingletonComponent::class) 22 | abstract class RepositoryModule { 23 | 24 | @Binds 25 | @Singleton 26 | abstract fun bindModRepository( 27 | offlineModsRepository: OfflineModsRepository 28 | ): ModRepository 29 | 30 | @Binds 31 | @Singleton 32 | abstract fun bindBackupRepository( 33 | offlineBackupRepository: OfflineBackupRepository 34 | ): BackupRepository 35 | 36 | @Binds 37 | @Singleton 38 | abstract fun bindAntiHarmonyRepository( 39 | offlineAntiHarmonyRepository: OfflineAntiHarmonyRepository 40 | ): AntiHarmonyRepository 41 | 42 | @Binds 43 | @Singleton 44 | abstract fun bindScanFileRepository( 45 | offlineScanFileRepository: OfflineScanFileRepository 46 | ): ScanFileRepository 47 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/di/SpecialGameToolsModule.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.di 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import top.laoxin.modmanager.tools.specialGameTools.ArknightsTools 8 | import top.laoxin.modmanager.tools.specialGameTools.BaseSpecialGameTools 9 | import top.laoxin.modmanager.tools.specialGameTools.ProjectSnowTools 10 | import javax.inject.Qualifier 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | abstract class SpecialGameToolsModule { 14 | 15 | @Qualifier 16 | @Retention(AnnotationRetention.BINARY) 17 | annotation class ArknightsToolsImpl 18 | 19 | @Qualifier 20 | @Retention(AnnotationRetention.BINARY) 21 | annotation class ProjectSnowToolsImpl 22 | 23 | 24 | @Binds 25 | @ArknightsToolsImpl 26 | abstract fun bindArknightsTools(arknightsTools: ArknightsTools): BaseSpecialGameTools 27 | 28 | @Binds 29 | @ProjectSnowToolsImpl 30 | abstract fun bindProjectSnowTools(projectSnowTools: ProjectSnowTools): BaseSpecialGameTools 31 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/app/CheckPermissionUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.app 2 | 3 | import top.laoxin.modmanager.constant.PathType 4 | import top.laoxin.modmanager.tools.PermissionTools 5 | import javax.inject.Inject 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | class CheckPermissionUserCase @Inject constructor( 10 | private val permissionTools: PermissionTools, 11 | ) { 12 | 13 | operator fun invoke(path: String): Boolean { 14 | return permissionTools.checkPermission(path) != PathType.NULL 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/app/CheckUpdateUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.app 2 | 3 | import android.util.Log 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.withContext 6 | import top.laoxin.modmanager.data.repository.VersionRepository 7 | import top.laoxin.modmanager.data.network.GithubApi 8 | import javax.inject.Inject 9 | import javax.inject.Singleton 10 | 11 | @Singleton 12 | class CheckUpdateUserCase @Inject constructor( 13 | private val versionRepository: VersionRepository 14 | ) { 15 | suspend operator fun invoke(currentVersion: String): Triple, List, Boolean> = 16 | withContext(Dispatchers.IO) { 17 | kotlin.runCatching { 18 | GithubApi.retrofitService.getLatestRelease() 19 | }.onFailure { exception -> 20 | if (versionRepository.getVersion() != currentVersion) { 21 | return@withContext Triple( 22 | listOf( 23 | versionRepository.getVersionUrl(), 24 | versionRepository.getUniversalUrl() 25 | ), 26 | listOf(versionRepository.getVersionInfo(), versionRepository.getVersion()), 27 | true 28 | ) 29 | } 30 | return@withContext Triple( 31 | listOf(), 32 | listOf(), 33 | false 34 | ) 35 | }.onSuccess { release -> 36 | if (release.version != currentVersion) { 37 | Log.d("Updater", "Update available: $release") 38 | versionRepository.saveVersion(release.version) 39 | versionRepository.saveVersionInfo(release.info) 40 | versionRepository.saveVersionUrl(release.getDownloadLink()) 41 | versionRepository.saveUniversalUrl(release.getDownloadLinkUniversal()) 42 | return@withContext Triple( 43 | listOf(release.getDownloadLink(), release.getDownloadLinkUniversal()), 44 | listOf(release.info, release.version), 45 | true 46 | ) 47 | } else { 48 | Log.d("Updater", "No update available") 49 | } 50 | } 51 | return@withContext Triple( 52 | listOf(), 53 | listOf(), 54 | false 55 | ) 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/app/GetInformationUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.app 2 | 3 | import android.util.Log 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.withContext 6 | import top.laoxin.modmanager.data.bean.InfoBean 7 | import top.laoxin.modmanager.data.network.ModManagerApi 8 | import top.laoxin.modmanager.di.FileToolsModule.FileToolsImpl 9 | import top.laoxin.modmanager.tools.filetools.BaseFileTools 10 | import top.laoxin.modmanager.tools.manager.AppPathsManager 11 | import java.nio.file.Paths 12 | import javax.inject.Inject 13 | import javax.inject.Singleton 14 | 15 | @Singleton 16 | class GetInformationUserCase @Inject constructor( 17 | @param:FileToolsImpl private val fileTools: BaseFileTools, 18 | private val appPathsManager: AppPathsManager 19 | ) { 20 | suspend operator fun invoke(): InfoBean? = withContext(Dispatchers.IO) { 21 | kotlin.runCatching { 22 | ModManagerApi.retrofitService.getInfo() 23 | }.onFailure { 24 | Log.e("ConsoleViewModel", "信息提示: $it") 25 | }.onSuccess { info -> 26 | if (info.version > getInfoVersion()) { 27 | val path = 28 | Paths.get(appPathsManager.getMyAppPath(), "informationVersion").toString() 29 | if (fileTools.isFileExist(path)) { 30 | fileTools.deleteFile(path) 31 | } 32 | fileTools.writeFile( 33 | appPathsManager.getMyAppPath(), 34 | "informationVersion", 35 | info.version.toString() 36 | ) 37 | return@withContext info 38 | } 39 | } 40 | return@withContext null 41 | } 42 | 43 | 44 | private fun getInfoVersion(): Double { 45 | val readFile = fileTools.readFile( 46 | Paths.get(appPathsManager.getMyAppPath(), "informationVersion").toString() 47 | ) 48 | return if (readFile.isEmpty()) { 49 | 0.0 50 | } else { 51 | readFile.toDouble() 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/app/UpdateLogToolUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.app 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | import top.laoxin.modmanager.tools.LogTools 6 | import javax.inject.Inject 7 | import javax.inject.Singleton 8 | 9 | @Singleton 10 | class UpdateLogToolUserCase @Inject constructor() { 11 | suspend operator fun invoke(logPath: String) = withContext(Dispatchers.IO) { 12 | LogTools.setLogPath(logPath) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/console/SaveSelectModDirectoryUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.console 2 | 3 | import android.util.Log 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.withContext 6 | import top.laoxin.modmanager.constant.UserPreferencesKeys 7 | import top.laoxin.modmanager.domain.usercase.userpreference.SaveUserPreferenceUseCase 8 | import top.laoxin.modmanager.tools.manager.AppPathsManager 9 | import top.laoxin.modmanager.tools.manager.GameInfoManager 10 | import java.io.File 11 | import javax.inject.Inject 12 | import javax.inject.Singleton 13 | 14 | @Singleton 15 | class SaveSelectModDirectoryUserCase @Inject constructor( 16 | private val appPathsManager: AppPathsManager, 17 | private val gameInfoManager: GameInfoManager, 18 | private val saveUserPreferenceUseCase: SaveUserPreferenceUseCase 19 | ) { 20 | suspend operator fun invoke(selectedDirectoryPath: String) : Boolean = withContext(Dispatchers.IO) { 21 | try { 22 | val gameConfigFile = File( 23 | (appPathsManager.getRootPath() + "/$selectedDirectoryPath/" + appPathsManager.getRootPath()).replace( 24 | "tree", "" 25 | ).replace("//", "/") 26 | ) 27 | val gameModsFile = File( 28 | (appPathsManager.getRootPath() + "/$selectedDirectoryPath/" + gameInfoManager.getGameInfo().packageName).replace( 29 | "tree", "" 30 | ).replace("//", "/") 31 | ) 32 | if (!gameConfigFile.absolutePath.contains("${appPathsManager.getRootPath()}/Android")) { 33 | gameConfigFile.mkdirs() 34 | if (gameInfoManager.getGameInfo().packageName != "") { 35 | gameModsFile.mkdirs() 36 | } 37 | saveUserPreferenceUseCase(UserPreferencesKeys.SELECTED_DIRECTORY, "/$selectedDirectoryPath/".replace("tree", "").replace("//", "/")) 38 | return@withContext true 39 | } else { 40 | return@withContext false 41 | } 42 | } catch (e: Exception) { 43 | Log.e("ConsoleViewModel", "setSelectedDirectory: $e") 44 | return@withContext false 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/console/SwitchAntiHarmonyUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.console 2 | 3 | 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.withContext 6 | 7 | import top.laoxin.modmanager.constant.PathType 8 | import top.laoxin.modmanager.constant.ResultCode 9 | 10 | import top.laoxin.modmanager.data.bean.GameInfoBean 11 | import top.laoxin.modmanager.data.repository.antiharmony.AntiHarmonyRepository 12 | import top.laoxin.modmanager.tools.PermissionTools 13 | import top.laoxin.modmanager.tools.filetools.BaseFileTools 14 | import top.laoxin.modmanager.tools.filetools.FileToolsManager 15 | import top.laoxin.modmanager.tools.manager.AppPathsManager 16 | import top.laoxin.modmanager.tools.manager.GameInfoManager 17 | import java.io.File 18 | import javax.inject.Inject 19 | import javax.inject.Singleton 20 | 21 | @Singleton 22 | class SwitchAntiHarmonyUserCase @Inject constructor( 23 | private val appPathsManager: AppPathsManager, 24 | private val antiHarmonyRepository: AntiHarmonyRepository, 25 | private val permissionTools: PermissionTools, 26 | private val gameInfoManager: GameInfoManager, 27 | private val fileToolsManager: FileToolsManager 28 | ) { 29 | private var fileTools : BaseFileTools? = null 30 | 31 | suspend operator fun invoke(b: Boolean) : Int = withContext(Dispatchers.IO) { 32 | val gameInfo = gameInfoManager.getGameInfo() 33 | if (gameInfo.antiHarmonyFile.isEmpty() || gameInfo.antiHarmonyContent.isEmpty()) { 34 | return@withContext ResultCode.NOT_SUPPORT 35 | } 36 | val pathType = permissionTools.checkPermission(gameInfo.gamePath) 37 | if (pathType == PathType.NULL) { 38 | return@withContext ResultCode.NO_PERMISSION 39 | } else if (pathType == PathType.DOCUMENT) { 40 | val myPathType = permissionTools.checkPermission(appPathsManager.getMyAppPath()) 41 | if (myPathType == PathType.NULL) { 42 | return@withContext ResultCode.NO_MY_APP_PERMISSION 43 | } 44 | } 45 | fileTools = fileToolsManager.getFileTools(pathType) 46 | antiHarmony(gameInfo, true) 47 | antiHarmonyRepository.updateByGamePackageName(gameInfo.packageName, b) 48 | return@withContext ResultCode.SUCCESS 49 | } 50 | 51 | // 反和谐 52 | fun antiHarmony(gameInfo: GameInfoBean, b: Boolean): Boolean { 53 | return try { 54 | if (b) { 55 | if (fileTools?.isFileExist(appPathsManager.getBackupPath() + gameInfo.modSavePath + File(gameInfo.antiHarmonyFile).name) != true) { 56 | fileTools?.copyFile( 57 | gameInfo.antiHarmonyFile, 58 | appPathsManager.getBackupPath() + gameInfo.packageName + "/" + File(gameInfo.antiHarmonyFile).name 59 | ) 60 | } 61 | 62 | fileTools?.writeFile( 63 | File(gameInfo.antiHarmonyFile).parent!!, 64 | File(gameInfo.antiHarmonyFile).name, 65 | gameInfo.antiHarmonyContent 66 | ) 67 | } else { 68 | fileTools?.copyFile( 69 | appPathsManager.getBackupPath() + gameInfo.packageName + "/" + File(gameInfo.antiHarmonyFile).name, 70 | gameInfo.antiHarmonyFile 71 | ) 72 | } 73 | true 74 | } catch (e: Exception) { 75 | e.printStackTrace() 76 | false 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/gameinfo/CheckGameConfigUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.gameinfo 2 | 3 | import android.util.Log 4 | import top.laoxin.modmanager.data.bean.GameInfoBean 5 | import javax.inject.Inject 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | class CheckGameConfigUserCase @Inject constructor() { 10 | operator fun invoke(gameInfo: GameInfoBean, rootPath: String): GameInfoBean { 11 | Log.d("LoadGameConfig", "gameInfo: $gameInfo") 12 | var result = gameInfo.copy() 13 | if (gameInfo.gameName.isEmpty()) { 14 | throw Exception("gameName : 游戏名称不能为空") 15 | } 16 | 17 | if (gameInfo.packageName.isEmpty()) { 18 | throw Exception("packageName不能为空") 19 | } else { 20 | val pattern = Regex("^([a-zA-Z_][a-zA-Z0-9_]*)+([.][a-zA-Z_][a-zA-Z0-9_]*)+\$\n") 21 | if (pattern.matches(gameInfo.packageName)) { 22 | throw Exception("packageName包名不合法") 23 | } 24 | } 25 | 26 | if (gameInfo.gamePath.isEmpty()) { 27 | throw Exception("gamePath : 游戏data根目录不能为空") 28 | } else { 29 | result = result.copy( 30 | gamePath = rootPath + "/Android/data/" + gameInfo.packageName + "/" 31 | ) 32 | 33 | } 34 | if (gameInfo.antiHarmonyFile.isNotEmpty()) { 35 | result = result.copy( 36 | antiHarmonyFile = (rootPath + "/" + gameInfo.antiHarmonyFile).replace( 37 | "//", 38 | "/" 39 | ) 40 | ) 41 | } 42 | 43 | if (gameInfo.modType.isEmpty()) { 44 | throw Exception("modType不能为空") 45 | } 46 | if (gameInfo.gameFilePath.isEmpty()) { 47 | throw Exception("gameFilePath不能为空") 48 | } else { 49 | val paths = mutableListOf() 50 | for (path in gameInfo.gameFilePath) { 51 | paths.add("$rootPath/$path/".replace("//", "/")) 52 | } 53 | result = result.copy(gameFilePath = paths) 54 | } 55 | if (gameInfo.serviceName.isEmpty()) { 56 | throw Exception("serviceName不能为空") 57 | } 58 | if (gameInfo.gameFilePath.size != gameInfo.modType.size) { 59 | throw Exception("gameFilePath和modType的列表必须对应") 60 | } 61 | return result 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/gameinfo/LoadGameConfigUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.gameinfo 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | import top.laoxin.modmanager.tools.manager.GameInfoManager 6 | import javax.inject.Inject 7 | import javax.inject.Singleton 8 | 9 | @Singleton 10 | class LoadGameConfigUserCase @Inject constructor( 11 | private val gameInfoManager: GameInfoManager 12 | ) { 13 | suspend operator fun invoke() = withContext(Dispatchers.IO) { 14 | gameInfoManager.loadGameInfo() 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/gameinfo/UpdateGameInfoUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.gameinfo 2 | 3 | 4 | import android.util.Log 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import top.laoxin.modmanager.data.bean.GameInfoBean 8 | import top.laoxin.modmanager.tools.AppInfoTools 9 | import top.laoxin.modmanager.tools.manager.GameInfoManager 10 | import top.laoxin.modmanager.tools.specialGameTools.SpecialGameToolsManager 11 | import java.io.File 12 | import javax.inject.Inject 13 | import javax.inject.Singleton 14 | 15 | @Singleton 16 | class UpdateGameInfoUserCase @Inject constructor( 17 | private val appInfoTools: AppInfoTools, 18 | private val gameInfoManager: GameInfoManager, 19 | private val specialGameToolsManager: SpecialGameToolsManager 20 | ) { 21 | suspend operator fun invoke( 22 | selectedGameIndex: Int, 23 | modPath: String, 24 | ) = withContext(Dispatchers.IO) { 25 | var gameInfo = gameInfoManager.getGameInfoByIndex(selectedGameIndex) 26 | createModsDirectory(gameInfo, modPath) 27 | Log.d("ConsoleViewModel", "getGameInfo: $gameInfo") 28 | if (gameInfo.packageName.isNotEmpty()) { 29 | 30 | gameInfo = if (appInfoTools.isAppInstalled(gameInfo.packageName)) { 31 | val modifyGameInfo = gameInfo.copy( 32 | version = appInfoTools.getVersionName(gameInfo.packageName), 33 | modSavePath = modPath + gameInfo.packageName + File.separator 34 | ) 35 | val specialGameTools = 36 | specialGameToolsManager.getSpecialGameTools(gameInfo.packageName) 37 | specialGameTools?.specialOperationUpdateGameInfo(modifyGameInfo) ?: modifyGameInfo 38 | } else { 39 | gameInfoManager.getGameInfoByIndex(0) 40 | } 41 | } 42 | gameInfoManager.setGameInfo(gameInfo) 43 | return@withContext gameInfo 44 | } 45 | 46 | private fun createModsDirectory(gameInfo: GameInfoBean, path: String) { 47 | try { 48 | File(path + gameInfo.packageName).mkdirs() 49 | } catch (e: Exception) { 50 | Log.e("UpdateGameInfoUserCase", "创建文件夹失败: $e") 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/mod/CheckCanFlashModsUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.mod 2 | 3 | 4 | import top.laoxin.modmanager.constant.ResultCode 5 | import top.laoxin.modmanager.constant.ScanModPath 6 | 7 | import top.laoxin.modmanager.domain.usercase.app.CheckPermissionUserCase 8 | 9 | import top.laoxin.modmanager.tools.manager.GameInfoManager 10 | 11 | import javax.inject.Inject 12 | import javax.inject.Singleton 13 | 14 | @Singleton 15 | class CheckCanFlashModsUserCase @Inject constructor( 16 | private val gameInfoManager: GameInfoManager, 17 | private val checkPermissionUserCase: CheckPermissionUserCase, 18 | ) { 19 | operator fun invoke(): Pair { 20 | return if (checkSelectGame()) { 21 | if (!checkPermissionUserCase(gameInfoManager.getGameInfo().gamePath)){ 22 | Pair(ResultCode.NO_PERMISSION, gameInfoManager.getGameInfo().gamePath) 23 | } else if (!checkPermissionUserCase(ScanModPath.MOD_PATH_QQ)){ 24 | Pair(ResultCode.NO_PERMISSION, ScanModPath.MOD_PATH_QQ) 25 | } else { 26 | Pair(ResultCode.SUCCESS, "") 27 | } 28 | } else { 29 | Pair(ResultCode.NO_SELECTED_GAME, "") 30 | } 31 | } 32 | 33 | private fun checkSelectGame(): Boolean { 34 | return gameInfoManager.getGameInfo().packageName.isNotEmpty() 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/mod/CheckCanSwitchModsUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.mod 2 | 3 | import top.laoxin.modmanager.constant.ResultCode 4 | import top.laoxin.modmanager.domain.usercase.app.CheckPermissionUserCase 5 | import top.laoxin.modmanager.tools.manager.AppPathsManager 6 | import top.laoxin.modmanager.tools.manager.GameInfoManager 7 | import javax.inject.Inject 8 | import javax.inject.Singleton 9 | 10 | @Singleton 11 | class CheckCanSwitchModsUserCase @Inject constructor( 12 | private val gameInfoManager: GameInfoManager, 13 | private val checkPermissionUserCase: CheckPermissionUserCase, 14 | private val appPathsManager: AppPathsManager 15 | 16 | ) { 17 | operator fun invoke(): Pair { 18 | return if (checkPermissionUserCase(gameInfoManager.getGameInfo().gamePath)) { 19 | if (checkPermissionUserCase(appPathsManager.getMyAppPath())) { 20 | Pair(ResultCode.SUCCESS, "") 21 | } else { 22 | Pair(ResultCode.NO_PERMISSION, appPathsManager.getMyAppPath()) 23 | } 24 | } else { 25 | Pair(ResultCode.NO_PERMISSION, gameInfoManager.getGameInfo().gamePath) 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/mod/DeleteModUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.mod 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.flow.first 5 | 6 | import kotlinx.coroutines.withContext 7 | import top.laoxin.modmanager.constant.ResultCode 8 | import top.laoxin.modmanager.data.bean.ModBean 9 | import top.laoxin.modmanager.data.repository.mod.ModRepository 10 | import top.laoxin.modmanager.observer.FlashModsObserverManager 11 | import top.laoxin.modmanager.tools.filetools.FileToolsManager 12 | 13 | import top.laoxin.modmanager.tools.manager.AppPathsManager 14 | import javax.inject.Inject 15 | import javax.inject.Singleton 16 | 17 | data class DeleteModResult( 18 | val code: Int, 19 | // 删除的已开启的mods 20 | val delEnableMods: List, 21 | // 删除的全部mods 22 | val delMods: List, 23 | ) 24 | @Singleton 25 | class DeleteModUserCase @Inject constructor( 26 | private val appPathsManager: AppPathsManager, 27 | private val fileObserverManager: FlashModsObserverManager, 28 | private val fileToolsManager: FileToolsManager, 29 | private val modRepository: ModRepository, 30 | ) { 31 | // 读取readme文件 32 | suspend operator fun invoke(delMod: ModBean): DeleteModResult = withContext(Dispatchers.IO) { 33 | fileObserverManager.stopWatching() 34 | val delMods = modRepository.getModsByPathAndGamePackageName( 35 | delMod.path!!, delMod.gamePackageName!! 36 | ).first() 37 | // 排除包含多个mod文件的压缩包 38 | val disableMods = delMods.filter { !it.isEnable } 39 | val enableMods = delMods.filter { it.isEnable } 40 | deleteMods(delMods) 41 | modRepository.deleteAll(disableMods) 42 | 43 | return@withContext DeleteModResult(ResultCode.SUCCESS, enableMods, delMods) 44 | } 45 | 46 | private fun deleteMods(mods: List): Boolean { 47 | return try { 48 | val fileTools = fileToolsManager.getFileTools() 49 | mods.forEach { 50 | fileTools.deleteFile(it.path!!) 51 | } 52 | true 53 | } catch (e: Exception) { 54 | e.printStackTrace() 55 | false 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/mod/DeleteSelectedModsUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.mod 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.flow.first 5 | import kotlinx.coroutines.withContext 6 | import top.laoxin.modmanager.constant.ResultCode 7 | import top.laoxin.modmanager.data.bean.ModBean 8 | import top.laoxin.modmanager.data.repository.mod.ModRepository 9 | import top.laoxin.modmanager.observer.FlashModsObserverManager 10 | import top.laoxin.modmanager.tools.filetools.FileToolsManager 11 | import javax.inject.Inject 12 | import javax.inject.Singleton 13 | 14 | data class DeleteSelectedModsResult( 15 | val code: Int, 16 | // 删除的已开启的mods 17 | val delEnableMods: List, 18 | ) 19 | @Singleton 20 | class DeleteSelectedModUserCase @Inject constructor( 21 | private val fileObserverManager: FlashModsObserverManager, 22 | private val fileToolsManager: FileToolsManager, 23 | private val modRepository: ModRepository, 24 | ) { 25 | // 读取readme文件 26 | suspend operator fun invoke(modIds: List): DeleteSelectedModsResult = withContext(Dispatchers.IO) { 27 | if (modIds.isEmpty()) { 28 | return@withContext DeleteSelectedModsResult(ResultCode.NO_EXECUTE, emptyList()) 29 | } 30 | fileObserverManager.stopWatching() 31 | val delMods = modRepository.getModsByIds(modIds).first() 32 | // 排除包含多个mod文件的压缩包 33 | val singleFileMods = 34 | delMods.filter { modRepository.getModsCountByPath(it.path!!).first() == 1 } 35 | val singleFileDisableMods = singleFileMods.filter { !it.isEnable } 36 | val enableMods = singleFileMods.filter { it.isEnable } 37 | deleteMods(singleFileMods) 38 | modRepository.deleteAll(singleFileDisableMods) 39 | fileObserverManager.startWatching() 40 | return@withContext DeleteSelectedModsResult(ResultCode.SUCCESS, enableMods) 41 | } 42 | 43 | 44 | private fun deleteMods(mods: List): Boolean { 45 | return try { 46 | val fileTools = fileToolsManager.getFileTools() 47 | mods.forEach { 48 | fileTools.deleteFile(it.path!!) == true 49 | } 50 | true 51 | } catch (e: Exception) { 52 | e.printStackTrace() 53 | false 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/mod/EnsureGameModPathUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.mod 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | import top.laoxin.modmanager.tools.filetools.FileToolsManager 6 | import javax.inject.Inject 7 | import javax.inject.Singleton 8 | 9 | @Singleton 10 | class EnsureGameModPathUserCase @Inject constructor( 11 | private val fileToolsManager: FileToolsManager, 12 | ) { 13 | // 读取readme文件 14 | suspend operator fun invoke(path: String) = withContext(Dispatchers.IO) { 15 | val fileTools = fileToolsManager.getFileTools() 16 | fileTools.createDictionary(path) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/mod/FlashModImageUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.mod 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | import top.laoxin.modmanager.data.bean.ModBean 6 | import top.laoxin.modmanager.tools.ArchiveUtil 7 | import top.laoxin.modmanager.tools.manager.AppPathsManager 8 | import java.io.File 9 | import javax.inject.Inject 10 | import javax.inject.Singleton 11 | 12 | @Singleton 13 | class FlashModImageUserCase @Inject constructor( 14 | private val appPathsManager: AppPathsManager 15 | ) { 16 | // 读取readme文件 17 | suspend operator fun invoke(modBean: ModBean): Boolean = withContext(Dispatchers.IO) { 18 | if (modBean.isEncrypted && modBean.password == null) { 19 | return@withContext true 20 | } 21 | if (modBean.isZipFile) { 22 | return@withContext try { 23 | ArchiveUtil.extractSpecificFile( 24 | modBean.path!!, 25 | modBean.images.orEmpty().map { 26 | it.replace(appPathsManager.getModsImagePath() + File(modBean.path).nameWithoutExtension + File.separator,"") 27 | }, 28 | appPathsManager.getModsImagePath() + File(modBean.path).nameWithoutExtension, 29 | modBean.password, 30 | false 31 | ) 32 | true 33 | } catch (_: Exception) { 34 | false 35 | } 36 | } 37 | return@withContext false 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/mod/ReadModReadmeFileUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.mod 2 | 3 | import top.laoxin.modmanager.data.bean.ModBean 4 | import top.laoxin.modmanager.tools.LogTools.logRecord 5 | import java.io.File 6 | import javax.inject.Inject 7 | import javax.inject.Singleton 8 | 9 | @Singleton 10 | class ReadModReadmeFileUserCase @Inject constructor() { 11 | // 读取readme文件 12 | operator fun invoke(unZipPath: String, modBean: ModBean): ModBean { 13 | try { 14 | var readmeFile: File? = null 15 | if (modBean.isZipFile && modBean.readmePath != null) { 16 | val path = unZipPath + modBean.readmePath 17 | readmeFile = File(path) 18 | } 19 | 20 | if (modBean.isZipFile && modBean.fileReadmePath != null) { 21 | val path = unZipPath + modBean.fileReadmePath 22 | readmeFile = File(path) 23 | } 24 | 25 | if (!modBean.isZipFile && modBean.readmePath != null) { 26 | readmeFile = File(modBean.readmePath!!) 27 | } 28 | 29 | if (!modBean.isZipFile && modBean.fileReadmePath != null) { 30 | readmeFile = File(modBean.fileReadmePath!!) 31 | } 32 | readmeFile?.let { 33 | return modBean.copy( 34 | description = readmeFile.readText() 35 | ) 36 | } 37 | } catch (_: Exception) { 38 | logRecord("ReadModReadmeFileUserCase: 读取readme文件失败") 39 | } 40 | 41 | return modBean 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/repository/AntiHarmonyRepositoryUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.repository 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.bean.AntiHarmonyBean 5 | import top.laoxin.modmanager.data.repository.antiharmony.AntiHarmonyRepository 6 | import top.laoxin.modmanager.tools.manager.GameInfoManager 7 | import javax.inject.Inject 8 | import javax.inject.Singleton 9 | 10 | @Singleton 11 | class GetAntiHarmonyUserCase @Inject constructor( 12 | private val antiHarmonyRepository: AntiHarmonyRepository, 13 | private val gameInfoManager: GameInfoManager 14 | ) { 15 | suspend operator fun invoke(): Flow { 16 | return antiHarmonyRepository.getByGamePackageName(gameInfoManager.getGameInfo().packageName) 17 | } 18 | } 19 | 20 | @Singleton 21 | class AddGameToAntiHarmonyUserCase @Inject constructor( 22 | private val antiHarmonyRepository: AntiHarmonyRepository, 23 | private val gameInfoManager: GameInfoManager 24 | ) { 25 | suspend operator fun invoke() { 26 | antiHarmonyRepository.insert( 27 | AntiHarmonyBean( 28 | gamePackageName = gameInfoManager.getGameInfo().packageName, 29 | isEnable = false 30 | ) 31 | ) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/repository/ModRepositoryUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.repository 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.bean.ModBean 5 | import top.laoxin.modmanager.data.repository.mod.ModRepository 6 | import top.laoxin.modmanager.tools.manager.GameInfoManager 7 | import javax.inject.Inject 8 | import javax.inject.Singleton 9 | 10 | @Singleton 11 | class GetGameModsCountUserCase @Inject constructor( 12 | private val modRepository: ModRepository, 13 | private val gameInfoManager: GameInfoManager 14 | 15 | ) { 16 | operator fun invoke(): Flow { 17 | return modRepository.getModsCountByGamePackageName(gameInfoManager.getGameInfo().packageName) 18 | } 19 | } 20 | 21 | @Singleton 22 | class GetGameEnableModsCountUserCase @Inject constructor( 23 | private val modRepository: ModRepository, 24 | private val gameInfoManager: GameInfoManager 25 | 26 | ) { 27 | operator fun invoke(): Flow { 28 | return modRepository.getEnableModsCountByGamePackageName(gameInfoManager.getGameInfo().packageName) 29 | } 30 | } 31 | 32 | @Singleton 33 | class GetGameEnableModsUserCase @Inject constructor( 34 | private val modRepository: ModRepository, 35 | private val gameInfoManager: GameInfoManager 36 | 37 | ) { 38 | operator fun invoke(): Flow> { 39 | return modRepository.getEnableMods(gameInfoManager.getGameInfo().packageName) 40 | } 41 | } 42 | 43 | @Singleton 44 | class GetGameAllModsUserCase @Inject constructor( 45 | private val modRepository: ModRepository, 46 | private val gameInfoManager: GameInfoManager 47 | 48 | ) { 49 | operator fun invoke(): Flow> { 50 | return modRepository.getModsByGamePackageName(gameInfoManager.getGameInfo().packageName) 51 | } 52 | } 53 | 54 | @Singleton 55 | class GetGameDisEnableUserCase @Inject constructor( 56 | private val modRepository: ModRepository, 57 | private val gameInfoManager: GameInfoManager 58 | 59 | ) { 60 | operator fun invoke(): Flow> { 61 | return modRepository.getDisableMods(gameInfoManager.getGameInfo().packageName) 62 | } 63 | } 64 | 65 | @Singleton 66 | class DeleteModsUserCase @Inject constructor( 67 | private val modRepository: ModRepository, 68 | private val gameInfoManager: GameInfoManager 69 | 70 | ) { 71 | suspend operator fun invoke(delModsList : List){ 72 | return modRepository.deleteAll(delModsList) 73 | } 74 | } 75 | 76 | @Singleton 77 | class SearchModsUserCase @Inject constructor( 78 | private val modRepository: ModRepository, 79 | private val gameInfoManager: GameInfoManager 80 | 81 | ) { 82 | suspend operator fun invoke(searchText: String): Flow> { 83 | return modRepository.search(searchText, gameInfoManager.getGameInfo().packageName) 84 | } 85 | } 86 | 87 | @Singleton 88 | class UpdateModUserCase @Inject constructor( 89 | private val modRepository: ModRepository, 90 | private val gameInfoManager: GameInfoManager 91 | 92 | ) { 93 | suspend operator fun invoke(mod : ModBean) { 94 | return modRepository.updateMod(mod) 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/setting/DeleteBackupUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.setting 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.flow.first 5 | import kotlinx.coroutines.withContext 6 | import top.laoxin.modmanager.constant.ResultCode 7 | import top.laoxin.modmanager.data.bean.GameInfoBean 8 | import top.laoxin.modmanager.data.repository.backup.BackupRepository 9 | import top.laoxin.modmanager.data.repository.mod.ModRepository 10 | import top.laoxin.modmanager.tools.filetools.FileToolsManager 11 | import top.laoxin.modmanager.tools.manager.AppPathsManager 12 | import top.laoxin.modmanager.tools.manager.GameInfoManager 13 | import java.io.File 14 | import javax.inject.Inject 15 | import javax.inject.Singleton 16 | 17 | @Singleton 18 | class DeleteBackupUserCase @Inject constructor( 19 | private val gameInfoManager: GameInfoManager, 20 | private val backupRepository: BackupRepository, 21 | private val modRepository: ModRepository, 22 | private val fileToolsManager: FileToolsManager, 23 | private val appPathsManager: AppPathsManager, 24 | ) { 25 | // 读取readme文件 26 | suspend operator fun invoke(): Pair = withContext(Dispatchers.IO) { 27 | val gameInfo = gameInfoManager.getGameInfo() 28 | if (gameInfo.packageName.isEmpty()) { 29 | return@withContext Pair(ResultCode.NO_SELECTED_GAME, null) 30 | } 31 | modRepository.getEnableMods(gameInfo.packageName).first().let { enableMods -> 32 | if (enableMods.isNotEmpty()) { 33 | return@withContext Pair(ResultCode.HAVE_ENABLE_MODS, null) 34 | } else { 35 | val delBackupFile: Boolean = deleteBackupFiles(gameInfo) 36 | if (delBackupFile) { 37 | backupRepository.deleteByGamePackageName(gameInfo.packageName) 38 | return@withContext Pair(ResultCode.SUCCESS, gameInfo.packageName) 39 | } else { 40 | return@withContext Pair(ResultCode.FAIL, gameInfo.packageName) 41 | } 42 | } 43 | } 44 | } 45 | 46 | private fun deleteBackupFiles(gameInfo: GameInfoBean) = 47 | if (File(appPathsManager.getBackupPath() + gameInfo.packageName).exists()) { 48 | val fileTools = fileToolsManager.getFileTools() 49 | kotlin.runCatching { fileTools.deleteFile(appPathsManager.getBackupPath() + gameInfo.packageName) }.isSuccess 50 | } else { 51 | true 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/setting/DeleteCacheUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.setting 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | import top.laoxin.modmanager.tools.filetools.FileToolsManager 6 | import top.laoxin.modmanager.tools.manager.AppPathsManager 7 | import java.io.File 8 | import javax.inject.Inject 9 | import javax.inject.Singleton 10 | 11 | @Singleton 12 | class DeleteCacheUserCase @Inject constructor( 13 | private val fileToolsManager: FileToolsManager, 14 | private val appPathsManager: AppPathsManager, 15 | ) { 16 | // 读取readme文件 17 | suspend operator fun invoke(): Boolean = withContext(Dispatchers.IO) { 18 | return@withContext if (File(appPathsManager.getModsImagePath()).exists()) { 19 | val fileTools = fileToolsManager.getFileTools() 20 | kotlin.runCatching { 21 | fileTools.deleteFile(appPathsManager.getModsIconPath()) 22 | fileTools.deleteFile(appPathsManager.getModsImagePath()) 23 | }.isSuccess 24 | } else { 25 | true 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/setting/DeleteTempUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.setting 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | import top.laoxin.modmanager.tools.filetools.FileToolsManager 6 | import top.laoxin.modmanager.tools.manager.AppPathsManager 7 | import java.io.File 8 | import javax.inject.Inject 9 | import javax.inject.Singleton 10 | 11 | @Singleton 12 | class DeleteTempUserCase @Inject constructor( 13 | private val fileToolsManager: FileToolsManager, 14 | private val appPathsManager: AppPathsManager, 15 | ) { 16 | // 读取readme文件 17 | suspend operator fun invoke(): Boolean = withContext(Dispatchers.IO) { 18 | return@withContext if (File(appPathsManager.getModsTempPath()).exists()) { 19 | val fileTools = fileToolsManager.getFileTools() 20 | fileTools.deleteFile(appPathsManager.getModsTempPath()) 21 | } else { 22 | true 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/setting/DownloadGameConfigUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.setting 2 | 3 | import android.util.Log 4 | import com.google.gson.GsonBuilder 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import top.laoxin.modmanager.data.bean.DownloadGameConfigBean 8 | import top.laoxin.modmanager.data.bean.GameInfoBean 9 | import top.laoxin.modmanager.data.network.ModManagerApi 10 | import top.laoxin.modmanager.domain.usercase.gameinfo.LoadGameConfigUserCase 11 | import top.laoxin.modmanager.tools.manager.AppPathsManager 12 | import java.io.File 13 | import javax.inject.Inject 14 | import javax.inject.Singleton 15 | 16 | @Singleton 17 | class DownloadGameConfigUserCase @Inject constructor( 18 | private val appPathsManager: AppPathsManager, 19 | private val loadGameConfigUserCase: LoadGameConfigUserCase 20 | 21 | ) { 22 | 23 | suspend operator fun invoke(downloadGameConfigBean: DownloadGameConfigBean): Boolean = 24 | withContext(Dispatchers.IO) { 25 | kotlin.runCatching { 26 | val downloadGameConfig = 27 | ModManagerApi.retrofitService.downloadGameConfig(downloadGameConfigBean.packageName) 28 | writeGameConfigFile(downloadGameConfig) 29 | }.onFailure { 30 | Log.e("SettingViewModel", "downloadGameConfig: $it") 31 | return@withContext false 32 | }.onSuccess { 33 | loadGameConfigUserCase() 34 | return@withContext true 35 | } 36 | return@withContext true 37 | 38 | } 39 | 40 | private fun writeGameConfigFile(downloadGameConfig: GameInfoBean) { 41 | try { 42 | val file = 43 | File(appPathsManager.getMyAppPath() + appPathsManager.getGameConfig() + downloadGameConfig.packageName + ".json") 44 | if (file.exists()) { 45 | file.delete() 46 | } 47 | if (file.parentFile?.exists() == false) { 48 | file.parentFile?.mkdirs() 49 | } 50 | file.createNewFile() 51 | val gson = GsonBuilder() 52 | .disableHtmlEscaping() 53 | .create() 54 | file.writeText(gson.toJson(downloadGameConfig, GameInfoBean::class.java)) 55 | } catch (e: Exception) { 56 | Log.e("DownloadGameConfigUserCase", "写入游戏配置失败: $e") 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/setting/SelectGameUserCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.setting 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.flow.first 5 | import kotlinx.coroutines.withContext 6 | import top.laoxin.modmanager.constant.UserPreferencesKeys 7 | import top.laoxin.modmanager.data.bean.GameInfoBean 8 | import top.laoxin.modmanager.data.repository.UserPreferencesRepository 9 | import top.laoxin.modmanager.domain.usercase.mod.EnsureGameModPathUserCase 10 | import top.laoxin.modmanager.tools.AppInfoTools 11 | import top.laoxin.modmanager.tools.filetools.FileToolsManager 12 | import top.laoxin.modmanager.tools.manager.AppPathsManager 13 | import top.laoxin.modmanager.tools.manager.GameInfoManager 14 | import top.laoxin.modmanager.tools.specialGameTools.SpecialGameToolsManager 15 | import java.io.File 16 | import javax.inject.Inject 17 | import javax.inject.Singleton 18 | 19 | @Singleton 20 | class SelectGameUserCase @Inject constructor( 21 | private val fileToolsManager: FileToolsManager, 22 | private val appPathsManager: AppPathsManager, 23 | private val appInfoTools: AppInfoTools, 24 | private val specialGameToolsManager: SpecialGameToolsManager, 25 | private val userPreferencesRepository: UserPreferencesRepository, 26 | private val gameInfoManager: GameInfoManager, 27 | private val ensureGameModPathUserCase: EnsureGameModPathUserCase 28 | ) { 29 | // 读取readme文件 30 | suspend operator fun invoke(gameInfo: GameInfoBean): Boolean = withContext(Dispatchers.IO) { 31 | val result = appInfoTools.isAppInstalled(gameInfo.packageName) 32 | if (result) { 33 | ensureGameModPathUserCase(appPathsManager.getRootPath() + userPreferencesRepository.getPreferenceFlow(UserPreferencesKeys.SELECTED_DIRECTORY, "").first() + gameInfo.packageName + File.separator) 34 | userPreferencesRepository.savePreference(UserPreferencesKeys.SELECTED_GAME, gameInfoManager.getGameInfoList().indexOf(gameInfo)) 35 | val modifyGameInfo = gameInfo.copy( 36 | version = appInfoTools.getVersionName(gameInfo.packageName), 37 | ) 38 | specialGameToolsManager.getSpecialGameTools(gameInfo.packageName) 39 | ?.specialOperationSelectGame(modifyGameInfo) 40 | return@withContext true 41 | } else { 42 | return@withContext false 43 | } 44 | 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/userpreference/GetUserPreferenceUseCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.userpreference 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import top.laoxin.modmanager.data.repository.UserPreferencesRepository 5 | import javax.inject.Inject 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | class GetUserPreferenceUseCase @Inject constructor( 10 | private val userPreferencesRepository: UserPreferencesRepository 11 | ) { 12 | operator fun invoke(key: String, defaultValue: T): Flow { 13 | return userPreferencesRepository.getPreferenceFlow(key, defaultValue) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/domain/usercase/userpreference/SaveUserPreferenceUseCase.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.domain.usercase.userpreference 2 | 3 | import top.laoxin.modmanager.data.repository.UserPreferencesRepository 4 | import javax.inject.Inject 5 | import javax.inject.Singleton 6 | 7 | @Singleton 8 | class SaveUserPreferenceUseCase @Inject constructor( 9 | private val userPreferencesRepository: UserPreferencesRepository 10 | ) { 11 | suspend operator fun invoke(key: String, value: T) { 12 | userPreferencesRepository.savePreference(key, value) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/exception/ModManagerException.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.exception 2 | 3 | // 权限不足异常 4 | class PermissionsException(message: String) : Exception(message) 5 | 6 | // 未选择游戏异常 7 | class NoSelectedGameException(message: String) : Exception(message) 8 | 9 | // 特殊操作失败 10 | class SpecialOperationFailedException(message: String) : Exception(message) 11 | 12 | // 密码错误异常 13 | class PasswordErrorException(message: String) : Exception(message) 14 | 15 | // 流复制失败异常 16 | class CopyStreamFailedException(message: String) : Exception(message) 17 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/listener/ProgressUpdateListener.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.listener 2 | 3 | interface ProgressUpdateListener { 4 | fun onProgressUpdate(progress: String) 5 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/observer/FlashModsObserver.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.observer 2 | 3 | import android.os.Build 4 | import android.os.FileObserver 5 | import androidx.annotation.RequiresApi 6 | import top.laoxin.modmanager.tools.LogTools 7 | import java.io.File 8 | 9 | @RequiresApi(Build.VERSION_CODES.Q) 10 | class FlashModsObserver(val path: String) : FileObserver(File(path), ALL_EVENTS) { 11 | companion object { 12 | var flashObserver: FlashObserverInterface? = null 13 | } 14 | 15 | override fun onEvent(event: Int, path: String?) { 16 | when (event) { 17 | CREATE, DELETE, MOVED_FROM, MOVED_TO -> { 18 | if (path == null || path == LogTools.LOG_FILE_NAME || path == LogTools.LOG_CAT_NAME) { 19 | return 20 | } 21 | FlashModsObserverLow.flashObserver?.onFlash() 22 | } 23 | 24 | else -> return 25 | } 26 | } 27 | } 28 | 29 | // 安卓10以下 30 | @Suppress("DEPRECATION") 31 | class FlashModsObserverLow(val path: String) : FileObserver(path, ALL_EVENTS) { 32 | companion object { 33 | var flashObserver: FlashObserverInterface? = null 34 | } 35 | 36 | override fun onEvent(event: Int, path: String?) { 37 | when (event) { 38 | CREATE, DELETE, MOVED_FROM, MOVED_TO -> { 39 | if (path == null || path == LogTools.LOG_FILE_NAME || path == LogTools.LOG_CAT_NAME) { 40 | return 41 | } 42 | flashObserver?.onFlash() 43 | } 44 | 45 | else -> return 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/observer/FlashModsObserverManager.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.observer 2 | 3 | import android.os.Build 4 | import android.os.FileObserver 5 | import android.util.Log 6 | import top.laoxin.modmanager.tools.manager.AppPathsManager 7 | import javax.inject.Inject 8 | import javax.inject.Singleton 9 | 10 | @Singleton 11 | class FlashModsObserverManager @Inject constructor( 12 | private val appPathsManager: AppPathsManager 13 | ) { 14 | 15 | private var selectedDictionaryFileObserver: FileObserver? = null 16 | private var modDictionaryFileObserver: FileObserver? = null 17 | 18 | companion object { 19 | private const val TAG = "FlashModsObserverManager" 20 | } 21 | fun openSelectedDictionaryObserver(selectedDirectory: String) { 22 | selectedDictionaryFileObserver?.stopWatching() 23 | 24 | selectedDictionaryFileObserver = 25 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 26 | FlashModsObserver(appPathsManager.getRootPath() + selectedDirectory) 27 | } else { 28 | FlashModsObserverLow(appPathsManager.getRootPath() + selectedDirectory) 29 | } 30 | selectedDictionaryFileObserver?.startWatching() 31 | } 32 | 33 | fun openModDictionaryObserver(modDirectory: String) { 34 | modDictionaryFileObserver?.stopWatching() 35 | 36 | modDictionaryFileObserver = 37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 38 | FlashModsObserver(appPathsManager.getRootPath() + modDirectory) 39 | } else { 40 | FlashModsObserverLow(appPathsManager.getRootPath() + modDirectory) 41 | } 42 | modDictionaryFileObserver?.startWatching() 43 | } 44 | 45 | fun stopWatching() { 46 | selectedDictionaryFileObserver?.stopWatching() 47 | modDictionaryFileObserver?.stopWatching() 48 | } 49 | 50 | fun startWatching() { 51 | Log.d(TAG, "startWatching: 开启文件监听") 52 | selectedDictionaryFileObserver?.startWatching() 53 | modDictionaryFileObserver?.startWatching() 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/observer/FlashObserverInterface.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.observer 2 | 3 | interface FlashObserverInterface { 4 | fun onFlash() 5 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/tools/LogTools.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.tools 2 | 3 | import android.icu.text.SimpleDateFormat 4 | import android.icu.util.Calendar 5 | import android.util.Log 6 | import java.io.File 7 | import java.io.FileWriter 8 | import java.io.InputStreamReader 9 | import java.util.Locale 10 | import java.util.concurrent.Executors 11 | 12 | object LogTools { 13 | private var logPath: String? = null 14 | 15 | fun setLogPath(path: String) { 16 | logPath = path 17 | startLogcatLogging() 18 | } 19 | 20 | const val TAG = "LogTools" 21 | const val LOG_CAT_NAME = "logcat.txt" 22 | const val LOG_FILE_NAME = "log.txt" 23 | 24 | fun startLogcatLogging() { 25 | try { 26 | if (logPath.isNullOrEmpty()) { 27 | Log.e(TAG, "日志路径未设置") 28 | return 29 | } 30 | val logFile = File(logPath, LOG_CAT_NAME) 31 | if (!logFile.exists()) { 32 | logFile.createNewFile() 33 | } else { 34 | logFile.delete() 35 | logFile.createNewFile() 36 | } 37 | 38 | val executor = Executors.newSingleThreadExecutor() 39 | executor.execute { 40 | var process: Process? = null 41 | try { 42 | process = Runtime.getRuntime().exec("logcat") 43 | InputStreamReader(process.inputStream).use { reader -> 44 | FileWriter(logFile, true).use { writer -> 45 | val buffer = CharArray(1024) 46 | var read: Int 47 | while (true) { 48 | read = reader.read(buffer) 49 | if (read == -1) break 50 | writer.write(buffer, 0, read) 51 | } 52 | } 53 | } 54 | } catch (e: Exception) { 55 | Log.e(TAG, "启动 logcat 失败: $e") 56 | } finally { 57 | process?.destroy() 58 | } 59 | } 60 | } catch (e: Exception) { 61 | Log.e(TAG, "启动 logcat 失败: $e") 62 | } 63 | } 64 | 65 | fun logRecord(log: String) { 66 | try { 67 | if (logPath?.isEmpty() == true) { 68 | Log.i(TAG, "日志路径为空") 69 | return 70 | } 71 | val file = File(logPath , LOG_FILE_NAME) 72 | if (!file.exists()) { 73 | file.createNewFile() 74 | } 75 | val fileWriter = FileWriter(file, true) 76 | 77 | // 获取当前日期和时间 78 | val currentDateAndTime = SimpleDateFormat( 79 | "yyyy-MM-dd HH:mm:ss", 80 | Locale.getDefault() 81 | ).format(Calendar.getInstance().time) 82 | 83 | // 在日志信息前添加日期和时间 84 | val logWithDateAndTime = "$currentDateAndTime: $log\n" 85 | 86 | fileWriter.write(logWithDateAndTime) 87 | fileWriter.close() 88 | } catch (e: Exception) { 89 | Log.e(TAG, "写入日志失败: $e") 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/tools/MD5Tools.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.tools 2 | 3 | import java.io.InputStream 4 | import java.security.MessageDigest 5 | 6 | object MD5Tools { 7 | fun calculateMD5(inputStream: InputStream): String { 8 | val buffer = ByteArray(8192) 9 | val md5 = MessageDigest.getInstance("MD5") 10 | 11 | var numRead: Int 12 | while (inputStream.read(buffer).also { numRead = it } > 0) { 13 | md5.update(buffer, 0, numRead) 14 | } 15 | 16 | val md5Bytes = md5.digest() 17 | val result = StringBuilder(md5Bytes.size * 2) 18 | 19 | md5Bytes.forEach { 20 | val i = it.toInt() 21 | result.append(Character.forDigit((i shr 4) and 0xf, 16)) 22 | result.append(Character.forDigit(i and 0xf, 16)) 23 | } 24 | 25 | return result.toString() 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/tools/ToastUtils.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.tools 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import android.widget.Toast 6 | import androidx.annotation.StringRes 7 | import top.laoxin.modmanager.App 8 | 9 | object ToastUtils { 10 | private var sToast: Toast? = null 11 | private val handler = Handler(Looper.getMainLooper()) 12 | fun shortCall(@StringRes resId: Int) { 13 | shortCall(App.get().getString(resId)) 14 | } 15 | 16 | private fun shortCall(text: String?) { 17 | handler.post { 18 | cancelToast() 19 | sToast = Toast.makeText(App.get(), text, Toast.LENGTH_SHORT) 20 | sToast!!.show() 21 | } 22 | 23 | } 24 | 25 | fun longCall(@StringRes resId: Int) { 26 | longCall(App.get().getString(resId)) 27 | } 28 | 29 | fun longCall(text: String?) { 30 | handler.post { 31 | cancelToast() 32 | sToast = Toast.makeText(App.get(), text, Toast.LENGTH_LONG) 33 | sToast!!.show() 34 | } 35 | } 36 | 37 | private fun cancelToast() { 38 | if (sToast != null) { 39 | sToast!!.cancel() 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/tools/filetools/FileToolsManager.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.tools.filetools 2 | 3 | import top.laoxin.modmanager.constant.PathType 4 | import top.laoxin.modmanager.di.FileToolsModule 5 | import javax.inject.Inject 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | class FileToolsManager @Inject constructor( 10 | @param:FileToolsModule.FileToolsImpl private val fileTools: BaseFileTools, 11 | @param:FileToolsModule.ShizukuFileToolsImpl private val shizukuFileTools: BaseFileTools, 12 | @param:FileToolsModule.DocumentFileToolsImpl private val documentFileTools: BaseFileTools 13 | ) { 14 | // 通过不同的权限获取方式,获取文件工具 15 | fun getFileTools(pathType : Int): BaseFileTools? { 16 | return when (pathType) { 17 | PathType.FILE -> fileTools 18 | PathType.DOCUMENT -> documentFileTools 19 | PathType.SHIZUKU -> shizukuFileTools 20 | else -> null 21 | } 22 | } 23 | // 获取fileTools 24 | fun getFileTools(): BaseFileTools { 25 | return fileTools 26 | } 27 | 28 | // 获取shizukuFileTools 29 | fun getShizukuFileTools(): BaseFileTools { 30 | return shizukuFileTools 31 | } 32 | 33 | // 获取documentFileTools 34 | fun getDocumentFileTools(): BaseFileTools { 35 | return documentFileTools 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/tools/manager/AppPathsManager.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.tools.manager 2 | 3 | import android.os.Environment 4 | import top.laoxin.modmanager.App 5 | import javax.inject.Inject 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | class AppPathsManager @Inject constructor() { 10 | private val rootPath: String = Environment.getExternalStorageDirectory().path 11 | private val myAppPath: String = "$rootPath/Android/data/${App.get().packageName}/" 12 | private val backupPath: String = myAppPath + "backup/" 13 | private val modsTempPath: String = myAppPath + "temp/" 14 | private val modsUnzipPath: String = myAppPath + "temp/unzip/" 15 | private val modsIconPath: String = myAppPath + "icon/" 16 | private val modsImagePath: String = myAppPath + "images/" 17 | private val gameCheckFilePath: String = myAppPath + "gameCheckFile/" 18 | 19 | companion object { 20 | const val GAME_CONFIG_Path = "GameConfig/" 21 | const val DOWNLOAD_MOD_PATH = "/Download/Mods/" 22 | var MOD_PATH = "" 23 | } 24 | 25 | fun getRootPath(): String { 26 | return rootPath 27 | } 28 | 29 | fun getMyAppPath(): String { 30 | return myAppPath 31 | } 32 | 33 | fun getBackupPath(): String { 34 | return backupPath 35 | } 36 | 37 | fun getModsTempPath(): String { 38 | return modsTempPath 39 | } 40 | 41 | fun getModsUnzipPath(): String { 42 | return modsUnzipPath 43 | } 44 | 45 | fun getModsIconPath(): String { 46 | return modsIconPath 47 | } 48 | 49 | fun getModsImagePath(): String { 50 | return modsImagePath 51 | } 52 | 53 | fun getGameCheckFilePath(): String { 54 | return gameCheckFilePath 55 | } 56 | 57 | fun getGameConfig(): String { 58 | return GAME_CONFIG_Path 59 | } 60 | 61 | fun getDownloadModPath(): String { 62 | return DOWNLOAD_MOD_PATH 63 | } 64 | 65 | fun setModPath(path: String) { 66 | MOD_PATH = rootPath + path 67 | } 68 | 69 | fun getModPath(): String { 70 | return MOD_PATH 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/tools/specialGameTools/BaseSpecialGameTools.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.tools.specialGameTools 2 | 3 | import android.util.Log 4 | import top.laoxin.modmanager.data.bean.BackupBean 5 | import top.laoxin.modmanager.data.bean.GameInfoBean 6 | import top.laoxin.modmanager.data.bean.ModBean 7 | import top.laoxin.modmanager.data.bean.ModBeanTemp 8 | import top.laoxin.modmanager.listener.ProgressUpdateListener 9 | import top.laoxin.modmanager.tools.ArchiveUtil 10 | import java.io.InputStream 11 | import java.security.MessageDigest 12 | 13 | 14 | interface BaseSpecialGameTools { 15 | 16 | companion object { 17 | var progressUpdateListener: ProgressUpdateListener? = null 18 | } 19 | 20 | 21 | fun specialOperationEnable(mod: ModBean, packageName: String): Boolean 22 | fun specialOperationDisable( 23 | backup: List, 24 | packageName: String, 25 | modBean: ModBean 26 | ): Boolean 27 | 28 | fun specialOperationStartGame(gameInfo: GameInfoBean): Boolean 29 | fun specialOperationCreateMods(gameInfo: GameInfoBean): List 30 | fun specialOperationScanMods(gameInfo: String, modFileName: String): Boolean 31 | fun specialOperationSelectGame(gameInfo: GameInfoBean): Boolean 32 | fun specialOperationNeedOpenVpn(): Boolean 33 | fun needGameService(): Boolean 34 | fun specialOperationUpdateGameInfo(gameInfo: GameInfoBean): GameInfoBean 35 | fun specialOperationBeforeStartGame(gameInfo: GameInfoBean) : Int 36 | 37 | fun onProgressUpdate(progress: String) { 38 | progressUpdateListener?.onProgressUpdate(progress) 39 | } 40 | 41 | 42 | fun calculateMD5(inputStream: InputStream): String? { 43 | try { 44 | val buffer = ByteArray(8192) 45 | val md5 = MessageDigest.getInstance("MD5") 46 | 47 | var numRead: Int 48 | while (inputStream.read(buffer).also { numRead = it } > 0) { 49 | md5.update(buffer, 0, numRead) 50 | } 51 | 52 | val md5Bytes = md5.digest() 53 | val result = StringBuilder(md5Bytes.size * 2) 54 | 55 | md5Bytes.forEach { 56 | val i = it.toInt() 57 | result.append(Character.forDigit((i shr 4) and 0xf, 16)) 58 | result.append(Character.forDigit(i and 0xf, 16)) 59 | } 60 | return result.toString() 61 | } catch (e: Exception) { 62 | Log.e("BaseSpecialGameTools", "calculateMD5: ${e.message}") 63 | return null 64 | } 65 | 66 | } 67 | 68 | // 读取zip文件流 69 | fun getZipFileInputStream( 70 | zipFilePath: String, 71 | fileName: String, 72 | password: String? 73 | ): InputStream? { 74 | kotlin.runCatching { 75 | ArchiveUtil.getArchiveItemInputStream( 76 | zipFilePath, 77 | fileName, 78 | password 79 | ) 80 | }.onSuccess { 81 | return it 82 | }.onFailure { 83 | Log.e("BaseSpecialGameTools", "getZipFileInputStream: ${it.message}") 84 | return null 85 | } 86 | return null 87 | } 88 | 89 | fun getInputStreamSize(inputStream: InputStream): Long { 90 | val buffer = ByteArray(8192) 91 | var count = 0L 92 | var n = 0 93 | while (-1 != inputStream.read(buffer).also { n = it }) { 94 | count += n.toLong() 95 | } 96 | return count 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/tools/specialGameTools/SpecialGameToolsManager.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.tools.specialGameTools 2 | 3 | import android.util.Log 4 | import top.laoxin.modmanager.di.SpecialGameToolsModule 5 | import javax.inject.Inject 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | class SpecialGameToolsManager @Inject constructor( 10 | @param:SpecialGameToolsModule.ArknightsToolsImpl private val arknightsTools: BaseSpecialGameTools, 11 | @param:SpecialGameToolsModule.ProjectSnowToolsImpl private val projectSnowTools: BaseSpecialGameTools 12 | ) { 13 | 14 | private val specialGameTools: Map = mapOf( 15 | "arknights" to arknightsTools, 16 | "com.mrfz" to arknightsTools, 17 | "projectsnow" to projectSnowTools, 18 | "snowbreak" to projectSnowTools, 19 | "Arknights" to arknightsTools 20 | ) 21 | 22 | companion object { 23 | const val TAG = "SpecialGameToolsManager" 24 | 25 | } 26 | 27 | fun getSpecialGameTools(packageName: String): BaseSpecialGameTools? { 28 | // 遍历map 29 | Log.d(TAG, "getSpecialGameTools查询: $packageName") 30 | for ((key, value) in specialGameTools) { 31 | if (packageName.contains(key)) { 32 | return value 33 | } 34 | } 35 | Log.d(TAG, "getSpecialGameTools查询失败: $packageName") 36 | return null 37 | } 38 | 39 | // 获取arknightsTools 40 | fun getArknightsTools(): BaseSpecialGameTools { 41 | return arknightsTools 42 | } 43 | 44 | // 获取projectSnowTools 45 | fun getProjectSnowTools(): BaseSpecialGameTools { 46 | return projectSnowTools 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/state/ConsoleUiState.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.state 2 | 3 | import top.laoxin.modmanager.constant.GameInfoConstant 4 | import top.laoxin.modmanager.data.bean.GameInfoBean 5 | import top.laoxin.modmanager.data.bean.InfoBean 6 | 7 | data class ConsoleUiState( 8 | var antiHarmony: Boolean = false, 9 | var scanQQDirectory: Boolean = false, 10 | var selectedDirectory: String = "未选择", 11 | val scanDownload: Boolean = false, 12 | val openPermissionRequestDialog: Boolean = false, 13 | // mod数量 14 | val modCount: Int = 0, 15 | // 已开启mod数量 16 | val enableModCount: Int = 0, 17 | // 扫描文件夹中的Mods 18 | val scanDirectoryMods: Boolean = true, 19 | // 游戏信息 20 | val gameInfo: GameInfoBean = GameInfoConstant.gameInfoList[0], 21 | // 是否可以安装mod 22 | val canInstallMod: Boolean = false, 23 | // 是否显示扫描文件夹中的Mods对话框 24 | val showScanDirectoryModsDialog: Boolean = false, 25 | // 显示升级弹窗 26 | val showUpgradeDialog: Boolean = false, 27 | // 显示信息弹窗 28 | val showInfoDialog: Boolean = false, 29 | // 信息弹窗内容 30 | val infoBean: InfoBean = InfoBean(0.0, ""), 31 | // 显示删除解压目录弹窗 32 | val showDeleteUnzipDialog: Boolean = false, 33 | // 自动删除解压目录 34 | val delUnzipDictionary: Boolean = false, 35 | // 展示分类视图 36 | val showCategoryView: Boolean = true, 37 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/state/ModUiState.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.state 2 | 3 | import top.laoxin.modmanager.data.bean.ModBean 4 | import top.laoxin.modmanager.ui.view.modView.NavigationIndex 5 | import java.io.File 6 | 7 | data class ModUiState( 8 | val modList: List = emptyList(), // 所有mod列表 9 | val enableModList: List = emptyList(), // 开启mod列表 10 | val disableModList: List = emptyList(), // 关闭mod列表 11 | val searchModList: List = emptyList(), // 搜索mod列表 12 | val isLoading: Boolean = false, // 是否正在加载 13 | val openPermissionRequestDialog: Boolean = false, // 是否打开权限请求对话框 14 | val modDetail: ModBean? = null, // 打开的mod详情 15 | val showModDetail: Boolean = false, // 是否显示mod详情 16 | val searchBoxVisible: Boolean = false, // 搜索框是否可见 17 | val loadingPath: String = "", // 加载路径 18 | val selectedMenuItem: Int = 0, // 选中的菜单项 19 | val showPasswordDialog: Boolean = false, // 是否打开密码对话框 20 | val tipsText: String = "", // 提示文本 21 | val showTips: Boolean = false, // 是否显示提示 22 | val modSwitchEnable: Boolean = true, // 是否正在切换mod 23 | val showUserTipsDialog: Boolean = false, // 显示用户提示对话框 24 | val delEnableModsList: List = emptyList(), // mod列表 25 | val showDisEnableModsDialog: Boolean = false, // 是否显示禁用mod对话框 26 | val unzipProgress: String = "", // 解压进度 27 | val modsView: NavigationIndex = NavigationIndex.MODS_BROWSER, // mod视图 28 | val modsSelected: List = emptyList(), // 选中的mod 29 | val isMultiSelect: Boolean = false, // 是否显示多选 30 | val multitaskingProgress: String = "", // 多任务进度 31 | // 显示删除选择MODS的弹窗 32 | val showDelSelectModsDialog: Boolean = false, // 是否显示删除选择mod对话框 33 | val showDelModDialog: Boolean = false, 34 | // 显示开启失败是否关闭MODS的弹窗 35 | val showOpenFailedDialog: Boolean = false, // 是否显示开启失败对话框 36 | // 开启失败的mods 37 | val openFailedMods: List = emptyList(), 38 | // 搜索框内容 39 | val searchContent: String = "", 40 | // 当前的游戏mod目录 41 | val currentGameModPath: String = "", 42 | // 当前页面的文件 43 | val currentFiles: List = emptyList(), 44 | // 当前页面的mods 45 | val currentMods: List = emptyList(), 46 | // 当前路径 47 | val currentPath: String = "", 48 | // 是否显示返回按钮 49 | val isBackPathExist: Boolean = false, 50 | // 执行返回操作 51 | val doBackFunction: Boolean = false, 52 | // 是否显示强制扫描对话框 53 | val showForceScanDialog: Boolean = false, 54 | ) 55 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/state/SettingUiState.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.state 2 | 3 | import top.laoxin.modmanager.data.bean.DownloadGameConfigBean 4 | import top.laoxin.modmanager.data.bean.GameInfoBean 5 | import top.laoxin.modmanager.data.bean.InfoBean 6 | import top.laoxin.modmanager.data.bean.ThanksBean 7 | 8 | data class SettingUiState( 9 | // 删除备份对话框 10 | val deleteBackupDialog: Boolean = false, 11 | // 删除缓存对话框 12 | val deleteCacheDialog: Boolean = false, 13 | val showAcknowledgments: Boolean = false, 14 | val showSwitchGame: Boolean = false, 15 | val gameInfoList: List = emptyList(), 16 | // 更新弹窗 17 | val showUpdateDialog: Boolean = false, 18 | // 当前的versionName 19 | val versionName: String = "", 20 | // 显示下载游戏配置弹窗 21 | val showDownloadGameConfigDialog: Boolean = false, 22 | // 下载游戏配置列表 23 | val downloadGameConfigList: List = emptyList(), 24 | // 感谢名单 25 | val thinksList: List = emptyList(), 26 | // 游戏提示弹窗 27 | val showGameTipsDialog: Boolean = false, 28 | 29 | //权限提示窗 30 | val openPermissionRequestDialog: Boolean = false, 31 | // 显示通知弹窗 32 | val showNotificationDialog: Boolean = false, 33 | // 通知 34 | val infoBean: InfoBean = InfoBean(0.0, ""), 35 | // 显示关于页面 36 | val showAbout: Boolean = false, 37 | ) 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/state/UserPreferencesState.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.state 2 | 3 | data class UserPreferencesState( 4 | var scanQQDirectory: Boolean = false, 5 | var selectedDirectory: String = "未选择", 6 | val scanDownload: Boolean = false, 7 | val installPath: String = "", 8 | val gameService: String = "", 9 | val selectedGameIndex: Int = 0, 10 | val scanDirectoryMods: Boolean = true, 11 | val delUnzipDictionary: Boolean = false, 12 | val showCategoryView: Boolean = true, 13 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val primaryLight = Color(0xFF415F91) 6 | val onPrimaryLight = Color(0xFFFFFFFF) 7 | val primaryContainerLight = Color(0xFFD6E3FF) 8 | val onPrimaryContainerLight = Color(0xFF001B3E) 9 | val secondaryLight = Color(0xFF565F71) 10 | val onSecondaryLight = Color(0xFFFFFFFF) 11 | val secondaryContainerLight = Color(0xFFDAE2F9) 12 | val onSecondaryContainerLight = Color(0xFF131C2B) 13 | val tertiaryLight = Color(0xFF705575) 14 | val onTertiaryLight = Color(0xFFFFFFFF) 15 | val tertiaryContainerLight = Color(0xFFFAD8FD) 16 | val onTertiaryContainerLight = Color(0xFF28132E) 17 | val errorLight = Color(0xFFBA1A1A) 18 | val onErrorLight = Color(0xFFFFFFFF) 19 | val errorContainerLight = Color(0xFFFFDAD6) 20 | val onErrorContainerLight = Color(0xFF410002) 21 | val backgroundLight = Color(0xFFF9F9FF) 22 | val onBackgroundLight = Color(0xFF191C20) 23 | val surfaceLight = Color(0xFFF9F9FF) 24 | val onSurfaceLight = Color(0xFF191C20) 25 | val surfaceVariantLight = Color(0xFFE0E2EC) 26 | val onSurfaceVariantLight = Color(0xFF44474E) 27 | val outlineLight = Color(0xFF74777F) 28 | val outlineVariantLight = Color(0xFFC4C6D0) 29 | val scrimLight = Color(0xFF000000) 30 | val inverseSurfaceLight = Color(0xFF2E3036) 31 | val inverseOnSurfaceLight = Color(0xFFF0F0F7) 32 | val inversePrimaryLight = Color(0xFFAAC7FF) 33 | val surfaceDimLight = Color(0xFFD9D9E0) 34 | val surfaceBrightLight = Color(0xFFF9F9FF) 35 | val surfaceContainerLowestLight = Color(0xFFFFFFFF) 36 | val surfaceContainerLowLight = Color(0xFFF3F3FA) 37 | val surfaceContainerLight = Color(0xFFEDEDF4) 38 | val surfaceContainerHighLight = Color(0xFFE7E8EE) 39 | val surfaceContainerHighestLight = Color(0xFFE2E2E9) 40 | 41 | val primaryDark = Color(0xFFAAC7FF) 42 | val onPrimaryDark = Color(0xFF0A305F) 43 | val primaryContainerDark = Color(0xFF284777) 44 | val onPrimaryContainerDark = Color(0xFFD6E3FF) 45 | val secondaryDark = Color(0xFFBEC6DC) 46 | val onSecondaryDark = Color(0xFF283141) 47 | val secondaryContainerDark = Color(0xFF3E4759) 48 | val onSecondaryContainerDark = Color(0xFFDAE2F9) 49 | val tertiaryDark = Color(0xFFDDBCE0) 50 | val onTertiaryDark = Color(0xFF3F2844) 51 | val tertiaryContainerDark = Color(0xFF573E5C) 52 | val onTertiaryContainerDark = Color(0xFFFAD8FD) 53 | val errorDark = Color(0xFFFFB4AB) 54 | val onErrorDark = Color(0xFF690005) 55 | val errorContainerDark = Color(0xFF93000A) 56 | val onErrorContainerDark = Color(0xFFFFDAD6) 57 | val backgroundDark = Color(0xFF111318) 58 | val onBackgroundDark = Color(0xFFE2E2E9) 59 | val surfaceDark = Color(0xFF111318) 60 | val onSurfaceDark = Color(0xFFE2E2E9) 61 | val surfaceVariantDark = Color(0xFF44474E) 62 | val onSurfaceVariantDark = Color(0xFFC4C6D0) 63 | val outlineDark = Color(0xFF8E9099) 64 | val outlineVariantDark = Color(0xFF44474E) 65 | val scrimDark = Color(0xFF000000) 66 | val inverseSurfaceDark = Color(0xFFE2E2E9) 67 | val inverseOnSurfaceDark = Color(0xFF2E3036) 68 | val inversePrimaryDark = Color(0xFF415F91) 69 | val surfaceDimDark = Color(0xFF111318) 70 | val surfaceBrightDark = Color(0xFF37393E) 71 | val surfaceContainerLowestDark = Color(0xFF0C0E13) 72 | val surfaceContainerLowDark = Color(0xFF191C20) 73 | val surfaceContainerDark = Color(0xFF1D2024) 74 | val surfaceContainerHighDark = Color(0xFF282A2F) 75 | val surfaceContainerHighestDark = Color(0xFF33353A) 76 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ), 18 | titleLarge = TextStyle( 19 | fontFamily = FontFamily.Default, 20 | fontWeight = FontWeight.Bold, 21 | fontSize = 18.sp, 22 | lineHeight = 28.sp, 23 | letterSpacing = 0.sp 24 | ), 25 | labelSmall = TextStyle( 26 | fontFamily = FontFamily.Default, 27 | fontWeight = FontWeight.Medium, 28 | fontSize = 11.sp, 29 | lineHeight = 16.sp, 30 | letterSpacing = 0.5.sp 31 | ) 32 | 33 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/view/commen/DialogCompose.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.view.commen 2 | 3 | import androidx.compose.foundation.layout.padding 4 | import androidx.compose.material3.AlertDialog 5 | import androidx.compose.material3.Text 6 | import androidx.compose.material3.TextButton 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.res.stringResource 10 | import androidx.compose.ui.text.TextStyle 11 | import androidx.compose.ui.text.style.TextAlign 12 | import androidx.compose.ui.unit.dp 13 | import androidx.compose.ui.unit.sp 14 | import dev.jeziellago.compose.markdowntext.MarkdownText 15 | import top.laoxin.modmanager.R 16 | 17 | // 通用对话框 18 | @Composable 19 | fun DialogCommon( 20 | title: String, 21 | content: String, 22 | onConfirm: () -> Unit, 23 | onCancel: () -> Unit, 24 | showDialog: Boolean = false 25 | ) { 26 | if (showDialog) { 27 | AlertDialog( 28 | onDismissRequest = {}, // 空的 lambda 函数,表示点击对话框外的区域不会关闭对话框 29 | title = { Text(text = title) }, 30 | text = { 31 | MarkdownText( 32 | content, 33 | Modifier.padding(16.dp), 34 | style = TextStyle( 35 | fontSize = 14.sp, 36 | textAlign = TextAlign.Justify, 37 | ), 38 | ) 39 | }, 40 | confirmButton = { 41 | TextButton(onClick = { 42 | onConfirm() 43 | }) { 44 | Text(stringResource(id = R.string.dialog_button_confirm)) 45 | } 46 | }, 47 | dismissButton = { 48 | TextButton(onClick = { 49 | onCancel() 50 | }) { 51 | Text(stringResource(id = R.string.dialog_button_request_close)) 52 | } 53 | } 54 | ) 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/view/commen/DialogComposeForUpdate.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.view.commen 2 | 3 | import androidx.compose.foundation.layout.padding 4 | import androidx.compose.material3.AlertDialog 5 | import androidx.compose.material3.Text 6 | import androidx.compose.material3.TextButton 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.res.stringResource 10 | import androidx.compose.ui.text.TextStyle 11 | import androidx.compose.ui.text.style.TextAlign 12 | import androidx.compose.ui.unit.dp 13 | import androidx.compose.ui.unit.sp 14 | import dev.jeziellago.compose.markdowntext.MarkdownText 15 | import top.laoxin.modmanager.R 16 | 17 | // 更新对话框 18 | @Composable 19 | fun DialogCommonForUpdate( 20 | title: String, 21 | content: String, 22 | onConfirm: () -> Unit, 23 | onDismiss: () -> Unit, 24 | showDialog: Boolean = false 25 | ) { 26 | if (showDialog) { 27 | AlertDialog( 28 | // 空的 lambda 函数,表示点击对话框外的区域不会关闭对话框 29 | onDismissRequest = {}, 30 | title = { Text(text = title) }, 31 | text = { 32 | MarkdownText( 33 | content, 34 | Modifier.padding(16.dp), 35 | style = TextStyle( 36 | fontSize = 14.sp, 37 | textAlign = TextAlign.Justify, 38 | ), 39 | ) 40 | }, 41 | confirmButton = { 42 | TextButton(onClick = { 43 | onConfirm() 44 | }) { 45 | Text(stringResource(id = R.string.download)) 46 | } 47 | }, 48 | dismissButton = { 49 | TextButton(onClick = { 50 | onDismiss() 51 | }) { 52 | Text(stringResource(id = R.string.download_universal)) 53 | } 54 | } 55 | ) 56 | 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/view/modView/AllMod.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.view.modView 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.res.stringResource 11 | import top.laoxin.modmanager.R 12 | import top.laoxin.modmanager.ui.state.ModUiState 13 | import top.laoxin.modmanager.ui.viewmodel.ModViewModel 14 | 15 | @Composable 16 | fun AllModPage( 17 | viewModel: ModViewModel, 18 | uiState: ModUiState 19 | ) { 20 | if (uiState.modList.isEmpty()) { 21 | NoMod() 22 | return 23 | } 24 | val modList = when (uiState.modsView) { 25 | NavigationIndex.ALL_MODS -> uiState.modList 26 | NavigationIndex.ENABLE_MODS -> uiState.enableModList 27 | NavigationIndex.DISABLE_MODS -> uiState.disableModList 28 | NavigationIndex.SEARCH_MODS -> uiState.searchModList 29 | else -> uiState.modList 30 | } 31 | 32 | if (modList.isEmpty()) { 33 | NoMod() 34 | return 35 | } 36 | ModList( 37 | mods = modList, 38 | showDialog = viewModel::openModDetail, 39 | enableMod = viewModel::switchMod, 40 | modSwitchEnable = uiState.modSwitchEnable, 41 | isMultiSelect = uiState.isMultiSelect, 42 | modsSelected = uiState.modsSelected, 43 | onLongClick = viewModel::modLongClick, 44 | onMultiSelectClick = viewModel::modMultiSelectClick, 45 | modViewModel = viewModel 46 | ) 47 | } 48 | 49 | @Composable 50 | fun NoMod() { 51 | Box( 52 | modifier = Modifier.fillMaxSize(), 53 | contentAlignment = Alignment.Center 54 | ) { 55 | Text( 56 | text = stringResource(id = R.string.mod_page_no_mod), 57 | style = MaterialTheme.typography.titleMedium 58 | ) 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/view/settingView/License.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.view.settingView 2 | 3 | import androidx.activity.compose.BackHandler 4 | import androidx.compose.foundation.layout.PaddingValues 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.platform.LocalContext 9 | import androidx.compose.ui.unit.dp 10 | import com.mikepenz.aboutlibraries.Libs 11 | import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults 12 | import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer 13 | import com.mikepenz.aboutlibraries.ui.compose.m3.libraryColors 14 | import com.mikepenz.aboutlibraries.util.withJson 15 | import top.laoxin.modmanager.R 16 | import top.laoxin.modmanager.ui.viewmodel.SettingViewModel 17 | 18 | @Composable 19 | fun License(modifier: Modifier, viewModel: SettingViewModel) { 20 | val context = LocalContext.current 21 | 22 | LibrariesContainer( 23 | modifier = modifier, 24 | librariesBlock = { ctx -> 25 | Libs.Builder() 26 | .withJson(ctx, R.raw.aboutlibraries) 27 | .build() 28 | }, 29 | colors = LibraryDefaults.libraryColors( 30 | badgeBackgroundColor = MaterialTheme.colorScheme.tertiary 31 | ), 32 | padding = LibraryDefaults.libraryPadding( 33 | badgeContentPadding = PaddingValues(4.dp) 34 | ), 35 | onLibraryClick = { library -> 36 | library.website?.let { website -> 37 | viewModel.openUrl(context, website) 38 | } 39 | }, 40 | ) 41 | 42 | BackHandler { 43 | viewModel.setAboutPage(false) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/view/startView/StartContent.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.view.startView 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.res.Configuration 6 | import androidx.compose.foundation.Image 7 | import androidx.compose.foundation.layout.Arrangement 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.Spacer 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.foundation.layout.height 12 | import androidx.compose.foundation.layout.size 13 | import androidx.compose.foundation.shape.RoundedCornerShape 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Alignment 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.draw.clip 18 | import androidx.compose.ui.layout.ContentScale 19 | import androidx.compose.ui.platform.LocalConfiguration 20 | import androidx.compose.ui.platform.LocalContext 21 | import androidx.compose.ui.res.painterResource 22 | import androidx.compose.ui.tooling.preview.Preview 23 | import androidx.compose.ui.unit.dp 24 | import androidx.core.content.res.ResourcesCompat 25 | import top.laoxin.modmanager.R 26 | import kotlin.random.Random 27 | 28 | @SuppressLint("NewApi", "ConfigurationScreenWidthHeight") 29 | @Composable 30 | fun StartContent() { 31 | val configuration = LocalConfiguration.current 32 | val context = LocalContext.current 33 | val screenWidth = configuration.screenWidthDp.dp 34 | val screenHeight = configuration.screenHeightDp.dp 35 | 36 | val imageSize = if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { 37 | screenWidth * 0.8f 38 | } else { 39 | screenHeight * 0.6f 40 | } 41 | 42 | val imageResIds = getDrawableResourcesByPattern(context, "start_") 43 | 44 | val randomImageResId = if (imageResIds.isNotEmpty()) { 45 | imageResIds[Random.nextInt(imageResIds.size)] 46 | } else { 47 | R.drawable.start_2 // 48 | } 49 | 50 | Column( 51 | modifier = Modifier.fillMaxSize(), 52 | verticalArrangement = Arrangement.Center 53 | ) { 54 | Spacer(modifier = Modifier.weight(1f)) 55 | // Add splash screen background 56 | Image( 57 | painter = painterResource(id = randomImageResId), 58 | contentDescription = null, 59 | modifier = Modifier 60 | .size(imageSize) 61 | .align(Alignment.CenterHorizontally), 62 | contentScale = ContentScale.Fit 63 | ) 64 | Spacer(modifier = Modifier.weight(1f)) 65 | // Bottom app icon 66 | Image( 67 | painter = painterResource(id = R.drawable.app_icon), 68 | contentDescription = null, 69 | modifier = Modifier 70 | .size(50.dp) 71 | .align(Alignment.CenterHorizontally) 72 | .clip(RoundedCornerShape(8.dp)), 73 | contentScale = ContentScale.Fit 74 | ) 75 | Spacer(modifier = Modifier.height(screenHeight * 0.1f)) 76 | } 77 | } 78 | 79 | @SuppressLint("DiscouragedApi") 80 | fun getDrawableResourcesByPattern(context: Context, pattern: String): List { 81 | val resources = context.resources 82 | val packageName = context.packageName 83 | val drawableResIds = mutableListOf() 84 | 85 | val fields = R.drawable::class.java.fields 86 | for (field in fields) { 87 | if (field.name.startsWith(pattern)) { 88 | val resId = resources.getIdentifier(field.name, "drawable", packageName) 89 | if (resId != 0) { 90 | val drawable = ResourcesCompat.getDrawable(resources, resId, context.theme) 91 | if (drawable != null) { 92 | drawableResIds.add(resId) 93 | } 94 | } 95 | } 96 | } 97 | return drawableResIds 98 | } 99 | 100 | @Composable 101 | @Preview(showBackground = true) 102 | fun PreviewStartContent() { 103 | StartContent() 104 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/ui/viewmodel/VersionViewModel.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.ui.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.launch 9 | import top.laoxin.modmanager.data.repository.VersionRepository 10 | import javax.inject.Inject 11 | 12 | @HiltViewModel 13 | class VersionViewModel @Inject constructor( 14 | private val versionRepository: VersionRepository 15 | ) : ViewModel() { 16 | 17 | 18 | private val _version = MutableLiveData() 19 | val version: LiveData = _version 20 | 21 | // 获取版本号 22 | suspend fun loadVersion(): String { 23 | return versionRepository.getVersion() 24 | } 25 | 26 | // 更新版本号 27 | fun updateVersion(version: String) { 28 | viewModelScope.launch { 29 | versionRepository.saveVersion(version) 30 | _version.value = version 31 | } 32 | } 33 | 34 | // 获取版本信息 35 | suspend fun loadVersionInfo(): String { 36 | return versionRepository.getVersionInfo() 37 | } 38 | 39 | // 更新版本信息 40 | fun updateVersionInfo(versionInfo: String) { 41 | viewModelScope.launch { 42 | versionRepository.saveVersionInfo(versionInfo) 43 | } 44 | } 45 | 46 | // 获取版本下载地址 47 | suspend fun loadVersionUrl(): String { 48 | return versionRepository.getVersionUrl() 49 | } 50 | 51 | // 更新版本下载地址 52 | fun updateVersionUrl(versionUrl: String) { 53 | viewModelScope.launch { 54 | versionRepository.saveVersionUrl(versionUrl) 55 | } 56 | } 57 | 58 | // 获取版本下载地址 59 | suspend fun loadUniversalUrl(): String { 60 | return versionRepository.getUniversalUrl() 61 | } 62 | 63 | // 更新版本下载地址 64 | fun updateUniversalUrl(universalUrl: String) { 65 | viewModelScope.launch { 66 | versionRepository.saveUniversalUrl(universalUrl) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/userService/gamestart/ProjectSnowStartService.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.userService.gamestart 2 | 3 | import android.app.Notification 4 | import android.app.NotificationChannel 5 | import android.app.NotificationManager 6 | import android.app.Service 7 | import android.content.Intent 8 | import android.os.Build 9 | import android.os.IBinder 10 | import android.util.Log 11 | import androidx.core.app.NotificationCompat 12 | import dagger.hilt.android.AndroidEntryPoint 13 | import kotlinx.coroutines.CoroutineScope 14 | import kotlinx.coroutines.Dispatchers 15 | import kotlinx.coroutines.Job 16 | import kotlinx.coroutines.cancel 17 | import kotlinx.coroutines.launch 18 | import top.laoxin.modmanager.R 19 | import top.laoxin.modmanager.tools.manager.AppPathsManager 20 | import top.laoxin.modmanager.tools.manager.GameInfoManager 21 | import top.laoxin.modmanager.tools.specialGameTools.ProjectSnowTools 22 | import top.laoxin.modmanager.tools.specialGameTools.SpecialGameToolsManager 23 | import javax.inject.Inject 24 | import javax.inject.Singleton 25 | 26 | @AndroidEntryPoint 27 | @Singleton 28 | class ProjectSnowStartService : Service() { 29 | companion object { 30 | const val TAG = "ProjectSnowStartService" 31 | } 32 | 33 | @Inject 34 | lateinit var specialGameToolsManager: SpecialGameToolsManager 35 | 36 | @Inject 37 | lateinit var appPathsManager: AppPathsManager 38 | 39 | @Inject 40 | lateinit var gameInfoManager: GameInfoManager 41 | 42 | private val serviceScope = CoroutineScope(Dispatchers.IO + Job()) 43 | // 添加一个无参数的构造函数 44 | // 声明一个 GameInfo 变量 45 | 46 | 47 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 48 | 49 | val projectSnowTools = specialGameToolsManager.getProjectSnowTools() 50 | val gameInfo = gameInfoManager.getGameInfo() 51 | 52 | val checkFilepath = 53 | "${appPathsManager.getRootPath()}/Android/data/${gameInfo.packageName}/files/${ProjectSnowTools.CHECK_FILENAME}" 54 | Log.d("TestService", "onStartCommand: $checkFilepath") 55 | 56 | // 显示通知 57 | val channelId = getString(R.string.channel_id) 58 | val channelName = getString(R.string.channel_name) 59 | val importance = NotificationManager.IMPORTANCE_HIGH 60 | val channel = NotificationChannel(channelId, channelName, importance) 61 | 62 | val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager 63 | notificationManager.createNotificationChannel(channel) 64 | 65 | val notification: Notification = Notification.Builder(this, channelId) 66 | .setContentTitle(getString(R.string.channel_title)) 67 | .setContentText(getString(R.string.channel_content)) 68 | .setSmallIcon(R.drawable.app_icon) 69 | .setCategory(NotificationCompat.CATEGORY_CALL) 70 | .setOngoing(true) 71 | .build() 72 | 73 | startForeground(1, notification) 74 | 75 | serviceScope.launch { 76 | 77 | if (projectSnowTools.specialOperationStartGame(gameInfo)) { 78 | stopService() 79 | } 80 | 81 | } 82 | return START_STICKY 83 | } 84 | 85 | override fun onBind(intent: Intent?): IBinder? { 86 | return null 87 | } 88 | 89 | override fun onDestroy() { 90 | super.onDestroy() 91 | serviceScope.cancel() 92 | } 93 | 94 | fun stopService() { 95 | Log.d(TAG, "stopService: 服务已关闭") 96 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 97 | stopForeground(STOP_FOREGROUND_REMOVE) 98 | stopForeground(STOP_FOREGROUND_DETACH) 99 | } else { 100 | @Suppress("DEPRECATION") 101 | stopForeground(true) 102 | } 103 | stopSelf() 104 | } 105 | 106 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/top/laoxin/modmanager/userService/shizuku/FileExplorerServiceManager.kt: -------------------------------------------------------------------------------- 1 | package top.laoxin.modmanager.userService.shizuku 2 | 3 | import android.content.ComponentName 4 | import android.content.ServiceConnection 5 | import android.os.IBinder 6 | import android.util.Log 7 | import rikka.shizuku.Shizuku 8 | import rikka.shizuku.Shizuku.UserServiceArgs 9 | import top.laoxin.modmanager.App 10 | import top.laoxin.modmanager.BuildConfig 11 | import top.laoxin.modmanager.R 12 | import top.laoxin.modmanager.service.IFileExplorerService 13 | import top.laoxin.modmanager.tools.ToastUtils 14 | import top.laoxin.modmanager.tools.filetools.impl.ShizukuFileTools 15 | 16 | 17 | object FileExplorerServiceManager { 18 | const val TAG = "FileExplorerServiceManager" 19 | private var isBind = false 20 | 21 | val USER_SERVICE_ARGS: UserServiceArgs = UserServiceArgs( 22 | ComponentName(App.get().packageName, FileExplorerService::class.java.getName()) 23 | ).daemon(false).debuggable(BuildConfig.DEBUG).processNameSuffix("file_explorer_service") 24 | .version(1) 25 | 26 | val SERVICE_CONNECTION: ServiceConnection = object : ServiceConnection { 27 | override fun onServiceConnected(name: ComponentName, service: IBinder) { 28 | Log.d(TAG, "onServiceConnected: ") 29 | isBind = true 30 | 31 | ShizukuFileTools.iFileExplorerService = IFileExplorerService.Stub.asInterface(service) 32 | if (!isBind) { 33 | ToastUtils.shortCall(R.string.toast_shizuku_connected) 34 | } 35 | } 36 | 37 | override fun onServiceDisconnected(name: ComponentName) { 38 | Log.d(TAG, "onServiceDisconnected: ") 39 | isBind = false 40 | ShizukuFileTools.iFileExplorerService = null 41 | ToastUtils.shortCall(R.string.toast_shizuku_disconnected) 42 | } 43 | } 44 | 45 | fun bindService() { 46 | try { 47 | App.get() 48 | } catch (_: IllegalStateException) { 49 | Log.e(TAG, "Cannot bind service before application initialization!") 50 | return 51 | } 52 | 53 | Log.d(TAG, "bindService: isBind = $isBind") 54 | Shizuku.bindUserService(USER_SERVICE_ARGS, SERVICE_CONNECTION) 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/alipay_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/alipay_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/app_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/back_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/back_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/book_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/book_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/discord_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/discord_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/folder_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/folder_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/folder_mod_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/folder_mod_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/github_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/github_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/qq_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/qq_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/shizuku_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/shizuku_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/start.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/start.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/start_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/start_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/start_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/start_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/thank_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/thank_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/update_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/update_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/zip_mod_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/drawable/zip_mod_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #8F9CDE 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/test/kotlin/top/laoxin/modmanager/ak.json: -------------------------------------------------------------------------------- 1 | { 2 | "antiHarmonyContent": "", 3 | "antiHarmonyFile": "", 4 | "enableBackup": true, 5 | "gameFilePath": [ 6 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/chararts/", 7 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/skinpack/", 8 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/arts/dynchars/", 9 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/avg/characters/", 10 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/avg/imgs/", 11 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/spritepack/", 12 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/refs/", 13 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/ui/rglktopic/", 14 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/arts/ui/namecardskin/", 15 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/battle/prefabs/enemies/", 16 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/battle/prefabs/effects/", 17 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/audio/sound_beta_2/", 18 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/audio/sound_beta_2/voice_cn", 19 | "Android/data/com.hypergryph.arknights.bilibili/files/AB/Android/arts/ui/homebackground/wrapper" 20 | ], 21 | "gameName": "明日方舟", 22 | "gamePath": "Android/data/com.hypergryph.arknights.bilibili/", 23 | "isGameFileRepeat": true, 24 | "modSavePath": "", 25 | "modType": [ 26 | "普通立绘", 27 | "静态皮肤", 28 | "动态皮肤", 29 | "剧情立绘", 30 | "剧情cg", 31 | "肉鸽道具贴图", 32 | "界面ui素材", 33 | "肉鸽主题背景", 34 | "个人名片背景", 35 | "敌人修改", 36 | "效果修改", 37 | "角色语音", 38 | "中文语音", 39 | "主页背景" 40 | ], 41 | "packageName": "com.hypergryph.arknights.bilibili", 42 | "serviceName": "B服-配置2.0", 43 | "tips": "注意如果此前自行修改过游戏文件,可能会出现反检测失败导致无法正常使用MOD,建议先清除游戏数据重新下载游戏资源后再使用MOD,如果出现反检测失败无论此前更改过游戏文件都需要重新下载游戏资源!", 44 | "version": "2.0.0" 45 | } -------------------------------------------------------------------------------- /app/src/test/kotlin/top/laoxin/modmanager/碧蓝航线示例配置.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName" : "碧蓝航线", // 必须填一个名字 3 | "serviceName" : "官服", // 必须填一个服务器名字 4 | "packageName": "com.bilibili.azurlane", // 游戏包名必须正确 5 | "version" : "1.0.0", // 随便填 6 | "gamePath" : "Android/data/com.bilibili.azurlane/", // 游戏的data目录 7 | "antiHarmonyFile" : "Android/data/com.bilibili.azurlane/files/localization.txt", // 反和谐文件路径 8 | "antiHarmonyContent" : "Localization = true\nLocalization_skin = true", // 写入到反和谐文件中的内容,换行使用\n 9 | // 用于替换mod文件的目录 10 | "gameFilePath" : [ 11 | "Android/data/com.bilibili.azurlane/files/AssetBundles/shipyardicon/", 12 | "Android/data/com.bilibili.azurlane/files/AssetBundles/paintingface/", 13 | "Android/data/com.bilibili.azurlane/files/AssetBundles/painting/" 14 | ], 15 | "modType" : ["shipyardicon","paintingface","painting"], // mod类型类必须和gameFilePath中的目录一一对应,可以是中文 16 | "modSavePath" : "", // 随便填,可为空 17 | "isGameFileRepeat" : true // gameFilePath中设置的游戏目录如果存在同名文件,必须设置为true,并且不能开启扫描文件夹中的mod 18 | 19 | // 注意当"isGameFileRepeat"为true时,只能扫描压缩包,同时压缩包内mod文件必须放到gameFilePath最后的路径 20 | // 比如"Android/data/com.bilibili.azurlane/files/AssetBundles/shipyardicon/",这个gameFilePath中要想扫描到mod 21 | // 必须在压缩包内存在"shipyardico"文件夹,将用于替换这个游戏路径内的mod文件放入其中 22 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.androidApplication) apply false 3 | alias(libs.plugins.jetbrainsKotlinAndroid) apply false 4 | alias(libs.plugins.ksp) apply false 5 | alias(libs.plugins.hilt) apply false 6 | alias(libs.plugins.aboutLibraries) apply false 7 | } 8 | 9 | buildscript { 10 | extra.apply { 11 | set("room_version", "2.6.0") 12 | } 13 | 14 | dependencies { 15 | classpath(libs.okhttp3.okhttp) 16 | 17 | } 18 | repositories { 19 | google() 20 | mavenCentral() 21 | gradlePluginPortal() 22 | maven { url = uri("https://jitpack.io") } 23 | } 24 | } 25 | 26 | allprojects { 27 | repositories { 28 | google() 29 | mavenCentral() 30 | gradlePluginPortal() 31 | maven { url = uri("https://jitpack.io") } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /gameConfig/api/gameConfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "gameName": "交错战线", 4 | "packageName": "com.gamebeans.gp.jczx", 5 | "serviceName": "国际服", 6 | "downloadUrl": "" 7 | }, 8 | { 9 | "gameName": "Muv Luv Dimensions", 10 | "packageName": "jp.co.nextninja.dimensions", 11 | "serviceName": "日服", 12 | "downloadUrl": "" 13 | }, 14 | { 15 | "gameName": "尘白禁区", 16 | "packageName": "com.seasun.snowbreak.google", 17 | "serviceName": "国际服(谷歌play版)", 18 | "downloadUrl": "" 19 | }, 20 | { 21 | "gameName": "\u78a7\u84dd\u822a\u7ebf-\u914d\u7f6e\u7248\u672c2.0", 22 | "packageName": "com.bilibili.azurlane", 23 | "serviceName": "\u5b98\u670d", 24 | "downloadUrl": "" 25 | }, 26 | { 27 | "gameName": "\u78a7\u84dd\u822a\u7ebf4399\u670d-\u914d\u7f6e\u7248\u672c2.0", 28 | "packageName": "com.bilibili.blhx.m4399", 29 | "serviceName": "4399\u670d", 30 | "downloadUrl": "" 31 | }, 32 | { 33 | "gameName": "\u5c18\u767d\u7981\u533a", 34 | "packageName": "com.dragonli.projectsnow.bilibili", 35 | "serviceName": "B\u670d", 36 | "downloadUrl": "" 37 | }, 38 | { 39 | "gameName": "\u5c18\u767d\u7981\u533a", 40 | "packageName": "com.dragonli.projectsnow.lhm", 41 | "serviceName": "\u5b98\u670d", 42 | "downloadUrl": "" 43 | }, 44 | { 45 | "gameName": "\u68d5\u8272\u5c18\u57c32", 46 | "packageName": "com.neowizgames.game.browndust2", 47 | "serviceName": "\u56fd\u9645\u670d", 48 | "downloadUrl": "" 49 | }, 50 | { 51 | "gameName": "\u851a\u84dd\u6863\u6848-by siranaine", 52 | "packageName": "com.nexon.bluearchive", 53 | "serviceName": "\u56fd\u9645\u670d", 54 | "downloadUrl": "" 55 | }, 56 | { 57 | "gameName": "\u851a\u84dd\u6863\u6848-by siranaine", 58 | "packageName": "com.RoamingStar.BlueArchive.bilibili", 59 | "serviceName": "B\u670d", 60 | "downloadUrl": "" 61 | }, 62 | { 63 | "gameName": "\u851a\u84dd\u6863\u6848-by siranaine", 64 | "packageName": "com.RoamingStar.BlueArchive", 65 | "serviceName": "\u5b98\u670d", 66 | "downloadUrl": "" 67 | }, 68 | { 69 | "gameName": "\u851a\u84dd\u6863\u6848-by siranaine", 70 | "packageName": "com.YostarJP.BlueArchive", 71 | "serviceName": "\u65e5\u670d", 72 | "downloadUrl": "" 73 | } 74 | 75 | ] -------------------------------------------------------------------------------- /gameConfig/api/information.json: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "3.1", 3 | "msg" : "更新最新版后,之前在readme里定义了mod名称的压缩包mod可能出现显示双倍mod的问题与路径异常的问题,此问题已知且无修复计划\n解决方法为关闭出现问题的mod,然后修改此mod的压缩包名称,再重新扫描即可\n如果有条件,更推荐的方法为关闭所有mod后卸载重装实验室" 4 | } 5 | -------------------------------------------------------------------------------- /gameConfig/api/thanks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "siranaine", 4 | "job": "文档完善", 5 | "link": "https://github.com/siranaine" 6 | }, 7 | { 8 | "name": "P点", 9 | "job": "启动看板娘绘制", 10 | "link": "" 11 | }, 12 | { 13 | "name": "竹墨", 14 | "job": "启动看板娘绘制", 15 | "link": "" 16 | }, 17 | { 18 | "name": "yijiu", 19 | "job": "教程制作", 20 | "link": "" 21 | }, 22 | { 23 | "name": "Alex Mercer", 24 | "job": "bug测试", 25 | "link": "" 26 | }, 27 | { 28 | "name": "乌", 29 | "job": "bug测试", 30 | "link": "" 31 | }, 32 | { 33 | "name": "屮艸芔茻", 34 | "job": "功能建议", 35 | "link": "" 36 | }, 37 | { 38 | "name": "Dengdl", 39 | "job": "功能建议, bug测试", 40 | "link": "" 41 | }, 42 | { 43 | "name": "释迦", 44 | "job": "技术交流, moder", 45 | "link": "https://www.pixiv.net/users/29083582" 46 | }, 47 | { 48 | "name": "Yukinwo", 49 | "job": "技术交流, moder", 50 | "link": "https://www.pixiv.net/users/19157128" 51 | } 52 | , 53 | { 54 | "name": "翼尔殇诗舞", 55 | "job": "配置完善, moder", 56 | "link": "https://next.nexusmods.com/profile/976812098" 57 | } 58 | , 59 | { 60 | "name": "不甜的砂糖", 61 | "job": "配置完善, 功能开发", 62 | "link": "https://github.com/lings03" 63 | } 64 | , 65 | { 66 | "name": "昏睡猫猫头", 67 | "job": "图标制作", 68 | "link": "https://github.com/lings03" 69 | } 70 | , 71 | { 72 | "name": "皮克斯跑快快", 73 | "job": "交错自动化任务", 74 | "link": "https://github.com/MaaXYZ/MCCA" 75 | } 76 | ] 77 | -------------------------------------------------------------------------------- /gameConfig/com.RoamingStar.BlueArchive.bilibili.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "蔚蓝档案-by siranaine", 3 | "serviceName": "B服", 4 | "packageName": "com.RoamingStar.BlueArchive.bilibili", 5 | "version": "1.0.0", 6 | "gamePath": "Android/data/com.RoamingStar.BlueArchive.bilibili/", 7 | "antiHarmonyFile": "Android/data/com.RoamingStar.BlueArchive.bilibili/files/Localizeconfig.txt", 8 | "antiHarmonyContent": "Eny=dey\nlsLocalize=false\nResUrls=http://mx.ivav.net.cn/asdf;http://mx.jvav.net.cn/asdf:http://mx.ivav.net.cn/asdf ", 9 | "gameFilePath": [ 10 | "Android/data/com.RoamingStar.BlueArchive.bilibili/files/AssetBundles/" 11 | ], 12 | "modType": [ "AssetBundles" ], 13 | "modSavePath": "", 14 | "isGameFileRepeat": false, 15 | "enableBackup" : true, 16 | "tips" : "" 17 | 18 | 19 | } -------------------------------------------------------------------------------- /gameConfig/com.RoamingStar.BlueArchive.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "蔚蓝档案-by siranaine", 3 | "serviceName": "官服", 4 | "packageName": "com.RoamingStar.BlueArchive", 5 | "version": "1.0.0", 6 | "gamePath": "Android/data/com.RoamingStar.BlueArchive/", 7 | "antiHarmonyFile": "Android/data/com.RoamingStar.BlueArchive/files/Localizeconfig.txt", 8 | "antiHarmonyContent": "Eny=dey\nlsLocalize=false\nResUrls=http://mx.ivav.net.cn/asdf;http://mx.jvav.net.cn/asdf:http://mx.ivav.net.cn/asdf ", 9 | "gameFilePath": [ 10 | "Android/data/com.RoamingStar.BlueArchive/files/AssetBundles/" 11 | ], 12 | "modType": [ "AssetBundles" ], 13 | "modSavePath": "", 14 | "isGameFileRepeat": false, 15 | "enableBackup" : true, 16 | "tips" : "" 17 | } -------------------------------------------------------------------------------- /gameConfig/com.YoStarEN.Arknights.json: -------------------------------------------------------------------------------- 1 | { 2 | "antiHarmonyContent": "", 3 | "antiHarmonyFile": "", 4 | "enableBackup": true, 5 | "gameFilePath": [ 6 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/chararts/", 7 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/skinpack/", 8 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/arts/dynchars/", 9 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/avg/characters/", 10 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/avg/imgs/", 11 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/spritepack/", 12 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/refs/", 13 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/ui/rglktopic/", 14 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/arts/ui/namecardskin/", 15 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/battle/prefabs/enemies/", 16 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/battle/prefabs/effects/", 17 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/audio/sound_beta_2/", 18 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/audio/sound_beta_2/voice/", 19 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/audio/sound_beta_2/voice_cn/", 20 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/audio/sound_beta_2/voice_en/", 21 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/audio/sound_beta_2/voice_kr/", 22 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/audio/sound_beta_2/voice_custom/", 23 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/arts/ui/homebackground/wrapper/", 24 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/battle/prefabs/", 25 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/audio/sound_beta_2/music/rogue_2", 26 | "Android/data/com.YoStarEN.Arknights/files/AB/Android/arts/charportraits/" 27 | ], 28 | "gameName": "明日方舟", 29 | "gamePath": "Android/data/com.YoStarEN.Arknights/", 30 | "isGameFileRepeat": true, 31 | "modSavePath": "", 32 | "modType": [ 33 | "普通立绘", 34 | "静态皮肤", 35 | "动态皮肤", 36 | "剧情立绘", 37 | "剧情cg", 38 | "肉鸽道具贴图", 39 | "界面ui素材", 40 | "肉鸽主题背景", 41 | "个人名片背景", 42 | "敌人修改", 43 | "特效修改", 44 | "角色语音", 45 | "日文语音", 46 | "中文语音", 47 | "英文语音", 48 | "韩文语音", 49 | "方言语音", 50 | "主页背景", 51 | "token修改", 52 | "水月肉鸽背景音乐", 53 | "编队立绘" 54 | ], 55 | "packageName": "com.YoStarEN.Arknights", 56 | "serviceName": "international server-2.5", 57 | "tips": "注意如果此前自行修改过游戏文件,可能会出现反检测失败导致无法正常使用MOD,建议先清除游戏数据重新下载游戏资源后再使用MOD,如果出现反检测失败无论此前更改过游戏文件都需要重新下载游戏资源!", 58 | "version": "2.5" 59 | } -------------------------------------------------------------------------------- /gameConfig/com.YostarJP.BlueArchive.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "蔚蓝档案-by siranaine", 3 | "serviceName": "日服", 4 | "packageName": "com.YostarJP.BlueArchive", 5 | "version": "1.0.0", 6 | "gamePath": "Android/data/com.YostarJP.BlueArchive/", 7 | "antiHarmonyFile": "", 8 | "antiHarmonyContent": "", 9 | "gameFilePath": [ 10 | "Android/data/com.YostarJP.BlueArchive/files/AssetBundles/" 11 | ], 12 | "modType": [ "AssetBundles" ], 13 | "modSavePath": "", 14 | "isGameFileRepeat": false, 15 | "enableBackup" : true, 16 | "tips" : "" 17 | 18 | } -------------------------------------------------------------------------------- /gameConfig/com.bilibili.azurlane.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "碧蓝航线-配置版本2.0", 3 | "serviceName": "官服", 4 | "packageName": "com.bilibili.azurlane", 5 | "version": "1.0.0", 6 | "gamePath": "Android/data/com.bilibili.azurlane/", 7 | "antiHarmonyFile": "Android/data/com.bilibili.azurlane/files/localization.txt", 8 | "antiHarmonyContent": "Localization = true\nLocalization_skin = true", 9 | "gameFilePath": [ 10 | "Android/data/com.bilibili.azurlane/files/AssetBundles/shipyardicon/", 11 | "Android/data/com.bilibili.azurlane/files/AssetBundles/paintingface/", 12 | "Android/data/com.bilibili.azurlane/files/AssetBundles/painting/", 13 | "Android/data/com.bilibili.azurlane/files/AssetBundles/strategyicon/", 14 | "Android/data/com.bilibili.azurlane/files/AssetBundles/squareicon/", 15 | "Android/data/com.bilibili.azurlane/files/AssetBundles/herohrzicon/", 16 | "Android/data/com.bilibili.azurlane/files/AssetBundles/live2d/", 17 | "Android/data/com.bilibili.azurlane/files/AssetBundles/loadingbg/", 18 | "Android/data/com.bilibili.azurlane/files/AssetBundles/spinepainting/" 19 | ], 20 | "modType": [ 21 | "shipyardicon", 22 | "paintingface", 23 | "painting", 24 | "strategyicon", 25 | "squareicon", 26 | "herohrzicon", 27 | "live2d", 28 | "loadingbg", 29 | "spinepainting" 30 | ], 31 | "modSavePath": "", 32 | "isGameFileRepeat": true, 33 | "enableBackup": true, 34 | "tips": "" 35 | } -------------------------------------------------------------------------------- /gameConfig/com.bilibili.blhx.m4399.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "碧蓝航线4399服-配置版本2.0", 3 | "serviceName": "4399服", 4 | "packageName": "com.bilibili.blhx.m4399", 5 | "version": "1.0.0", 6 | "gamePath": "Android/data/com.bilibili.blhx.m4399/", 7 | "antiHarmonyFile": "Android/data/com.bilibili.blhx.m4399/files/localization.txt", 8 | "antiHarmonyContent": "Localization = true\nLocalization_skin = true", 9 | "gameFilePath": [ 10 | "Android/data/com.bilibili.blhx.m4399/files/AssetBundles/shipyardicon/", 11 | "Android/data/com.bilibili.blhx.m4399/files/AssetBundles/paintingface/", 12 | "Android/data/com.bilibili.blhx.m4399/files/AssetBundles/painting/", 13 | "Android/data/com.bilibili.blhx.m4399/files/AssetBundles/strategyicon/", 14 | "Android/data/com.bilibili.blhx.m4399/files/AssetBundles/squareicon/", 15 | "Android/data/com.bilibili.blhx.m4399/files/AssetBundles/herohrzicon/", 16 | "Android/data/com.bilibili.blhx.m4399/files/AssetBundles/live2d/", 17 | "Android/data/com.bilibili.blhx.m4399/files/AssetBundles/loadingbg/", 18 | "Android/data/com.bilibili.blhx.m4399/files/AssetBundles/spinepainting/" 19 | ], 20 | "modType": [ 21 | "shipyardicon", 22 | "paintingface", 23 | "painting", 24 | "strategyicon", 25 | "squareicon", 26 | "herohrzicon", 27 | "live2d", 28 | "loadingbg", 29 | "spinepainting" 30 | ], 31 | "modSavePath": "", 32 | "isGameFileRepeat": true, 33 | "enableBackup": true, 34 | "tips": "" 35 | } -------------------------------------------------------------------------------- /gameConfig/com.dragonli.projectsnow.bilibili.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "尘白禁区", 3 | "serviceName": "B服", 4 | "packageName": "com.dragonli.projectsnow.bilibili", 5 | "version": "2.3.0", 6 | "gamePath": "Android/data/com.dragonli.projectsnow.bilibili/", 7 | "antiHarmonyFile": "", 8 | "antiHarmonyContent": "", 9 | "gameFilePath": [ 10 | "Android/data/com.dragonli.projectsnow.bilibili/files/" 11 | ], 12 | "modType": [ "人物模型" ], 13 | "modSavePath": "", 14 | "isGameFileRepeat": false, 15 | "enableBackup" : false, 16 | "tips" : "注意!!!\n第一次以及后续游戏更新客户端等大更新后均需要执行下述操作\n\n国产系统需要在设置中允许实验室后台运行或者将电池优化(省电策略)设置为无限制\n选择尘白禁区游戏需要先清除游戏数据或者卸载重装再选择(清除或者卸载重装后先运行游戏到登录界面,然后回到这里选择游戏后再运行游戏下载游戏数据),否则会提示MOD文件复制失败,后续有大更新也需要再次操作\n\n同时启动游戏需要在实验室控制台启动才能生效,在启动过程中推荐点击屏幕跳过切换账号5秒的等待, 若游戏提示下载资源失败点击重试即可\n\n若游戏有小资源更新建议先正常启动一次游戏再从实验室启动注入MOD" 17 | } -------------------------------------------------------------------------------- /gameConfig/com.dragonli.projectsnow.lhm.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "尘白禁区", 3 | "serviceName": "官服", 4 | "packageName": "com.dragonli.projectsnow.lhm", 5 | "version": "2.3.0", 6 | "gamePath": "Android/data/com.dragonli.projectsnow.lhm/", 7 | "antiHarmonyFile": "", 8 | "antiHarmonyContent": "", 9 | "gameFilePath": [ 10 | "Android/data/com.dragonli.projectsnow.lhm/files/" 11 | ], 12 | "modType": [ "人物模型" ], 13 | "modSavePath": "", 14 | "isGameFileRepeat": false, 15 | "enableBackup" : false, 16 | "tips" : "注意!!!\n第一次以及后续游戏更新客户端等大更新后均需要执行下述操作\n\n国产系统需要在设置中允许实验室后台运行或者将电池优化(省电策略)设置为无限制\n选择尘白禁区游戏需要先清除游戏数据或者卸载重装再选择(清除或者卸载重装后先运行游戏到登录界面,然后回到这里选择游戏后再运行游戏下载游戏数据),否则会提示MOD文件复制失败,后续有大更新也需要再次操作\n\n同时启动游戏需要在实验室控制台启动才能生效,在启动过程中推荐点击屏幕跳过切换账号5秒的等待, 若游戏提示下载资源失败点击重试即可\n\n若游戏有小资源更新建议先正常启动一次游戏再从实验室启动注入MOD" 17 | } -------------------------------------------------------------------------------- /gameConfig/com.gamebeans.gp.jczx.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "交错战线", 3 | "serviceName": "国际服", 4 | "packageName": "com.gamebeans.gp.jczx", 5 | "version": "2.1.1", 6 | "gamePath": "Android/data/com.gamebeans.gp.jczx/", 7 | "antiHarmonyFile": "", 8 | "antiHarmonyContent": "", 9 | "gameFilePath": [ 10 | "Android/data/com.gamebeans.gp.jczx/files/Custom/", 11 | "Android/data/com.gamebeans.gp.jczx/files/videos/login/" 12 | ], 13 | "modType": [ "人物模型","登录动画" ], 14 | "modSavePath": "", 15 | "isGameFileRepeat": false, 16 | "enableBackup" : true, 17 | "tips" : "需要先下载游戏资源,才能扫描到MOD" 18 | } -------------------------------------------------------------------------------- /gameConfig/com.neowizgames.game.browndust2.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "棕色尘埃2", 3 | "serviceName": "国际服", 4 | "packageName": "com.neowizgames.game.browndust2", 5 | "version": "1.0.0", 6 | "gamePath": "Android/data/com.neowizgames.game.browndust2/", 7 | "antiHarmonyFile": "", 8 | "antiHarmonyContent": "", 9 | "gameFilePath": [ 10 | "Android/data/com.neowizgames.game.browndust2/files/UnityCache/Shared/" 11 | ], 12 | "modType": [ "Shared" ], 13 | "modSavePath": "", 14 | "isGameFileRepeat": false, 15 | "enableBackup" : true, 16 | "tips" : "此游戏配置未经验证,谨慎使用!" 17 | 18 | } -------------------------------------------------------------------------------- /gameConfig/com.nexon.bluearchive.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "蔚蓝档案-by siranaine", 3 | "serviceName": "国际服", 4 | "packageName": "com.nexon.bluearchive", 5 | "version": "1.0.0", 6 | "gamePath": "Android/data/com.nexon.bluearchive/", 7 | "antiHarmonyFile": "", 8 | "antiHarmonyContent": "", 9 | "gameFilePath": [ 10 | "Android/data/com.nexon.bluearchive/files/AssetBundles/" 11 | ], 12 | "modType": [ "AssetBundles" ], 13 | "modSavePath": "", 14 | "isGameFileRepeat": false, 15 | "enableBackup" : true, 16 | "tips" : "" 17 | 18 | } -------------------------------------------------------------------------------- /gameConfig/com.seasun.snowbreak.google.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "尘白禁区", 3 | "serviceName": "谷歌2.3.0", 4 | "packageName": "com.seasun.snowbreak.google", 5 | "version": "2.3.0", 6 | "gamePath": "Android/data/com.seasun.snowbreak.google/", 7 | "antiHarmonyFile": "", 8 | "antiHarmonyContent": "", 9 | "gameFilePath": [ 10 | "Android/data/com.seasun.snowbreak.google/files/" 11 | ], 12 | "modType": [ "人物模型" ], 13 | "modSavePath": "", 14 | "isGameFileRepeat": false, 15 | "enableBackup" : false, 16 | "tips" : "注意!!!\n第一次以及后续游戏更新客户端等大更新后均需要执行下述操作\n\n国产系统需要在设置中允许实验室后台运行或者将电池优化(省电策略)设置为无限制\n选择尘白禁区游戏需要先清除游戏数据或者卸载重装再选择(清除或者卸载重装后先运行游戏到登录界面,然后回到这里选择游戏后再运行游戏下载游戏数据),否则会提示MOD文件复制失败,后续有大更新也需要再次操作\n\n同时启动游戏需要在实验室控制台启动才能生效,在启动过程中推荐点击屏幕跳过切换账号5秒的等待, 若游戏提示下载资源失败点击重试即可\n\n若游戏有小资源更新建议先正常启动一次游戏再从实验室启动注入MOD" 17 | } -------------------------------------------------------------------------------- /gameConfig/jp.co.nextninja.dimensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "gameName": "Muv Luv Dimensions", 3 | "serviceName": "日服", 4 | "packageName": "jp.co.nextninja.dimensions", 5 | "version": "1.1", 6 | "gamePath": "Android/data/jp.co.nextninja.dimensions/", 7 | "antiHarmonyFile": "", 8 | "antiHarmonyContent": "", 9 | "gameFilePath": [ 10 | "Android/data/jp.co.nextninja.dimensions/files/Contents", 11 | "Android/data/jp.co.nextninja.dimensions/cache/Embedded/Tutorial/Contents/Android" 12 | ], 13 | "modType": [ 14 | "DLC", 15 | "本体" 16 | ], 17 | "modSavePath": "", 18 | "isGameFileRepeat": true, 19 | "enableBackup": true, 20 | "tips": "务必在游戏获取资源包后再扫描启用mod" 21 | 22 | } -------------------------------------------------------------------------------- /gameConfig/配置文件使用说明.txt: -------------------------------------------------------------------------------- 1 | 放入MOD实验 `配置的MOD目录`里的 `GameConfig`文件夹,再到设置中点击 `读取游戏配置`,然后点击 `选择游戏`即可使用自定义配置文件 -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. For more details, visit 12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | org.gradle.parallel=true 25 | org.gradle.caching=true 26 | android.r8.integratedResourceShrinking=true 27 | android.suppressUnsupportedCompileSdk=36 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /image/readme/1715962256872.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1715962256872.png -------------------------------------------------------------------------------- /image/readme/1715962345763.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1715962345763.png -------------------------------------------------------------------------------- /image/readme/1715962369620.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1715962369620.png -------------------------------------------------------------------------------- /image/readme/1715962378369.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1715962378369.png -------------------------------------------------------------------------------- /image/readme/1715962396435.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1715962396435.png -------------------------------------------------------------------------------- /image/readme/1715962416212.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1715962416212.png -------------------------------------------------------------------------------- /image/readme/1715963940732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1715963940732.png -------------------------------------------------------------------------------- /image/readme/1715964083599.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1715964083599.png -------------------------------------------------------------------------------- /image/readme/1715964191813.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1715964191813.png -------------------------------------------------------------------------------- /image/readme/1718686384539.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1718686384539.png -------------------------------------------------------------------------------- /image/readme/1718686686335.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laoxinH/crosscore-mod-manager/6b7abbed01371da8a6f4dda824022ed1804d27a7/image/readme/1718686686335.png -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "automerge": true, 4 | "extends": [ 5 | "config:recommended" 6 | ], 7 | "packageRules": [ 8 | { 9 | "matchUpdateTypes": [ 10 | "minor" 11 | ], 12 | "groupName": "minor dependencies" 13 | }, 14 | { 15 | "matchUpdateTypes": [ 16 | "patch" 17 | ], 18 | "groupName": "patch dependencies" 19 | }, 20 | { 21 | "matchUpdateTypes": [ 22 | "major" 23 | ], 24 | "groupName": "major dependencies" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | maven { url = uri("https://jitpack.io") } 7 | } 8 | } 9 | /*pluginManagement { 10 | repositories { 11 | gradlePluginPortal() 12 | google() 13 | mavenCentral() 14 | maven { url = uri("https://maven.aliyun.com/repository/google") } 15 | maven { url = uri("https://maven.aliyun.com/repository/jcenter") } 16 | } 17 | } 18 | dependencyResolutionManagement { 19 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 20 | repositories { 21 | google() 22 | mavenCentral() 23 | maven { url = uri("https://maven.aliyun.com/repository/google") } 24 | maven { url = uri("https://maven.aliyun.com/repository/jcenter") } 25 | } 26 | }*/ 27 | //pluginManagement { 28 | // repositories { 29 | // maven { url=uri ("https://jitpack.io") } 30 | // maven { url=uri ("https://maven.aliyun.com/repository/releases") } 31 | //// maven { url 'https://maven.aliyun.com/repository/jcenter' } 32 | // maven { url=uri ("https://maven.aliyun.com/repository/google") } 33 | // maven { url=uri ("https://maven.aliyun.com/repository/central") } 34 | // maven { url=uri ("https://maven.aliyun.com/repository/gradle-plugin") } 35 | // maven { url=uri ("https://maven.aliyun.com/repository/public") } 36 | // google() 37 | // mavenCentral() 38 | // gradlePluginPortal() 39 | // } 40 | //} 41 | //dependencyResolutionManagement { 42 | // repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 43 | // repositories { 44 | // maven { url=uri ("https://jitpack.io") } 45 | // maven { url=uri ("https://maven.aliyun.com/repository/releases") } 46 | //// maven { url 'https://maven.aliyun.com/repository/jcenter' } 47 | // maven { url=uri ("https://maven.aliyun.com/repository/google") } 48 | // maven { url=uri ("https://maven.aliyun.com/repository/central") } 49 | // maven { url=uri ("https://maven.aliyun.com/repository/gradle-plugin") } 50 | // maven { url=uri ("https://maven.aliyun.com/repository/public") } 51 | // google() 52 | // mavenCentral() 53 | // } 54 | //} 55 | 56 | rootProject.name = "Mod Manager" 57 | include(":app") 58 | --------------------------------------------------------------------------------