* Add search to the userscript list and the Redirect rules The userscript list dialog gets a search button in its header that uses the same search bar as the other book lists. Redirect rules get a search button next to Delete all that swaps the title for a text field and filters rules by description, include and exclude patterns, redirect URL and example URL. Back closes the search first. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Keep the search inline and fix the Redirect rules crash Redirect rules crashed on open because R$string has no search_menu_title field; use search_url, which exists. In the userscript list the search bar now stops at the header buttons instead of covering the whole header. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Filter the userscript list in place and polish both search bars In the userscript list, search mode now reads the same script list as the normal view and filters it by name, instead of an empty search-result store. The input shows the Search placeholder. In Redirect rules the input has a rounded background and the back chevron cancels the search before leaving. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Match the Redirect rules search bar to the userscript one The bar now has the back chevron, input and a clear button on one rounded background with the same height and inset as the userscript bar. The search button comes first among the header buttons. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Keep filtered scripts out of the shared script cache while searching Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Fill the whole Redirect rules search bar with its children Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Hide the Redirect rules title while the search bar is open Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Move userscript Update all into the 3-dot menu The refresh button is removed from the userscript list header and the menu gets an Update all item that runs the same action. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Close the gap left by the removed refresh button in the userscript header Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
638 lines
27 KiB
Bash
Executable file
638 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 search_url = 0x7f120461;
|
|
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_cancel_black_18 = 0x7f080302;
|
|
public static int outline_cancel_dark_18 = 0x7f080304;
|
|
public static int outline_chevron_left_black_24 = 0x7f080318;
|
|
public static int outline_chevron_left_dark_24 = 0x7f080319;
|
|
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_search_black_20 = 0x7f080443;
|
|
public static int outline_search_dark_20 = 0x7f080445;
|
|
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 G1;
|
|
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 b1;
|
|
public static int F1;
|
|
public static int G1;
|
|
public static int g1;
|
|
public static int f1;
|
|
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
|