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>
260 lines
8.2 KiB
Python
260 lines
8.2 KiB
Python
import unittest
|
|
|
|
from lihont import theme_patch
|
|
|
|
ACCESSOR = """\
|
|
.class public final LX/0LAr;
|
|
.super Ljava/lang/Object;
|
|
.source "SourceFile"
|
|
|
|
.method public static LIZ(LX/0LYA;)LX/0LFj;
|
|
.registers 2
|
|
|
|
sget-object v0, LX/0LAs;->LIZ:LX/0MO7;
|
|
|
|
invoke-interface {p0, v0}, LX/0LYA;->LJJIZ(LX/0MNv;)Ljava/lang/Object;
|
|
|
|
move-result-object v0
|
|
|
|
check-cast v0, LX/0LFj;
|
|
|
|
return-object v0
|
|
.end method
|
|
|
|
.method public static LIZIZ(LX/0LYA;)LX/1MiX;
|
|
.registers 2
|
|
.prologue
|
|
|
|
sget-object v0, LX/1MiY;->LIZ:LX/0MO7;
|
|
|
|
invoke-interface {p0, v0}, LX/0LYA;->LJJIZ(LX/0MNv;)Ljava/lang/Object;
|
|
|
|
move-result-object v0
|
|
|
|
check-cast v0, LX/1MiX;
|
|
|
|
return-object v0
|
|
.end method
|
|
"""
|
|
|
|
LABEL = """.class public final LX/0Lis;
|
|
.super Ljava/lang/Object;
|
|
|
|
.method public static final LIZIZ(LX/0DMR;LX/0LYA;I)V
|
|
.registers 20
|
|
|
|
invoke-static {v0}, LX/0LAr;->LIZIZ(LX/0LYA;)LX/1MiX;
|
|
|
|
move-result-object v1
|
|
|
|
iget-wide v14, v1, LX/1MiX;->d4:J
|
|
|
|
return-void
|
|
.end method
|
|
|
|
.method public static final LIZJ(Ljava/lang/String;ZZZLkotlin/jvm/functions/Function0;LX/0LYA;II)V
|
|
.registers 40
|
|
|
|
invoke-static {v0}, LX/0LAr;->LIZIZ(LX/0LYA;)LX/1MiX;
|
|
|
|
move-result-object v11
|
|
|
|
iget-wide v12, v11, LX/1MiX;->d4:J
|
|
|
|
invoke-static {v0}, LX/0LAr;->LJ(LX/0LYA;)LX/0LAb;
|
|
|
|
.line 83886186
|
|
move-result-object v3
|
|
|
|
.line 83886187
|
|
iget-wide v8, v3, LX/1MiX;->d4:J
|
|
|
|
:goto_6d
|
|
return-void
|
|
.end method
|
|
"""
|
|
|
|
ROW = """\
|
|
.class public final LX/0LDf;
|
|
.super Ljava/lang/Object;
|
|
|
|
.method public final invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
|
|
.registers 52
|
|
|
|
invoke-interface {v0}, LX/0LYA;->LJ()V
|
|
|
|
.line 33555008
|
|
.line 33555009
|
|
.line 33555010
|
|
move-object/from16 v9, v17
|
|
|
|
.line 33555011
|
|
.line 33555012
|
|
invoke-static {v9, v1, v13}, LX/0M8m;->LIZIZ(LX/0M1d;ZLkotlin/jvm/functions/Function1;)LX/0M1d;
|
|
|
|
.line 33555013
|
|
move-result-object v1
|
|
|
|
invoke-interface {v4, v0}, LX/0LDi;->LJIIJJI(LX/0LYA;)J
|
|
|
|
move-result-wide v4
|
|
|
|
.line 33555175
|
|
invoke-static {v8, v8, v7, v7}, LX/0Kzn;->LIZJ(FFFF)LX/0LQf;
|
|
|
|
.line 33555176
|
|
.line 33555177
|
|
move-result-object v7
|
|
|
|
move-object/from16 v6, v17
|
|
|
|
return-object v1
|
|
.end method
|
|
"""
|
|
|
|
|
|
CARD = """.class public final LX/0Lk9;
|
|
.super Ljava/lang/Object;
|
|
|
|
.method public static final LIZ(LX/0CX2;ZZLcom/ss/android/ugc/aweme/setting/ui/rvmpcompose/group/content/cell/LanguageVM;LX/0LYA;I)V
|
|
.registers 40
|
|
|
|
invoke-static {v15}, LX/0Kzn;->LIZIZ(F)LX/0LQf;
|
|
|
|
move-result-object v15
|
|
|
|
invoke-static {v11, v0, v1, v15}, LX/0L5k;->LIZIZ(LX/0M1d;JLX/0LQx;)LX/0M1d;
|
|
|
|
.line 33555200
|
|
.line 33555201
|
|
move-result-object v0
|
|
|
|
sget-object v1, LX/0L0L;->LIZ:LX/0L0J;
|
|
|
|
return-void
|
|
.end method
|
|
"""
|
|
|
|
|
|
class ColoursAccessorTest(unittest.TestCase):
|
|
def test_hook_sits_between_the_read_and_the_cast(self):
|
|
text = theme_patch.rewrite_colors_accessor(ACCESSOR)
|
|
self.assertIn(
|
|
" move-result-object v0\n\n"
|
|
" invoke-static {v0, p0}, %s\n\n"
|
|
" move-result-object v0\n\n"
|
|
" check-cast v0, LX/1MiX;\n" % theme_patch.HOOK, text)
|
|
|
|
def test_other_methods_are_untouched(self):
|
|
text = theme_patch.rewrite_colors_accessor(ACCESSOR)
|
|
self.assertEqual(text.count("NativeTheme"), 1)
|
|
self.assertIn("check-cast v0, LX/0LFj;\n\n return-object v0\n", text)
|
|
|
|
def test_a_second_pass_is_refused(self):
|
|
once = theme_patch.rewrite_colors_accessor(ACCESSOR)
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_colors_accessor(once)
|
|
|
|
def test_a_missing_method_is_refused(self):
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_colors_accessor(ACCESSOR.replace("LIZIZ", "LIZZZ"))
|
|
|
|
def test_an_unexpected_register_layout_is_refused(self):
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_colors_accessor(
|
|
ACCESSOR.replace(".registers 2\n .prologue", ".registers 3\n .prologue"))
|
|
|
|
def test_runtime_target_matches_the_hook(self):
|
|
self.assertEqual(theme_patch.runtime_targets(), [theme_patch.HOOK])
|
|
|
|
|
|
class SectionLabelTest(unittest.TestCase):
|
|
def test_both_reads_in_the_group_title_are_rewritten(self):
|
|
text = theme_patch.rewrite_section_label(LABEL)
|
|
self.assertIn(
|
|
" invoke-static/range {v11 .. v11}, %s\n\n move-result-wide v12\n"
|
|
% theme_patch.SECTION_HOOK, text)
|
|
self.assertIn(
|
|
" invoke-static/range {v3 .. v3}, %s\n\n move-result-wide v8\n"
|
|
% theme_patch.SECTION_HOOK, text)
|
|
self.assertEqual(text.count("sectionLabel"), 2)
|
|
# The other composable keeps reading the token itself.
|
|
self.assertIn("iget-wide v14, v1, LX/1MiX;->d4:J", text)
|
|
|
|
def test_a_second_pass_is_refused(self):
|
|
once = theme_patch.rewrite_section_label(LABEL)
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_section_label(once)
|
|
|
|
def test_a_missing_composable_is_refused(self):
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_section_label(
|
|
LABEL.replace(".method public static final LIZJ(", ".method public static final LIZZ("))
|
|
|
|
def test_a_title_with_a_different_number_of_reads_is_refused(self):
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_section_label(
|
|
LABEL.replace("v8, v3, LX/1MiX;->d4:J", "v8, v3, LX/1MiX;->a4:J"))
|
|
|
|
def test_section_target_is_the_hook(self):
|
|
self.assertEqual(theme_patch.section_targets(), [theme_patch.SECTION_HOOK])
|
|
|
|
|
|
class RowLambdaTest(unittest.TestCase):
|
|
def test_modifier_and_shape_are_routed_through_the_hooks(self):
|
|
text = theme_patch.rewrite_row_lambda(ROW)
|
|
self.assertIn(
|
|
" move-object/from16 v9, v17\n"
|
|
" invoke-static {v9, v0}, %s\n\n"
|
|
" move-result-object v9\n\n"
|
|
" check-cast v9, LX/0M1d;\n" % theme_patch.ROW_MODIFIER_HOOK, text)
|
|
self.assertIn("invoke-static {v8, v8, v7, v7, v0}, %s\n" % theme_patch.ROW_SHAPE_HOOK, text)
|
|
self.assertIn(" move-result-object v7\n\n check-cast v7, LX/0LQf;\n", text)
|
|
self.assertNotIn("LX/0Kzn;->LIZJ", text)
|
|
# The original semantics call still follows the padded modifier.
|
|
self.assertIn("invoke-static {v9, v1, v13}, LX/0M8m;->LIZIZ", text)
|
|
|
|
def test_a_second_pass_is_refused(self):
|
|
once = theme_patch.rewrite_row_lambda(ROW)
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_row_lambda(once)
|
|
|
|
def test_a_missing_shape_call_is_refused(self):
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_row_lambda(ROW.replace("LIZJ(FFFF)", "LIZJ(FF)"))
|
|
|
|
def test_a_missing_modifier_start_is_refused(self):
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_row_lambda(
|
|
ROW.replace("move-object/from16 v9, v17", "move-object/from16 v9, v18"))
|
|
|
|
def test_row_targets_are_the_hooks(self):
|
|
self.assertEqual(theme_patch.row_targets(),
|
|
[theme_patch.ROW_MODIFIER_HOOK, theme_patch.ROW_SHAPE_HOOK])
|
|
|
|
|
|
class LanguageCardTest(unittest.TestCase):
|
|
def test_the_card_background_call_goes_through_the_hook(self):
|
|
text = theme_patch.rewrite_language_card(CARD)
|
|
self.assertIn(" invoke-static {v11, v0, v1, v15, v3}, %s\n" % theme_patch.CARD_HOOK, text)
|
|
self.assertIn(" move-result-object v0\n\n check-cast v0, LX/0M1d;\n", text)
|
|
self.assertNotIn("LX/0L5k;->LIZIZ", text)
|
|
# The rounded shape built just before it is TikTok's own call and stays.
|
|
self.assertIn("LX/0Kzn;->LIZIZ(F)LX/0LQf;", text)
|
|
|
|
def test_a_second_pass_is_refused(self):
|
|
once = theme_patch.rewrite_language_card(CARD)
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_language_card(once)
|
|
|
|
def test_a_missing_background_call_is_refused(self):
|
|
with self.assertRaises(ValueError):
|
|
theme_patch.rewrite_language_card(CARD.replace("v11, v0, v1, v15}", "v10, v0, v1, v15}"))
|
|
|
|
def test_card_target_is_the_hook(self):
|
|
self.assertEqual(theme_patch.card_targets(), [theme_patch.CARD_HOOK])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|