1
0
mirror of https://github.com/areteruhiro/LIME-beta-hiro.git synced 2025-02-05 21:11:39 +09:00

音声バグの修正と、プロフィール画像の取得

This commit is contained in:
areteruhiro 2025-02-04 18:07:52 +09:00
parent 5882253f70
commit 3fa559c792
12 changed files with 254 additions and 163 deletions

View File

@ -10,7 +10,7 @@ android {
minSdk 28
targetSdk 35
versionCode 116160
versionName "1.17.0β"
versionName "1.16.17.1"
multiDexEnabled false
proguardFiles += 'proguard-rules.pro'
buildConfigField 'String', 'HOOK_TARGET_VERSION', '"141910383"'

View File

@ -58,7 +58,7 @@ public class LimeOptions {
public Option AgeCheckSkip = new Option("AgeCheckSkip", R.string.AgeCheckSkip, false);
public Option hide_canceled_message = new Option("hide_canceled_message", R.string.hide_canceled_message, false);
public Option RemoveNotification = new Option("RemoveNotification", R.string.removeNotification, false);
public Option RemoveNotification = new Option("RemoveProfileNotification", R.string.removeNotification, false);
public Option DarkColor = new Option("DarkColor", R.string.DarkColor, false);
public Option NoMuteMessage = new Option("NoMuteMessage", R.string.NoMuteMessage, false);
public Option MuteGroup = new Option("Disabled_Group_notification", R.string.MuteGroup, false);

View File

@ -2,7 +2,6 @@ package io.github.hiro.lime;
import android.content.res.XModuleResources;
import android.os.Environment;
import android.view.View;
import androidx.annotation.NonNull;
@ -16,8 +15,6 @@ import de.robv.android.xposed.callbacks.XC_LayoutInflated;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import io.github.hiro.lime.hooks.*;
import java.io.File;
public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResources, IXposedHookZygoteInit {
public static String modulePath;
@ -54,7 +51,7 @@ public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResou
new DarkColor(),
new KeepUnreadLSpatch(),
new AutomaticBackup(),
new RemoveNotification(),
new RemoveProfileNotification(),
new Disabled_Group_notification(),
new PhotoAddNotification(),
new RemoveVoiceRecord(),

View File

@ -46,8 +46,6 @@ public class AgeCheckSkip implements IHook {
builder1.addOpenFlags(SQLiteDatabase.OPEN_READWRITE);
SQLiteDatabase.OpenParams dbParams1 = builder1.build();
SQLiteDatabase db3 = SQLiteDatabase.openDatabase(dbFile3, dbParams1);
// 既に "AGE_VERIFICATION_RESULT" が存在するか確認
String query = "SELECT * FROM key_value_text WHERE key = ?";
Cursor cursor = db3.rawQuery(query, new String[]{"AGE_VERIFICATION_RESULT"});
@ -67,9 +65,8 @@ public class AgeCheckSkip implements IHook {
} else {
return;
}
cursor.close(); // カーソルを閉じる
db3.close(); // データベースを閉じる
cursor.close();
db3.close();
}
}

View File

@ -72,8 +72,7 @@ public class AutomaticBackup implements IHook {
try (FileChannel destinationWithTimestamp = new FileOutputStream(backupFileWithTimestamp).getChannel()) {
destinationWithTimestamp.transferFrom(source, 0, source.size());
}
showToast(appContext,moduleContext.getResources().getString(R.string.Talk_Auto_Back_up_Success)); // トーストをUIスレッドで表示
showToast(appContext,moduleContext.getResources().getString(R.string.Talk_Auto_Back_up_Success));
} catch (IOException ignored) {
showToast(appContext, moduleContext.getResources().getString(R.string.Talk_Auto_Back_up_Error));
}

View File

@ -29,7 +29,6 @@ public class BlockTracking implements IHook {
"me1.hd", "me1.jd", "me1.Lb", "me1.Nb"
};
String[] methodsToHook = {"read", "write"};
// Hook 'read' and 'write' methods for each specified class
for (String className : classesToHook) {
for (String methodName : methodsToHook) {
XposedBridge.hookAllMethods(
@ -59,8 +58,7 @@ public class BlockTracking implements IHook {
}
);
}
// Hook specific method in 'jp.naver.line.android.thrift.client.impl.LegacyTalkServiceClientImpl'
XposedBridge.hookAllMethods(
XposedBridge.hookAllMethods(
loadPackageParam.classLoader.loadClass("jp.naver.line.android.thrift.client.impl.LegacyTalkServiceClientImpl"),
"F2",
new XC_MethodHook() {

View File

@ -21,7 +21,6 @@ public class CustomPreferences {
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), SETTINGS_DIR);
if (!dir.exists() && !dir.mkdirs()) {
// 最初のディレクトリの作成に失敗した場合
dir = new File(Environment.getExternalStorageDirectory(), "Android/data/jp.naver.line.android/");
}
settingsFile = new File(dir, SETTINGS_FILE);
@ -29,20 +28,28 @@ public class CustomPreferences {
public void saveSetting(String key, String value) {
Properties properties = new Properties();
try (FileInputStream fis = new FileInputStream(settingsFile)) {
properties.load(fis);
} catch (IOException e) {
// ファイルが存在しない場合新規作成する
}
try (FileOutputStream fos = new FileOutputStream(settingsFile)) {
properties.setProperty(key, value);
properties.store(fos, null);
} catch (IOException e) {
e.printStackTrace();
if (settingsFile.exists()) {
settingsFile.delete();
}
try {
properties.setProperty(key, value);
properties.store(new FileOutputStream(settingsFile), null);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public String getSetting(String key, String defaultValue) {
Properties properties = new Properties();
try (FileInputStream fis = new FileInputStream(settingsFile)) {

View File

@ -900,17 +900,13 @@ public class EmbedOptions implements IHook {
layout.addView(readCheckerVerticalInput);
layout.addView(saveButton);
layout.addView(resetButton);
// ScrollView を作成
ScrollView scrollView = new ScrollView(context);
scrollView.addView(layout);
// ダイアログを作成
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(moduleContext.getResources().getString(R.string.edit_margin_settings));
builder.setView(scrollView); // ScrollView をダイアログのビューとして設定
builder.setNegativeButton(moduleContext.getResources().getString(R.string.cancel), null);
// アクティビティがまだ有効であるか確認
if (context instanceof Activity && !((Activity) context).isFinishing()) {
builder.show();
}

View File

@ -140,10 +140,10 @@ public class PhotoAddNotification implements IHook {
if (notification.extras != null) {
Bundle extras = notification.extras;
//XposedBridge.log("Notification Extras:");
for (String key : extras.keySet()) {
Object value = extras.get(key);
//XposedBridge.log(" " + key + ": " + (value != null ? value.toString() : "null"));
}
// for (String key : extras.keySet()) {
// Object value = extras.get(key);
// //XposedBridge.log(" " + key + ": " + (value != null ? value.toString() : "null"));
// }
if (extras.containsKey("line.sticker.url")) {
String stickerUrl = extras.getString("line.sticker.url");
if (stickerUrl != null) {

View File

@ -1,58 +0,0 @@
package io.github.hiro.lime.hooks;
import java.lang.reflect.Field;
import java.util.ArrayList;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import io.github.hiro.lime.LimeOptions;
public class RemoveNotification implements IHook {
@Override
public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (!limeOptions.RemoveNotification.checked) return;
XposedBridge.hookAllMethods(
loadPackageParam.classLoader.loadClass(Constants.RESPONSE_HOOK.className),
Constants.RESPONSE_HOOK.methodName,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (!"sync".equals(param.args[0].toString())) return;
try {
Object wrapper = param.args[1].getClass().getDeclaredField("a").get(param.args[1]);
if (wrapper == null) return;
Field operationResponseField = wrapper.getClass().getSuperclass().getDeclaredField("value_");
operationResponseField.setAccessible(true);
Object operationResponse = operationResponseField.get(wrapper);
if (operationResponse == null) return;
ArrayList<?> operations = (ArrayList<?>) operationResponse.getClass().getDeclaredField("a").get(operationResponse);
if (operations == null) return;
for (int i = operations.size() - 1; i >= 0; i--) {
Object operation = operations.get(i);
Field typeField = operation.getClass().getDeclaredField("c");
typeField.setAccessible(true);
Object type = typeField.get(operation);
if ("NOTIFIED_UPDATE_PROFILE".equals(type.toString())) {
typeField.set(operation, type.getClass().getMethod("valueOf", String.class).invoke(type, "DUMMY"));
}
}
} catch (NoSuchFieldException | IllegalAccessException |
IllegalArgumentException e) {
// XposedBridge.log("RemoveNotification: Error accessing fields - " + e.getMessage());
} catch (Exception e) {
// XposedBridge.log("RemoveNotification: Unexpected error - " + e.getMessage());
}
}
}
);
}
}

View File

@ -0,0 +1,124 @@
package io.github.hiro.lime.hooks;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import io.github.hiro.lime.LimeOptions;
public class RemoveProfileNotification implements IHook {
@Override
public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (!limeOptions.RemoveNotification.checked) return;
XposedBridge.hookAllMethods(Application.class, "onCreate", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Application appContext = (Application) param.thisObject;
if (appContext == null) {
return;
}
Context moduleContext;
try {
moduleContext = appContext.createPackageContext(
"io.github.hiro.lime", Context.CONTEXT_IGNORE_SECURITY);
} catch (PackageManager.NameNotFoundException ignored) {
return;
}
File dbFile1 = appContext.getDatabasePath("naver_line");
File dbFile2 = appContext.getDatabasePath("contact");
if (dbFile1.exists() && dbFile2.exists()) {
SQLiteDatabase.OpenParams.Builder builder1 = new SQLiteDatabase.OpenParams.Builder();
builder1.addOpenFlags(SQLiteDatabase.OPEN_READWRITE);
SQLiteDatabase.OpenParams dbParams1 = builder1.build();
SQLiteDatabase.OpenParams.Builder builder2 = new SQLiteDatabase.OpenParams.Builder();
builder2.addOpenFlags(SQLiteDatabase.OPEN_READWRITE);
SQLiteDatabase.OpenParams dbParams2 = builder2.build();
SQLiteDatabase db1 = SQLiteDatabase.openDatabase(dbFile1, dbParams1);
SQLiteDatabase db2 = SQLiteDatabase.openDatabase(dbFile2, dbParams2);
RemoveProfileNotifications(loadPackageParam, appContext, db1, db2, moduleContext);
}
}
});
}
private void RemoveProfileNotifications(XC_LoadPackage.LoadPackageParam loadPackageParam, Context context, SQLiteDatabase db1, SQLiteDatabase db2, Context moduleContext) throws ClassNotFoundException {
XposedBridge.hookAllMethods(
loadPackageParam.classLoader.loadClass(Constants.RESPONSE_HOOK.className),
Constants.RESPONSE_HOOK.methodName,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (!"sync".equals(param.args[0].toString())) return;
try {
String paramValue = param.args[1].toString();
String[] operations = paramValue.split("Operation\\(");
for (String operation : operations) {
if (operation.trim().isEmpty()) continue;
String type = null;
String param1 = null;
String[] parts = operation.split(",");
for (String part : parts) {
part = part.trim();
if (part.startsWith("type:")) {
type = part.substring("type:".length()).trim();
} else if (part.startsWith("param1:")) {
param1 = part.substring("param1:".length()).trim();
}
}
if ("NOTIFIED_UPDATE_PROFILE".equals(type)) {
XposedBridge.log("取得したparam1: " + param1);
Cursor cursor = db2.rawQuery("SELECT mid, contact_type, profile_updated_time_millis, profile_name FROM contacts WHERE mid=?", new String[]{param1});
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
String mid = cursor.getString(cursor.getColumnIndex("mid"));
int rowsDeleted = db2.delete("contacts", "mid=?", new String[]{mid});
}
} finally {
cursor.close();
}
}
Object operationObject = param.args[1].getClass().getDeclaredField("a").get(param.args[1]);
if (operationObject != null) {
Field operationResponseField = operationObject.getClass().getSuperclass().getDeclaredField("value_");
operationResponseField.setAccessible(true);
Object operationResponse = operationResponseField.get(operationObject);
if (operationResponse != null) {
ArrayList<?> operationList = (ArrayList<?>) operationResponse.getClass().getDeclaredField("a").get(operationResponse);
for (Object op : operationList) {
Field typeField = op.getClass().getDeclaredField("c");
typeField.setAccessible(true);
Object typeFieldValue = typeField.get(op);
if ("NOTIFIED_UPDATE_PROFILE".equals(typeFieldValue.toString())) {
typeField.set(op, typeFieldValue.getClass().getMethod("valueOf", String.class).invoke(typeFieldValue, "DUMMY"));
}
}
}
}
}
}
} catch (Exception e) {
XposedBridge.log("RemoveProfileNotification: 予期しないエラー - " + e.getMessage());
}
}
});
}
}

View File

@ -37,6 +37,32 @@ public class RingTone implements IHook {
return;
}
Context moduleContext = AndroidAppHelper.currentApplication().createPackageContext(
"io.github.hiro.lime", Context.CONTEXT_IGNORE_SECURITY);
String resourceNameA = "dial_tone";
int resourceIdA = moduleContext.getResources().getIdentifier(resourceNameA, "raw", "io.github.hiro.lime");
File ringtoneDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup");
if (!ringtoneDir.exists()) {
ringtoneDir.mkdirs();
}
File destFileA = new File(ringtoneDir, resourceNameA + ".wav");
if (!destFileA.exists()) {
try (InputStream in = moduleContext.getResources().openRawResource(resourceIdA);
OutputStream out = new FileOutputStream(destFileA)) {
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
} catch (IOException e) {
e.printStackTrace();
}
}
XposedBridge.hookAllMethods(
loadPackageParam.classLoader.loadClass(Constants.RESPONSE_HOOK.className),
Constants.RESPONSE_HOOK.methodName,
@ -72,21 +98,19 @@ public class RingTone implements IHook {
}
if (paramValue.contains("type:NOTIFIED_RECEIVED_CALL,")) {
XposedBridge.log(paramValue);
if (context != null) {
// MediaPlayerが初期化されているか確認
if (mediaPlayer != null) {
// MediaPlayerが再生中の場合は停止
if (mediaPlayer.isPlaying()) {
Log.d("Xposed", "MediaPlayer is already playing. Stopping playback.");
mediaPlayer.stop(); // 再生中の場合は停止
}
mediaPlayer.release(); // MediaPlayerを解放
mediaPlayer = null; // MediaPlayerのインスタンスをnullに設定
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
Log.d("Xposed", "MediaPlayer is already playing. Not starting new playback.");
return; // すでに再生中の場合は新しい再生を開始しない
}
Uri ringtoneUri = Uri.fromFile(destFile); // コピーしたファイルのURIを取得
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
return;
}
Uri ringtoneUri = Uri.fromFile(destFile);
mediaPlayer = MediaPlayer.create(context, ringtoneUri);
mediaPlayer.setLooping(true); // 繰り返し再生を設定
mediaPlayer.setLooping(true);
if (mediaPlayer != null) {
mediaPlayer.start();
@ -97,7 +121,6 @@ public class RingTone implements IHook {
}
});
Class<?> targetClass = loadPackageParam.classLoader.loadClass("com.linecorp.andromeda.audio.AudioManager");
Method[] methods = targetClass.getDeclaredMethods();
@ -106,108 +129,116 @@ public class RingTone implements IHook {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
String methodName = method.getName();
Context context = AndroidAppHelper.currentApplication().getApplicationContext();
if (methodName.equals("getVoiceComplexityLevel")) {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
return;
}
XposedBridge.log("getVoiceComplexityLevel");
File destFile = new File(ringtoneDir, "ringtone" + ".wav");
Uri ringtoneUri = Uri.fromFile(destFile);
mediaPlayer = MediaPlayer.create(context, ringtoneUri);
mediaPlayer.setLooping(true);
mediaPlayer.start();
isPlaying = true;
return;
}
if (method.getName().equals("setServerConfig")) {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release(); // MediaPlayerを解放
mediaPlayer = null; // MediaPlayerのインスタンスをnullに設定
mediaPlayer.release();
mediaPlayer = null;
}
}
if (method.getName().equals("stop")) {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release(); // MediaPlayerを解放
mediaPlayer = null; // MediaPlayerのインスタンスをnullに設定
mediaPlayer.release();
mediaPlayer = null;
}
}
if (method.getName().equals("processToneEvent")) {
if( param.args != null && param.args.length > 0) {
Object arg0 = param.args[0];
if (method.getName().equals("ACTIVATED") )
if ("ACTIVATED".equals(arg0)) {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
}
if (method.getName().equals("processToneEvent")) {
if (limeOptions.DialTone.checked) {
Log.d("Xposed", "MuteTone is enabled. Suppressing tone event.");
param.setResult(null);
return;
}
if (limeOptions.MuteTone.checked) {
if (method.getName().equals("setTonePlayer")) {
param.setResult(null);
}
}
if (arg0.toString().contains("START")) {
if (appContext != null) {
// MediaPlayerが初期化されている場合
if (mediaPlayer != null) {
// MediaPlayerが再生中の場合は停止
if (mediaPlayer.isPlaying()) {
Log.d("Xposed", "MediaPlayer is already playing. Stopping playback.");
mediaPlayer.stop(); // 再生中の場合は停止
}
mediaPlayer.release(); // MediaPlayerを解放
mediaPlayer = null; // MediaPlayerのインスタンスをnullに設定
}
Context moduleContext = AndroidAppHelper.currentApplication().createPackageContext(
"io.github.hiro.lime", Context.CONTEXT_IGNORE_SECURITY);
// 音楽が再生中の場合MediaPlayerの再生を抑制
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
Log.d("Xposed", "音楽が再生中のため、MediaPlayerの再生を抑制します。");
return; // ここで何もしないことで再生を抑制
}
String resourceNameA = "dial_tone";
int resourceId = moduleContext.getResources().getIdentifier(resourceNameA, "raw", "io.github.hiro.lime");
File ringtoneDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup");
if (!ringtoneDir.exists()) {
ringtoneDir.mkdirs(); // ディレクトリが存在しない場合は作成
}
File destFile = new File(ringtoneDir, resourceNameA + ".wav");
// リソースをストリームとして読み込みファイルに書き込む
if (!destFile.exists()) {
try (InputStream in = moduleContext.getResources().openRawResource(resourceId);
OutputStream out = new FileOutputStream(destFile)) {
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
if( param.args != null && param.args.length > 0) {
Object arg0 = param.args[0];
if (arg0.toString().contains("START")) {
if (appContext != null) {
if (mediaPlayer != null) {
if (mediaPlayer.isPlaying()) {
Log.d("Xposed", "MediaPlayer is already playing. Stopping playback.");
mediaPlayer.stop();
}
} catch (IOException e) {
e.printStackTrace();
mediaPlayer.release();
mediaPlayer = null;
}
}
Uri ringtoneUri = Uri.fromFile(destFile); // コピーしたファイルのURIを取得
mediaPlayer = MediaPlayer.create(appContext, ringtoneUri);
mediaPlayer.setLooping(true); // 繰り返し再生を設定
Uri ringtoneUriA = Uri.fromFile(destFileA);
mediaPlayer = MediaPlayer.create(appContext, ringtoneUriA);
mediaPlayer.setLooping(true);
if (mediaPlayer != null) {
Log.d("Xposed", "Playing media.");
mediaPlayer.start();
} else {
Log.d("Xposed", "MediaPlayer is null. Cannot play media.");
return;
}
} else {
Log.d("Xposed", "appContext is null. Cannot play media.");
return;
}
} else {
Log.d("Xposed", "Argument is not 'START'. Actual value: " + arg0);
}
}
if (limeOptions.MuteTone.checked) {
if (method.getName().equals("setTonePlayer")) {
param.setResult(null);
}
}
if (method.getName().equals("ACTIVATED") && param.args != null && param.args.length > 0) {
Object arg0 = param.args[0];
if ("ACTIVATED".equals(arg0)) {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release(); // MediaPlayerを解放
mediaPlayer = null; // MediaPlayerのインスタンスをnullに設定
}
}
}
//
// // 引数の値を取得してログに出力
// StringBuilder argsLog = new StringBuilder("Method: " + methodName + ", Arguments: ");
// for (Object arg : param.args) {
// argsLog.append(arg).append(", ");
// }
//
// // 最後のカンマとスペースを削除
// if (argsLog.length() > 0) {
// argsLog.setLength(argsLog.length() - 2);
// }
//
// // XposedBridge.logを使用してログ出力
// XposedBridge.log(argsLog.toString());
}
});