From af42d9ad7c817b016b7543795449579caba29798 Mon Sep 17 00:00:00 2001 From: MisterGatto Date: Tue, 27 Jan 2026 16:35:14 +0100 Subject: [PATCH 01/15] 1.7.0 TEST --- app/src/main/java/com/tgc/sky/BuildConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tgc/sky/BuildConfig.java b/app/src/main/java/com/tgc/sky/BuildConfig.java index d17a067..e59e6e7 100644 --- a/app/src/main/java/com/tgc/sky/BuildConfig.java +++ b/app/src/main/java/com/tgc/sky/BuildConfig.java @@ -6,7 +6,7 @@ public class BuildConfig { public static String SKY_STAGE_NAME = "Live"; public static String SKY_SERVER_HOSTNAME = "live.radiance.thatgamecompany.com"; public static String SKY_VERSION = "0.17.5"; - public static final String SKY_BUILD_ACCESS_KEY = "1743442606-0c9545db24db0fee924a028c500c75f968145e6f15d2535ff2ca414798eba96a"; + public static final String SKY_BUILD_ACCESS_KEY = "KEY_TEST"; public static int VERSION_CODE = 192395; } From ac33fac6c9589184848ac2ca8ecf7af98d680861 Mon Sep 17 00:00:00 2001 From: MisterGatto Date: Tue, 27 Jan 2026 16:41:05 +0100 Subject: [PATCH 02/15] Update Canvas release URL in updater service --- .../git/artdeell/skymodloader/updater/CanvasUpdaterService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java b/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java index 355e977..8c469f9 100644 --- a/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java +++ b/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java @@ -13,7 +13,7 @@ protected String getCacheFileName() { @Override protected String getUpdateCheckerURL() { - return "https://api.github.com/repos/skyprotocol/canvas-distribution/releases/latest"; + return "https://api.github.com/MisterGatto/Canvas-Open-Source-test/releases/latest"; } @Override From 662fda4b6b00f9051de80107d471d2f78df4e7e9 Mon Sep 17 00:00:00 2001 From: MisterGatto Date: Tue, 27 Jan 2026 17:04:49 +0100 Subject: [PATCH 03/15] Typo Fix --- .../git/artdeell/skymodloader/updater/CanvasUpdaterService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java b/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java index 8c469f9..5908f2c 100644 --- a/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java +++ b/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java @@ -13,7 +13,7 @@ protected String getCacheFileName() { @Override protected String getUpdateCheckerURL() { - return "https://api.github.com/MisterGatto/Canvas-Open-Source-test/releases/latest"; + return "https://api.github.com/repos/MisterGatto/Canvas-Open-Source-test/releases/latest"; } @Override From 7ab9b2c0eb8a9f63ce1c9f73292c7384a1e87cce Mon Sep 17 00:00:00 2001 From: MisterGatto Date: Tue, 27 Jan 2026 17:30:20 +0100 Subject: [PATCH 04/15] Local Import --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 51 +-- app/src/main/cpp/CMakeLists.txt | 12 +- .../main/java/com/tgc/sky/BuildConfig.java | 2 +- .../java/com/tgc/sky/SystemIO_android.java | 1 + .../main/java/com/tgc/sky/io/DeviceKey.java | 153 ++++---- .../skymodloader/LogcatMonitorService.java | 173 +++++++++ .../artdeell/skymodloader/MainActivity.java | 137 ++++--- .../artdeell/skymodloader/SMLApplication.java | 17 +- .../skymodloader/SettingsActivity.java | 331 +++++++++++++++++ .../elfmod/ModManagerActivity.java | 267 +++++++++----- .../updater/CanvasUpdaterService.java | 2 +- app/src/main/res/layout/mod_manager.xml | 12 +- app/src/main/res/layout/setting_layout.xml | 347 +++++++++++++++--- 14 files changed, 1161 insertions(+), 348 deletions(-) create mode 100644 app/src/main/java/git/artdeell/skymodloader/LogcatMonitorService.java create mode 100644 app/src/main/java/git/artdeell/skymodloader/SettingsActivity.java diff --git a/app/build.gradle b/app/build.gradle index 653b166..5dab5e0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { applicationId "git.artdeell.skymodloader" minSdk 26 targetSdk 34 - versionCode 54 - versionName "1.6.9" + versionCode 55 + versionName "1.7.0TEST" externalNativeBuild { cmake { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 009c484..68a0ee0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -15,7 +16,6 @@ - + - + + + + + android:name=".SettingsActivity" + android:exported="false" + android:screenOrientation="portrait" + android:theme="@style/AppTheme" /> + + + + - - - + diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 0229f9f..24ba44a 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -1,20 +1,14 @@ cmake_minimum_required(VERSION 3.18.1) - -#set(CMAKE_BUILD_TYPE RelWithDebInfo) -#set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Release) - if(CMAKE_BUILD_TYPE STREQUAL "Release") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -flto -fuse-linker-plugin -finline-functions -fomit-frame-pointer -falign-functions -falign-jumps") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -flto -fuse-linker-plugin -finline-functions -fomit-frame-pointer -falign-functions -falign-jumps") endif() - project("imgui") - add_library(ktx_read SHARED IMPORTED) set_target_properties(ktx_read PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/Utils/ktx/libktx_read.so) @@ -34,7 +28,6 @@ add_library( scroll.cpp Core/imgui/backends/imgui_impl_opengl3.cpp Utils/imgui_androidbk/androidbk.cpp - ) target_include_directories( @@ -46,9 +39,7 @@ target_include_directories( target_compile_options(ciphered PUBLIC $<$:-Werror -std=c++17 -Wno-error=c++11-narrowing -Wall>) target_compile_options(ciphered PUBLIC -w -s -Wno-error=format-security -fpermissive -fexceptions) -#target_link_options(cpihered PUBLIC -Wl,--gc-sections,--strip-all,-llog) -#add_definitions(-DkNO_KEYSTONE) if (NOT kNO_KEYSTONE) set(keystone ${CMAKE_CURRENT_SOURCE_DIR}/Utils/KittyMemory/KittyMemory/Deps/Keystone/libs-android/arm64-v8a/libkeystone.a) target_link_libraries(ciphered ${keystone}) @@ -56,8 +47,7 @@ endif() find_package(shadowhook REQUIRED CONFIG) - -target_link_libraries( # Specifies the target library. +target_link_libraries( ciphered ktx_read log diff --git a/app/src/main/java/com/tgc/sky/BuildConfig.java b/app/src/main/java/com/tgc/sky/BuildConfig.java index e59e6e7..0c090c0 100644 --- a/app/src/main/java/com/tgc/sky/BuildConfig.java +++ b/app/src/main/java/com/tgc/sky/BuildConfig.java @@ -6,7 +6,7 @@ public class BuildConfig { public static String SKY_STAGE_NAME = "Live"; public static String SKY_SERVER_HOSTNAME = "live.radiance.thatgamecompany.com"; public static String SKY_VERSION = "0.17.5"; - public static final String SKY_BUILD_ACCESS_KEY = "KEY_TEST"; + public static String SKY_BUILD_ACCESS_KEY = "1743442606-0c9545db24db0fee924a028c500c75f968145e6f15d2535ff2ca414798eba96a"; public static int VERSION_CODE = 192395; } diff --git a/app/src/main/java/com/tgc/sky/SystemIO_android.java b/app/src/main/java/com/tgc/sky/SystemIO_android.java index 0ad7a23..cc52658 100644 --- a/app/src/main/java/com/tgc/sky/SystemIO_android.java +++ b/app/src/main/java/com/tgc/sky/SystemIO_android.java @@ -163,6 +163,7 @@ public static SystemIO_android getInstance() { SystemIO_android(GameActivity gameActivity) { this.m_activity = gameActivity; + DeviceKey.setContext(gameActivity.getApplicationContext()); boolean z = false; this.m_isOtherAudioPlaying = false; this.m_isPhonecallActive = false; diff --git a/app/src/main/java/com/tgc/sky/io/DeviceKey.java b/app/src/main/java/com/tgc/sky/io/DeviceKey.java index ccea86c..c25ac03 100644 --- a/app/src/main/java/com/tgc/sky/io/DeviceKey.java +++ b/app/src/main/java/com/tgc/sky/io/DeviceKey.java @@ -1,49 +1,43 @@ package com.tgc.sky.io; -import android.os.Build; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; +import android.content.Context; import android.util.Base64; -import java.io.IOException; -import java.math.BigInteger; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.nio.charset.StandardCharsets; import java.security.AlgorithmParameters; -import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; -import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; -import java.security.UnrecoverableEntryException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.ECPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.InvalidParameterSpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.math.BigInteger; import java.util.Arrays; -import java.util.Date; -import java.util.Random; -import javax.security.auth.x500.X500Principal; -/* renamed from: com.tgc.sky.io.DeviceKey */ public class DeviceKey { - private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; private static final BigInteger CURVE_A = new BigInteger("3", 10); private static final BigInteger CURVE_B = new BigInteger("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16); private static final BigInteger MODULUS = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16); - private static final String kDeviceKeyAlias = "com.tgc.sky.devicekey"; + private static final String PRIVATE_KEY_FILE = "device_private.key"; + private static final String PUBLIC_KEY_FILE = "device_public.key"; + private static Context sContext; + + public static void setContext(Context context) { + sContext = context; + } public static boolean Delete() { DeleteKeyPair(); @@ -106,74 +100,96 @@ public static boolean VerifyWithPublicKeyAndSignature(String str, String str2, S } private static KeyPair GetKeyPair() { - PrivateKey GetPrivateKey = GetPrivateKey(); - PublicKey GetPublicKey = GetPublicKey(); - if (GetPrivateKey != null && GetPublicKey != null) { - return new KeyPair(GetPublicKey, GetPrivateKey); + if (sContext == null) { + return CreateKeyPairLegacy(); } - DeleteKeyPair(); - return CreateKeyPair(); - } - - private static KeyPair CreateKeyPair() { + try { - KeyPairGenerator instance = KeyPairGenerator.getInstance("EC", ANDROID_KEY_STORE); - long currentTimeMillis = System.currentTimeMillis(); - instance.initialize(new KeyGenParameterSpec.Builder(kDeviceKeyAlias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_SIGN).setRandomizedEncryptionRequired(false).setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")).setDigests(new String[]{"SHA-256", "SHA-512"}).setKeySize(256).setSignaturePaddings(new String[]{"PKCS1"}).setCertificateSubject(new X500Principal("CN=Android, O=Android Authority")).setCertificateSerialNumber(new BigInteger(256, new Random())).setCertificateNotBefore(new Date(currentTimeMillis - (currentTimeMillis % 1000))).setCertificateNotAfter(new Date(new Date(currentTimeMillis - (currentTimeMillis % 1000)).getTime() + 3155673600000L)).build()); - return instance.generateKeyPair(); - } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchProviderException e) { + File filesDir = sContext.getFilesDir(); + File privateKeyFile = new File(filesDir, PRIVATE_KEY_FILE); + File publicKeyFile = new File(filesDir, PUBLIC_KEY_FILE); + + if (privateKeyFile.exists() && publicKeyFile.exists()) { + FileInputStream privateFis = new FileInputStream(privateKeyFile); + byte[] privateKeyBytes = new byte[(int) privateKeyFile.length()]; + privateFis.read(privateKeyBytes); + privateFis.close(); + + FileInputStream publicFis = new FileInputStream(publicKeyFile); + byte[] publicKeyBytes = new byte[(int) publicKeyFile.length()]; + publicFis.read(publicKeyBytes); + publicFis.close(); + + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privateKeyBytes); + X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(publicKeyBytes); + + PrivateKey privateKey = keyFactory.generatePrivate(privSpec); + PublicKey publicKey = keyFactory.generatePublic(pubSpec); + + return new KeyPair(publicKey, privateKey); + } + } catch (Exception e) { e.printStackTrace(); - return null; + DeleteKeyPair(); } + + return CreateKeyPair(); } - private static void DeleteKeyPair() { + private static KeyPair CreateKeyPair() { + if (sContext == null) { + return CreateKeyPairLegacy(); + } + try { - KeyStore instance = KeyStore.getInstance(ANDROID_KEY_STORE); - instance.load((KeyStore.LoadStoreParameter) null); - instance.deleteEntry(kDeviceKeyAlias); - } catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) { + KeyPairGenerator instance = KeyPairGenerator.getInstance("EC"); + instance.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = instance.generateKeyPair(); + + File filesDir = sContext.getFilesDir(); + + FileOutputStream privateFos = new FileOutputStream(new File(filesDir, PRIVATE_KEY_FILE)); + privateFos.write(kp.getPrivate().getEncoded()); + privateFos.close(); + + FileOutputStream publicFos = new FileOutputStream(new File(filesDir, PUBLIC_KEY_FILE)); + publicFos.write(kp.getPublic().getEncoded()); + publicFos.close(); + + return kp; + } catch (Exception e) { e.printStackTrace(); + return CreateKeyPairLegacy(); } } - private static PrivateKey GetPrivateKey() { + private static KeyPair CreateKeyPairLegacy() { try { - KeyStore instance = KeyStore.getInstance(ANDROID_KEY_STORE); - instance.load((KeyStore.LoadStoreParameter) null); - if (Build.VERSION.SDK_INT >= 28) { - return (PrivateKey) instance.getKey(kDeviceKeyAlias, (char[]) null); - } - KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) instance.getEntry(kDeviceKeyAlias, (KeyStore.ProtectionParameter) null); - if (privateKeyEntry != null) { - return privateKeyEntry.getPrivateKey(); - } - return null; - } catch (IOException | KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException | CertificateException e) { + KeyPairGenerator instance = KeyPairGenerator.getInstance("EC"); + instance.initialize(new ECGenParameterSpec("secp256r1")); + return instance.generateKeyPair(); + } catch (Exception e) { e.printStackTrace(); return null; } } - private static PublicKey GetPublicKey() { - Certificate certificate; + private static void DeleteKeyPair() { try { - KeyStore instance = KeyStore.getInstance(ANDROID_KEY_STORE); - instance.load((KeyStore.LoadStoreParameter) null); - if (Build.VERSION.SDK_INT < 28) { - KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) instance.getEntry(kDeviceKeyAlias, (KeyStore.ProtectionParameter) null); - if (privateKeyEntry != null) { - return privateKeyEntry.getCertificate().getPublicKey(); + if (sContext != null) { + File filesDir = sContext.getFilesDir(); + File privateKeyFile = new File(filesDir, PRIVATE_KEY_FILE); + File publicKeyFile = new File(filesDir, PUBLIC_KEY_FILE); + if (privateKeyFile.exists()) { + privateKeyFile.delete(); + } + if (publicKeyFile.exists()) { + publicKeyFile.delete(); } - return null; - } else if (!instance.containsAlias(kDeviceKeyAlias) || (certificate = instance.getCertificate(kDeviceKeyAlias)) == null) { - return null; - } else { - return certificate.getPublicKey(); } - } catch (IOException | KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException | CertificateException e) { + } catch (Exception e) { e.printStackTrace(); - return null; } } @@ -214,14 +230,13 @@ private static PublicKey GetPublicKeyFromBase64(String str) { AlgorithmParameters instance = AlgorithmParameters.getInstance("EC"); instance.init(new ECGenParameterSpec("secp256r1")); return KeyFactory.getInstance("EC").generatePublic(new ECPublicKeySpec(eCPoint, (ECParameterSpec) instance.getParameterSpec(ECParameterSpec.class))); - } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidParameterSpecException e) { + } catch (Exception e) { e.printStackTrace(); return null; } } private static BigInteger sqrtMod(BigInteger bigInteger) { - BigInteger bigInteger2 = MODULUS; return bigInteger.modPow(MODULUS.add(BigInteger.ONE).shiftRight(2), MODULUS); } } diff --git a/app/src/main/java/git/artdeell/skymodloader/LogcatMonitorService.java b/app/src/main/java/git/artdeell/skymodloader/LogcatMonitorService.java new file mode 100644 index 0000000..4279b42 --- /dev/null +++ b/app/src/main/java/git/artdeell/skymodloader/LogcatMonitorService.java @@ -0,0 +1,173 @@ +package git.artdeell.skymodloader; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Intent; +import android.os.Build; +import android.os.IBinder; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.core.app.NotificationCompat; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class LogcatMonitorService extends Service { + private static final String TAG = "LogcatMonitor"; + private static final String CHANNEL_ID = "logcat_monitor_channel"; + private static final int NOTIFICATION_ID = 1001; + + private Process logcatProcess; + private Thread monitorThread; + private volatile boolean isRunning = false; + private FileWriter logWriter; + private File logFile; + private int lineCount = 0; + + @Override + public void onCreate() { + super.onCreate(); + Log.d(TAG, "Service created"); + + // IMPORTANTE: Chiama startForeground() IMMEDIATAMENTE + createNotificationChannel(); + startForeground(NOTIFICATION_ID, createNotification("Starting...")); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (!isRunning) { + isRunning = true; + startMonitoring(); + } + return START_STICKY; + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID, + "Logcat Monitor", + NotificationManager.IMPORTANCE_LOW + ); + channel.setDescription("Monitoring logcat in background"); + NotificationManager manager = getSystemService(NotificationManager.class); + if (manager != null) { + manager.createNotificationChannel(channel); + } + } + } + + private Notification createNotification(String text) { + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("Logcat Monitor") + .setContentText(text) + .setSmallIcon(android.R.drawable.ic_dialog_info) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true); + + return builder.build(); + } + + private void startMonitoring() { + Log.d(TAG, "Full monitoring started"); + + monitorThread = new Thread(() -> { + try { + // Crea directory logs + File logsDir = new File(getExternalFilesDir(null), "logs"); + if (!logsDir.exists()) { + logsDir.mkdirs(); + } + + // Crea file log con timestamp + String timestamp = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault()) + .format(new Date()); + logFile = new File(logsDir, "canvas_full_logcat_" + timestamp + ".txt"); + logWriter = new FileWriter(logFile, true); + + Log.d(TAG, "Log file: " + logFile.getAbsolutePath()); + + // Avvia logcat + ProcessBuilder processBuilder = new ProcessBuilder("logcat", "-v", "threadtime"); + logcatProcess = processBuilder.start(); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(logcatProcess.getInputStream()) + ); + + String line; + while (isRunning && (line = reader.readLine()) != null) { + logWriter.write(line + "\n"); + lineCount++; + + // Aggiorna notifica ogni 50 righe + if (lineCount % 50 == 0) { + updateNotification("Captured " + lineCount + " lines"); + } + } + + logWriter.flush(); + logWriter.close(); + Log.d(TAG, "Captured " + lineCount + " lines"); + + } catch (IOException e) { + Log.e(TAG, "Error monitoring logcat", e); + } + }); + + monitorThread.start(); + } + + private void updateNotification(String text) { + NotificationManager manager = getSystemService(NotificationManager.class); + if (manager != null) { + manager.notify(NOTIFICATION_ID, createNotification(text)); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.d(TAG, "Service destroyed"); + + isRunning = false; + + if (logcatProcess != null) { + logcatProcess.destroy(); + } + + if (monitorThread != null) { + try { + monitorThread.join(1000); + } catch (InterruptedException e) { + Log.e(TAG, "Error stopping monitor thread", e); + } + } + + if (logWriter != null) { + try { + logWriter.close(); + } catch (IOException e) { + Log.e(TAG, "Error closing log file", e); + } + } + + Log.d(TAG, "Captured " + lineCount + " lines"); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/git/artdeell/skymodloader/MainActivity.java b/app/src/main/java/git/artdeell/skymodloader/MainActivity.java index 36cc269..d22f84a 100644 --- a/app/src/main/java/git/artdeell/skymodloader/MainActivity.java +++ b/app/src/main/java/git/artdeell/skymodloader/MainActivity.java @@ -7,7 +7,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.res.AssetManager; import android.os.Build; import android.os.Bundle; import android.provider.Settings; @@ -40,17 +39,32 @@ public class MainActivity extends Activity { private boolean hideCanvasMenu; public static String SKY_PACKAGE_NAME; private Map skyPackages; - public static DeviceInfo deviceInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + deviceInfo = getDeviceInfo(); sharedPreferences = getSharedPreferences("package_configs", Context.MODE_PRIVATE); + + boolean logcatEnabled = sharedPreferences.getBoolean("logcat_enabled", false); + if (logcatEnabled) { + startLogcatMonitoring(); + } + SKY_PACKAGE_NAME = sharedPreferences.getString("sky_package_name", "com.tgc.sky.android"); ceserverEnabled = sharedPreferences.getBoolean("ceserver", false); hideCanvasMenu = sharedPreferences.getBoolean("hide_canvas_menu", false); + + if (sharedPreferences.getBoolean("custom_build_key", false)) { + String buildKey = sharedPreferences.getString("build_access_key", ""); + if (!buildKey.isEmpty()) { + BuildConfig.SKY_BUILD_ACCESS_KEY = buildKey; + Log.i("MainActivity", "Applied custom build key: " + buildKey.substring(0, Math.min(20, buildKey.length())) + "..."); + } + } + sharedPreferences.edit().putString("sky_package_name", SKY_PACKAGE_NAME).apply(); skyPackages = new HashMap<>(); skyPackages.put("com.tgc.sky.android", 0); @@ -58,6 +72,20 @@ protected void onCreate(Bundle savedInstanceState) { loadGame(); } + private void startLogcatMonitoring() { + try { + Intent logcatIntent = new Intent(this, LogcatMonitorService.class); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(logcatIntent); + } else { + startService(logcatIntent); + } + Log.d("MainActivity", "Logcat monitoring started"); + } catch (Exception e) { + Log.e("MainActivity", "Failed to start logcat monitoring", e); + } + } + private void loadGame() { PackageManager pm = getPackageManager(); try { @@ -68,14 +96,10 @@ private void loadGame() { String versionName = info.versionName; BuildConfig.SKY_VERSION = versionName.substring(0, versionName.indexOf(' ')).trim(); BuildConfig.VERSION_CODE = info.versionCode; - String nativeLibraryDir = info.applicationInfo.nativeLibraryDir; String libPath = nativeLibraryDir; - - // Check if nativeLibraryDir is empty or doesn't contain libs (split APK case) File libDir = new File(nativeLibraryDir); if (!libDir.exists() || libDir.listFiles() == null || libDir.listFiles().length == 0) { - // Extract from split APK libPath = extractLibrariesFromApk(info.applicationInfo); } @@ -83,13 +107,9 @@ private void loadGame() { File configDir = new File(getFilesDir(), "config"); if (!configDir.isDirectory() && !configDir.mkdirs()) throw new IOException("Failed to create mod configuration directory"); - android.util.Log.i("MainActivity", "Pre-loading FMOD dependencies from: " + libPath); - org.fmod.FMOD.init(this); - File fmodLibDir = new File(libPath); - android.util.Log.i("MainActivity", "Listing all files in: " + libPath); File[] allFiles = fmodLibDir.listFiles(); if (allFiles != null) { @@ -101,12 +121,11 @@ private void loadGame() { } String[] libsToLoad = { - "libc++_shared.so", - "libOpenSLES.so", - "libfmod.so", - "libfmodstudio.so" + "libc++_shared.so", + "libOpenSLES.so", + "libfmod.so", + "libfmodstudio.so" }; - for (String libName : libsToLoad) { File lib = new File(fmodLibDir, libName); if (lib.exists()) { @@ -122,36 +141,31 @@ private void loadGame() { android.util.Log.w("MainActivity", "Not found: " + libName + " at " + lib.getAbsolutePath()); } } - ElfLoader loader = new ElfLoader(libPath + ":/system/lib64"); loader.loadLib("libBootloader.so"); System.loadLibrary("ciphered"); - setDeviceInfoNative( - deviceInfo.xdpi, - deviceInfo.ydpi, - deviceInfo.density, - Optional.ofNullable(deviceInfo.deviceName).orElse(""), - Optional.ofNullable(deviceInfo.deviceManufacturer).orElse(""), - Optional.ofNullable(deviceInfo.deviceModel).orElse("")); - + deviceInfo.xdpi, + deviceInfo.ydpi, + deviceInfo.density, + Optional.ofNullable(deviceInfo.deviceName).orElse(""), + Optional.ofNullable(deviceInfo.deviceManufacturer).orElse(""), + Optional.ofNullable(deviceInfo.deviceModel).orElse("")); IconLoader.findIcons(); BuildConfig.VERSION_CODE = sharedPreferences.getBoolean("skip_updates", false) ? 0x99999 : info.versionCode; Integer gameType = skyPackages.getOrDefault(SKY_PACKAGE_NAME, 0); MainActivity.settle( - info.versionCode, - gameType == null ? 0 : gameType, - BuildConfig.SKY_SERVER_HOSTNAME, - configDir.getAbsolutePath(), - SMLApplication.skyRes.getAssets(), - ceserverEnabled, - hideCanvasMenu + info.versionCode, + gameType == null ? 0 : gameType, + BuildConfig.SKY_SERVER_HOSTNAME, + configDir.getAbsolutePath(), + SMLApplication.skyRes.getAssets(), + ceserverEnabled, + hideCanvasMenu ); - - if (sharedPreferences.getBoolean("custom_server", false)) { BuildConfig.SKY_SERVER_HOSTNAME = sharedPreferences.getString("server_host", - BuildConfig.SKY_SERVER_HOSTNAME); + BuildConfig.SKY_SERVER_HOSTNAME); MainActivity.customServer(BuildConfig.SKY_SERVER_HOSTNAME); } @@ -173,8 +187,6 @@ private String extractLibrariesFromApk(ApplicationInfo appInfo) throws IOExcepti android.util.Log.i("MainActivity", "Looking for split APKs..."); android.util.Log.i("MainActivity", "sourceDir: " + appInfo.sourceDir); - - // Find split APK containing native libs String[] splitSourceDirs = appInfo.splitSourceDirs; if (splitSourceDirs != null) { android.util.Log.i("MainActivity", "Found " + splitSourceDirs.length + " split APKs"); @@ -198,19 +210,16 @@ private void extractLibsFromZip(String apkPath, File destDir) throws IOException android.util.Log.i("MainActivity", "Extracting libs from: " + apkPath); java.util.zip.ZipFile zipFile = new java.util.zip.ZipFile(apkPath); java.util.Enumeration entries = zipFile.entries(); - int libCount = 0; while (entries.hasMoreElements()) { java.util.zip.ZipEntry entry = entries.nextElement(); String name = entry.getName(); - if (name.startsWith("lib/arm64-v8a/") && name.endsWith(".so")) { String libName = name.substring(name.lastIndexOf('/') + 1); File destFile = new File(destDir, libName); - if (!destFile.exists()) { try (java.io.InputStream in = zipFile.getInputStream(entry); - java.io.FileOutputStream out = new java.io.FileOutputStream(destFile)) { + java.io.FileOutputStream out = new java.io.FileOutputStream(destFile)) { byte[] buffer = new byte[8192]; int read; while ((read = in.read(buffer)) != -1) { @@ -241,9 +250,7 @@ public void alertDialog(Throwable th) { th.printStackTrace(pw); String stackTrace = sw.toString(); pw.close(); - AlertDialog dialog = getAlertDialog(stackTrace); - dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> { copyToClipboard(stackTrace); }); @@ -252,20 +259,11 @@ public void alertDialog(Throwable th) { private @NonNull AlertDialog getAlertDialog(String stackTrace) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(stackTrace); - - // Existing OK button to dismiss the dialog builder.setPositiveButton(android.R.string.ok, (d, w) -> finish()); - - // Show the dialog without setting neutral button here, so it doesn't close by - // default AlertDialog dialog = builder.create(); - - // Add the "Copy" button manually after creating the dialog dialog.setButton(AlertDialog.BUTTON_NEUTRAL, "Copy", (d, which) -> { copyToClipboard(stackTrace); }); - - // Show the dialog dialog.show(); return dialog; } @@ -274,8 +272,6 @@ private void copyToClipboard(String stackTrace) { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("Stack Trace", stackTrace); clipboard.setPrimaryClip(clip); - - // Only show the toast for versions below API level 33 (Android 13) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { Toast.makeText(this, "Stack trace copied to clipboard", Toast.LENGTH_SHORT).show(); } @@ -294,44 +290,39 @@ public DeviceInfo getDeviceInfo() { deviceInfo.xdpi = displayMetrics.xdpi; deviceInfo.ydpi = displayMetrics.ydpi; deviceInfo.density = displayMetrics.density; - deviceInfo.deviceName = Settings.Global.getString(getContentResolver(), "device_name"); if (deviceInfo.deviceName == null || deviceInfo.deviceName.isEmpty()) { deviceInfo.deviceName = Settings.Secure.getString(getContentResolver(), "bluetooth_name"); } - deviceInfo.deviceName = (deviceInfo.deviceName == null || deviceInfo.deviceName.isEmpty()) ? "NO_DEVICE_NAME" - : deviceInfo.deviceName; + deviceInfo.deviceName = (deviceInfo.deviceName == null || deviceInfo.deviceName.isEmpty()) ? "NO_DEVICE_NAME" + : deviceInfo.deviceName; deviceInfo.deviceManufacturer = Build.MANUFACTURER; deviceInfo.deviceModel = Build.MODEL; return deviceInfo; } public static native void settle( - int _gameVersion, - int _gameType, - String _hostName, - String _configDir, - AssetManager _gameAssets, - boolean _ceserverEnabled, - boolean _hideCanvasMenu + int _gameVersion, + int _gameType, + String _hostName, + String _configDir, + android.content.res.AssetManager _gameAssets, + boolean _ceserverEnabled, + boolean _hideCanvasMenu ); public static native void setDeviceInfoNative( - float _xdpi, - float _ydpi, - float _density, - String _deviceName, - String _manufacturer, - String _model + float _xdpi, + float _ydpi, + float _density, + String _deviceName, + String _manufacturer, + String _model ); public static native void onKeyboardCompleteNative(String message); - public static native void customServer(String url); - public static native void lateInitUserLibs(); - public static native void getSysetemUI(Object systemUI); - } \ No newline at end of file diff --git a/app/src/main/java/git/artdeell/skymodloader/SMLApplication.java b/app/src/main/java/git/artdeell/skymodloader/SMLApplication.java index 3ad061b..87e35fd 100644 --- a/app/src/main/java/git/artdeell/skymodloader/SMLApplication.java +++ b/app/src/main/java/git/artdeell/skymodloader/SMLApplication.java @@ -5,16 +5,16 @@ import android.app.NotificationManager; import android.content.res.Resources; import android.content.Context; - import android.os.Build; +import android.util.Log; public class SMLApplication extends Application { public static final String MOD_UPDATER_SERVICE_NOTIFICATION_CHANNEL = "canvas_mod_updater_service_notif_ch"; - private static SMLApplication smlApplication; public static String skyPName; public static Resources skyRes; public static Resources smlRes; + public static SMLApplication deez() { return smlApplication; } @@ -22,24 +22,23 @@ public static SMLApplication deez() { @Override public void onCreate() { super.onCreate(); + Log.i("Canvas", "SMLApplication.onCreate() called"); smlApplication = this; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationManager notifManager = ((NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE)); - NotificationChannel notifChannel = notifManager.getNotificationChannel(MOD_UPDATER_SERVICE_NOTIFICATION_CHANNEL); - if (notifChannel == null) { notifChannel = new NotificationChannel( - MOD_UPDATER_SERVICE_NOTIFICATION_CHANNEL, - "Mod Updater Status", - NotificationManager.IMPORTANCE_MIN + MOD_UPDATER_SERVICE_NOTIFICATION_CHANNEL, + "Mod Updater Status", + NotificationManager.IMPORTANCE_MIN ); notifChannel.setDescription("Notification showing the state of mod update"); - notifManager.createNotificationChannel(notifChannel); } } + } @Override @@ -47,4 +46,4 @@ public void onTerminate() { super.onTerminate(); smlApplication = null; } -} +} \ No newline at end of file diff --git a/app/src/main/java/git/artdeell/skymodloader/SettingsActivity.java b/app/src/main/java/git/artdeell/skymodloader/SettingsActivity.java new file mode 100644 index 0000000..e4c9c58 --- /dev/null +++ b/app/src/main/java/git/artdeell/skymodloader/SettingsActivity.java @@ -0,0 +1,331 @@ +package git.artdeell.skymodloader; + +import android.app.AlertDialog; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Switch; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.tgc.sky.BuildConfig; + +import java.io.File; + +public class SettingsActivity extends AppCompatActivity { + private static final String TAG = "ClearAppData"; + private Switch skipUpdateSwitch; + private Switch hideCanvasMenuSwitch; + private Switch ceserverSwitch; + private Switch customServerSwitch; + private Switch buildKeySwitch; + private Switch logcatSwitch; + private EditText serverUrlInput; + private EditText buildKeyInput; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.setting_layout); + + ImageView backButton = findViewById(R.id.back_button); + skipUpdateSwitch = findViewById(R.id.mm_enableSkipUpdate); + hideCanvasMenuSwitch = findViewById(R.id.mm_hideCanvasMenu); + ceserverSwitch = findViewById(R.id.mm_enableCeserver); + customServerSwitch = findViewById(R.id.mm_enableCustomServer); + buildKeySwitch = findViewById(R.id.mm_enableBuildKey); + logcatSwitch = findViewById(R.id.mm_enableLogcat); + serverUrlInput = findViewById(R.id.server_url_input); + buildKeyInput = findViewById(R.id.build_key_input); + Button btnClearAppData = findViewById(R.id.btn_clear_app_data); + + backButton.setOnClickListener(v -> finish()); + + skipUpdateSwitch.setChecked(getSharedPreferences("package_configs", MODE_PRIVATE) + .getBoolean("skip_updates", false)); + skipUpdateSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> + getSharedPreferences("package_configs", MODE_PRIVATE) + .edit().putBoolean("skip_updates", isChecked).apply() + ); + + hideCanvasMenuSwitch.setChecked(getSharedPreferences("package_configs", MODE_PRIVATE) + .getBoolean("hide_canvas_menu", false)); + hideCanvasMenuSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> + getSharedPreferences("package_configs", MODE_PRIVATE) + .edit().putBoolean("hide_canvas_menu", isChecked).apply() + ); + + ceserverSwitch.setChecked(getSharedPreferences("package_configs", MODE_PRIVATE) + .getBoolean("ceserver", false)); + ceserverSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> + getSharedPreferences("package_configs", MODE_PRIVATE) + .edit().putBoolean("ceserver", isChecked).apply() + ); + + customServerSwitch.setChecked(getSharedPreferences("package_configs", MODE_PRIVATE) + .getBoolean("custom_server", false)); + customServerSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> + getSharedPreferences("package_configs", MODE_PRIVATE) + .edit().putBoolean("custom_server", isChecked).apply() + ); + + serverUrlInput.setText(getSharedPreferences("package_configs", MODE_PRIVATE) + .getString("server_host", "")); + serverUrlInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + getSharedPreferences("package_configs", MODE_PRIVATE) + .edit().putString("server_host", s.toString()).apply(); + } + }); + + buildKeySwitch.setChecked(getSharedPreferences("package_configs", MODE_PRIVATE) + .getBoolean("custom_build_key", false)); + buildKeySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + getSharedPreferences("package_configs", MODE_PRIVATE) + .edit().putBoolean("custom_build_key", isChecked).apply(); + if (isChecked) { + String buildKey = buildKeyInput.getText().toString().trim(); + if (buildKey.isEmpty()) { + Toast.makeText(this, "Please enter a build key first", Toast.LENGTH_SHORT).show(); + buildKeySwitch.setChecked(false); + return; + } + + applyBuildKey(buildKey); + } else { + BuildConfig.SKY_BUILD_ACCESS_KEY = ""; + Toast.makeText(this, "Build key disabled", Toast.LENGTH_SHORT).show(); + } + }); + + buildKeyInput.setText(getSharedPreferences("package_configs", MODE_PRIVATE) + .getString("build_access_key", "")); + buildKeyInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + String buildKey = s.toString().trim(); + getSharedPreferences("package_configs", MODE_PRIVATE) + .edit().putString("build_access_key", buildKey).apply(); + if (buildKeySwitch.isChecked() && !buildKey.isEmpty()) { + applyBuildKey(buildKey); + } + } + }); + + logcatSwitch.setChecked(getSharedPreferences("package_configs", MODE_PRIVATE) + .getBoolean("logcat_enabled", false)); + logcatSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + getSharedPreferences("package_configs", MODE_PRIVATE) + .edit().putBoolean("logcat_enabled", isChecked).apply(); + + Intent logcatIntent = new Intent(this, LogcatMonitorService.class); + if (isChecked) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(logcatIntent); + } else { + startService(logcatIntent); + } + Toast.makeText(this, "Logcat monitoring enabled", Toast.LENGTH_SHORT).show(); + } else { + stopService(logcatIntent); + Toast.makeText(this, "Logcat monitoring disabled", Toast.LENGTH_SHORT).show(); + } + }); + + btnClearAppData.setOnClickListener(v -> clearAppDataComplete()); + } + + private void applyBuildKey(String buildKey) { + BuildConfig.SKY_BUILD_ACCESS_KEY = buildKey; + Log.i("BuildKey", "Applied build key: " + buildKey.substring(0, Math.min(20, buildKey.length())) + "..."); + Toast.makeText(this, "Build key applied", Toast.LENGTH_SHORT).show(); + } + + private void clearAppDataComplete() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("⚠️ Clear App Data"); + builder.setMessage("Delete all game data?\n\n✅ PRESERVED:\n• mods/\n• Accounts/\n• config/\n• AccountAuthInfo.bin\n\n❌ DELETED:\n• Everything else"); + builder.setPositiveButton("Clear", (dialog, which) -> { + Toast.makeText(this, "Clearing data...", Toast.LENGTH_SHORT).show(); + new Thread(() -> { + int deletedFiles = 0; + int deletedDirs = 0; + try { + Log.i(TAG, "========================================"); + Log.i(TAG, "Starting Complete Clear App Data"); + Log.i(TAG, "========================================"); + + File externalFilesDir = getExternalFilesDir(null); + if (externalFilesDir != null) { + File externalDataRoot = externalFilesDir.getParentFile(); + if (externalDataRoot != null && externalDataRoot.exists()) { + Log.i(TAG, "Clearing external: " + externalDataRoot.getAbsolutePath()); + int[] counts = deleteRecursiveCount(externalDataRoot); + deletedFiles += counts[0]; + deletedDirs += counts[1]; + Log.i(TAG, "External cleared: " + counts[0] + " files, " + counts[1] + " dirs"); + } + } + + File internalDataRoot = getFilesDir().getParentFile(); + if (internalDataRoot != null && internalDataRoot.exists()) { + Log.i(TAG, "Processing root: " + internalDataRoot.getAbsolutePath()); + int[] rootCounts = clearInternalDataRoot(internalDataRoot); + deletedFiles += rootCounts[0]; + deletedDirs += rootCounts[1]; + Log.i(TAG, "Internal root cleared: " + rootCounts[0] + " files, " + rootCounts[1] + " dirs"); + } + + final int totalFiles = deletedFiles; + final int totalDirs = deletedDirs; + Log.i(TAG, "========================================"); + Log.i(TAG, "TOTAL DELETED: " + totalFiles + " files, " + totalDirs + " dirs"); + Log.i(TAG, "========================================"); + + runOnUiThread(() -> { + String message = "Cleared " + totalFiles + " files, " + totalDirs + " dirs\nRestarting..."; + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + new android.os.Handler().postDelayed(() -> { + Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName()); + if (intent != null) { + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + System.exit(0); + }, 1500); + }); + } catch (Exception e) { + Log.e(TAG, "Error clearing data", e); + runOnUiThread(() -> + Toast.makeText(this, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show() + ); + } + }).start(); + }); + builder.setNegativeButton("Cancel", null); + builder.show(); + } + + private int[] clearInternalDataRoot(File dataRoot) { + int fileCount = 0; + int dirCount = 0; + File[] contents = dataRoot.listFiles(); + if (contents == null) return new int[]{0, 0}; + + for (File item : contents) { + String name = item.getName(); + if (name.equals("files")) { + Log.i(TAG, "Processing files/ directory selectively..."); + int[] filesCounts = clearFilesDirectory(item); + fileCount += filesCounts[0]; + dirCount += filesCounts[1]; + } else { + Log.i(TAG, "Deleting ROOT item: " + name); + int[] counts = deleteRecursiveCount(item); + fileCount += counts[0]; + dirCount += counts[1]; + Log.i(TAG, "❌ DELETED ROOT: " + name + " (" + counts[0] + " files, " + counts[1] + " dirs)"); + } + } + return new int[]{fileCount, dirCount}; + } + + private int[] clearFilesDirectory(File filesDir) { + int fileCount = 0; + int dirCount = 0; + if (!filesDir.exists() || !filesDir.isDirectory()) return new int[]{0, 0}; + + String[] preservedDirs = {"mods", "Accounts", "config"}; + String[] preservedFiles = {"AccountAuthInfo.bin"}; + File[] contents = filesDir.listFiles(); + if (contents == null) return new int[]{0, 0}; + + for (File item : contents) { + boolean shouldPreserve = false; + String itemName = item.getName(); + + if (item.isDirectory()) { + for (String preserved : preservedDirs) { + if (itemName.equals(preserved)) { + shouldPreserve = true; + Log.i(TAG, "✅ PRESERVED DIR: files/" + itemName + "/"); + break; + } + } + } else if (item.isFile()) { + for (String preserved : preservedFiles) { + if (itemName.equals(preserved)) { + shouldPreserve = true; + Log.i(TAG, "✅ PRESERVED FILE: files/" + itemName); + break; + } + } + } + + if (!shouldPreserve) { + if (item.isFile()) { + if (item.delete()) { + fileCount++; + Log.i(TAG, "❌ DELETED FILE: files/" + itemName); + } else { + Log.w(TAG, "Failed to delete file: files/" + itemName); + } + } else if (item.isDirectory()) { + int[] counts = deleteRecursiveCount(item); + fileCount += counts[0]; + dirCount += counts[1]; + Log.i(TAG, "❌ DELETED DIR: files/" + itemName + " (" + counts[0] + " files, " + counts[1] + " dirs)"); + } + } + } + return new int[]{fileCount, dirCount}; + } + + private int[] deleteRecursiveCount(File fileOrDirectory) { + int fileCount = 0; + int dirCount = 0; + + if (fileOrDirectory.isDirectory()) { + File[] children = fileOrDirectory.listFiles(); + if (children != null) { + for (File child : children) { + int[] childCounts = deleteRecursiveCount(child); + fileCount += childCounts[0]; + dirCount += childCounts[1]; + } + } + if (fileOrDirectory.delete()) { + dirCount++; + } else { + Log.w(TAG, "Failed to delete dir: " + fileOrDirectory.getAbsolutePath()); + } + } else { + if (fileOrDirectory.delete()) { + fileCount++; + } else { + Log.w(TAG, "Failed to delete file: " + fileOrDirectory.getAbsolutePath()); + } + } + return new int[]{fileCount, dirCount}; + } +} \ No newline at end of file diff --git a/app/src/main/java/git/artdeell/skymodloader/elfmod/ModManagerActivity.java b/app/src/main/java/git/artdeell/skymodloader/elfmod/ModManagerActivity.java index 4f13021..48a48ed 100644 --- a/app/src/main/java/git/artdeell/skymodloader/elfmod/ModManagerActivity.java +++ b/app/src/main/java/git/artdeell/skymodloader/elfmod/ModManagerActivity.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; @@ -34,8 +35,10 @@ import git.artdeell.skymodloader.BuildConfig; import git.artdeell.skymodloader.DialogY; +import git.artdeell.skymodloader.LogcatMonitorService; import git.artdeell.skymodloader.MainActivity; import git.artdeell.skymodloader.R; +import git.artdeell.skymodloader.SettingsActivity; import git.artdeell.skymodloader.SMLApplication; import git.artdeell.skymodloader.updater.CanvasUpdaterConnection; import git.artdeell.skymodloader.updater.CanvasUpdaterService; @@ -47,14 +50,12 @@ public class ModManagerActivity extends Activity implements LoadingListener, Mod private static final int REQUEST_MOD = 1024 * 121; @SuppressLint("StaticFieldLeak") private static ElfUIBackbone loader; - private RecyclerView modListView; private View addModButton; private View loadingBar; private Button btnLaunchLive; private Button btnLaunchHuawei; private String skyPackageName; - private SharedPreferences sharedPreferences; private ArrayList skyPackages; private ModUpdaterDialogManager mDialogManager; @@ -64,33 +65,25 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); runUpdater(); setContentView(R.layout.mod_manager); - modListView = findViewById(R.id.mm_modList); addModButton = findViewById(R.id.mm_addMod); loadingBar = findViewById(R.id.mm_loadBar); btnLaunchLive = findViewById(R.id.mm_launch_live); btnLaunchHuawei = findViewById(R.id.mm_launch_huawei); - ((TextView) findViewById(R.id.mm_versionName)).setText(getString(R.string.mod_canvas_version, BuildConfig.VERSION_NAME)); - initializeSkyPackages(); sharedPreferences = getSharedPreferences("package_configs", Context.MODE_PRIVATE); updateButtonTextColor(); - initializeModUpdater(); initializeLoader(); - modListView.setLayoutManager(new LinearLayoutManager(this)); modListView.setAdapter(new ModListAdapter(loader)); - setButtonClickListeners(); setButtonLongClickListeners(); } private void initializeModUpdater() { mDialogManager = new ModUpdaterDialogManager(this); - // This will not do anything if the service isn't already started, and it's only - // started when a mod is in the process of updating. bindService(new Intent(this, ModUpdaterService.class), mDialogManager, 0); } @@ -100,11 +93,12 @@ public void startModUpdater(ElfModUIMetadata metadata) { Toast.makeText(this, R.string.updater_busy, Toast.LENGTH_SHORT).show(); return; } + Intent serviceStartIntent = new Intent(this, ModUpdaterService.class); serviceStartIntent.putExtra(ModUpdaterService.EXTRA_UPDATE_URL, metadata.getGithubReleasesUrl()); serviceStartIntent.putExtra(ModUpdaterService.EXTRA_LIB_NAME, metadata.name); serviceStartIntent.putExtra(ModUpdaterService.EXTRA_VERSION_NUMBER, - new VersionNumber(metadata.majorVersion, metadata.minorVersion, metadata.patchVersion) + new VersionNumber(metadata.majorVersion, metadata.minorVersion, metadata.patchVersion) ); startService(serviceStartIntent); bindService(new Intent(this, ModUpdaterService.class), mDialogManager, 0); @@ -123,6 +117,7 @@ private void updateButtonTextColor() { skyPackageName = "com.tgc.sky.android"; sharedPreferences.edit().putString("sky_package_name", skyPackageName).apply(); } + setButtonTextColor(btnLaunchLive, skyPackages.get(0)); setButtonTextColor(btnLaunchHuawei, skyPackages.get(1)); } @@ -149,18 +144,15 @@ private void initializeLoader() { } } - private void setButtonClickListeners() { btnLaunchLive.setOnClickListener(view -> { skyPackageName = skyPackages.get(0); launchGame(); }); - btnLaunchHuawei.setOnClickListener(view -> { skyPackageName = skyPackages.get(1); launchGame(); }); - } private void setButtonLongClickListeners() { @@ -168,7 +160,6 @@ private void setButtonLongClickListeners() { setSkyPackageName(skyPackages.get(0)); return true; }); - btnLaunchHuawei.setOnLongClickListener(view -> { setSkyPackageName(skyPackages.get(1)); return true; @@ -181,9 +172,9 @@ public void setSkyPackageName(String pkg) { updateButtonTextColor(); } else { Toast.makeText( - getApplicationContext(), - getResources().getString(R.string.game_not_installed_warning), - Toast.LENGTH_SHORT + getApplicationContext(), + getResources().getString(R.string.game_not_installed_warning), + Toast.LENGTH_SHORT ).show(); } } @@ -205,9 +196,23 @@ public void setCustomServer(boolean flag) { } public void setServerUrl(String url) { - sharedPreferences.edit().putString("server_host", url).apply(); + } + public void setLogcatEnabled(boolean flag) { + sharedPreferences.edit().putBoolean("logcat_enabled", flag).apply(); + Intent logcatIntent = new Intent(this, LogcatMonitorService.class); + if (flag) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(logcatIntent); + } else { + startService(logcatIntent); + } + Toast.makeText(this, "Logcat monitoring enabled", Toast.LENGTH_SHORT).show(); + } else { + stopService(logcatIntent); + Toast.makeText(this, "Logcat monitoring disabled", Toast.LENGTH_SHORT).show(); + } } public void onAddMod(View v) { @@ -263,7 +268,6 @@ public void refreshModList(int mode, int which) { case 3: adapter.notifyDataSetChanged(); } - } else modListView.setAdapter(new ModListAdapter(loader)); }); } @@ -289,8 +293,7 @@ public void signalModRemovalError() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.mod_remove_unable); builder.setMessage(R.string.mod_ioe); - builder.setPositiveButton(android.R.string.ok, (d, w) -> { - }); + builder.setPositiveButton(android.R.string.ok, (d, w) -> {}); builder.show(); }); } @@ -335,6 +338,7 @@ private void handleUnsafeModRemoval() { sb.append(getString(R.string.mod_remove_dep, ModListAdapter.getVisibleModName(meta))); sb.append('\n'); } + AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.mod_remove_unable); builder.setMessage(sb.toString()); @@ -368,84 +372,16 @@ public void launchGame() { startActivity(new Intent(this, MainActivity.class)); } else { Toast.makeText( - getApplicationContext(), - getResources().getString(R.string.game_not_installed_warning), - Toast.LENGTH_SHORT + getApplicationContext(), + getResources().getString(R.string.game_not_installed_warning), + Toast.LENGTH_SHORT ).show(); } } - @SuppressLint("SetTextI18n") public void onExtraSettingsDialog(View view) { - DialogY dialogY = DialogY.createFromActivity(this); - dialogY.positiveButton.setVisibility(View.GONE); - dialogY.content.setVisibility(View.GONE); - dialogY.title.setText(R.string.settings_title); - dialogY.negativeButton.setText(R.string.close); - dialogY.negativeButton.setOnClickListener((v)->dialogY.dialog.dismiss()); - - SwitchMaterial hideCanvasMenu = new SwitchMaterial(this); - SwitchMaterial bypassUpdate = new SwitchMaterial(this); - SwitchMaterial enableCeserver = new SwitchMaterial(this); - SwitchMaterial enableServer = new SwitchMaterial(this); - TextInputEditText serverSelector = new TextInputEditText(this); - - - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - - - - int marginPx = dpToPixels(10); - layoutParams.setMargins(marginPx, 0, 0, marginPx); - - hideCanvasMenu.setTextSize(15); - hideCanvasMenu.setLayoutParams(layoutParams); - hideCanvasMenu.setText(R.string.switch_hide_canvas_menu); - hideCanvasMenu.setChecked(sharedPreferences.getBoolean("hide_canvas_menu", false)); - hideCanvasMenu.setOnCheckedChangeListener((buttonView, isChecked) -> setHideCanvasMenu(isChecked)); - - bypassUpdate.setTextSize(15); - bypassUpdate.setLayoutParams(layoutParams); - bypassUpdate.setText(R.string.switch_skip_updates); - bypassUpdate.setChecked(sharedPreferences.getBoolean("skip_updates", false)); - bypassUpdate.setOnCheckedChangeListener((buttonView, isChecked) -> setSkipUpdates(isChecked)); - - enableCeserver.setTextSize(15); - enableCeserver.setLayoutParams(layoutParams); - enableCeserver.setText(R.string.enable_cheat_engine_server); - enableCeserver.setChecked(sharedPreferences.getBoolean("ceserver", false)); - enableCeserver.setOnCheckedChangeListener((buttonView, isChecked) -> setCeserver(isChecked)); - - enableServer.setTextSize(15); - enableServer.setLayoutParams(layoutParams); - enableServer.setText(R.string.switch_custom_server); - enableServer.setChecked(sharedPreferences.getBoolean("custom_server", false)); - enableServer.setOnCheckedChangeListener((buttonView, isChecked) -> setCustomServer(isChecked)); - - serverSelector.setText(sharedPreferences.getString("server_host", "insert-url")); - serverSelector.setSingleLine(true); - serverSelector.setImeOptions(EditorInfo.IME_ACTION_DONE); - serverSelector.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) {} - @Override - public void afterTextChanged(Editable s) { - String url = s.toString(); - setServerUrl(url); - } - }); - - dialogY.container.addView(hideCanvasMenu, layoutParams); - dialogY.container.addView(bypassUpdate, layoutParams); - dialogY.container.addView(enableCeserver, layoutParams); - dialogY.container.addView(enableServer, layoutParams); - dialogY.container.addView(serverSelector, layoutParams); - dialogY.dialog.show(); + Intent intent = new Intent(this, SettingsActivity.class); + startActivity(intent); } public void runUpdater() { @@ -457,4 +393,143 @@ private int dpToPixels(int dp) { float scale = getResources().getDisplayMetrics().density; return (int) (dp * scale + 0.5f); } -} + + public void onClearAppData(View view) { + clearAppDataSelective(); + } + + private void clearAppDataSelective() { + new AlertDialog.Builder(this) + .setTitle("Clear App Data") + .setMessage("This will delete all Canvas data except:\n\n" + + "✓ AccountAuthInfo.bin\n" + + "✓ mods folder\n" + + "✓ Accounts folder\n" + + "✓ config/configs folders\n\n" + + "⚠️ The app will restart after clearing.") + .setPositiveButton("Clear", (dialog, which) -> { + new Thread(() -> { + try { + boolean isEmulator = getPackageName().equals("com.tgc.sky.android.sml"); + String packageName = isEmulator ? "com.tgc.sky.android.sml" : "git.artdeell.skymodloader"; + File dataDir = new File("/data/data/" + packageName); + File filesDir = getFilesDir(); + File externalDataDir = new File("/sdcard/Android/data/" + packageName); + + Log.i("ClearData", "Starting selective clear for package: " + packageName); + + clearDirectorySelective(filesDir, new String[]{"mods", "Accounts", "config"}, new String[]{"AccountAuthInfo.bin"}); + + File cacheDir = getCacheDir(); + if (cacheDir != null && cacheDir.exists()) { + deleteRecursive(cacheDir); + Log.i("ClearData", "Cache cleared"); + } + + File codeCacheDir = getCodeCacheDir(); + if (codeCacheDir != null && codeCacheDir.exists()) { + deleteRecursive(codeCacheDir); + Log.i("ClearData", "Code cache cleared"); + } + + File extractedLibs = new File(filesDir, "extracted_libs"); + if (extractedLibs.exists()) { + deleteRecursive(extractedLibs); + Log.i("ClearData", "Extracted libs cleared"); + } + + File logsDir = new File(externalDataDir, "files/logs"); + if (logsDir.exists()) { + deleteRecursive(logsDir); + Log.i("ClearData", "Logs cleared"); + } + + if (externalDataDir.exists()) { + clearDirectorySelective(externalDataDir, new String[]{"mods", "Accounts", "config", "configs"}, new String[]{}); + Log.i("ClearData", "External data cleared (selective)"); + } + + File sharedPrefsDir = new File(dataDir, "shared_prefs"); + if (sharedPrefsDir.exists()) { + File[] prefFiles = sharedPrefsDir.listFiles(); + if (prefFiles != null) { + for (File prefFile : prefFiles) { + if (!prefFile.getName().contains("package_configs")) { + prefFile.delete(); + Log.i("ClearData", "Deleted pref: " + prefFile.getName()); + } + } + } + } + + Log.i("ClearData", "Selective clear completed successfully"); + runOnUiThread(() -> { + Toast.makeText(this, "Data cleared successfully. Restarting...", Toast.LENGTH_SHORT).show(); + new android.os.Handler().postDelayed(() -> { + Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName()); + if (intent != null) { + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + System.exit(0); + }, 500); + }); + } catch (Exception e) { + Log.e("ClearData", "Error clearing data", e); + runOnUiThread(() -> { + Toast.makeText(this, "Error clearing data: " + e.getMessage(), Toast.LENGTH_LONG).show(); + }); + } + }).start(); + }) + .setNegativeButton("Cancel", null) + .show(); + } + + private void clearDirectorySelective(File dir, String[] preserveFolders, String[] preserveFiles) { + if (!dir.exists() || !dir.isDirectory()) return; + File[] files = dir.listFiles(); + if (files == null) return; + for (File file : files) { + boolean shouldPreserve = false; + if (file.isDirectory()) { + for (String folder : preserveFolders) { + if (file.getName().equals(folder)) { + shouldPreserve = true; + Log.i("ClearData", "Preserving folder: " + file.getName()); + break; + } + } + } + if (file.isFile()) { + for (String fileName : preserveFiles) { + if (file.getName().equals(fileName)) { + shouldPreserve = true; + Log.i("ClearData", "Preserving file: " + file.getName()); + break; + } + } + } + if (!shouldPreserve) { + if (file.isDirectory()) { + deleteRecursive(file); + } else { + file.delete(); + } + Log.i("ClearData", "Deleted: " + file.getName()); + } + } + } + + private void deleteRecursive(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) { + File[] children = fileOrDirectory.listFiles(); + if (children != null) { + for (File child : children) { + deleteRecursive(child); + } + } + } + fileOrDirectory.delete(); + } +} \ No newline at end of file diff --git a/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java b/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java index 5908f2c..355e977 100644 --- a/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java +++ b/app/src/main/java/git/artdeell/skymodloader/updater/CanvasUpdaterService.java @@ -13,7 +13,7 @@ protected String getCacheFileName() { @Override protected String getUpdateCheckerURL() { - return "https://api.github.com/repos/MisterGatto/Canvas-Open-Source-test/releases/latest"; + return "https://api.github.com/repos/skyprotocol/canvas-distribution/releases/latest"; } @Override diff --git a/app/src/main/res/layout/mod_manager.xml b/app/src/main/res/layout/mod_manager.xml index c6850c8..14deb19 100644 --- a/app/src/main/res/layout/mod_manager.xml +++ b/app/src/main/res/layout/mod_manager.xml @@ -7,7 +7,6 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - + - - - - + android:fillViewport="true"> + android:orientation="vertical" + android:padding="8dp"> - - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_height="wrap_content" + android:hint="insert-url" + android:singleLine="true" + android:inputType="textUri" + android:textSize="14sp" /> + - - - - + + + + + + + + + + + + + + + + + + +