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

210
tests/test_feed_patch.py Normal file
View File

@@ -0,0 +1,210 @@
import unittest
from lihont import feed_patch as fp
AWEME = "Lcom/ss/android/ugc/aweme/feed/model/Aweme;"
FEED_PAGE = """\
.class public Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;
.super Ljava/lang/Object;
.method public getItems()Ljava/util/List;
.registers 4
.annotation system Ldalvik/annotation/Signature;
value = {
"()",
"Ljava/util/List<",
"Lcom/ss/android/ugc/aweme/feed/model/Aweme;",
">;"
}
.end annotation
.prologue
.line 1
iget-object v0, p0, Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;->items:Ljava/util/List;
return-object v0
.end method
.method public getPreloadAds()Ljava/util/List;
.registers 2
iget-object v0, p0, Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;->preloadAds:Ljava/util/List;
return-object v0
.end method
.method public isHasAd()Z
.registers 2
iget-boolean v0, p0, Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;->hasAd:Z
return v0
.end method
.method public setItems(Ljava/util/List;)V
.registers 4
iput-object p1, p0, Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;->items:Ljava/util/List;
return-void
.end method
"""
MID_AD = """\
.class public final Lcom/ss/android/ugc/aweme/commercialize/feed/assem/midad/MidAdResponse;
.super Ljava/lang/Object;
.method public final getAweme()Lcom/ss/android/ugc/aweme/feed/model/Aweme;
.registers 2
iget-object v0, p0, Lcom/ss/android/ugc/aweme/commercialize/feed/assem/midad/MidAdResponse;->aweme:Lcom/ss/android/ugc/aweme/feed/model/Aweme;
return-object v0
.end method
"""
CANDIDATES = """\
.class public final Lcom/ss/android/ugc/aweme/commercialize/feed/candidate/CandidateRequestImpl;
.super Ljava/lang/Object;
.method public final LIZ(Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
.registers 4
invoke-interface {v0}, Lcom/ss/android/ugc/aweme/commercialize/feed/candidate/CandidateAdRequestApi;->request(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)LX/12b3;
return-void
.end method
"""
TOP_VIEW = """\
.class public final Lcom/bytedance/ies/ugc/aweme/commercialize/splash/topview/TopViewJsonManager;
.super Ljava/lang/Object;
.method public final LIZIZ(Ljava/lang/String;)Lcom/ss/android/ugc/aweme/feed/model/Aweme;
.registers 5
invoke-virtual {p0, p1}, Lcom/bytedance/ies/ugc/aweme/commercialize/splash/topview/TopViewJsonManager;->LIZLLL(Ljava/lang/String;)Ljava/util/Map;
const/4 v0, 0x0
return-object v0
.end method
.method public final declared-synchronized LJ()Ljava/util/Map;
.registers 2
.annotation system Ldalvik/annotation/Signature;
value = {
"()",
"Ljava/util/Map<",
"Ljava/lang/String;",
"LX/0cWK;",
">;"
}
.end annotation
monitor-enter p0
iget-object v0, p0, Lcom/bytedance/ies/ugc/aweme/commercialize/splash/topview/TopViewJsonManager;->LJ:Ljava/util/concurrent/ConcurrentHashMap;
monitor-exit p0
return-object v0
.end method
"""
class HelperTest(unittest.TestCase):
def test_parameter_registers_count_this_and_wide_arguments(self):
self.assertEqual(1, fp._parameter_registers("public final x()V"))
self.assertEqual(0, fp._parameter_registers("public static x()V"))
self.assertEqual(4, fp._parameter_registers("public x(IJ)V"))
self.assertEqual(4, fp._parameter_registers("public x(Ljava/lang/String;[ILjava/util/List;)V"))
self.assertEqual(3, fp._parameter_registers("public static x(DZ)V"))
def test_a_local_is_made_when_the_method_has_none(self):
self.assertIn(".registers 3", fp._with_local("public x(I)V", " .registers 2\n return-void\n"))
self.assertIn(".registers 4", fp._with_local("public x(I)V", " .registers 4\n return-void\n"))
def test_the_first_instruction_comes_after_annotations_and_debug_lines(self):
body = (" .registers 4\n .annotation system Lx/Y;\n value = {\n }\n .end annotation\n\n"
" .prologue\n .line 3\n const/4 v0, 0x1\n")
self.assertEqual(" const/4 v0, 0x1\n", body[fp._first_instruction(body):])
class RewriteTest(unittest.TestCase):
def test_the_feed_page_is_cleaned_when_read_or_written(self):
patched, hits = fp.rewrite_feed_page(FEED_PAGE)
self.assertEqual({"items_read", "items_write", "preload_read", "ad_flag"}, set(hits))
self.assertEqual(4, patched.count(fp.MARKER))
self.assertEqual(4, patched.count(fp.CLEAN))
self.assertEqual(1, patched.count(fp.FILTER))
# the call goes after the annotation block and before the method's own code
items = patched.split(".method public getItems()")[1].split(".end method")[0]
self.assertLess(items.index(".end annotation"), items.index(fp.CLEAN))
self.assertLess(items.index(fp.CLEAN), items.index("iget-object v0"))
self.assertEqual(FEED_PAGE.count(".method "), patched.count(".method "))
def test_the_filtered_list_replaces_the_argument_of_set_items(self):
patched, _ = fp.rewrite_feed_page(FEED_PAGE)
setter = patched.split(".method public setItems(")[1]
self.assertLess(setter.index("move-result-object p1"), setter.index("iput-object p1"))
def test_the_mid_ad_response_ends_early_while_ads_are_off(self):
patched, hits = fp.rewrite_mid_ad(MID_AD)
self.assertEqual({"mid_ad": 1}, hits)
self.assertLess(patched.index(fp.ADS), patched.index("\n :lihont_keep\n"))
self.assertIn("const/4 v0, 0x0\n\n return-object v0", patched)
self.assertIn("iget-object v0, p0, Lcom/ss/android/ugc/aweme/commercialize/feed/assem/midad/MidAdResponse;->aweme", patched)
def test_the_candidate_request_is_not_sent_while_ads_are_off(self):
patched, hits = fp.rewrite_candidates(CANDIDATES)
self.assertEqual({"candidate_request": 1}, hits)
label = "\n :lihont_keep\n"
self.assertLess(patched.index("return-void"), patched.index(label))
self.assertLess(patched.index(label), patched.index("CandidateAdRequestApi"))
def test_the_top_view_store_is_empty_while_ads_are_off(self):
patched, hits = fp.rewrite_top_view(TOP_VIEW)
self.assertEqual({"top_view", "top_view_store"}, set(hits))
store = patched.split("declared-synchronized LJ()")[1]
self.assertLess(store.index(fp.ADS), store.index("monitor-enter p0"))
self.assertIn("new-instance v0, Ljava/util/HashMap;", store)
# LIZIZ(String) has p0 and p1 (two registers) and five in all: v0 is already a local there
self.assertIn(".registers 5", patched)
def test_a_method_without_a_local_gets_one(self):
tight = MID_AD.replace(".registers 2", ".registers 1").replace("iget-object v0, p0,", "iget-object p0, p0,").replace("return-object v0", "return-object p0")
patched, _ = fp.rewrite_mid_ad(tight)
self.assertIn(".registers 2", patched)
def test_a_second_pass_is_refused(self):
for rewrite, text in ((fp.rewrite_feed_page, FEED_PAGE), (fp.rewrite_mid_ad, MID_AD),
(fp.rewrite_candidates, CANDIDATES), (fp.rewrite_top_view, TOP_VIEW)):
with self.assertRaises(ValueError):
rewrite(rewrite(text)[0])
def test_a_class_that_changed_is_refused(self):
with self.assertRaises(ValueError):
fp.rewrite_feed_page(FEED_PAGE.replace("->items:Ljava/util/List;", "->list:Ljava/util/List;"))
with self.assertRaises(ValueError):
fp.rewrite_feed_page(FEED_PAGE.replace("isHasAd", "hasAnAd"))
with self.assertRaises(ValueError):
fp.rewrite_mid_ad(MID_AD.replace("->aweme:", "->other:"))
with self.assertRaises(ValueError):
fp.rewrite_candidates(CANDIDATES.replace("CandidateAdRequestApi", "Other"))
with self.assertRaises(ValueError):
fp.rewrite_top_view(TOP_VIEW.replace("->LJ:Ljava/util/concurrent/ConcurrentHashMap;", "->LJ:Ljava/util/Map;"))
with self.assertRaises(ValueError):
fp.rewrite_feed_page(MID_AD)
def test_every_rewrite_has_its_dex_and_runtime_calls(self):
self.assertEqual(set(fp.REWRITES), set(fp.DEXES))
both = "".join(fp.REWRITES[owner](text)[0] for owner, text in (
(fp.FEED_PAGE, FEED_PAGE), (fp.MID_AD, MID_AD), (fp.CANDIDATES, CANDIDATES), (fp.TOP_VIEW, TOP_VIEW)))
for target in fp.runtime_targets():
self.assertIn(target, both)
if __name__ == "__main__":
unittest.main()