diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7838af9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# TEST REPOSITORY
+DON'T INSTALL RELEASES!
diff --git a/app/build.gradle b/app/build.gradle
index 653b166..eec14f1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -24,7 +24,7 @@ android {
minSdk 26
targetSdk 34
versionCode 54
- versionName "1.6.9"
+ 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 d17a067..cc4e035 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 String SKY_BUILD_ACCESS_KEY = "1743442606-3de96bd2ce8644c9d7144775e38bb8de13b3ac9b15e2984b0304445c5bbe0174";
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..a3fbe4b
--- /dev/null
+++ b/app/src/main/java/git/artdeell/skymodloader/LogcatMonitorService.java
@@ -0,0 +1,172 @@
+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");
+
+ 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 {
+ // create directory logs
+ File logsDir = new File(getExternalFilesDir(null), "logs");
+ if (!logsDir.exists()) {
+ logsDir.mkdirs();
+ }
+
+ // create log file with 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());
+
+ // starts 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++;
+
+ // update every 50 lines
+ 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;
+ }
+}
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 extends java.util.zip.ZipEntry> 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/jnilibs/arm64-v8a/libktx_read.so b/app/src/main/jnilibs/arm64-v8a/libktx_read.so
new file mode 100644
index 0000000..9dc9166
Binary files /dev/null and b/app/src/main/jnilibs/arm64-v8a/libktx_read.so differ
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" />
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
\ No newline at end of file
+