LihonT 2 Luna beta: source release

Mod for TikTok 47.1.3 (arm64) in Material 3 style: bottom bar, profile menu, TikTok settings and
comments; downloads without the watermark; switches for ads, live streams and the Shop tab; badges of
other mods next to names and the niche mod badge (members service); app icons; mod settings screen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
cheb0chik
2026-10-22 20:32:13 +03:00
commit ce1d870d54
121 changed files with 21570 additions and 0 deletions

203
tests/test_profile_flags.py Normal file
View File

@@ -0,0 +1,203 @@
"""Native flag reads are routed through LihonTFlags, and LihonTFlags obeys the settings.
The smali rewriter is exercised on small synthetic methods. The runtime class is
compiled and run on a real JVM against the production settings classes; Android
itself is only on the classpath, as in test_settings_model.
"""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
from lihont import profile_features
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "src" / "lihont" / "two"
HARNESS = ROOT / "tests" / "java" / "lihont" / "two" / "FlagsTest.java"
HOOK = "Llihont/two/LihonTFlags;"
ALL_FLAGS = {**profile_features.PROFILE_FLAGS, **profile_features.REPOST_FLAGS}
INT_READ = """\
.class public Lcom/example/Reader;
.super Ljava/lang/Object;
.method public static read()I
.registers 3
const-string v0, "profile_left_align"
const/4 v1, 0x0
invoke-static {v0, v1}, Lcom/bytedance/ies/abmock/SettingsManager;->LIZ(Ljava/lang/String;I)I
move-result v2
return v2
.end method
"""
BOOL_READ = INT_READ.replace("(Ljava/lang/String;I)I", "(Ljava/lang/String;Z)Z").replace(
"read()I", "read()Z")
PARAMETER_READ = """\
.class public Lcom/example/Reader;
.super Ljava/lang/Object;
.method public run(I)I
.registers 5
const-string v0, "repost_support_digg"
invoke-static {v0, p1}, LX/0abc;->LIZ(Ljava/lang/String;I)I
move-result p1
return p1
.end method
"""
LEGACY = """\
.class public Lcom/example/Reader;
.super Ljava/lang/Object;
.method public static read()I
.registers 3
const-string v0, "profile_left_align"
const/4 v1, 0x0
invoke-static {v0, v1}, Lcom/bytedance/ies/abmock/SettingsManager;->LIZ(Ljava/lang/String;I)I
move-result v2
# lihont feature flag: profile_left_align
const/16 v2, 0x1
return v2
.end method
"""
class RewriteTest(unittest.TestCase):
def test_int_read_goes_through_the_hook(self):
text, hits = profile_features.rewrite_smali(INT_READ, ALL_FLAGS)
self.assertEqual(hits, {"profile_left_align": 1})
self.assertIn(
" move-result v2\n"
" # lihont feature flag: profile_left_align\n"
" invoke-static/range {v2 .. v2}, %s->profile_left_align(I)I\n"
" move-result v2\n" % HOOK, text)
self.assertNotIn("const/16", text)
self.assertIn("SettingsManager;->LIZ(Ljava/lang/String;I)I", text)
self.assertIn(" return v2\n", text)
def test_boolean_read_uses_the_boolean_overload(self):
text, hits = profile_features.rewrite_smali(BOOL_READ, ALL_FLAGS)
self.assertEqual(hits, {"profile_left_align": 1})
self.assertIn("%s->profile_left_align(Z)Z" % HOOK, text)
def test_parameter_register_is_kept(self):
text, hits = profile_features.rewrite_smali(PARAMETER_READ, ALL_FLAGS)
self.assertEqual(hits, {"repost_support_digg": 1})
self.assertIn("invoke-static/range {p1 .. p1}, %s->repost_support_digg(I)I" % HOOK, text)
self.assertIn(" move-result p1\n\n return p1\n", text)
def test_unknown_keys_are_left_alone(self):
text, hits = profile_features.rewrite_smali(
INT_READ.replace("profile_left_align", "some_other_flag"), ALL_FLAGS)
self.assertEqual(hits, {})
self.assertNotIn("LihonTFlags", text)
def test_rewriting_twice_changes_nothing(self):
once, _ = profile_features.rewrite_smali(INT_READ, ALL_FLAGS)
twice, hits = profile_features.rewrite_smali(once, ALL_FLAGS)
self.assertEqual(hits, {})
self.assertEqual(once, twice)
def test_old_const_override_is_replaced(self):
text, hits = profile_features.rewrite_smali(LEGACY, ALL_FLAGS)
self.assertEqual(hits, {"profile_left_align": 1})
self.assertNotIn("const/16", text)
self.assertEqual(text.count("# lihont feature flag"), 1)
self.assertIn("(I)I\n move-result v2\n\n return v2\n", text)
def test_marker_without_a_known_block_does_not_eat_code(self):
stray = INT_READ.replace(
" move-result v2\n",
" move-result v2\n # lihont feature flag: profile_left_align\n")
text, hits = profile_features.rewrite_smali(stray, ALL_FLAGS)
self.assertEqual(hits, {"profile_left_align": 1})
self.assertIn(" return v2\n", text)
class RuntimeTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.javac = shutil.which("javac")
cls.java = shutil.which("java")
if not cls.javac or not cls.java:
raise RuntimeError("Flag runtime tests require a JDK with java and javac")
cls.android_jar = ROOT / "tools" / "android.jar"
if not cls.android_jar.is_file():
raise RuntimeError("tools/android.jar must be available")
cls.temporary = tempfile.TemporaryDirectory(prefix="lihont-flags-tests-")
cls.addClassCleanup(cls.temporary.cleanup)
cls.classes = Path(cls.temporary.name) / "classes"
cls.classes.mkdir()
cls.environment = dict(os.environ)
cls.environment.pop("JAVA_TOOL_OPTIONS", None)
settings = SRC / "settings"
cls.run_command([
cls.javac, "--release", "17", "-encoding", "UTF-8",
"-cp", str(cls.android_jar), "-d", str(cls.classes),
str(settings / "ModSettings.java"), str(settings / "SettingsCodec.java"),
str(settings / "SettingsRepository.java"), str(settings / "AndroidSettingsStorage.java"),
str(settings / "SettingsHost.java"), str(SRC / "LihonTFlags.java"), str(HARNESS),
])
@classmethod
def run_command(cls, command):
result = subprocess.run(
command, capture_output=True, text=True, encoding="utf-8",
env=cls.environment, timeout=60,
)
if result.returncode:
raise AssertionError(
f"Command failed with exit {result.returncode}: {command[0]}\n"
+ result.stdout + result.stderr
)
return result
@staticmethod
def entries():
return (["banners:%s:%d" % item for item in profile_features.PROFILE_FLAGS.items()]
+ ["reposts:%s:%d" % item for item in profile_features.REPOST_FLAGS.items()])
def run_scenario(self, name):
result = self.run_command([
self.java, "-cp", str(self.classes) + os.pathsep + str(self.android_jar),
"lihont.two.FlagsTest", name,
] + self.entries())
return result.stdout.strip()
def test_each_switch_controls_only_its_own_flags(self):
self.assertEqual(self.run_scenario("matrix"), "PASS matrix")
def test_saved_change_applies_to_the_next_process(self):
self.assertEqual(self.run_scenario("snapshot"), "PASS snapshot")
def test_defaults_apply_while_settings_cannot_be_read(self):
self.assertEqual(self.run_scenario("unavailable"), "PASS unavailable")
def test_runtime_offers_exactly_what_the_patcher_calls(self):
offered = set(self.run_scenario("surface").splitlines())
called = {target.split("->", 1)[1] for target in profile_features.runtime_targets()}
self.assertEqual(offered, called)
if __name__ == "__main__":
unittest.main()