diff --git a/app/src/main/java/com/example/firefly_go_android/FireflyModMenu.kt b/app/src/main/java/com/example/firefly_go_android/FireflyModMenu.kt index 958c676..371cbbb 100644 --- a/app/src/main/java/com/example/firefly_go_android/FireflyModMenu.kt +++ b/app/src/main/java/com/example/firefly_go_android/FireflyModMenu.kt @@ -6,6 +6,7 @@ import android.util.Log import libandroid.Libandroid import java.io.File import java.io.FileOutputStream +import java.util.zip.ZipFile object FireflyModMenu { @@ -14,64 +15,265 @@ object FireflyModMenu { @JvmStatic fun init(activity: Activity) { + val modPackageName = "com.kain344.firefly_go_android" + Log.d("FireflyMod", "Initializing FireflyModMenu for module: $modPackageName") + val appDataPath = File(activity.getExternalFilesDir(null), "FireflyGo").absolutePath val dataDir = File("$appDataPath/data") if (!dataDir.exists()) dataDir.mkdirs() val sharedPrefs = activity.getSharedPreferences("FireflyModPrefs", Context.MODE_PRIVATE) - val modPackageName = "com.kain344.firefly_go_android" - val currentUpdateTime = try { - val packageInfo = activity.packageManager.getPackageInfo(modPackageName, 0) - packageInfo.lastUpdateTime - } catch (e: Exception) { - 0L + val apkPath = getModuleApkPath(FireflyModMenu::class.java.classLoader) + + val currentUpdateTime = if (apkPath != null) { + try { + val lastModified = File(apkPath).lastModified() + Log.d("FireflyMod", "Module APK path: $apkPath, Last modified: $lastModified") + lastModified + } catch (e: Exception) { + Log.e("FireflyMod", "Lỗi khi lấy last modified của APK: ${e.message}") + 0L + } + } else { + // Fallback to PackageManager if APK path could not be resolved + try { + val packageInfo = activity.packageManager.getPackageInfo(modPackageName, 0) + Log.d("FireflyMod", "Module package found. Last update: ${packageInfo.lastUpdateTime}") + packageInfo.lastUpdateTime + } catch (e: Exception) { + Log.e("FireflyMod", "Module package $modPackageName not found: ${e.message}") + 0L + } } val lastUpdateTime = sharedPrefs.getLong("last_update_time", 0L) val shouldOverride = currentUpdateTime != 0L && currentUpdateTime != lastUpdateTime + if (shouldOverride) { + Log.d("FireflyMod", "Module version changed (old: $lastUpdateTime, new: $currentUpdateTime). Forcing asset override.") + } if (!isServerStarted) { - Log.d("FireflyMod", "Start Server") + Log.d("FireflyMod", "Starting Server thread...") isServerStarted = true Thread { try { - // Lấy context của module để truy cập assets đúng cách trong môi trường Xposed - val modContext = try { - activity.createPackageContext(modPackageName, Context.CONTEXT_IGNORE_SECURITY) - } catch (e: Exception) { - Log.e("FireflyMod", "Không thể tạo mod context: ${e.message}") - activity + val isCopyDone = if (apkPath != null) { + copyRawFilesFromApk(apkPath, dataDir, shouldOverride) + } else { + // Fallback to createPackageContext + val modContext = try { + activity.createPackageContext(modPackageName, Context.CONTEXT_INCLUDE_CODE or Context.CONTEXT_IGNORE_SECURITY) + } catch (e: Exception) { + Log.e("FireflyMod", "Không thể tạo mod context cho $modPackageName: ${e.message}") + null + } + + if (modContext != null) { + copyRawFiles(modContext, dataDir, shouldOverride) + } else { + false + } } - val isCopyDone = copyRawFiles(modContext, dataDir, shouldOverride) if (isCopyDone) { if (shouldOverride) { sharedPrefs.edit().putLong("last_update_time", currentUpdateTime).apply() } Libandroid.setPathDataLocal(appDataPath) Libandroid.setServerRunning(true) + Log.d("FireflyMod", "Server started successfully.") } else { + Log.e("FireflyMod", "Lỗi khi copy assets, server không được khởi chạy.") isServerStarted = false } } catch (e: Exception) { - Log.e("FireflyMod", "Error starting server: ${e.message}", e) + Log.e("FireflyMod", "Error in server thread: ${e.message}", e) isServerStarted = false } }.start() } } + private fun getModuleApkPath(classLoader: ClassLoader?): String? { + var cl = classLoader + while (cl != null) { + Log.d("FireflyMod", "Checking ClassLoader: ${cl.javaClass.name}") + // 1. Try parsing toString() first (reflection-free, safe from hidden API checks) + val pathFromToString = getModuleApkPathFromToString(cl) + if (pathFromToString != null) { + Log.d("FireflyMod", "Found APK path via ClassLoader toString(): $pathFromToString") + return pathFromToString + } + + // 2. Try reflection on this class loader + val pathFromReflection = getModuleApkPathViaReflection(cl) + if (pathFromReflection != null) { + Log.d("FireflyMod", "Found APK path via reflection: $pathFromReflection") + return pathFromReflection + } + + cl = cl.parent + } + Log.w("FireflyMod", "Could not find module APK path in ClassLoader hierarchy.") + return null + } + + private fun getModuleApkPathFromToString(classLoader: ClassLoader): String? { + val clString = classLoader.toString() + val regexes = listOf( + Regex("""zip file "([^"]+)""""), + Regex("""zip file '([^']+)'"""), + Regex("""\[([^\]]+\.(apk|zip))\]"""), + Regex("""file:([^:\s]+\.(apk|zip))""") + ) + + var fallbackPath: String? = null + for (regex in regexes) { + val matches = regex.findAll(clString) + for (match in matches) { + if (match.groupValues.size > 1) { + val path = match.groupValues[1] + val file = File(path) + if (file.exists() && file.isFile) { + if (path.contains("com.kain344.firefly_go_android") && path.endsWith(".apk")) { + return path + } + if (path.endsWith(".apk") && fallbackPath == null) { + fallbackPath = path + } + } + } + } + } + + // General path match for any absolute path ending with .apk or .zip + val pathRegex = Regex("""(/[^:\s"']+\.(apk|zip))""") + val pathMatches = pathRegex.findAll(clString) + for (match in pathMatches) { + val path = match.groupValues[1] + val file = File(path) + if (file.exists() && file.isFile) { + if (path.contains("com.kain344.firefly_go_android") && path.endsWith(".apk")) { + return path + } + if (path.endsWith(".apk") && fallbackPath == null) { + fallbackPath = path + } + } + } + + return fallbackPath + } + + private fun getModuleApkPathViaReflection(classLoader: ClassLoader): String? { + try { + val baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader") + if (!baseDexClassLoaderClass.isInstance(classLoader)) return null + + val pathListField = baseDexClassLoaderClass.getDeclaredField("pathList") + pathListField.isAccessible = true + val pathList = pathListField.get(classLoader) ?: return null + + val dexElementsField = pathList.javaClass.getDeclaredField("dexElements") + dexElementsField.isAccessible = true + val dexElements = dexElementsField.get(pathList) as? Array<*> ?: return null + + var fallbackApkPath: String? = null + for (element in dexElements) { + if (element == null) continue + val elementClass = element.javaClass + val fileField = try { + elementClass.getDeclaredField("file") + } catch (e: NoSuchFieldException) { + try { + elementClass.getDeclaredField("path") + } catch (ex: NoSuchFieldException) { + null + } + } + if (fileField != null) { + fileField.isAccessible = true + val file = fileField.get(element) as? File + if (file != null && file.exists() && file.isFile) { + val path = file.absolutePath + if (path.contains("com.kain344.firefly_go_android") && path.endsWith(".apk")) { + return path + } + if (path.endsWith(".apk") && fallbackApkPath == null) { + fallbackApkPath = path + } + } + } + } + return fallbackApkPath + } catch (e: Exception) { + Log.d("FireflyMod", "Reflection failed on ClassLoader ${classLoader.javaClass.name}: ${e.message}") + } + return null + } + + private fun copyRawFilesFromApk(apkPath: String, targetDir: File, override: Boolean = false): Boolean { + val files = listOf( + "data-in-game.json" to "data-in-game.json", + "freesr-data.json" to "freesr-data.json", + "version.json" to "version.json" + ) + + return try { + ZipFile(apkPath).use { zip -> + for ((assetFile, outName) in files) { + val outFile = File(targetDir, outName) + if (outFile.exists() && !override) { + Log.d("FireflyMod", "Bỏ qua $outName (đã tồn tại)") + continue + } + + val entryName = "assets/$assetFile" + val entry = zip.getEntry(entryName) + if (entry == null) { + Log.e("FireflyMod", "Không tìm thấy entry $entryName trong APK") + return false + } + + zip.getInputStream(entry).use { input -> + FileOutputStream(outFile).use { output -> + input.copyTo(output) + output.fd.sync() + } + } + Log.d("FireflyMod", "Đã copy $assetFile từ APK thành công (override=$override)") + } + } + true + } catch (e: Exception) { + Log.e("FireflyMod", "Lỗi khi copy file từ APK: ${e.message}", e) + false + } + } + private fun copyRawFiles(context: Context, targetDir: File, override: Boolean = false): Boolean { val files = listOf( "data-in-game.json" to "data-in-game.json", "freesr-data.json" to "freesr-data.json", "version.json" to "version.json" ) + return try { + // Kiểm tra danh sách assets có sẵn để debug + val availableAssets = context.assets.list("")?.toList() ?: emptyList() + Log.d("FireflyMod", "Assets có sẵn trong module: $availableAssets") + for ((assetFile, outName) in files) { val outFile = File(targetDir, outName) - if (outFile.exists() && !override) continue + if (outFile.exists() && !override) { + Log.d("FireflyMod", "Bỏ qua $outName (đã tồn tại)") + continue + } + + if (!availableAssets.contains(assetFile)) { + Log.w("FireflyMod", "Cảnh báo: Không tìm thấy $assetFile trong danh sách assets của module!") + } context.assets.open(assetFile).use { input -> FileOutputStream(outFile).use { output -> @@ -79,12 +281,12 @@ object FireflyModMenu { output.fd.sync() } } - Log.d("FireflyMod", "Copied $assetFile to ${outFile.absolutePath} (override=$override)") + Log.d("FireflyMod", "Đã copy $assetFile thành công (override=$override)") } true } catch (e: Exception) { - Log.e("FireflyMod", "Error copying file: ${e.message}", e) + Log.e("FireflyMod", "Lỗi khi copy file từ assets của ${context.packageName}: ${e.message}", e) false } } -} \ No newline at end of file +} diff --git a/script/release.json b/script/release.json index b5e6c07..a800038 100644 --- a/script/release.json +++ b/script/release.json @@ -1,4 +1,4 @@ { - "tag": "4.3.2-02", - "title": "PreBuild Version 4.3.52 - 02" + "tag": "4.3.2-03", + "title": "PreBuild Version 4.3.52 - 03" }