* Add desktop Viewport width slider under Tab/Site/Default UA. When the effective user agent is Desktop, DialogSetDesk shows a slider (Default … 960–1920) that sets width on an existing meta viewport tag. Co-authored-by: Cursor <cursoragent@cursor.com> * Keep the viewport width row without bottom corner radii. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
233 lines
9.2 KiB
Bash
Executable file
233 lines
9.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
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)"
|
|
D2J="$(ls -1 "${ROOT_DIR}/tools"/dex-tools-*/d2j-dex2jar.sh 2>/dev/null | head -1)"
|
|
BAKSMALI_JAR="$(ls -1 "${ROOT_DIR}/tools"/baksmali*.jar 2>/dev/null | head -1)"
|
|
|
|
if [[ ! -f "${ANDROID_JAR}" || -z "${D8}" || ! -f "${APK}" || ! -f "${D2J}" || ! -f "${BAKSMALI_JAR}" ]]; then
|
|
echo "missing tools: jar=$ANDROID_JAR d8=$D8 apk=$APK d2j=$D2J bak=$BAKSMALI_JAR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
STAGE="$(mktemp -d)"
|
|
trap 'rm -rf "${STAGE}"' EXIT
|
|
|
|
echo "Building classpath..."
|
|
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
|
|
|
|
# Ensure sources have Desktop + WebTabItem fields
|
|
python3 - <<PY
|
|
from pathlib import Path
|
|
p = Path("${ROOT_DIR}/sources/java/com/mycompany/app/web/WebTabAdapter.java")
|
|
t = p.read_text()
|
|
if "public int s;" not in t:
|
|
t = t.replace(
|
|
" /** Per-tab homepage URL (DbBookTab3_table._rsv1). */\n public String r;\n }",
|
|
" /** Per-tab homepage URL (DbBookTab3_table._rsv1). */\n public String r;\n"
|
|
" /** Per-tab UA type (-1 Default). */\n public int s = -1;\n"
|
|
" /** Per-tab custom UA text. */\n public String t;\n }")
|
|
p.write_text(t)
|
|
p = Path("${ROOT_DIR}/sources/java/com/mycompany/app/main/MainConst.java")
|
|
t = p.read_text()
|
|
t2 = t.replace('G = new String[]{"Soul", "Android", "iPhone", "iPad"};',
|
|
'G = new String[]{"Soul", "Android", "iPhone", "iPad", "Desktop"};')
|
|
if t2 != t: p.write_text(t2)
|
|
PY
|
|
|
|
# Stubs only for WebTabItem.s/t (ahead of APK classpath). R comes from the APK jar.
|
|
# Stub WebNestFrame so WebTabItem.p keeps the real field type (Object breaks iget at runtime).
|
|
mkdir -p "${STAGE}/stub/com/mycompany/app/web"
|
|
cat > "${STAGE}/stub/com/mycompany/app/web/WebNestFrame.java" << 'JAVA'
|
|
package com.mycompany.app.web;
|
|
public class WebNestFrame {
|
|
public WebTabAdapter.WebTabItem m;
|
|
public WebNestView getPageValid() { return null; }
|
|
}
|
|
JAVA
|
|
cat > "${STAGE}/stub/com/mycompany/app/web/WebNestView.java" << 'JAVA'
|
|
package com.mycompany.app.web;
|
|
import android.content.Context;
|
|
import android.view.ViewParent;
|
|
import android.webkit.WebSettings;
|
|
public class WebNestView {
|
|
public int M;
|
|
public WebNestFrame F2;
|
|
public String getUrl() { return null; }
|
|
public void setDeskMode(boolean desk) {}
|
|
public WebSettings getSettings() { return null; }
|
|
public boolean K(Context context, int type, WebSettings settings) { return false; }
|
|
public ViewParent getParent() { return null; }
|
|
public Context getContext() { return null; }
|
|
}
|
|
JAVA
|
|
mkdir -p "${STAGE}/stub/com/mycompany/app/web"
|
|
cat > "${STAGE}/stub/com/mycompany/app/web/WebViewActivity.java" << 'JAVA'
|
|
package com.mycompany.app.web;
|
|
import java.util.List;
|
|
public class WebViewActivity extends android.app.Activity {
|
|
public android.content.Context i1;
|
|
public java.util.List O2;
|
|
public int P2;
|
|
public WebNestView I2;
|
|
public WebTabAdapter.WebTabItem n3(int i) { return null; }
|
|
}
|
|
JAVA
|
|
cat > "${STAGE}/stub/com/mycompany/app/web/WebTabAdapter.java" << 'JAVA'
|
|
package com.mycompany.app.web;
|
|
import java.util.List;
|
|
public class WebTabAdapter {
|
|
public static class WebTabItem {
|
|
public long c;
|
|
public String j;
|
|
public String k;
|
|
public boolean l;
|
|
public String r;
|
|
public int s;
|
|
public String t;
|
|
public WebNestFrame p;
|
|
public List q;
|
|
}
|
|
}
|
|
JAVA
|
|
# ChildItem has no declared ctor in decompile; provide one so AgentOverflow compiles.
|
|
mkdir -p "${STAGE}/stub/com/mycompany/app/main"
|
|
cat > "${STAGE}/stub/com/mycompany/app/main/MainItem.java" << 'JAVA'
|
|
package com.mycompany.app.main;
|
|
public class MainItem {
|
|
public static class ChildItem {
|
|
public ChildItem() {}
|
|
public String g;
|
|
public String h;
|
|
}
|
|
}
|
|
JAVA
|
|
# PrefZtwo.f0 (and fields used by UA helpers) ahead of the APK jar classpath.
|
|
mkdir -p "${STAGE}/stub/com/mycompany/app/pref"
|
|
cat > "${STAGE}/stub/com/mycompany/app/pref/PrefCore.java" << 'JAVA'
|
|
package com.mycompany.app.pref;
|
|
import android.content.Context;
|
|
public class PrefCore {
|
|
public PrefCore(Context context, String name) {}
|
|
}
|
|
JAVA
|
|
cat > "${STAGE}/stub/com/mycompany/app/pref/PrefZtwo.java" << 'JAVA'
|
|
package com.mycompany.app.pref;
|
|
import android.content.Context;
|
|
public class PrefZtwo extends PrefCore {
|
|
public static String n;
|
|
public static String o;
|
|
public static int p;
|
|
public static String q;
|
|
public static int f0;
|
|
public PrefZtwo(Context context) { super(context, "PrefZtwo"); }
|
|
public static PrefZtwo r(Context context, boolean z) { return new PrefZtwo(context); }
|
|
public static void t(Context context) {}
|
|
public void n(int value, String key) {}
|
|
public void a() {}
|
|
}
|
|
JAVA
|
|
mkdir -p "${STAGE}/stubclasses"
|
|
javac --release 11 -classpath "${CP}" -d "${STAGE}/stubclasses" \
|
|
"${STAGE}/stub/com/mycompany/app/web/WebTabAdapter.java" \
|
|
"${STAGE}/stub/com/mycompany/app/web/WebNestFrame.java" \
|
|
"${STAGE}/stub/com/mycompany/app/web/WebNestView.java" \
|
|
"${STAGE}/stub/com/mycompany/app/web/WebViewActivity.java" \
|
|
"${STAGE}/stub/com/mycompany/app/main/MainItem.java" \
|
|
"${STAGE}/stub/com/mycompany/app/pref/PrefCore.java" \
|
|
"${STAGE}/stub/com/mycompany/app/pref/PrefZtwo.java"
|
|
jar cf "${STAGE}/stub.jar" -C "${STAGE}/stubclasses" .
|
|
CP="${STAGE}/stub.jar:${CP}"
|
|
|
|
mkdir -p "${STAGE}/classes"
|
|
echo "Compiling feature classes..."
|
|
javac --release 11 -classpath "${CP}" -d "${STAGE}/classes" \
|
|
"${ROOT_DIR}/sources/java/com/mycompany/app/db/book/DbBookUa.java" \
|
|
"${ROOT_DIR}/sources/java/com/mycompany/app/main/UserAgentHelper.java" \
|
|
"${ROOT_DIR}/sources/java/com/mycompany/app/main/AgentOverflow.java" \
|
|
"${ROOT_DIR}/sources/java/com/mycompany/app/dialog/DialogSetDesk.java"
|
|
|
|
echo "Dexing..."
|
|
mkdir -p "${STAGE}/out"
|
|
mapfile -t CLASSFILES < <(find "${STAGE}/classes" -name '*.class')
|
|
"${D8}" --release --lib "${ANDROID_JAR}" --output "${STAGE}/out" "${CLASSFILES[@]}"
|
|
|
|
echo "Baksmali..."
|
|
D2J_BAK="$(ls -1 "${ROOT_DIR}/tools"/dex-tools-*/d2j-baksmali.sh 2>/dev/null | head -1)"
|
|
if [[ -x "${D2J_BAK}" ]]; then
|
|
bash "${D2J_BAK}" -f -o "${STAGE}/smali" "${STAGE}/out/classes.dex"
|
|
else
|
|
java -cp "${BAKSMALI_JAR}" org.jf.baksmali.Main d "${STAGE}/out/classes.dex" -o "${STAGE}/smali"
|
|
fi
|
|
|
|
# Fix invalid const/high16 from baksmali (imm16 << 16) -> const with full value.
|
|
python3 - <<PY
|
|
from pathlib import Path
|
|
import re
|
|
root = Path("${STAGE}/smali")
|
|
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, c)
|
|
PY
|
|
|
|
rm -f "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/dialog/DialogSetDesk"*.smali
|
|
rm -f "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/main/AgentOverflow"*.smali
|
|
mkdir -p "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/db/book"
|
|
mkdir -p "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/main"
|
|
mkdir -p "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/dialog"
|
|
cp -a "${STAGE}/smali/com/mycompany/app/db/book/DbBookUa"* "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/db/book/"
|
|
cp -a "${STAGE}/smali/com/mycompany/app/main/UserAgentHelper"* "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/main/"
|
|
cp -a "${STAGE}/smali/com/mycompany/app/main/AgentOverflow"* "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/main/"
|
|
cp -a "${STAGE}/smali/com/mycompany/app/dialog/DialogSetDesk"* "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/dialog/"
|
|
|
|
# ChildItem has no declared <init>; javac emits ChildItem.<init> which VerifyErrors.
|
|
# onClick needs .registers 4 so instance-of does not clobber p0.
|
|
python3 - <<PY
|
|
from pathlib import Path
|
|
import re
|
|
p = Path("${ROOT_DIR}/app/smali_classes3/com/mycompany/app/main/AgentOverflow.smali")
|
|
t = p.read_text()
|
|
t2 = t.replace(
|
|
"invoke-direct { v1 }, Lcom/mycompany/app/main/MainItem\$ChildItem;-><init>()V",
|
|
"invoke-direct { v1 }, Ljava/lang/Object;-><init>()V",
|
|
)
|
|
# Widen onClick locals if still too narrow
|
|
t2 = re.sub(
|
|
r'(\.method public final onClick\(Landroid/view/View;\)V\n) \.registers 3\n',
|
|
r'\1 .registers 4\n',
|
|
t2,
|
|
count=1,
|
|
)
|
|
if t2 != t:
|
|
p.write_text(t2)
|
|
print("patched AgentOverflow ChildItem/onClick")
|
|
PY
|
|
|
|
echo "OK:"
|
|
ls "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/dialog/DialogSetDesk"* | wc -l
|
|
ls "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/main/UserAgentHelper"*
|
|
ls "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/main/AgentOverflow"*
|
|
ls "${ROOT_DIR}/app/smali_classes3/com/mycompany/app/db/book/DbBookUa"*
|