soul-browser/scripts/compile-ua-feature.sh
KaKi87 f3b159999a
Add desktop Viewport width slider (#132)
* 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>
2026-10-06 16:27:17 +02:00

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"*