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:
210
tests/test_feed_patch.py
Normal file
210
tests/test_feed_patch.py
Normal 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()
|
||||
Reference in New Issue
Block a user