forked from VibedByKaKi/soul-browser
* Add Redirector-compatible main-frame URL rewrite rules. Port Redirector's rule engine for address-bar navigations, with settings CRUD, JSON import/export, and PrefRedirect included in Storage backup. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix Redirector settings click IDs to match list items. The Clean-mode rows use ids 21/22 but the listener checked 30/31, so toggle and rules did nothing. Co-authored-by: Cursor <cursoragent@cursor.com> * Use Soul settings chrome for Redirect rules. Make Redirect rules a bottom-corner row under the enable switch, and rebuild RedirectorActivity as a SettingActivity list with add/overflow actions so it matches other settings screens. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove obsolete RedirectorActivity inner-class smali. Co-authored-by: Cursor <cursoragent@cursor.com> * Style Redirect rule editor as a Soul bottom sheet. Replace the stock AlertDialog add/edit form with DialogEditRedirect (MyDialogBottom + MyEditText / popup choices / Apply), matching other settings dialogs. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop unused RedirectorActivity AlertDialog inner classes. Co-authored-by: Cursor <cursoragent@cursor.com> * Move Redirector under Web content with userscript-style list. Place enable/rules below userscripts, use in-list switches and a ⋮ delete menu, and drop the edit modal Disabled/Delete controls. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove obsolete DialogEditRedirect inner-class smali. Co-authored-by: Cursor <cursoragent@cursor.com> * Map RedirectorListAdapter to Soul's obfuscated RecyclerView API. notifyDataSetChanged/getItemCount/onBind/onCreate were compiled against stock AndroidX names; remap them to g/d/n/o like SettingListAdapter. Co-authored-by: Cursor <cursoragent@cursor.com> * Match Redirect rules list chrome to userscripts. Add delete-all, Sort / Show detail / Single line title, ExpandView-style list background, and long-press multi-select delete. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove obsolete RedirectorActivity nested listener smali. Co-authored-by: Cursor <cursoragent@cursor.com> * Open DialogSetSort for Redirect rules like userscripts. Replace the custom sort popup with the shared Sort by / Order sheet (type 27 UI), keeping redirector prefs separate from userscript sort. Co-authored-by: Cursor <cursoragent@cursor.com> * Dismiss Delete-all confirm and show rule count. DialogSetMsg does not close itself on Apply; hold the dialog like SettingMenu and dismiss after clear. Message includes (N). Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
628 lines
27 KiB
Bash
Executable file
628 lines
27 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
SRC_HELPER="${ROOT_DIR}/sources/java/com/mycompany/app/web/RedirectorHelper.java"
|
|
SRC_ACTIVITY="${ROOT_DIR}/sources/java/com/mycompany/app/setting/RedirectorActivity.java"
|
|
SRC_ADAPTER="${ROOT_DIR}/sources/java/com/mycompany/app/setting/RedirectorListAdapter.java"
|
|
SRC_PREFS="${ROOT_DIR}/sources/java/com/mycompany/app/setting/RedirectorListPrefs.java"
|
|
SRC_DIALOG="${ROOT_DIR}/sources/java/com/mycompany/app/dialog/DialogEditRedirect.java"
|
|
OUT_WEB="${ROOT_DIR}/app/smali_classes4/com/mycompany/app/web"
|
|
OUT_SETTING="${ROOT_DIR}/app/smali_classes4/com/mycompany/app/setting"
|
|
OUT_DIALOG="${ROOT_DIR}/app/smali_classes4/com/mycompany/app/dialog"
|
|
TOOLS="${ROOT_DIR}/tools"
|
|
ANDROID_HOME="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-${HOME}/android-sdk}}"
|
|
ANDROID_JAR="$(ls -1 "${ANDROID_HOME}/platforms"/android-*/android.jar 2>/dev/null | sort -V | tail -1)"
|
|
D8="$(find "${ANDROID_HOME}/build-tools" -name d8 -type f 2>/dev/null | sort -V | tail -1)"
|
|
APK="$(ls -1 "${ROOT_DIR}/dist"/soul-browser-*-merged-unsigned.apk 2>/dev/null | sort | tail -1 || true)"
|
|
if [[ -z "${APK}" || ! -f "${APK}" ]]; then
|
|
APK="$(ls -1 /home/kaki/.t3/worktrees/soul-browser/*/dist/soul-browser-*-merged-unsigned.apk 2>/dev/null | sort | tail -1 || true)"
|
|
fi
|
|
DEX_TOOLS_ZIP="${TOOLS}/dex-tools-v2.4.zip"
|
|
DEX_TOOLS_DIR="${TOOLS}/dex-tools-v2.4"
|
|
|
|
mkdir -p "${TOOLS}" "${OUT_WEB}" "${OUT_SETTING}" "${OUT_DIALOG}"
|
|
|
|
if [[ ! -f "${ANDROID_JAR}" ]]; then
|
|
echo "error: android.jar not found" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -z "${D8}" ]]; then
|
|
echo "error: d8 not found" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -z "${APK}" || ! -f "${APK}" ]]; then
|
|
echo "error: merged unsigned APK not found; run ./scripts/build.sh first" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "${DEX_TOOLS_DIR}/d2j-dex2jar.sh" ]]; then
|
|
if [[ ! -f "${DEX_TOOLS_ZIP}" ]]; then
|
|
echo "Downloading dex-tools v2.4..."
|
|
curl -fsSL -o "${DEX_TOOLS_ZIP}" \
|
|
"https://github.com/pxb1988/dex2jar/releases/download/v2.4/dex-tools-v2.4.zip"
|
|
fi
|
|
unzip -qo "${DEX_TOOLS_ZIP}" -d "${TOOLS}"
|
|
fi
|
|
chmod +x "${DEX_TOOLS_DIR}/"*.sh "${DEX_TOOLS_DIR}/d2j_invoke.sh" 2>/dev/null || true
|
|
D2J="${DEX_TOOLS_DIR}/d2j-dex2jar.sh"
|
|
|
|
STAGE="$(mktemp -d)"
|
|
cleanup() { rm -rf "${STAGE}"; }
|
|
trap cleanup EXIT
|
|
|
|
echo "Building classpath from APK dex files..."
|
|
CP="${ANDROID_JAR}"
|
|
for dex in classes.dex classes2.dex classes3.dex classes4.dex; do
|
|
unzip -p "${APK}" "${dex}" > "${STAGE}/${dex}" 2>/dev/null || true
|
|
if [[ -s "${STAGE}/${dex}" ]]; then
|
|
jar="${STAGE}/${dex%.dex}.jar"
|
|
bash "${D2J}" --force "${STAGE}/${dex}" -o "${jar}" >/dev/null
|
|
CP="${CP}:${jar}"
|
|
fi
|
|
done
|
|
|
|
# Compile-time stubs ahead of APK jars (dex2jar drops no-arg ctors; MainActivity.R shadows resources).
|
|
STUBS="${STAGE}/stubs"
|
|
mkdir -p "${STUBS}/com/mycompany/app/soulbrowser" \
|
|
"${STUBS}/com/mycompany/app/setting" \
|
|
"${STUBS}/com/mycompany/app/main" \
|
|
"${STUBS}/com/mycompany/app/view"
|
|
cat > "${STUBS}/com/mycompany/app/soulbrowser/R.java" <<'EOF'
|
|
package com.mycompany.app.soulbrowser;
|
|
public final class R {
|
|
public static final class string {
|
|
public static int redirector_title = 0x7f1205cd;
|
|
public static int redirector_on = 0x7f1205ce;
|
|
public static int redirector_on_info = 0x7f1205cf;
|
|
public static int redirector_rules = 0x7f1205d0;
|
|
public static int redirector_add = 0x7f1205d1;
|
|
public static int redirector_edit = 0x7f1205d2;
|
|
public static int redirector_import = 0x7f1205d3;
|
|
public static int redirector_export = 0x7f1205d4;
|
|
public static int redirector_include = 0x7f1205d5;
|
|
public static int redirector_exclude = 0x7f1205d6;
|
|
public static int redirector_to = 0x7f1205d7;
|
|
public static int redirector_example = 0x7f1205d8;
|
|
public static int redirector_pattern_type = 0x7f1205d9;
|
|
public static int redirector_process = 0x7f1205da;
|
|
public static int redirector_disabled_rule = 0x7f1205db;
|
|
public static int redirector_description = 0x7f1205dc;
|
|
public static int redirector_example_result = 0x7f1205dd;
|
|
public static int redirector_wildcard = 0x7f1205de;
|
|
public static int redirector_regex = 0x7f1205df;
|
|
public static int redirector_saved = 0x7f1205e0;
|
|
public static int redirector_import_ok = 0x7f1205e1;
|
|
public static int redirector_delete = 0x7f1205e2;
|
|
public static int redirector_empty = 0x7f1205e3;
|
|
public static int apply = 0x7f12005d;
|
|
public static int delete = 0x7f120154;
|
|
public static int delete_all = 0x7f120155;
|
|
public static int del_all_confirm = 0x7f120153;
|
|
public static int sort = 0x7f1204a5;
|
|
public static int sort_name = 0x7f1204ab;
|
|
public static int show_detail = 0x7f120481;
|
|
public static int show_single = 0x7f12048b;
|
|
public static int order_descend = 0x7f1203bc;
|
|
}
|
|
public static final class drawable {
|
|
public static int outline_add_black_24 = 0x7f0802d8;
|
|
public static int outline_add_dark_24 = 0x7f0802dc;
|
|
public static int outline_more_vert_black_24 = 0x7f0803e6;
|
|
public static int outline_more_vert_dark_24 = 0x7f0803e8;
|
|
public static int outline_delete_black_20 = 0x7f08033f;
|
|
public static int outline_delete_dark_20 = 0x7f080341;
|
|
public static int baseline_check_circle_black_24 = 0x7f080081;
|
|
public static int baseline_check_circle_dark_24 = 0x7f080082;
|
|
public static int outline_radio_button_unchecked_black_24 = 0x7f080426;
|
|
public static int outline_radio_button_unchecked_dark_24 = 0x7f080427;
|
|
public static int edit_cursor = 0x7f080170;
|
|
public static int selector_normal = 0x7f080554;
|
|
public static int selector_normal_dark = 0x7f080555;
|
|
public static int selector_list_back = 0x7f08054e;
|
|
public static int selector_list_back_dark = 0x7f080550;
|
|
}
|
|
}
|
|
EOF
|
|
# Minimal hierarchy with public no-arg ctors so RedirectorActivity can extend SettingActivity.
|
|
cat > "${STUBS}/com/mycompany/app/main/MainActivity.java" <<'EOF'
|
|
package com.mycompany.app.main;
|
|
import android.app.Activity;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
public class MainActivity extends Activity {
|
|
public Handler O0;
|
|
public MainActivity() {}
|
|
@Override public void onCreate(Bundle b) { super.onCreate(b); }
|
|
@Override public void onActivityResult(int requestCode, int resultCode, android.content.Intent data) {
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
}
|
|
public void onBackPressed() { super.onBackPressed(); }
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/setting/CastActivity.java" <<'EOF'
|
|
package com.mycompany.app.setting;
|
|
import com.mycompany.app.main.MainActivity;
|
|
public class CastActivity extends MainActivity {
|
|
public CastActivity() {}
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/setting/SettingActivity.java" <<'EOF'
|
|
package com.mycompany.app.setting;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.view.View;
|
|
import com.mycompany.app.view.MyButtonImage;
|
|
import com.mycompany.app.view.MyMainRelative;
|
|
import com.mycompany.app.view.MyManagerLinear;
|
|
import com.mycompany.app.view.MyRecyclerView;
|
|
import java.util.List;
|
|
public class SettingActivity extends CastActivity {
|
|
public Handler O0;
|
|
public MyButtonImage I1;
|
|
public MyButtonImage J1;
|
|
public MyRecyclerView L1;
|
|
public MyManagerLinear M1;
|
|
public SettingListAdapter N1;
|
|
public MyMainRelative E1;
|
|
public androidx.appcompat.widget.AppCompatTextView H1;
|
|
public int O1;
|
|
public SettingActivity() {}
|
|
public List D0() { return null; }
|
|
public final void F0(int i, boolean z, boolean z2) {}
|
|
public final void M0() {}
|
|
@Override public void onCreate(Bundle b) { super.onCreate(b); }
|
|
public void onBackPressed() { super.onBackPressed(); }
|
|
}
|
|
EOF
|
|
# Need AppCompatTextView for H1 field type — already stubbed.
|
|
cat > "${STUBS}/com/mycompany/app/setting/SettingListAdapter.java" <<'EOF'
|
|
package com.mycompany.app.setting;
|
|
public class SettingListAdapter {}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyButtonImage.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.content.Context;
|
|
import android.widget.ImageView;
|
|
public class MyButtonImage extends ImageView {
|
|
public MyButtonImage(Context c) { super(c); }
|
|
public void setBgPreColor(int color) {}
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyRoundItem.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.content.Context;
|
|
import android.widget.RelativeLayout;
|
|
public class MyRoundItem extends RelativeLayout {
|
|
public MyRoundItem(Context c) { super(c); }
|
|
public final void a(int i) {}
|
|
public void setDrawLine(boolean z) {}
|
|
public void setRound(int i) {}
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MySwitchView.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.content.Context;
|
|
import android.widget.FrameLayout;
|
|
public class MySwitchView extends FrameLayout {
|
|
public MySwitchView(Context c) { super(c); }
|
|
public final void b(boolean z, boolean z2) {}
|
|
}
|
|
EOF
|
|
mkdir -p "${STUBS}/androidx/recyclerview/widget" \
|
|
"${STUBS}/androidx/appcompat/widget" \
|
|
"${STUBS}/androidx/core/widget"
|
|
cat > "${STUBS}/androidx/recyclerview/widget/RecyclerView.java" <<'EOF'
|
|
package androidx.recyclerview.widget;
|
|
import android.content.Context;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
public class RecyclerView extends ViewGroup {
|
|
public static final int NO_POSITION = -1;
|
|
public RecyclerView(Context c) { super(c); }
|
|
public void setAdapter(Adapter adapter) {}
|
|
@Override protected void onLayout(boolean c, int l, int t, int r, int b) {}
|
|
public static abstract class Adapter<VH extends ViewHolder> {
|
|
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
|
|
public abstract void onBindViewHolder(VH holder, int position);
|
|
public abstract int getItemCount();
|
|
public int getItemViewType(int position) { return 0; }
|
|
public void notifyDataSetChanged() {}
|
|
}
|
|
public static class ViewHolder {
|
|
public ViewHolder(View itemView) {}
|
|
public final int getAdapterPosition() { return -1; }
|
|
}
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/androidx/appcompat/widget/AppCompatTextView.java" <<'EOF'
|
|
package androidx.appcompat.widget;
|
|
import android.content.Context;
|
|
import android.util.AttributeSet;
|
|
import android.widget.TextView;
|
|
public class AppCompatTextView extends TextView {
|
|
public AppCompatTextView(Context c, AttributeSet a) { super(c); }
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/androidx/core/widget/NestedScrollView.java" <<'EOF'
|
|
package androidx.core.widget;
|
|
import android.content.Context;
|
|
import android.util.AttributeSet;
|
|
import android.widget.FrameLayout;
|
|
public class NestedScrollView extends FrameLayout {
|
|
public NestedScrollView(Context c, AttributeSet a) { super(c); }
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyRecyclerView.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.content.Context;
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
public class MyRecyclerView extends RecyclerView {
|
|
public MyRecyclerView(Context c) { super(c); }
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyManagerLinear.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
public class MyManagerLinear {}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyMainRelative.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.content.Context;
|
|
import android.widget.RelativeLayout;
|
|
public class MyMainRelative extends RelativeLayout {
|
|
public MyMainRelative(Context c) { super(c); }
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyPopupAdapter.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
public class MyPopupAdapter {
|
|
public static class PopMenuItem {
|
|
public PopMenuItem(int id, int titleRes) {}
|
|
public PopMenuItem(int id, String title) {}
|
|
public PopMenuItem(int id, int titleRes, int unused, boolean checked) {}
|
|
}
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyPopupMenu.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.view.View;
|
|
import com.mycompany.app.main.MainActivity;
|
|
import java.util.ArrayList;
|
|
public class MyPopupMenu {
|
|
public interface MyPopupListener {
|
|
void a();
|
|
boolean b(View view, int which);
|
|
}
|
|
public MyPopupMenu(MainActivity a, View root, View anchor, ArrayList items, boolean dark, MyPopupListener l) {}
|
|
public void a() {}
|
|
public final boolean d(int id) { return false; }
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyButtonCheck.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.content.Context;
|
|
import android.view.View;
|
|
public class MyButtonCheck extends View {
|
|
public boolean D;
|
|
public MyButtonCheck(Context c) { super(c); }
|
|
public final void p(int a, int b) {}
|
|
public final void q(boolean z, boolean z2) { this.D = z; }
|
|
public void setBgPreColor(int c) {}
|
|
}
|
|
EOF
|
|
mkdir -p "${STUBS}/com/mycompany/app/dialog"
|
|
cat > "${STUBS}/com/mycompany/app/dialog/DialogSetFull.java" <<'EOF'
|
|
package com.mycompany.app.dialog;
|
|
public class DialogSetFull {
|
|
public interface DialogApplyListener { void a(); }
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/dialog/DialogSetMsg.java" <<'EOF'
|
|
package com.mycompany.app.dialog;
|
|
import android.app.Activity;
|
|
import android.app.Dialog;
|
|
import android.content.DialogInterface;
|
|
public class DialogSetMsg extends Dialog {
|
|
public DialogSetMsg(Activity a, int msgRes, int titleRes, DialogSetFull.DialogApplyListener l) { super(a); }
|
|
public DialogSetMsg(Activity a, String msg, int titleRes, DialogSetFull.DialogApplyListener l) { super(a); }
|
|
@Override public void setOnDismissListener(DialogInterface.OnDismissListener l) { super.setOnDismissListener(l); }
|
|
@Override public void dismiss() { super.dismiss(); }
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/dialog/DialogSetSort.java" <<'EOF'
|
|
package com.mycompany.app.dialog;
|
|
import android.app.Dialog;
|
|
import android.content.DialogInterface;
|
|
import com.mycompany.app.main.MainActivity;
|
|
public class DialogSetSort extends Dialog {
|
|
public DialogSetSort(MainActivity a, int type, DialogSetFull.DialogApplyListener l) { super(a); }
|
|
@Override public void setOnDismissListener(DialogInterface.OnDismissListener l) { super.setOnDismissListener(l); }
|
|
@Override public void dismiss() { super.dismiss(); }
|
|
}
|
|
EOF
|
|
mkdir -p "${STUBS}/com/mycompany/app/pref"
|
|
cat > "${STUBS}/com/mycompany/app/pref/PrefList.java" <<'EOF'
|
|
package com.mycompany.app.pref;
|
|
import android.content.Context;
|
|
public class PrefList {
|
|
public static int D0;
|
|
public static boolean E0;
|
|
public static PrefList r(Context c, boolean z) { return new PrefList(); }
|
|
public final void n(int i, String key) {}
|
|
public final void l(String key, boolean z) {}
|
|
public final void a() {}
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/main/MainApp.java" <<'EOF'
|
|
package com.mycompany.app.main;
|
|
public class MainApp {
|
|
public static boolean K1;
|
|
public static int E1;
|
|
public static int F1;
|
|
public static int G1;
|
|
public static int g1;
|
|
public static int h1;
|
|
public static int i1;
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/main/MainUtil.java" <<'EOF'
|
|
package com.mycompany.app.main;
|
|
import android.content.Context;
|
|
public class MainUtil {
|
|
public static float G(Context c, float v) { return v; }
|
|
public static int l1() { return 0; }
|
|
}
|
|
EOF
|
|
# AppCompatTextView already stubbed above.
|
|
cat > "${STUBS}/com/mycompany/app/view/MyDialogBottom.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.app.Dialog;
|
|
import android.content.Context;
|
|
import android.os.Handler;
|
|
import android.view.View;
|
|
public class MyDialogBottom extends Dialog {
|
|
public interface BotViewListener { void a(View view); }
|
|
public Handler i;
|
|
public boolean f18633c;
|
|
public MyDialogBottom(Context c) { super(c); this.i = new Handler(); }
|
|
public final void g(View view, BotViewListener l) {}
|
|
@Override public void dismiss() { super.dismiss(); }
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyDialogLinear.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.content.Context;
|
|
import android.widget.LinearLayout;
|
|
public class MyDialogLinear extends LinearLayout {
|
|
public MyDialogLinear(Context c) { super(c); }
|
|
public final void b() {}
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyEditText.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.content.Context;
|
|
import android.widget.EditText;
|
|
public class MyEditText extends EditText {
|
|
public MyEditText(Context c) { super(c); }
|
|
public void setElineColor(int c) {}
|
|
public void c() {}
|
|
}
|
|
EOF
|
|
cat > "${STUBS}/com/mycompany/app/view/MyLineText.java" <<'EOF'
|
|
package com.mycompany.app.view;
|
|
import android.content.Context;
|
|
import android.widget.TextView;
|
|
public class MyLineText extends TextView {
|
|
public MyLineText(Context c) { super(c); }
|
|
public void setLinePad(int p) {}
|
|
public void setLineUp(boolean z) {}
|
|
public void setFilterColor(int c) {}
|
|
public void u() {}
|
|
}
|
|
EOF
|
|
mkdir -p "${STAGE}/stubclasses"
|
|
javac --release 11 -classpath "${ANDROID_JAR}" -d "${STAGE}/stubclasses" \
|
|
"${STUBS}/androidx/recyclerview/widget/RecyclerView.java" \
|
|
"${STUBS}/androidx/appcompat/widget/AppCompatTextView.java" \
|
|
"${STUBS}/androidx/core/widget/NestedScrollView.java" \
|
|
"${STUBS}/com/mycompany/app/soulbrowser/R.java" \
|
|
"${STUBS}/com/mycompany/app/main/MainApp.java" \
|
|
"${STUBS}/com/mycompany/app/main/MainUtil.java" \
|
|
"${STUBS}/com/mycompany/app/main/MainActivity.java" \
|
|
"${STUBS}/com/mycompany/app/setting/CastActivity.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyButtonImage.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyButtonCheck.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyRoundItem.java" \
|
|
"${STUBS}/com/mycompany/app/view/MySwitchView.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyRecyclerView.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyManagerLinear.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyMainRelative.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyPopupAdapter.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyPopupMenu.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyDialogBottom.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyDialogLinear.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyEditText.java" \
|
|
"${STUBS}/com/mycompany/app/view/MyLineText.java" \
|
|
"${STUBS}/com/mycompany/app/dialog/DialogSetFull.java" \
|
|
"${STUBS}/com/mycompany/app/dialog/DialogSetMsg.java" \
|
|
"${STUBS}/com/mycompany/app/dialog/DialogSetSort.java" \
|
|
"${STUBS}/com/mycompany/app/pref/PrefList.java" \
|
|
"${STUBS}/com/mycompany/app/setting/SettingListAdapter.java" \
|
|
"${STUBS}/com/mycompany/app/setting/SettingActivity.java"
|
|
jar cf "${STAGE}/stub.jar" -C "${STAGE}/stubclasses" .
|
|
CP="${STAGE}/stub.jar:${CP}"
|
|
|
|
echo "Compiling RedirectorHelper + RedirectorActivity + RedirectorListAdapter + DialogEditRedirect..."
|
|
mkdir -p "${STAGE}/classes"
|
|
javac --release 11 -classpath "${CP}" -d "${STAGE}/classes" \
|
|
"${SRC_HELPER}" "${SRC_PREFS}" "${SRC_ADAPTER}" "${SRC_ACTIVITY}" "${SRC_DIALOG}"
|
|
mkdir -p "${STAGE}/dex"
|
|
mapfile -t CLASS_FILES < <(find "${STAGE}/classes" -name '*.class' -type f)
|
|
"${D8}" --min-api 23 --lib "${ANDROID_JAR}" --output "${STAGE}/dex" "${CLASS_FILES[@]}"
|
|
bash "${DEX_TOOLS_DIR}/d2j-baksmali.sh" -f -o "${STAGE}/out" "${STAGE}/dex/classes.dex"
|
|
find "${STAGE}/out" -name '*.smali' -print0 | xargs -0 sed -i \
|
|
-e 's/\\u002d\$\$Nest\$/\-$$Nest$/g' \
|
|
-e 's|Lcom/mycompany/app/soulbrowser/R\$|Lnet/kaki87/soul2/testing/R\$|g'
|
|
|
|
# Fix invalid const/high16 from baksmali (imm16 << 16) -> const with full value.
|
|
python3 - <<PY
|
|
from pathlib import Path
|
|
import re
|
|
root = Path("${STAGE}/out")
|
|
pat = re.compile(r'const/high16\s+(\w+),\s*(-?0x[0-9a-fA-F]+|-?\d+)')
|
|
for p in root.rglob('*.smali'):
|
|
t = p.read_text()
|
|
def repl(m):
|
|
reg, val = m.group(1), m.group(2)
|
|
num = int(val, 16) if '0x' in val.lower() else int(val)
|
|
full = (num & 0xFFFF) << 16
|
|
if full >= 0x80000000:
|
|
full -= 0x100000000
|
|
return f'const {reg}, {full}'
|
|
n, c = pat.subn(repl, t)
|
|
if c:
|
|
p.write_text(n)
|
|
print('fixed const/high16 in', p.name, c)
|
|
PY
|
|
|
|
rm -f "${OUT_WEB}"/RedirectorHelper*.smali
|
|
rm -f "${OUT_SETTING}"/RedirectorActivity*.smali
|
|
rm -f "${OUT_SETTING}"/RedirectorListAdapter*.smali
|
|
rm -f "${OUT_SETTING}"/RedirectorListPrefs*.smali
|
|
rm -f "${OUT_DIALOG}"/DialogEditRedirect*.smali
|
|
cp -a "${STAGE}/out/com/mycompany/app/web/RedirectorHelper"* "${OUT_WEB}/"
|
|
cp -a "${STAGE}/out/com/mycompany/app/setting/RedirectorActivity"* "${OUT_SETTING}/"
|
|
cp -a "${STAGE}/out/com/mycompany/app/setting/RedirectorListAdapter"* "${OUT_SETTING}/"
|
|
cp -a "${STAGE}/out/com/mycompany/app/setting/RedirectorListPrefs"* "${OUT_SETTING}/"
|
|
mkdir -p "${OUT_DIALOG}"
|
|
cp -a "${STAGE}/out/com/mycompany/app/dialog/DialogEditRedirect"* "${OUT_DIALOG}/"
|
|
|
|
# Stub classpath produces a few wrong method refs; rewrite to match shipped smali.
|
|
OUT_SETTING_ESC="${OUT_SETTING}" OUT_DIALOG_ESC="${OUT_DIALOG}" python3 - <<'PY'
|
|
from pathlib import Path
|
|
import os
|
|
root = Path(os.environ["OUT_SETTING_ESC"])
|
|
for p in list(root.glob("RedirectorActivity*.smali")) + list(root.glob("RedirectorListAdapter*.smali")):
|
|
t = p.read_text()
|
|
orig = t
|
|
t = t.replace(
|
|
"invoke-direct { p0 }, Lcom/mycompany/app/setting/SettingActivity;-><init>()V",
|
|
"invoke-direct { p0 }, Landroidx/appcompat/app/AppCompatActivity;-><init>()V",
|
|
)
|
|
t = t.replace(
|
|
"invoke-virtual { p0, p1, v0, v0 }, Lcom/mycompany/app/setting/RedirectorActivity;->F0(IZZ)V",
|
|
"invoke-virtual { p0, p1, v0, v0 }, Lcom/mycompany/app/setting/SettingActivity;->F0(IZZ)V",
|
|
)
|
|
t = t.replace(
|
|
"invoke-super { p0, p1, p2, p3 }, Lcom/mycompany/app/setting/SettingActivity;->onActivityResult(IILandroid/content/Intent;)V",
|
|
"invoke-super { p0, p1, p2, p3 }, Landroidx/fragment/app/FragmentActivity;->onActivityResult(IILandroid/content/Intent;)V",
|
|
)
|
|
t = t.replace(
|
|
"invoke-super { p0 }, Lcom/mycompany/app/setting/SettingActivity;->onBackPressed()V",
|
|
"invoke-super { p0 }, Lcom/mycompany/app/main/MainActivity;->onBackPressed()V",
|
|
)
|
|
t = t.replace(
|
|
"invoke-virtual { v2, v3 }, Lcom/mycompany/app/dialog/DialogSetSort;->setOnDismissListener(Landroid/content/DialogInterface$OnDismissListener;)V",
|
|
"invoke-virtual { v2, v3 }, Lcom/mycompany/app/view/MyDialogBottom;->setOnDismissListener(Landroid/content/DialogInterface$OnDismissListener;)V",
|
|
)
|
|
t = t.replace(
|
|
"invoke-virtual { v0, v1 }, Lcom/mycompany/app/dialog/DialogSetSort;->setOnDismissListener(Landroid/content/DialogInterface$OnDismissListener;)V",
|
|
"invoke-virtual { v0, v1 }, Lcom/mycompany/app/view/MyDialogBottom;->setOnDismissListener(Landroid/content/DialogInterface$OnDismissListener;)V",
|
|
)
|
|
t = t.replace(
|
|
"Lcom/mycompany/app/dialog/DialogSetMsg;->setOnDismissListener(Landroid/content/DialogInterface$OnDismissListener;)V",
|
|
"Lcom/mycompany/app/view/MyDialogBottom;->setOnDismissListener(Landroid/content/DialogInterface$OnDismissListener;)V",
|
|
)
|
|
# Stub Dialog.dismiss → typed dismiss (overrides on MyDialogBottom subclasses).
|
|
import re
|
|
t = re.sub(
|
|
r"(iget-object (v\d+), [^\n]+->mDeleteAllDialog:Lcom/mycompany/app/dialog/DialogSetMsg;\n(?:.*\n){0,6}?)invoke-virtual \{ \2 \}, Landroid/app/Dialog;->dismiss\(\)V",
|
|
r"\1invoke-virtual { \2 }, Lcom/mycompany/app/dialog/DialogSetMsg;->dismiss()V",
|
|
t,
|
|
)
|
|
t = re.sub(
|
|
r"(iget-object (v\d+), [^\n]+->mSortDialog:Lcom/mycompany/app/dialog/DialogSetSort;\n(?:.*\n){0,6}?)invoke-virtual \{ \2 \}, Landroid/app/Dialog;->dismiss\(\)V",
|
|
r"\1invoke-virtual { \2 }, Lcom/mycompany/app/dialog/DialogSetSort;->dismiss()V",
|
|
t,
|
|
)
|
|
t = t.replace(
|
|
"invoke-virtual { v1, v0 }, Lcom/mycompany/app/view/MyRecyclerView;->setAdapter(Ljava/lang/Object;)V",
|
|
"invoke-virtual { v1, v0 }, Landroidx/recyclerview/widget/RecyclerView;->setAdapter(Landroidx/recyclerview/widget/RecyclerView$Adapter;)V",
|
|
)
|
|
t = t.replace(
|
|
"invoke-virtual { v0, v1 }, Lcom/mycompany/app/view/MyRecyclerView;->setAdapter(Ljava/lang/Object;)V",
|
|
"invoke-virtual { v0, v1 }, Landroidx/recyclerview/widget/RecyclerView;->setAdapter(Landroidx/recyclerview/widget/RecyclerView$Adapter;)V",
|
|
)
|
|
t = t.replace(
|
|
"invoke-virtual { v0, v1 }, Lcom/mycompany/app/view/MyRecyclerView;->setAdapter(Landroidx/recyclerview/widget/RecyclerView$Adapter;)V",
|
|
"invoke-virtual { v0, v1 }, Landroidx/recyclerview/widget/RecyclerView;->setAdapter(Landroidx/recyclerview/widget/RecyclerView$Adapter;)V",
|
|
)
|
|
# Soul ships an R8-obfuscated AndroidX RecyclerView (see SettingListAdapter).
|
|
# Map standard Adapter/ViewHolder API names to the obfuscated ones.
|
|
t = t.replace(
|
|
".method public getItemCount()I",
|
|
".method public d()I",
|
|
)
|
|
t = t.replace(
|
|
".method public getItemViewType(I)I",
|
|
".method public f(I)I",
|
|
)
|
|
t = t.replace(
|
|
".method public bridge synthetic onBindViewHolder(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)V",
|
|
".method public bridge synthetic n(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)V",
|
|
)
|
|
t = t.replace(
|
|
".method public bridge synthetic onCreateViewHolder(Landroid/view/ViewGroup;I)Landroidx/recyclerview/widget/RecyclerView$ViewHolder;",
|
|
".method public bridge synthetic o(Landroid/view/ViewGroup;I)Landroidx/recyclerview/widget/RecyclerView$ViewHolder;",
|
|
)
|
|
t = t.replace(
|
|
"invoke-virtual { p0 }, Lcom/mycompany/app/setting/RedirectorListAdapter;->notifyDataSetChanged()V",
|
|
"invoke-virtual { p0 }, Landroidx/recyclerview/widget/RecyclerView$Adapter;->g()V",
|
|
)
|
|
t = t.replace(
|
|
"invoke-virtual { p1 }, Lcom/mycompany/app/setting/RedirectorListAdapter$Holder;->getAdapterPosition()I",
|
|
"invoke-virtual { p1 }, Landroidx/recyclerview/widget/RecyclerView$ViewHolder;->d()I",
|
|
)
|
|
t = t.replace(
|
|
"invoke-virtual { v0 }, Lcom/mycompany/app/setting/RedirectorListAdapter$Holder;->getAdapterPosition()I",
|
|
"invoke-virtual { v0 }, Landroidx/recyclerview/widget/RecyclerView$ViewHolder;->d()I",
|
|
)
|
|
if t != orig:
|
|
p.write_text(t)
|
|
print("patched", p.name)
|
|
dialog_root = Path(os.environ["OUT_DIALOG_ESC"])
|
|
for p in dialog_root.glob("DialogEditRedirect*.smali"):
|
|
t = p.read_text()
|
|
orig = t
|
|
# JADX name f18633c is field `c` in shipped MyDialogBottom smali.
|
|
t = t.replace(
|
|
"iput-boolean v0, p0, Lcom/mycompany/app/dialog/DialogEditRedirect;->f18633c:Z",
|
|
"iput-boolean v0, p0, Lcom/mycompany/app/view/MyDialogBottom;->c:Z",
|
|
)
|
|
t = t.replace(
|
|
"iput-boolean v0, p0, Lcom/mycompany/app/view/MyDialogBottom;->f18633c:Z",
|
|
"iput-boolean v0, p0, Lcom/mycompany/app/view/MyDialogBottom;->c:Z",
|
|
)
|
|
t = t.replace(
|
|
"invoke-virtual { p0, v0, v1 }, Lcom/mycompany/app/dialog/DialogEditRedirect;->g(Landroid/view/View;Lcom/mycompany/app/view/MyDialogBottom$BotViewListener;)V",
|
|
"invoke-virtual { p0, v0, v1 }, Lcom/mycompany/app/view/MyDialogBottom;->g(Landroid/view/View;Lcom/mycompany/app/view/MyDialogBottom$BotViewListener;)V",
|
|
)
|
|
t = t.replace(
|
|
"Lcom/mycompany/app/dialog/DialogEditRedirect;->i:Landroid/os/Handler;",
|
|
"Lcom/mycompany/app/view/MyDialogBottom;->i:Landroid/os/Handler;",
|
|
)
|
|
t = t.replace(
|
|
"Lcom/mycompany/app/view/MyEditText;->setTextCursorDrawable(I)V",
|
|
"Landroid/widget/EditText;->setTextCursorDrawable(I)V",
|
|
)
|
|
if t != orig:
|
|
p.write_text(t)
|
|
print("patched", p.name)
|
|
PY
|
|
|
|
echo "Wrote ${OUT_WEB}/RedirectorHelper*.smali"
|
|
echo "Wrote ${OUT_SETTING}/RedirectorActivity*.smali"
|
|
echo "Wrote ${OUT_SETTING}/RedirectorListAdapter*.smali"
|
|
echo "Wrote ${OUT_DIALOG}/DialogEditRedirect*.smali"
|
|
ls "${OUT_SETTING}"/RedirectorActivity*.smali "${OUT_SETTING}"/RedirectorListAdapter*.smali | wc -l
|
|
ls "${OUT_DIALOG}"/DialogEditRedirect*.smali | wc -l
|