Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Sign in / Register
Toggle navigation
A
appzxhy
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wanglei
appzxhy
Commits
c5b183f7
Commit
c5b183f7
authored
Jun 10, 2025
by
wanglei
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[打包]提交混淆脚本
parent
e55ec528
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
21282 additions
and
0 deletions
+21282
-0
test9.py
app/test9.py
+1884
-0
word_file.json
app/word_file.json
+19398
-0
No files found.
app/test9.py
0 → 100644
View file @
c5b183f7
import
json
import
os
import
random
import
re
import
shutil
from
base64
import
b64encode
,
b64decode
import
cv2
import
numpy
as
np
import
regex
as
re2
# from Cryptodome.Cipher import AES
# from Cryptodome.Random import get_random_bytes
# import chardet
import
string
import
random
def
detect_encoding
(
file_path
):
with
open
(
file_path
,
'rb'
)
as
f
:
raw_data
=
f
.
read
(
1024
)
result
=
chardet
.
detect
(
raw_data
)
return
result
[
'encoding'
]
def
get_classes_impl
(
result
,
class_type
,
path
=
'.'
):
print
(
"get_classes_impl start"
)
listdir
=
os
.
listdir
(
path
)
for
i
in
listdir
:
print
(
i
)
path_join
=
os
.
path
.
join
(
path
,
i
)
if
os
.
path
.
isdir
(
path_join
):
if
path
.
endswith
(
'.'
)
and
i
in
[
"build"
,
"debug"
,
"release"
]:
continue
if
path
.
endswith
(
'src'
)
and
i
in
[
"androidTest"
,
"test"
]:
continue
get_classes_impl
(
result
,
class_type
,
path_join
)
elif
os
.
path
.
isfile
(
path_join
):
if
os
.
path
.
splitext
(
i
)[
-
1
][
1
:]
in
class_type
:
result
.
append
(
path_join
)
print
(
result
)
print
(
"get_classes_impl over"
)
def
get_classes
(
class_type
=
None
):
print
(
"get_classes start"
)
if
class_type
is
None
:
class_type
=
[
"kt"
,
"java"
]
result
=
[]
get_classes_impl
(
result
,
class_type
)
print
(
result
)
print
(
"get_classes over"
)
return
result
def
find_text_impl
(
reg
,
result
,
skip_name
,
path
,
is_one
):
listdir
=
os
.
listdir
(
path
)
for
i
in
listdir
:
if
is_one
and
len
(
result
)
>
0
:
return
path_join
=
os
.
path
.
join
(
path
,
i
)
if
os
.
path
.
isdir
(
path_join
):
if
path
.
endswith
(
'.'
)
and
i
in
[
"build"
,
"debug"
,
"release"
]:
continue
if
path
.
endswith
(
'src'
)
and
i
in
[
"androidTest"
,
"test"
]:
continue
find_text_impl
(
reg
,
result
,
skip_name
,
path_join
,
is_one
)
elif
os
.
path
.
isfile
(
path_join
):
if
path
.
endswith
(
'.'
)
and
i
.
split
(
'.'
)[
-
1
]
in
[
'py'
]:
continue
is_skip
=
False
for
j
in
skip_name
:
if
j
in
i
:
is_skip
=
True
break
if
is_skip
:
continue
with
open
(
path_join
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
if
not
f
.
readable
():
continue
try
:
text
=
f
.
read
()
except
ValueError
:
continue
findall
=
re
.
findall
(
reg
,
text
)
if
len
(
findall
)
>
0
:
result
.
extend
(
findall
)
def
find_text
(
reg
,
path
=
'.'
,
is_one
=
False
,
skip_name
=
None
):
if
skip_name
is
None
:
skip_name
=
[]
result
=
[]
if
os
.
path
.
isdir
(
path
):
find_text_impl
(
reg
,
result
,
skip_name
,
path
,
is_one
)
else
:
with
open
(
path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
if
f
.
readable
():
try
:
text
=
f
.
read
()
findall
=
re
.
findall
(
reg
,
text
)
if
len
(
findall
)
>
0
:
result
.
extend
(
findall
)
except
ValueError
:
pass
if
is_one
:
if
len
(
result
)
>
0
:
return
result
[
0
]
else
:
return
None
return
result
def
find_file_impl
(
reg
,
result
,
skip_name
,
path
=
'.'
):
listdir
=
os
.
listdir
(
path
)
for
i
in
listdir
:
if
len
(
result
)
>
0
:
return
path_join
=
os
.
path
.
join
(
path
,
i
)
if
os
.
path
.
isdir
(
path_join
):
if
path
.
endswith
(
'.'
)
and
i
in
[
"build"
,
"debug"
,
"release"
]:
continue
if
path
.
endswith
(
'src'
)
and
i
in
[
"androidTest"
,
"test"
]:
continue
find_file_impl
(
reg
,
result
,
skip_name
,
path_join
)
elif
os
.
path
.
isfile
(
path_join
):
if
path
.
endswith
(
'.'
)
and
i
not
in
[
"pro"
]:
continue
is_skip
=
False
for
j
in
skip_name
:
if
j
in
i
:
is_skip
=
True
break
if
is_skip
:
continue
with
open
(
path_join
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
if
not
f
.
readable
():
continue
try
:
text
=
f
.
read
()
except
ValueError
:
continue
findall
=
re
.
findall
(
reg
,
text
)
if
len
(
findall
)
>
0
:
result
.
append
(
path_join
)
def
find_file
(
reg
,
path
=
'.'
,
skip_name
=
None
):
print
(
find_file
)
if
skip_name
is
None
:
skip_name
=
[]
result
=
[]
if
os
.
path
.
isdir
(
path
):
find_file_impl
(
reg
,
result
,
skip_name
,
path
)
else
:
with
open
(
path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
if
f
.
readable
():
try
:
text
=
f
.
read
()
findall
=
re
.
findall
(
reg
,
text
)
if
len
(
findall
)
>
0
:
result
.
append
(
path
)
except
ValueError
:
pass
return
result
[
0
]
if
len
(
result
)
>
0
else
""
def
get_class_path
(
path
):
with
open
(
path
,
'r'
)
as
f
:
text
=
f
.
read
()
package
=
re
.
search
(
'package (.*?)
\\
s'
,
text
)
.
group
(
1
)
name
=
os
.
path
.
basename
(
path
)
name
=
name
[:
name
.
rfind
(
'.'
)]
return
package
+
'.'
+
name
def
replace_map_text
(
mapping
,
path
=
'.'
):
listdir
=
os
.
listdir
(
path
)
for
i
in
listdir
:
path_join
=
os
.
path
.
join
(
path
,
i
)
if
os
.
path
.
isdir
(
path_join
):
if
path
.
endswith
(
'.'
)
and
i
in
[
"build"
,
"debug"
,
"release"
]:
continue
if
path
.
endswith
(
'src'
)
and
i
in
[
"androidTest"
,
"test"
]:
continue
replace_map_text
(
mapping
,
path_join
)
elif
os
.
path
.
isfile
(
path_join
):
if
path
.
endswith
(
'.'
)
and
os
.
path
.
splitext
(
i
)[
-
1
][
1
:]
not
in
[
"pro"
]:
continue
with
open
(
path_join
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
if
not
f
.
readable
():
continue
try
:
text
=
f
.
read
()
except
ValueError
:
continue
with
open
(
path_join
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
reverse
=
sorted
(
list
(
mapping
.
keys
()),
key
=
len
,
reverse
=
True
)
for
name
in
reverse
:
# text = text.replace(str(name), mapping[name])
text_result
=
re
.
sub
(
rf
'
\b
{name}
\b
'
,
mapping
[
name
],
text_result
)
f
.
write
(
text
)
# 修复某些字符无法匹配的问题,如android:OEM
def
sub_map_text
(
mapping
,
is_reverse
=
True
,
path
=
'.'
,
skip_type
=
None
,
skip_name
=
None
):
print
(
"sub_map_text start"
)
if
len
(
mapping
)
==
0
:
return
[]
if
skip_type
is
None
:
skip_type
=
[]
if
skip_name
is
None
:
skip_name
=
[]
skip_name
.
append
(
'databinding'
)
# 跳过 databinding
result
=
[]
sub_map_text_impl
(
mapping
,
result
,
skip_type
,
skip_name
,
is_reverse
,
path
)
print
(
result
)
print
(
"sub_map_text over"
)
return
result
# def sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse=True, path='.'):
# print("sub_map_text_impl start")
# listdir = os.listdir(path)
# for i in listdir:
# path_join = os.path.join(path, i)
# if os.path.isdir(path_join):
# if path.endswith('.') and i in ["build", "debug", "release"]:
# continue
# if path.endswith('src') and i in ["androidTest", "test"]:
# continue
# sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path_join)
# elif os.path.isfile(path_join):
# if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
# continue
# if os.path.splitext(i)[-1][1:] in skip_type:
# continue
# is_skip = False
# for j in skip_name:
# if j in i:
# print(j)
# is_skip = True
# break
# if is_skip:
# continue
# with open(path_join, 'r', encoding='utf-8') as f:
# if not f.readable():
# continue
# try:
# text = f.read()
# except ValueError:
# continue
# with open(path_join, 'w', encoding='utf-8') as f:
# reverse = sorted(list(mapping.keys()), key=len, reverse=is_reverse)
# text_result = text
# for name in reverse:
# text_result = re2.sub(str(name), mapping[name], text_result)
# print(text_result)
# if text_result != text:
# result.append(path_join)
# print(result)
# f.write(text_result)
# print("sub_map_text_impl over")
# 修复datbinding问题
def
sub_map_text_impl
(
mapping
,
result
,
skip_type
,
skip_name
,
is_reverse
=
True
,
path
=
'.'
):
print
(
"sub_map_text_impl start"
)
listdir
=
os
.
listdir
(
path
)
for
i
in
listdir
:
path_join
=
os
.
path
.
join
(
path
,
i
)
if
os
.
path
.
isdir
(
path_join
):
if
path
.
endswith
(
'.'
)
and
i
in
[
"build"
,
"debug"
,
"release"
]:
continue
if
path
.
endswith
(
'src'
)
and
i
in
[
"androidTest"
,
"test"
]:
continue
sub_map_text_impl
(
mapping
,
result
,
skip_type
,
skip_name
,
is_reverse
,
path_join
)
elif
os
.
path
.
isfile
(
path_join
):
if
path
.
endswith
(
'.'
)
and
os
.
path
.
splitext
(
i
)[
-
1
][
1
:]
not
in
[
"pro"
]:
continue
if
os
.
path
.
splitext
(
i
)[
-
1
][
1
:]
in
skip_type
:
continue
is_skip
=
False
for
j
in
skip_name
:
if
j
in
i
:
print
(
j
)
is_skip
=
True
break
if
is_skip
:
continue
# 开始读取文件内容
with
open
(
path_join
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
if
not
f
.
readable
():
continue
try
:
text
=
f
.
read
()
except
ValueError
:
continue
# 替换逻辑
with
open
(
path_join
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
reverse
=
sorted
(
list
(
mapping
.
keys
()),
key
=
len
,
reverse
=
is_reverse
)
text_result
=
text
patterns
=
sorted
(
mapping
.
keys
(),
key
=
len
,
reverse
=
is_reverse
)
for
pattern
in
patterns
:
try
:
print
(
f
"正在替换 pattern: {pattern}"
)
text_result
=
re
.
sub
(
pattern
,
mapping
[
pattern
],
text_result
)
except
Exception
as
e
:
print
(
f
"替换失败: pattern={pattern}, 错误: {e}"
)
# # 遍历映射规则替换
# for name in reverse:
# text_result = re2.sub(str(name), mapping[name], text_result)
# 方案 3:针对特定 import 的直接替换
text_result
=
re
.
sub
(
r'import varietyed\.scratched\.assemblyed\.(R|databinding\.\w+)'
,
r'import com.pots.qasdf.phonemanager.\1'
,
text_result
)
# 检查替换结果
if
text_result
!=
text
:
result
.
append
(
path_join
)
f
.
write
(
text_result
)
print
(
"sub_map_text_impl over"
)
def
replace_text
(
old
,
new
,
path
=
'.'
):
print
(
"replace_text"
)
listdir
=
os
.
listdir
(
path
)
for
i
in
listdir
:
path_join
=
os
.
path
.
join
(
path
,
i
)
if
os
.
path
.
isdir
(
path_join
):
if
path
.
endswith
(
'.'
)
and
i
in
[
"build"
,
"debug"
,
"release"
]:
continue
if
path
.
endswith
(
'src'
)
and
i
in
[
"androidTest"
,
"test"
]:
continue
replace_text
(
old
,
new
,
path_join
)
elif
os
.
path
.
isfile
(
path_join
):
if
path
.
endswith
(
'.'
)
and
os
.
path
.
splitext
(
i
)[
-
1
][
1
:]
not
in
[
"pro"
]:
continue
with
open
(
path_join
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
if
not
f
.
readable
():
continue
try
:
text
=
f
.
read
()
except
ValueError
:
continue
with
open
(
path_join
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
text
.
replace
(
old
,
new
))
print
(
text
)
def
get_random_package
():
packages
=
[
get_random_string
(
4
)
for
_
in
range
(
get_random_int
(
1
,
3
))]
return
'.'
.
join
(
packages
)
def
check_mapping
():
print
(
"check_mapping start"
)
try
:
loads
=
json
.
load
(
open
(
'mapping.json'
,
'r'
,
encoding
=
'utf-8'
))
except
(
ValueError
,
IOError
):
loads
=
{}
old_mapping
=
loads
.
get
(
'dir'
,
{})
packages
=
find_text
(
'package (.*?)[;]?
\\
s'
,
'src'
)
packages
.
append
(
applicationId
)
packages
=
sorted
(
set
(
packages
),
key
=
packages
.
index
)
packages
.
sort
(
key
=
len
)
print
(
packages
)
mapping
=
{}
for
i
in
packages
:
# 解决databingding包被混淆
if
'databinding'
in
i
:
# 跳过 databinding 包
mapping
[
i
]
=
i
continue
if
applicationId
not
in
i
:
continue
value
=
old_mapping
.
get
(
i
)
if
value
is
not
None
:
mapping
[
i
]
=
value
continue
mapping
[
i
]
=
get_random_package
()
value
=
mapping
.
get
(
i
[:
i
.
rfind
(
'.'
)])
if
value
is
not
None
:
mapping
[
i
]
=
value
+
'.'
+
mapping
[
i
]
loads
[
"dir"
]
=
mapping
json
.
dump
(
loads
,
open
(
'mapping.json'
,
'w'
,
encoding
=
'utf-8'
),
indent
=
4
)
print
(
json
)
print
(
"check_mapping over"
)
return
mapping
def
check_class_mapping
():
print
(
"check_class_mapping start"
)
try
:
loads
=
json
.
load
(
open
(
'mapping.json'
,
'r'
,
encoding
=
'utf-8'
))
print
(
loads
)
except
(
ValueError
,
IOError
):
loads
=
{}
old_mapping
=
loads
.
get
(
'class'
,
{})
mapping
=
{}
classes
=
get_classes
()
for
i
in
classes
:
if
'_D'
in
i
or
os
.
path
.
basename
(
i
)
==
"GlobalConfig.kt"
:
# 跳过 GlobalConfig.kt
continue
print
(
i
)
name
=
os
.
path
.
basename
(
i
)
name
=
name
[:
name
.
rfind
(
'.'
)]
value
=
old_mapping
.
get
(
name
)
print
(
value
)
if
value
is
not
None
:
mapping
[
name
]
=
value
continue
mapping
[
name
]
=
get_title
(
get_random_string
(
4
),
True
)
print
(
mapping
[
name
])
# method_variable_mapping = generate_method_variable_mapping(i) # 生成方法名和变量名的混淆映射
# for old, new in method_variable_mapping.items():
# mapping[old] = new # 添加方法名和变量名的混淆映射
loads
[
"class"
]
=
mapping
json
.
dump
(
loads
,
open
(
'mapping.json'
,
'w'
,
encoding
=
'utf-8'
),
indent
=
4
)
print
(
json
)
print
(
"check_class_mapping over"
)
return
mapping
# def generate_method_variable_mapping(class_file):
# # 生成方法名和变量名的混淆映射
# mapping = {}
#
# with open(class_file, 'r', encoding='utf-8') as f:
# text = f.read()
#
# methods = re.findall(
# r'\b(public|private|protected|static|final|abstract|synchronized)?\s*(<[^>]+>\s*)?(\w+)\s+(\w+)\s*\(',
# text
# )
# variables = re.findall(
# r'\b(public|private|protected|static|final|transient|volatile)?\s*(<[^>]+>\s*)?(\w+)\s+(\w+)\s*[;=]',
# text
# )
#
# for method in methods:
# old_name = method[3]
# new_name = get_random_string(6)
# mapping[old_name] = new_name
#
# for variable in variables:
# old_name = variable[3]
# new_name = get_random_string(6)
# mapping[old_name] = new_name
#
# return mapping
def
check_type_ids_mapping
(
ids_type
):
try
:
loads
=
json
.
load
(
open
(
'mapping.json'
,
'r'
,
encoding
=
'utf-8'
))
except
(
ValueError
,
IOError
):
loads
=
{}
old_mapping
=
loads
.
get
(
ids_type
+
"_ids"
,
{})
mapping
=
{}
type_ids
=
find_text
(
'<'
+
ids_type
+
' name="(.*?)".*?>'
,
get_path
(
'.
\\
src
\\
main
\\
res'
))
for
i
in
type_ids
:
if
i
.
endswith
(
'_D'
):
continue
value
=
old_mapping
.
get
(
i
)
if
value
is
not
None
:
mapping
[
i
]
=
value
continue
mapping
[
i
]
=
get_random_string
(
5
,
False
)
type_ids
=
find_text
(
'<item name="(.*?)".*? type="'
+
ids_type
+
'">'
,
get_path
(
'.
\\
src
\\
main
\\
res'
))
for
i
in
type_ids
:
value
=
old_mapping
.
get
(
i
)
if
value
is
not
None
:
mapping
[
i
]
=
value
continue
mapping
[
i
]
=
get_random_string
(
5
,
False
)
if
len
(
mapping
)
!=
0
:
loads
[
ids_type
+
"_ids"
]
=
mapping
json
.
dump
(
loads
,
open
(
'mapping.json'
,
'w'
,
encoding
=
'utf-8'
),
indent
=
4
)
return
mapping
def
check_styleable_mapping
():
try
:
loads
=
json
.
load
(
open
(
'mapping.json'
,
'r'
,
encoding
=
'utf-8'
))
except
(
ValueError
,
IOError
):
loads
=
{}
old_mapping
=
loads
.
get
(
"styleable"
,
{})
mapping
=
{}
styleables
=
find_text
(
'<declare-styleable name=".*?">[
\\
s
\\
S]*?</declare-styleable>'
)
for
i
in
styleables
:
styleable_id
=
re
.
findall
(
'<declare-styleable name="(.*?)">'
,
i
)[
0
]
mapping
[
styleable_id
]
=
old_mapping
.
get
(
styleable_id
,
get_random_string
(
5
,
False
))
# styleables_attr = re.findall('<attr name="(.*?)".*?/>', i)
styleables_attr
=
re
.
findall
(
'<attr name="(.*?)".*?>'
,
i
)
# 包括单行和多行定义
for
attr
in
styleables_attr
:
mapping
[
attr
+
'_A'
]
=
old_mapping
.
get
(
attr
+
'_A'
,
get_random_string
(
5
))
if
len
(
mapping
)
!=
0
:
loads
[
"styleable"
]
=
mapping
json
.
dump
(
loads
,
open
(
'mapping.json'
,
'w'
,
encoding
=
'utf-8'
),
indent
=
4
)
return
mapping
def
deal_ids_type
(
ids_type
):
type_ids_mapping
=
check_type_ids_mapping
(
ids_type
)
if
len
(
type_ids_mapping
)
==
0
:
return
print
(
type_ids_mapping
)
sub_map
=
{}
for
i
in
type_ids_mapping
:
if
i
in
type_ids_mapping
and
type_ids_mapping
[
i
]
.
startswith
(
"android"
):
continue
# mapped = type_ids_mapping[i]
#
# sub_map['<' + ids_type + ' name="' + i + '"'] = '<' + ids_type + ' name="' + mapped + '"'
# sub_map['<item name="' + i + '"(.*? type="' + ids_type + '")>'] = '<item name="' + mapped + '"\\g<1>>'
# sub_map[r'(?<=[^\.])R\.' + ids_type + r'\.' + re.escape(i) + r'(?=\W)'] = 'R.' + ids_type + '.' + mapped
# sub_map[r'(?<=>|")@' + ids_type + '/' + re.escape(i) + r'(?=<|")'] = '@' + ids_type + '/' + mapped
# sub_map[r'(?<=[@=:])@' + ids_type + '/' + re.escape(i) + r'(?=[}"\'\s])'] = '@' + ids_type + '/' + mapped
#
# # ✅ DataBinding 表达式中所有 @xxx/yyy(包括拼接字符串等复杂表达式)
# sub_map[r'(?<=@{[^}]*?)@' + ids_type + r'/' + re.escape(i) + r'(?=[^}]*})'] = '@' + ids_type + '/' + mapped
#
# # ✅ <style name="..." parent="xxx"> 资源引用
# sub_map[r'<' + ids_type + r'(.*?) parent="' + re.escape(i) + r'"(.*?)>'] = (
# r'<' + ids_type + r'\g<1> parent="' + mapped + r'"\g<2>>'
# )
sub_map
[
'<'
+
ids_type
+
' name="'
+
i
+
'"'
]
=
'<'
+
ids_type
+
' name="'
+
type_ids_mapping
[
i
]
+
'"'
sub_map
[
'<item name="'
+
i
+
'"(.*? type="'
+
ids_type
+
'")>'
]
=
'<item name="'
+
type_ids_mapping
[
i
]
+
'"
\\
g<1>>'
sub_map
[
'(?<=[^
\\
.])R
\\
.'
+
ids_type
+
'
\\
.'
+
i
+
'(?=
\\
W)'
]
=
'R.'
+
ids_type
+
'.'
+
type_ids_mapping
[
i
]
sub_map
[
'(?<=>|")@'
+
ids_type
+
'/'
+
i
+
'(?=<|")'
]
=
'@'
+
ids_type
+
'/'
+
type_ids_mapping
[
i
]
sub_map
[
r'(?<=[@=:])@'
+
ids_type
+
'/'
+
i
+
r'(?=[}"\'\s])'
]
=
'@'
+
ids_type
+
'/'
+
type_ids_mapping
[
i
]
sub_map
[
r'(?<=@{[^}]*?)@'
+
ids_type
+
r'/'
+
i
+
r'(?=[^}]*})'
]
=
'@'
+
ids_type
+
'/'
+
type_ids_mapping
[
i
]
# ⬅ 适配 data binding 表达式
sub_map
[
'<'
+
ids_type
+
'(.*?) parent="'
+
i
+
'"(.*?)>'
]
=
'<'
+
ids_type
+
'
\\
g<1> parent="'
+
\
type_ids_mapping
[
i
]
+
'"
\\
g<2>>'
escaped_i
=
re
.
escape
(
i
)
sub_map
[
rf
'(?<![a-zA-Z0-9_/])@{ids_type}/{escaped_i}(?![a-zA-Z0-9_])'
]
=
f
'@{ids_type}/{type_ids_mapping[i]}'
# 带包名前缀的资源路径 com.retrytech.ledgeapp.R.color.xxx
# sub_map[rf'(\b[\w\.]+)\.R\.{ids_type}\.{re.escape(i)}\b'] = rf'\1.R.{ids_type}.{type_ids_mapping[i]}'
# sub_map[
# rf'(?<!android\.)(\b[\w\.]+)\.R\.{ids_type}\.{re.escape(i)}\b'] = rf'\1.R.{ids_type}.{type_ids_mapping[i]}'
sub_map
[
rf
'(?<!
\b
android
\
.)
\b
([
\
w
\
.]+)
\
.R
\
.{ids_type}
\
.{re.escape(i)}
\b
'
]
=
rf
'
\1
.R.{ids_type}.{type_ids_mapping[i]}'
# ✅ 推荐的替换逻辑(万能适配 DataBinding 场景)
# sub_map[f'@{ids_type}/{i}'] = f'@{ids_type}/{type_ids_mapping[i]}'
# # ✅ 适配 DataBinding 表达式中包括拼接、三目等形式的字符串资源引用
# sub_map[rf'(?<=@{{[^}}]*?)@{ids_type}/{re.escape(i)}(?=[^}}]*}})'] = f'@{ids_type}/{type_ids_mapping[i]}'
# # DataBinding 表达式中字符串拼接,如:@{@string/subscribe_to+" "+@string/pro}
# sub_map[r'(?<=@{[^}]*?)@' + ids_type + r'/' + re.escape(i) + r'(?=[^}]*})'] = '@' + ids_type + '/' + \
# type_ids_mapping[i]
# sub_map['<' + ids_type + ' name="' + i + '"'] = '<' + ids_type + ' name="' + type_ids_mapping[i] + '"'
# sub_map['<item name="' + i + '"(.*? type="' + ids_type + '")>'] = \
# '<item name="' + type_ids_mapping[i] + '"\\g<1>>'
# sub_map['(?<=[^\\.])R\\.' + ids_type + '\\.' + i + '(?=\\W)'] = 'R.' + ids_type + '.' + type_ids_mapping[i]
# sub_map['(?<=>|")@' + ids_type + '/' + i + '(?=<|")'] = '@' + ids_type + '/' + type_ids_mapping[i]
# sub_map['(?<=[@=:])@' + ids_type + '/' + i + r'(?=[}"\'\s])'] = '@' + ids_type + '/' + type_ids_mapping[i]
# sub_map['<' + ids_type + '(.*?) parent="' + i + '"(.*?)>'] = \
# '<' + ids_type + '\\g<1> parent="' + type_ids_mapping[i] + '"\\g<2>>'
# # 匹配 @color/xxx 出现在 @{} 表达式中,适配 data binding 多种情况
# sub_map[r'(?<=@{[^}]*?)@' + ids_type + r'/' + i + r'(?=[^}]*})'] = '@' + ids_type + '/' + type_ids_mapping[i]
sub_map
[
rf
'android
\
.R
\
.color
\
.(
\
w+)'
]
=
lambda
m
:
'android.R.color.'
+
type_ids_mapping
.
get
(
m
.
group
(
1
),
m
.
group
(
1
))
sub_map_text
(
sub_map
)
def
check_view_ids_mapping
():
try
:
loads
=
json
.
load
(
open
(
'mapping.json'
,
'r'
,
encoding
=
'utf-8'
))
except
(
ValueError
,
IOError
):
loads
=
{}
mapping
=
loads
.
get
(
'view_ids'
,
{})
view_ids
=
find_text
(
'"
\\
@
\\
+id/(.*?)"'
,
get_path
(
'.
\\
src
\\
main
\\
res'
))
for
i
in
view_ids
:
if
i
==
'root'
:
continue
key
=
get_title
(
i
)
if
mapping
.
get
(
key
)
is
not
None
:
continue
mapping
[
key
]
=
get_random_string
(
7
)
loads
[
"view_ids"
]
=
mapping
json
.
dump
(
loads
,
open
(
'mapping.json'
,
'w'
,
encoding
=
'utf-8'
),
indent
=
4
)
return
mapping
def
check_layout_mapping
():
try
:
loads
=
json
.
load
(
open
(
'mapping.json'
,
'r'
,
encoding
=
'utf-8'
))
except
(
ValueError
,
IOError
):
loads
=
{}
mapping
=
loads
.
get
(
'layout'
,
{})
layout_path
=
get_path
(
'.
\\
src
\\
main
\\
res
\\
layout'
)
listdir
=
os
.
listdir
(
layout_path
)
for
i
in
listdir
:
name
=
i
[:
i
.
rfind
(
'.'
)]
if
mapping
.
get
(
name
)
is
not
None
:
continue
mapping
[
name
]
=
get_random_string
(
6
)
loads
[
"layout"
]
=
mapping
json
.
dump
(
loads
,
open
(
'mapping.json'
,
'w'
,
encoding
=
'utf-8'
),
indent
=
4
)
return
mapping
def
check_res_mapping
(
res_type
):
try
:
loads
=
json
.
load
(
open
(
'mapping.json'
,
'r'
,
encoding
=
'utf-8'
))
except
(
ValueError
,
IOError
):
loads
=
{}
mapping
=
loads
.
get
(
res_type
,
{})
res_path
=
get_path
(
'.
\\
src
\\
main
\\
res'
)
listdir
=
os
.
listdir
(
res_path
)
for
i
in
listdir
:
if
not
i
.
startswith
(
res_type
):
continue
for
j
in
os
.
listdir
(
os
.
path
.
join
(
res_path
,
i
)):
if
j
.
startswith
(
'book_'
):
continue
if
j
.
endswith
(
'.9.png'
):
name
=
j
[:
-
len
(
'.9.png'
)]
else
:
name
=
j
.
rsplit
(
'.'
,
1
)[
0
]
if
mapping
.
get
(
name
)
is
not
None
:
continue
mapping
[
name
]
=
normalize_filename
(
get_random_string
(
8
))
if
len
(
mapping
)
!=
0
:
loads
[
res_type
]
=
mapping
json
.
dump
(
loads
,
open
(
'mapping.json'
,
'w'
,
encoding
=
'utf-8'
),
indent
=
4
)
return
mapping
used_words
=
set
()
def
get_random_string
(
length
=
8
,
is_ascii
=
True
):
global
used_words
# 使用全局变量 used_words
words
=
load_words_from_file
(
'word_file.json'
)
if
words
:
while
words
:
random_word
=
random
.
choice
(
words
)
if
random_word
not
in
used_words
:
used_words
.
add
(
random_word
)
if
is_ascii
and
all
(
ord
(
c
)
<
128
for
c
in
random_word
):
return
random_word
elif
not
is_ascii
:
return
random_word
return
None
def
load_words_from_file
(
file_path
):
try
:
with
open
(
file_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
data
=
json
.
load
(
f
)
return
data
.
get
(
"word"
,
[])
# 返回单词列表
except
FileNotFoundError
:
print
(
f
"Error: Word file not found at: {file_path}"
)
return
[]
except
json
.
JSONDecodeError
:
print
(
f
"Error: Invalid JSON format in: {file_path}"
)
return
[]
def
get_random_int
(
a
=
0
,
b
=
10000
):
return
random
.
randint
(
a
,
b
)
def
get_dictionary_string
(
length
=
10
):
random_str
=
random
.
choice
(
string
.
ascii_letters
)
random_str
+=
''
.
join
(
random
.
choice
(
string
.
ascii_letters
+
string
.
digits
)
for
_
in
range
(
length
-
1
))
return
random_str
def
check_obfuscation_dictionary
():
if
not
os
.
path
.
exists
(
'dictionary.txt'
):
dictionary
=
set
()
while
len
(
dictionary
)
!=
5000
:
dictionary
.
add
(
get_dictionary_string
())
with
open
(
'dictionary.txt'
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
'
\n
'
.
join
(
dictionary
))
with
open
(
'proguard-rules.pro'
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
text
=
f
.
read
()
if
'-obfuscationdictionary'
in
text
:
return
text
+=
'
\n
-obfuscationdictionary dictionary.txt'
text
+=
'
\n
-classobfuscationdictionary dictionary.txt'
text
+=
'
\n
-packageobfuscationdictionary dictionary.txt'
with
open
(
'proguard-rules.pro'
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
text
)
def
mkdir
(
dir_path
):
try
:
os
.
makedirs
(
dir_path
)
except
OSError
:
pass
def
replace_click_method
():
print
(
"replace_click_method start"
)
try
:
path
=
get_path
(
'.
\\
src
\\
main
\\
java'
)
file
=
find_file
(
'fun View.setTrackedOnClickListener'
,
path
)
if
file
:
class_path
=
get_class_path
(
file
)
+
'.setTrackedOnClickListener'
# java类不能直接使用扩展函数
sub_map_text
({
'([.@])setOnClickListener'
:
'
\\
g<1>setTrackedOnClickListener'
,
},
path
=
path
,
skip_type
=
[
'java'
])
add_import
(
class_path
,
get_classes
([
'kt'
]))
print
(
"replace_click_method over"
)
except
IndexError
:
return
def
replace_package_in_imports
(
root_path
,
old_package
,
new_package
):
# 匹配旧的 import 包名部分的正则表达式
patterns
=
[
(
re
.
compile
(
rf
'import {re.escape(old_package)}(
\
.databinding
\
.
\
w+)'
),
rf
'import {new_package}
\1
'
),
(
re
.
compile
(
rf
'import {re.escape(old_package)}
\
.R
\b
'
),
rf
'import {new_package}.R'
),
(
re
.
compile
(
rf
'import {re.escape(old_package)}
\
.BuildConfig'
),
rf
'import {new_package}.BuildConfig'
),
(
re
.
compile
(
rf
'{re.escape(old_package)}
\
.R
\b
'
),
rf
'{new_package}.R'
),
]
# 遍历目录中的所有文件
for
dirpath
,
_
,
filenames
in
os
.
walk
(
root_path
):
for
filename
in
filenames
:
# 只处理 .kt 和 .java 文件
if
filename
.
endswith
((
'.kt'
,
'.java'
)):
file_path
=
os
.
path
.
join
(
dirpath
,
filename
)
with
open
(
file_path
,
'r'
,
encoding
=
'utf-8'
)
as
file
:
content
=
file
.
read
()
# 替换匹配到的旧包名
updated_content
=
content
for
pattern
,
replacement
in
patterns
:
updated_content
=
pattern
.
sub
(
replacement
,
updated_content
)
# 如果内容发生了改变,写回文件
if
content
!=
updated_content
:
with
open
(
file_path
,
'w'
,
encoding
=
'utf-8'
)
as
file
:
file
.
write
(
updated_content
)
print
(
f
"已更新文件: {file_path}"
)
# def get_file_names_without_extension(root_path):
# # 遍历 root_path 下的所有 .kt 和 .java 文件
# all_files = []
# for dirpath, _, filenames in os.walk(root_path):
# for filename in filenames:
# if filename.endswith(('.kt', '.java')):
# all_files.append(os.path.join(dirpath, filename))
#
# return all_files
# def get_class_name(text, filename):
# """
# 获取文件中定义的类名(适用于 Java 和 Kotlin 文件)
# :param text: 文件内容
# :param filename: 文件名
# :return: 类名列表
# """
# if filename.endswith('.kt'):
# class_pattern = r'\bclass\s+(\w+)'
# elif filename.endswith('.java'):
# class_pattern = r'\bclass\s+(\w+)'
# else:
# return []
#
# # 获取类名
# return re.findall(class_pattern, text)
def
get_method_names
(
text
,
filename
):
"""
获取 Java 或 Kotlin 文件中的所有方法名
:param text: 文件内容
:param filename: 文件名
:return: 方法名列表
"""
if
filename
.
endswith
(
'.kt'
):
# Kotlin 方法名:匹配包括访问修饰符(private/public)和 fun 关键字,不排除 override
method_pattern
=
r'\b(?:private|protected|public|internal|inline|suspend|)?\s*fun\s+(\w+)\s*\((.*?)\)\s*(?::\s*([\w<>,\s?*]*))?\s*{'
elif
filename
.
endswith
(
'.java'
):
# Java 方法名:包括常见的修饰符和方法名
method_pattern
=
r'\b(?:public|private|protected|static|final|void|int|boolean|float|long|double|char|short|byte|void|[\w<>]+)\s+(\w+)\s*\(.*\)\s*\{'
else
:
return
[]
# 匹配所有方法名
return
re
.
findall
(
method_pattern
,
text
)
def
get_variable_names
(
text
,
filename
):
"""
获取 Java 或 Kotlin 文件中的所有变量名
:param text: 文件内容
:param filename: 文件名
:return: 变量名列表
"""
if
filename
.
endswith
(
'.kt'
):
# Kotlin 变量名:支持 `val` 和 `var` 关键字的变量声明
variable_pattern
=
r'\b(?:val|var)\s+(\w+)\s*(?=[;=])'
elif
filename
.
endswith
(
'.java'
):
# Java 变量名:包括常见的类型声明
variable_pattern
=
r'\b(?:int|boolean|float|long|double|char|short|byte|String|[\w<>]+)\s+(\w+)\s*(?=[;=])'
else
:
return
[]
# 匹配所有变量名
return
re
.
findall
(
variable_pattern
,
text
)
def
get_file_names_without_extension
(
root_path
):
"""
获取所有 `.kt` 和 `.java` 文件的路径(无扩展名)
:param root_path: 根目录路径
:return: 所有文件的路径列表
"""
all_files
=
[]
for
dirpath
,
_
,
filenames
in
os
.
walk
(
root_path
):
for
filename
in
filenames
:
if
filename
.
endswith
((
'.kt'
,
'.java'
)):
all_files
.
append
(
os
.
path
.
join
(
dirpath
,
filename
))
return
all_files
# 存储每个文件名的映射关系
file_to_class_map
=
{}
def
get_constant_names
(
text
,
filename
):
"""
获取 Java 或 Kotlin 文件中的所有常量名
:param text: 文件内容
:param filename: 文件名
:return: 常量名列表
"""
if
filename
.
endswith
(
'.kt'
):
# Kotlin 常量名:匹配以 `const val` 声明的常量
constant_pattern
=
r'const\s+val\s+(\w+)\s*=\s*.*'
elif
filename
.
endswith
(
'.java'
):
# Java 常量名:匹配以 `static final` 声明的常量
constant_pattern
=
r'public\s+static\s+final\s+\w+\s+(\w+)\s*=\s*.*'
else
:
return
[]
# 匹配所有常量名
return
re
.
findall
(
constant_pattern
,
text
)
def
obfuscate_code_in_files
(
all_files
):
"""
遍历所有文件,混淆其中的方法名、变量名和常量名,并返回混淆名称的映射。
:param all_files: 所有文件的路径列表。
:return: 一个字典,键为原始名称,值为混淆后的名称。
"""
name_mapping
=
{}
# 用于存储原始名称和混淆名称的映射
constant_mapping
=
{}
# 用于存储常量的映射
for
file_path
in
all_files
:
current_file_name
=
os
.
path
.
basename
(
file_path
)
.
split
(
'.'
)[
0
]
# 获取文件名
# 读取文件内容
with
open
(
file_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
lines
=
f
.
readlines
()
# 获取文件中的常量名
constants
=
get_constant_names
(
""
.
join
(
lines
),
file_path
)
# 为常量名生成混淆名称
for
constant
in
constants
:
if
constant
not
in
name_mapping
:
name_mapping
[
constant
]
=
get_random_string
()
constant_mapping
[
constant
]
=
name_mapping
[
constant
]
# 获取类名(用于判断常量是否在类内引用)
new_lines
=
[]
for
line
in
lines
:
if
line
.
startswith
(
"import "
):
# 跳过 import 行
new_lines
.
append
(
line
)
else
:
# 替换常量名:进行统一的替换,不区分引用的类型
for
constant
,
obfuscated_name
in
constant_mapping
.
items
():
# 直接替换常量,无论是在类内还是外部引用
line
=
re
.
sub
(
r'\b'
+
re
.
escape
(
constant
)
+
r'\b'
,
obfuscated_name
,
line
)
# 替换方法名和变量名
for
original_name
,
obfuscated_name
in
name_mapping
.
items
():
line
=
re
.
sub
(
r'\b'
+
re
.
escape
(
original_name
)
+
r'\b'
,
obfuscated_name
,
line
)
new_lines
.
append
(
line
)
# 写回文件
with
open
(
file_path
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
writelines
(
new_lines
)
return
constant_mapping
def
replace_with_mapped_names
(
all_files
,
name_mapping
,
constant_mapping
):
"""
在所有文件中替换已经混淆的名称,保证不同文件间共享混淆名称。
:param all_files: 所有文件的路径列表。
:param name_mapping: 名称映射字典。
:param constant_mapping: 常量名称映射字典。
"""
for
file_path
in
all_files
:
# 使用在 obfuscate_code_in_files 中存储的文件名
current_file_name
=
file_to_class_map
.
get
(
file_path
,
None
)
if
not
current_file_name
:
print
(
f
"未找到文件 {file_path} 的类名映射,跳过。"
)
continue
with
open
(
file_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
lines
=
f
.
readlines
()
new_lines
=
[]
excluded_constants
=
set
()
# 第一步:提取 `const val` 定义的常量名并排除混淆
for
line
in
lines
:
match
=
re
.
match
(
r'^\s*const\s+val\s+(\w+)\s*=.*$'
,
line
)
if
match
:
constant_name
=
match
.
group
(
1
)
excluded_constants
.
add
(
constant_name
)
for
line
in
lines
:
if
line
.
startswith
(
"import "
):
# 跳过 import 行
new_lines
.
append
(
line
)
continue
# 跳过 context 或 activity 的调用
if
'context.'
in
line
or
'activity.'
in
line
:
new_lines
.
append
(
line
)
continue
# 替换变量和方法名
for
original_name
,
obfuscated_name
in
name_mapping
.
items
():
if
isinstance
(
original_name
,
tuple
):
original_name
=
''
.
join
(
original_name
)
# 如果是 `excluded_constants` 中的常量名,跳过替换
if
original_name
in
excluded_constants
:
continue
# 替换代码中的变量或方法名
line
=
re
.
sub
(
r'\b'
+
re
.
escape
(
original_name
)
+
r'\b'
,
obfuscated_name
,
line
)
new_lines
.
append
(
line
)
with
open
(
file_path
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
writelines
(
new_lines
)
print
(
f
"文件 {file_path} 引用的名称已进行统一混淆处理。"
)
def
deal_code
():
print
(
"deal_code start"
)
# 替换点击事件
replace_click_method
()
# 生成包映射文件
mapping
=
check_mapping
()
packages
=
list
(
mapping
.
keys
())
print
(
"mapping: "
+
json
.
dumps
(
mapping
,
indent
=
4
))
# 移动文件
root_path
=
get_path
(
'.
\\
src
\\
main
\\
java'
)
for
key
in
packages
:
key
=
str
(
key
)
print
(
key
)
old_path
=
get_path
(
root_path
+
'
\\
'
+
key
.
replace
(
'.'
,
'
\\
'
))
new_path
=
get_path
(
root_path
+
'
\\
'
+
mapping
[
key
]
.
replace
(
'.'
,
'
\\
'
))
print
(
"mapping[key]"
+
mapping
[
key
])
if
not
os
.
path
.
exists
(
old_path
):
mkdir
(
old_path
)
if
not
os
.
path
.
exists
(
new_path
):
mkdir
(
new_path
)
listdir
=
os
.
listdir
(
old_path
)
for
i
in
listdir
:
path_join
=
os
.
path
.
join
(
old_path
,
i
)
if
os
.
path
.
isdir
(
path_join
):
continue
shutil
.
move
(
path_join
,
os
.
path
.
join
(
new_path
,
i
))
shutil
.
rmtree
(
get_path
(
root_path
+
'
\\
'
+
applicationId
[:
applicationId
.
find
(
'.'
)]))
# 修改manifest
with
open
(
get_path
(
'.
\\
src
\\
main
\\
AndroidManifest.xml'
),
'r'
,
encoding
=
'utf-8'
)
as
f
:
text
=
f
.
read
()
findall
=
re
.
findall
(
'android:name=(".*?")'
,
text
)
for
i
in
findall
:
if
i
.
startswith
(
'".'
):
text
=
text
.
replace
(
i
,
'"'
+
applicationId
+
i
[
1
:
-
1
]
+
'"'
)
print
(
text
)
print
(
applicationId
)
with
open
(
get_path
(
'.
\\
src
\\
main
\\
AndroidManifest.xml'
),
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
text
)
# 修改依赖
sub_map
=
{}
for
i
in
mapping
:
sub_map
[
'(?<=
\\
W)'
+
i
+
'(?=
\\
W)'
]
=
mapping
[
i
]
print
(
sub_map
[
'(?<=
\\
W)'
+
i
+
'(?=
\\
W)'
])
print
(
mapping
[
i
])
# 过滤修改
sub_map
[
'(?<=
\\
W)'
+
mapping
[
applicationId
]
+
'.R(?=
\\
W)'
]
=
applicationId
+
'.R'
sub_map
[
'(?<=
\\
W)'
+
mapping
[
applicationId
]
+
'.databinding(?=
\\
W)'
]
=
applicationId
+
'.databinding'
sub_map
[
'(?<=
\\
W)'
+
mapping
[
applicationId
]
+
'.BuildConfig(?=
\\
W)'
]
=
applicationId
+
'.BuildConfig'
sub_map_text
(
sub_map
)
# 根包名下 R 单独处理
new_path
=
get_path
(
root_path
+
'
\\
'
+
mapping
[
packages
[
0
]]
.
replace
(
'.'
,
'
\\
'
))
listdir
=
os
.
listdir
(
new_path
)
result_path
=
[]
for
i
in
listdir
:
if
os
.
path
.
isdir
(
os
.
path
.
join
(
new_path
,
i
)):
continue
result_path
.
append
(
os
.
path
.
join
(
new_path
,
i
))
# add_import(applicationId + '.R', result_path)
# add_import(applicationId + '.BuildConfig', result_path)
# 类名
print
(
"类名 start"
)
class_mapping
=
check_class_mapping
()
classes
=
get_classes
()
print
(
class_mapping
)
for
i
in
classes
:
if
'_D'
in
i
or
os
.
path
.
basename
(
i
)
==
"GlobalConfig.kt"
:
# 跳过 GlobalConfig.kt
continue
name
=
os
.
path
.
basename
(
i
)
name
=
name
[:
name
.
rfind
(
'.'
)]
shutil
.
move
(
i
,
i
.
replace
(
name
,
class_mapping
[
name
]))
sub_map
=
{}
for
i
in
class_mapping
:
# 原规则匹配整个单词
# 添加负向环视以排除前后是引号的情况,解决匹配attrs的问题,
# 添加排除前缀 "styleable." 的逻辑
# 原规则匹配整个单词,添加排除前缀 "styleable." 的逻辑,并允许后面有引号的匹配
regex_pattern
=
r'(?<!")(?<!\bstyleable\.)\b'
+
i
+
r'\b'
dagger_pattern
=
rf
'(?<!")(?<!
\b
styleable
\
.)
\b
DaggerAppComponent
\b
'
obfuscated_name
=
class_mapping
[
'AppComponent'
]
sub_map
[
dagger_pattern
]
=
f
'Dagger{obfuscated_name}'
# 保持原有规则的匹配,优先处理非引号的情况
sub_map
[
regex_pattern
]
=
class_mapping
[
i
]
print
(
class_mapping
[
i
])
sub_map_text
(
sub_map
)
print
(
"类名 over"
)
# 混淆字典
check_obfuscation_dictionary
()
print
(
root_path
)
print
(
mapping
[
applicationId
])
print
(
applicationId
)
# 获取混淆名称映射
# all_files = get_file_names_without_extension(root_path)
# name_mapping = obfuscate_code_in_files(all_files)
#
# # 获取常量映射
# constant_mapping = {} # 获取常量的映射
# for file_path in all_files:
# with open(file_path, 'r', encoding='utf-8') as f:
# lines = f.readlines()
# constants = get_constant_names("".join(lines), file_path)
# for constant in constants:
# if constant not in name_mapping:
# name_mapping[constant] = get_random_string()
# constant_mapping[constant] = name_mapping[constant]
#
# # 第一步:混淆代码,并获取所有混淆的名称映射
# name_mapping = obfuscate_code_in_files(all_files)
#
# # 第二步:替换其他文件中的引用,保证同样的混淆名
# replace_with_mapped_names(all_files, name_mapping, constant_mapping)
replace_package_in_imports
(
root_path
,
mapping
[
applicationId
],
applicationId
)
def
deal_res_type
(
res_type
):
type_mapping
=
check_res_mapping
(
res_type
)
if
len
(
type_mapping
)
==
0
:
return
print
(
type_mapping
)
res_path
=
get_path
(
'.
\\
src
\\
main
\\
res'
)
listdir
=
os
.
listdir
(
res_path
)
for
i
in
listdir
:
if
not
i
.
startswith
(
res_type
):
continue
path_join
=
os
.
path
.
join
(
res_path
,
i
)
for
j
in
os
.
listdir
(
path_join
):
if
j
.
startswith
(
'book_'
):
continue
if
j
.
endswith
(
'.9.png'
):
name
=
j
[:
-
len
(
'.9.png'
)]
else
:
name
=
j
.
rsplit
(
'.'
,
1
)[
0
]
path
=
os
.
path
.
join
(
path_join
,
j
)
if
'.DS_Store'
not
in
path
:
# shutil.move(path, path.replace(name, type_mapping[name]))
# 仅替换文件名部分
dir_path
,
file_name
=
os
.
path
.
split
(
path
)
new_file_name
=
file_name
.
replace
(
name
,
type_mapping
[
name
])
new_path
=
os
.
path
.
join
(
dir_path
,
new_file_name
)
# 打印路径调试信息
print
(
f
"源路径: {path}"
)
print
(
f
"目标路径: {new_path}"
)
# 移动文件
shutil
.
move
(
path
,
new_path
)
sub_map
=
{}
for
i
in
type_mapping
:
mapped
=
type_mapping
[
i
]
# 普通 R.xxx.xxx
sub_map
[
r'R\.'
+
res_type
+
r'\.'
+
re
.
escape
(
i
)
+
r'(\W)'
]
=
r'R.'
+
res_type
+
'.'
+
mapped
+
r'\1'
# # # 包名限定的 com.retrytech.ledgeapp.R.drawable.xxx
# sub_map[r'(\b[\w\.]+)\.R\.' + res_type + r'\.' + re.escape(
# i) + r'(\W)'] = r'\1.R.' + res_type + '.' + mapped + r'\2'
# sub_map[r'(\b[\w\.]+)\.R\.' + res_type + r'\.' + re.escape(
# i) + r'(?=[\s)\];,])'] = r'\1.R.' + res_type + '.' + mapped
# ✅ 推荐统一写法:以单词边界结尾,适配更多结尾符号(避免重复)
sub_map
[
rf
'(
\b
[
\
w
\
.]+)
\
.R
\
.{res_type}
\
.{re.escape(i)}
\b
'
]
=
rf
'
\1
.R.{res_type}.{mapped}'
# XML 资源:@drawable/xxx
sub_map
[
r'(>|")@'
+
res_type
+
r'/'
+
re
.
escape
(
i
)
+
r'(<|")'
]
=
r'\1@'
+
res_type
+
'/'
+
mapped
+
r'\2'
# DataBinding 表达式中:@{ condition ? @drawable/xxx :@drawable/yyy }
# # 匹配 ? 后的资源
# sub_map[r'(?<=\? *@' + res_type + r'\/)' + re.escape(i) + r'(?= *:)'] = mapped
# # 匹配 : 后的资源
# sub_map[r'(?<=: *@' + res_type + r'\/)' + re.escape(i) + r'(?= *})'] = mapped
# ? 后的资源
sub_map
[
r'(?<=\? *@'
+
res_type
+
r'/)'
+
re
.
escape
(
i
)
+
r'(?=\b)'
]
=
mapped
# : 后的资源
sub_map
[
r'(?<=: *@'
+
res_type
+
r'/)'
+
re
.
escape
(
i
)
+
r'(?=\b)'
]
=
mapped
# ✅ 通配 DataBinding 内的任意资源引用
sub_map
[
r'(@\{[^}]*?)@'
+
res_type
+
r'/'
+
re
.
escape
(
i
)
+
r'(?=[^}]*})'
]
=
r'\1@'
+
res_type
+
'/'
+
mapped
# 通用 DataBinding 表达式中任意位置(不拆三元)
# sub_map[r'(?<=@{[^}]*?)@' + res_type + r'/' + re.escape(i) + r'(?=[^}]*})'] = '@' + res_type + '/' + mapped
# sub_map['R\\.' + res_type + '\\.' + i + '(\\W)'] = 'R.' + res_type + '.' + type_mapping[i] + '\\g<1>'
# sub_map[
# r'(\b[\w\.]+)\.R\.' + res_type + r'\.' + i + r'(\W)'
# ] = r'\1.R.' + res_type + '.' + type_mapping[i] + r'\2'
#
# sub_map['(>|")@' + res_type + '/' + i + '(<|")'] = '\\g<1>@' + res_type + '/' + type_mapping[i] + '\\g<2>'
# # 适配 DataBinding 表达式中的 @drawable/xxx 写法
# sub_map[r'(?<=@{[^}]*?)@' + res_type + r'/' + i + r'(?=[^}]*})'] = '@' + res_type + '/' + type_mapping[i]
sub_map_text
(
sub_map
)
# 解决mipmip命名中含有大写字母造成的报错
# 'E' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore
def
generate_random_letter
():
# 生成一个随机小写字母
return
random
.
choice
(
string
.
ascii_lowercase
)
def
normalize_filename
(
name
):
# 将名称转为小写
normalized_name
=
name
.
lower
()
# 替换所有非法字符为随机字母
normalized_name
=
re
.
sub
(
r'[^a-z]'
,
lambda
match
:
generate_random_letter
(),
normalized_name
)
# 确保名称以字母开头,如果不是,则以随机字母开头
if
not
normalized_name
[
0
]
.
isalpha
():
normalized_name
=
generate_random_letter
()
+
normalized_name
[
1
:]
return
normalized_name
def
add_image_noise
(
path
):
try
:
image
=
cv2
.
imread
(
path
,
cv2
.
IMREAD_UNCHANGED
)
if
path
.
endswith
(
'.9.png'
):
return
# # 添加随机噪声
# noise = np.random.randint(0, 2, image.shape, np.uint8)
# image = cv2.add(image, noise)
# 添加 ±10 的随机噪声
noise
=
np
.
random
.
randint
(
0
,
2
,
image
.
shape
,
np
.
int16
)
# 噪声范围 [-10, 10]
noisy_image
=
np
.
clip
(
image
.
astype
(
np
.
int16
)
+
noise
,
0
,
255
)
.
astype
(
np
.
uint8
)
# 防止溢出
#计算原始图像与添加噪声后图像的差异:如果差值的范围包含 10(噪声范围),说明噪声添加成功。
difference
=
np
.
abs
(
image
.
astype
(
np
.
int16
)
-
noisy_image
.
astype
(
np
.
int16
))
print
(
"Difference range:"
,
difference
.
min
(),
"-"
,
difference
.
max
())
new_path
=
path
.
rsplit
(
'.'
,
1
)[
0
]
+
'.webp'
cv2
.
imwrite
(
new_path
,
noisy_image
,
[
cv2
.
IMWRITE_WEBP_QUALITY
,
75
])
if
new_path
!=
path
:
os
.
remove
(
path
)
except
AttributeError
:
return
def
deal_image
():
res_path
=
get_path
(
'.
\\
src
\\
main
\\
res'
)
listdir
=
os
.
listdir
(
res_path
)
for
i
in
listdir
:
path_join
=
os
.
path
.
join
(
res_path
,
i
)
if
os
.
path
.
isdir
(
path_join
):
for
j
in
os
.
listdir
(
path_join
):
# add_image_noise(os.path.join(path_join, j))
if
j
.
endswith
((
'.png'
,
'.jpg'
,
'.jpeg'
,
'.bmp'
,
'.tiff'
,
'.webp'
)):
add_image_noise
(
os
.
path
.
join
(
path_join
,
j
))
def
get_title
(
string
,
is_all_upper
=
False
):
splits
=
string
.
split
(
'_'
)
print
(
splits
)
s
=
''
for
i
in
range
(
len
(
splits
)):
if
i
==
0
and
not
is_all_upper
:
s
=
splits
[
i
]
elif
len
(
splits
[
i
])
>
0
:
s
+=
splits
[
i
][
0
]
.
upper
()
+
splits
[
i
][
1
:]
return
s
def
add_import
(
path
,
listfile
):
for
i
in
listfile
:
if
'databinding'
in
path
:
# 跳过 databinding
continue
with
open
(
i
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
text
=
f
.
read
()
if
len
(
re
.
findall
(
'import '
+
path
,
text
))
!=
0
:
continue
if
i
.
endswith
(
'kt'
):
text
=
re
.
sub
(
'(package
\\
S+?
\\
s+?)(?=
\\
S)'
,
'
\\
g<1>import '
+
path
+
'
\n
'
,
text
,
flags
=
re
.
S
)
print
(
text
)
elif
i
.
endswith
(
'java'
):
text
=
re
.
sub
(
'(package
\\
S+?
\\
s+?)(?=
\\
S)'
,
'
\\
g<1>import '
+
path
+
';
\n
'
,
text
,
flags
=
re
.
S
)
with
open
(
i
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
text
)
def
check_class_string_mapping
():
try
:
loads
=
json
.
load
(
open
(
'mapping.json'
,
'r'
,
encoding
=
'utf-8'
))
except
(
ValueError
,
IOError
):
loads
=
{}
mapping
=
loads
.
get
(
'class_string'
,
{})
classes
=
get_classes
([
'kt'
])
for
path
in
classes
:
if
os
.
path
.
basename
(
path
)
.
startswith
(
'AESHelper'
):
continue
with
open
(
path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
text
=
f
.
read
()
text
=
re
.
sub
(
'@SuppressLint.*?
\n
'
,
'
\n
'
,
text
)
text
=
re
.
sub
(
'//[^"]*?(("[^"]*?){2})*?
\n
'
,
'
\n
'
,
text
)
text
=
re
.
sub
(
' +
\n
'
,
'
\n
'
,
text
)
text
=
re
.
sub
(
'=
\\
s+'
,
'= '
,
text
)
text
=
re
.
sub
(
':
\\
s+'
,
': '
,
text
)
text
=
re
.
sub
(
',
\\
s+'
,
', '
,
text
)
text
=
re
.
sub
(
'
\n
{2,}'
,
'
\n\n
'
,
text
)
result_text
=
text
strings
=
re
.
findall
(
'".*?[^
\\\\
]"'
,
re
.
sub
(
r'@[^)]*'
,
lambda
x
:
x
.
group
()
.
replace
(
'"'
,
"'"
),
result_text
))
last_string
=
''
for
string
in
strings
:
if
len
(
string
)
<=
4
or
string
.
isspace
()
or
\
string
[
1
:
-
1
]
in
[
'
\\\\
n'
,
'
\\\\
r'
,
'
\\\\\'
'
,
'
\\\\\\
"'
,
'
\\\\
?'
,
'&'
,
'UTF-8'
]:
continue
if
string
in
last_string
:
continue
start
=
text
.
find
(
string
)
if
start
==
-
1
:
continue
index
=
start
+
1
sign_stack
=
[
'"'
]
while
len
(
sign_stack
)
!=
0
:
if
text
[
index
]
==
'
\\
'
:
index
+=
1
elif
text
[
index
]
==
'"'
and
sign_stack
[
-
1
]
==
'"'
:
sign_stack
.
pop
()
elif
text
[
index
]
==
'"'
:
sign_stack
.
append
(
'"'
)
elif
text
[
index
]
==
'}'
and
sign_stack
[
-
1
]
==
'${'
:
sign_stack
.
pop
()
elif
text
[
index
-
1
:
index
+
1
]
==
'${'
:
sign_stack
.
append
(
'${'
)
index
+=
1
string
=
text
[
start
:
index
]
last_string
=
string
params
=
[]
result
=
string
if
'$'
in
string
:
count
=
0
last
=
-
1
res
=
[]
for
i
in
range
(
len
(
string
)):
if
last
==
-
1
and
string
[
i
]
==
'$'
:
if
count
==
0
:
last
=
i
elif
last
!=
-
1
:
if
string
[
i
]
==
'{'
:
count
+=
1
elif
string
[
i
]
==
'}'
:
count
-=
1
if
count
==
0
:
res
.
append
((
last
,
i
+
1
))
last
=
-
1
elif
count
==
0
:
last
=
-
1
for
i
in
re
.
findall
(
'
\\
$
\\
w*'
,
string
):
if
i
==
'$'
:
continue
find
=
string
.
find
(
i
)
res
.
append
((
find
,
find
+
len
(
i
)))
res
=
sorted
(
res
,
key
=
lambda
l
:
l
[
0
])
last_res
=
None
for
i
in
res
:
if
last_res
is
not
None
and
i
[
0
]
<
last_res
[
1
]:
continue
params
.
append
(
string
[
i
[
0
]:
i
[
1
]])
last_res
=
i
for
i
in
params
:
result
=
result
.
replace
(
i
,
"[str]"
)
result
=
result
[
1
:
-
1
]
.
replace
(
'
%
'
,
'
%%
'
)
.
replace
(
'
\\\\
'
,
'
\\
'
)
.
replace
(
'[str]'
,
'
%
s'
)
if
mapping
.
get
(
result
)
is
not
None
:
continue
mapping
[
result
]
=
get_random_string
(
5
,
False
)
+
'_D'
loads
[
'class_string'
]
=
mapping
json
.
dump
(
loads
,
open
(
'mapping.json'
,
'w'
,
encoding
=
'utf-8'
),
indent
=
4
)
return
mapping
def
deal_class_string
():
# 获取映射
string_mapping
=
check_class_string_mapping
()
print
(
string_mapping
)
with
open
(
get_path
(
'.
\\
src
\\
main
\\
res
\\
values
\\
strings.xml'
),
'r'
,
encoding
=
'utf-8'
)
as
f
:
text
=
f
.
read
()
rfind
=
text
.
rfind
(
'
\n
'
)
text_result
=
text
[:
rfind
]
for
i
in
string_mapping
:
string
=
i
string
=
string
.
replace
(
'&'
,
'&'
)
string
=
string
.
replace
(
'?'
,
'
\\
?'
)
string
=
string
.
replace
(
'
\'
'
,
'
\\\'
'
)
text_result
+=
'
\n
<string name="'
+
string_mapping
[
i
]
+
'" translatable="false">'
+
string
+
'</string>'
text_result
+=
text
[
rfind
:]
with
open
(
get_path
(
'.
\\
src
\\
main
\\
res
\\
values
\\
strings.xml'
),
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
text_result
)
# 搜索 Int.string() 路径
file_path
=
find_file
(
'fun Int.string'
,
get_path
(
'.
\\
src
\\
main
\\
java'
))
class_path
=
get_class_path
(
file_path
)
+
'.string'
if
file_path
else
""
classes
=
get_classes
([
'kt'
])
for
path
in
classes
:
# 跳过 AESHelper 类中的字符串,避免嵌套
if
os
.
path
.
basename
(
path
)
.
startswith
(
'AESHelper'
):
continue
with
open
(
path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
text
=
f
.
read
()
text
=
re
.
sub
(
'@SuppressLint.*?
\n
'
,
'
\n
'
,
text
)
text
=
re
.
sub
(
'//[^"]*?(("[^"]*?){2})*?
\n
'
,
'
\n
'
,
text
)
text
=
re
.
sub
(
' +
\n
'
,
'
\n
'
,
text
)
text
=
re
.
sub
(
'=
\\
s+'
,
'= '
,
text
)
text
=
re
.
sub
(
':
\\
s+'
,
': '
,
text
)
text
=
re
.
sub
(
',
\\
s+'
,
', '
,
text
)
text
=
re
.
sub
(
'
\n
{2,}'
,
'
\n\n
'
,
text
)
result_text
=
text
strings
=
re
.
findall
(
'".*?[^
\\\\
]"'
,
re
.
sub
(
r'@[^)]*'
,
lambda
x
:
x
.
group
()
.
replace
(
'"'
,
"'"
),
result_text
))
last_string
=
''
for
string
in
strings
:
if
len
(
string
)
<=
4
or
string
.
isspace
()
or
\
string
[
1
:
-
1
]
in
[
'
\\\\
n'
,
'
\\\\
r'
,
'
\\\\\'
'
,
'
\\\\\\
"'
,
'
\\\\
?'
,
'&'
,
'UTF-8'
]:
continue
if
string
in
last_string
:
continue
start
=
text
.
find
(
string
)
if
start
==
-
1
:
continue
index
=
start
+
1
sign_stack
=
[
'"'
]
while
len
(
sign_stack
)
!=
0
:
if
text
[
index
]
==
'
\\
'
:
index
+=
1
elif
text
[
index
]
==
'"'
and
sign_stack
[
-
1
]
==
'"'
:
sign_stack
.
pop
()
elif
text
[
index
]
==
'"'
:
sign_stack
.
append
(
'"'
)
elif
text
[
index
]
==
'}'
and
sign_stack
[
-
1
]
==
'${'
:
sign_stack
.
pop
()
elif
text
[
index
-
1
:
index
+
1
]
==
'${'
:
sign_stack
.
append
(
'${'
)
index
+=
1
# 得到完整字符串
string
=
text
[
start
:
index
]
last_string
=
string
# 参数
params
=
[]
result
=
string
# 处理字符串拼接
if
'$'
in
string
:
count
=
0
last
=
-
1
res
=
[]
for
i
in
range
(
len
(
string
)):
if
last
==
-
1
and
string
[
i
]
==
'$'
:
if
count
==
0
:
last
=
i
elif
last
!=
-
1
:
if
string
[
i
]
==
'{'
:
count
+=
1
elif
string
[
i
]
==
'}'
:
count
-=
1
if
count
==
0
:
res
.
append
((
last
,
i
+
1
))
last
=
-
1
elif
count
==
0
:
last
=
-
1
for
i
in
re
.
findall
(
'
\\
$
\\
w*'
,
string
):
if
i
==
'$'
:
continue
find
=
string
.
find
(
i
)
res
.
append
((
find
,
find
+
len
(
i
)))
res
=
sorted
(
res
,
key
=
lambda
l
:
l
[
0
])
last_res
=
None
for
i
in
res
:
if
last_res
is
not
None
and
i
[
0
]
<
last_res
[
1
]:
continue
params
.
append
(
string
[
i
[
0
]:
i
[
1
]])
last_res
=
i
for
i
in
params
:
result
=
result
.
replace
(
i
,
"[str]"
)
for
i
in
range
(
len
(
params
)):
if
params
[
i
][
1
]
==
'{'
:
params
[
i
]
=
params
[
i
][
2
:
-
1
]
else
:
params
[
i
]
=
params
[
i
][
1
:]
# 每个参数加上 toString
params
[
i
]
=
'('
+
params
[
i
]
+
').toString()'
# 拼接部分替换为 %s
result
=
result
[
1
:
-
1
]
.
replace
(
'
%
'
,
'
%%
'
)
.
replace
(
'
\\\\
'
,
'
\\
'
)
.
replace
(
'[str]'
,
'
%
s'
)
# 添加 Int.string() 方法引用
if
class_path
:
if
len
(
re
.
findall
(
'import '
+
class_path
,
text
))
==
0
:
result_text
=
re
.
sub
(
'(package
\\
S+?
\\
s+?)(?=
\\
S)'
,
'
\\
g<1>import '
+
class_path
+
'
\n
'
,
result_text
,
flags
=
re
.
S
)
# 添加资源 R 引用
if
len
(
re
.
findall
(
'import '
+
applicationId
+
'.R'
,
text
))
==
0
:
result_text
=
re
.
sub
(
'(package
\\
S+?
\\
s+?)(?=
\\
S)'
,
'
\\
g<1>import '
+
applicationId
+
'.R'
+
'
\n
'
,
result_text
,
flags
=
re
.
S
)
# 去除 const
result_text
=
re
.
sub
(
'const (val.*?R
\\
.string
\\
.
\\
S+?
\\
.string
\\
(
\\
))'
,
'
\\
g<1>'
,
result_text
)
with
open
(
path
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
result_text
)
def
check_xml_string_mapping
():
try
:
loads
=
json
.
load
(
open
(
'mapping.json'
,
'r'
,
encoding
=
'utf-8'
))
except
(
ValueError
,
IOError
):
loads
=
{}
old_mapping
=
loads
.
get
(
'xml_string'
,
{})
strings
=
find_text
(
'android:text="(?<!@string/)([^@"]+?)"'
,
get_path
(
'.
\\
src
\\
main
\\
res
\\
layout'
),
skip_name
=
[
'notify'
,
'notity'
])
mapping
=
{}
for
i
in
strings
:
value
=
old_mapping
.
get
(
i
)
if
value
is
not
None
:
mapping
[
i
]
=
value
continue
# 添加 '_D' 结尾,资源混淆时过滤不用再次改名
mapping
[
i
]
=
get_random_string
(
5
,
False
)
+
'_D'
loads
[
'xml_string'
]
=
mapping
json
.
dump
(
loads
,
open
(
'mapping.json'
,
'w'
,
encoding
=
'utf-8'
),
indent
=
4
)
return
mapping
def
deal_xml_string
():
# 获取映射
string_mapping
=
check_xml_string_mapping
()
print
(
string_mapping
)
with
open
(
get_path
(
'.
\\
src
\\
main
\\
res
\\
values
\\
strings.xml'
),
'r'
,
encoding
=
'utf-8'
)
as
f
:
text
=
f
.
read
()
rfind
=
text
.
rfind
(
'
\n
'
)
text_result
=
text
[:
rfind
]
for
i
in
string_mapping
:
string
=
i
string
=
string
.
replace
(
'&'
,
'&'
)
string
=
string
.
replace
(
'?'
,
'
\\
?'
)
string
=
string
.
replace
(
'
\'
'
,
'
\\\'
'
)
text_result
+=
'
\n
<string name="'
+
string_mapping
[
i
]
+
'" translatable="false">'
+
string
+
'</string>'
text_result
+=
text
[
rfind
:]
with
open
(
get_path
(
'.
\\
src
\\
main
\\
res
\\
values
\\
strings.xml'
),
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
text_result
)
sub_map
=
{}
for
i
in
string_mapping
:
sub_map
[
'(?<=android:text=")'
+
re
.
escape
(
i
)
+
'(?=")'
]
=
'@string/'
+
re
.
escape
(
string_mapping
[
i
])
# 替换时跳过通知布局
sub_map_text
(
sub_map
,
path
=
get_path
(
'.
\\
src
\\
main
\\
res
\\
layout'
),
skip_name
=
[
'notify'
,
'notity'
])
def
deal_code_string
():
# 处理布局文件中的明文字符串
deal_xml_string
()
# 处理代码文件中的明文字符串
deal_class_string
()
def
deal_res
():
# 改 string id
deal_ids_type
(
'string'
)
# 改 color id
deal_ids_type
(
'color'
)
# 改 dimen id
deal_ids_type
(
'dimen'
)
# 改 style id
deal_ids_type
(
'style'
)
# 改 declare-styleable
styleable_mapping
=
check_styleable_mapping
()
print
(
styleable_mapping
)
sub_map
=
{}
styleables
=
find_text
(
'<declare-styleable name=".*?">[
\\
s
\\
S]*?</declare-styleable>'
)
for
i
in
styleables
:
result
=
i
styleable_id
=
re
.
findall
(
'<declare-styleable name="(.*?)">'
,
i
)[
0
]
result
=
result
.
replace
(
'<declare-styleable name="'
+
styleable_id
+
'">'
,
'<declare-styleable name="'
+
styleable_mapping
[
styleable_id
]
+
'">'
)
sub_map
[
'R
\\
.styleable
\\
.'
+
styleable_id
+
'(
\\
W)'
]
=
\
'R.styleable.'
+
styleable_mapping
[
styleable_id
]
+
'
\\
g<1>'
# styleables_attr = re.findall('<attr name="(.*?)".*?/>', i)
styleables_attr
=
re
.
findall
(
'<attr name="(.*?)".*?>'
,
i
)
# 包括单行和多行定义
for
attr
in
styleables_attr
:
# else:
# new_attr = attr
result
=
result
.
replace
(
'<attr name="'
+
attr
+
'"'
,
'<attr name="'
+
styleable_mapping
[
attr
+
'_A'
]
+
'"'
)
sub_map
[
'R
\\
.styleable
\\
.'
+
styleable_id
+
'_'
+
attr
+
'(
\\
W)'
]
=
\
'R.styleable.'
+
styleable_mapping
[
styleable_id
]
+
'_'
+
styleable_mapping
[
attr
+
'_A'
]
+
'
\\
g<1>'
sub_map
[
'app:'
+
attr
+
'='
]
=
'app:'
+
styleable_mapping
[
attr
+
'_A'
]
+
'='
sub_map
[
'
\\
?attr/'
+
attr
]
=
'?attr/'
+
styleable_mapping
[
attr
+
'_A'
]
# 新增的替换规则
sub_map
[
'<item name=
\"
'
+
attr
+
'
\"
'
]
=
'<item name=
\"
'
+
styleable_mapping
[
attr
+
'_A'
]
+
'
\"
'
# 新增的替换规则
# 判断 attr 是否以 "android:" 开头
if
attr
.
startswith
(
"android:"
):
new_attr
=
"android_"
+
attr
[
8
:]
# 去掉 "android:" 并替换为 "android_"
sub_map
[
'R
\\
.styleable
\\
.'
+
styleable_id
+
'_'
+
new_attr
+
'(
\\
W)'
]
=
\
'R.styleable.'
+
styleable_mapping
[
styleable_id
]
+
'_'
+
styleable_mapping
[
attr
+
'_A'
]
+
'
\\
g<1>'
sub_map
[
re
.
escape
(
i
)]
=
result
sub_map_text
(
sub_map
)
# 改 view id
view_ids
=
find_text
(
'"
\\
@
\\
+id/(.*?)"'
,
get_path
(
'.
\\
src
\\
main
\\
res'
))
view_ids_mapping
=
check_view_ids_mapping
()
print
(
view_ids_mapping
)
sub_map
=
{}
for
i
in
view_ids
:
if
i
==
'root'
:
continue
key
=
get_title
(
i
)
sub_map
[
'(>|")@(
\\
+)?id/'
+
i
+
'(<|")'
]
=
'
\\
g<1>@
\\
g<2>id/'
+
view_ids_mapping
[
key
]
+
'
\\
g<3>'
sub_map
[
'R
\\
.id
\\
.'
+
i
+
'(
\\
W)'
]
=
'R.id.'
+
view_ids_mapping
[
key
]
+
'
\\
g<1>'
sub_map
[
'([bB]inding
\\
??(?:
\\
.
\\
w+)?
\\
??)
\\
.'
+
key
+
'(
\\
W)'
]
=
'
\\
g<1>.'
+
view_ids_mapping
[
key
]
+
'
\\
g<2>'
sub_map
[
'([bB]inding
\\
??(?:
\\
.
\\
w+)?
\\
??)
\\
.'
+
key
+
'
\\
.'
+
key
+
'(
\\
W)'
]
=
'
\\
g<1>.'
+
view_ids_mapping
[
key
]
+
'.'
+
view_ids_mapping
[
key
]
+
'
\\
g<2>'
sub_map
[
r'\(binding as (\w+Binding)\)\.'
+
key
+
r'(\W)'
]
=
r'(binding as \1).'
+
view_ids_mapping
[
key
]
+
r'\2'
sub_map
[
'(?<=app:constraint_referenced_ids=".*?)'
+
i
+
'(?=.*?")'
]
=
view_ids_mapping
[
key
]
# 新增 Kotlin 特有写法适配
sub_map
[
r'(binding!!(?:\.\w+)*?)\.'
+
key
+
r'(\W)'
]
=
r'\1.'
+
view_ids_mapping
[
key
]
+
r'\2'
sub_map
[
r'(binding\?\?(?:\.\w+)*?)\.'
+
key
+
r'(\W)'
]
=
r'\1.'
+
view_ids_mapping
[
key
]
+
r'\2'
sub_map
[
r'(binding(?:\.\w+)*?)\.'
+
key
+
r'(\W)'
]
=
r'\1.'
+
view_ids_mapping
[
key
]
+
r'\2'
sub_map
[
'this
\\
.'
+
i
+
'(
\\
W)'
]
=
'this.'
+
view_ids_mapping
[
key
]
+
'
\\
g<1>'
sub_map
[
'this
\\
.'
+
i
+
'(
\\
.[
\\
w
\\
?]+(?:
\\
([^)]*
\\
))?)'
]
=
'this.'
+
view_ids_mapping
[
key
]
+
'
\\
g<1>'
# sub_map['this\\.(\\w+)(\\.[\\w\\?]+(?:\\([^)]*\\))?)'] = 'this.' + view_ids_mapping[key] + '\\g<1>\\g<2>'
sub_map
[
'popupView
\\
.'
+
i
+
'(
\\
W)'
]
=
'popupView.'
+
view_ids_mapping
[
key
]
+
'
\\
g<1>'
# sub_map['\\b' + i + '\\.(\\w+)(\\s*=\\s*[^\\s]+)'] = view_ids_mapping[key] + '.\\g<1>\\g<2>'
sub_map
[
'(?<!
\\
()
\\
b'
+
i
+
'
\\
.(
\\
w+)(
\\
s*=
\\
s*[^
\\
s]+)'
]
=
view_ids_mapping
[
key
]
+
'.
\\
g<1>
\\
g<2>'
# sub_map['this\\.' + view_ids_mapping['title']] = 'this.title'
sub_map
[
r'\.into\(\s*this\.(imgIcon)\s*\)'
]
=
r'.into(this.'
+
view_ids_mapping
[
'imgIcon'
]
+
')'
sub_map_text
(
sub_map
)
# 改 layout 文件名
layout_mapping
=
check_layout_mapping
()
print
(
layout_mapping
)
layout_path
=
get_path
(
'.
\\
src
\\
main
\\
res
\\
layout'
)
listdir
=
os
.
listdir
(
layout_path
)
for
i
in
listdir
:
name
=
i
[:
i
.
rfind
(
'.'
)]
if
layout_mapping
.
get
(
name
)
is
None
:
continue
path_join
=
os
.
path
.
join
(
layout_path
,
i
)
#解决layout出现大写的问题
shutil
.
move
(
path_join
,
path_join
.
replace
(
name
,
normalize_filename
(
layout_mapping
[
name
])))
sub_map
=
{}
for
i
in
layout_mapping
:
sub_map
[
'R
\\
.layout
\\
.'
+
i
+
'(
\\
W)'
]
=
'R.layout.'
+
layout_mapping
[
i
]
+
'
\\
g<1>'
# ViewBinding 同步修改
sub_map
[
get_title
(
i
,
True
)
+
'Binding'
]
=
get_title
(
layout_mapping
[
i
],
True
)
+
'Binding'
sub_map
[
'(>|")@layout/'
+
i
+
'(<|")'
]
=
'
\\
g<1>@layout/'
+
layout_mapping
[
i
]
+
'
\\
g<2>'
sub_map_text
(
sub_map
)
# 改 drawable 文件名
deal_res_type
(
'drawable'
)
# 改 mipmap 文件名
deal_res_type
(
'mipmap'
)
# 改 raw 文件名
deal_res_type
(
'raw'
)
# 改 raw 文件名
deal_res_type
(
'xml'
)
# 改图片内容
deal_image
()
# 主执行逻辑
mapping_file
=
"./mapping.json"
java_path
=
get_path
(
'.
\\
src
\\
main
\\
java'
)
mapping_data
=
load_color_mapping
(
mapping_file
)
# 调用正确的函数
# 调试信息:检查 color_ids_mapping 内容
print
(
"Color IDs Mapping:"
)
for
original_color
,
mapped_color
in
mapping_data
.
items
():
print
(
f
"{original_color}: {mapped_color}"
)
# 进行文件处理
process_color_files
(
java_path
,
mapping_data
)
def
process_color_files
(
src_path
,
color_ids
):
for
root
,
dirs
,
files
in
os
.
walk
(
src_path
):
for
file
in
files
:
if
file
.
endswith
(
".kt"
)
or
file
.
endswith
(
".java"
):
file_path
=
os
.
path
.
join
(
root
,
file
)
print
(
f
"Processing file: {file_path}"
)
replace_color_ids_in_file
(
file_path
,
color_ids
)
def
replace_color_ids_in_file
(
file_path
,
color_ids_mapping
):
print
(
"replace_color_ids_in_file"
)
print
(
color_ids_mapping
)
with
open
(
file_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
content
=
f
.
read
()
if
"paperedto"
in
content
:
print
(
f
"Found 'paperedto' in: {file_path}"
)
if
"android.R.color.paperedto"
in
content
:
print
(
f
"Found 'android.R.color.paperedto' in: {file_path}"
)
changed
=
False
for
original_color
,
mapped_color
in
color_ids_mapping
.
items
():
print
(
'original_color'
+
original_color
)
print
(
'mapped_color'
+
mapped_color
)
if
mapped_color
in
content
:
pattern
=
r'android.R.color.'
+
re
.
escape
(
mapped_color
)
+
r'(?=[^\w])'
print
(
pattern
)
replacement
=
'android.R.color.'
+
original_color
print
(
replacement
)
if
re
.
search
(
pattern
,
content
):
print
(
f
"Matched: {mapped_color} → {original_color}"
)
matches
=
re
.
findall
(
pattern
,
content
)
if
matches
:
print
(
f
"Matched: {mapped_color} → {original_color} ({len(matches)} times)"
)
content
=
re
.
sub
(
pattern
,
replacement
,
content
)
changed
=
True
if
changed
:
with
open
(
file_path
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
content
)
def
get_path
(
path
):
paths
=
path
.
split
(
'
\\
'
)
return
str
(
os
.
path
.
join
(
*
paths
))
def
main
():
if
not
os
.
path
.
exists
(
get_path
(
'.
\\
src
\\
main
\\
java
\\
'
+
applicationId
[:
applicationId
.
find
(
'.'
)])):
print
(
get_path
(
'.
\\
src
\\
main
\\
java
\\
'
+
applicationId
[:
applicationId
.
find
(
'.'
)]))
return
# 资源混淆
deal_res
()
deal_code
()
def
load_mapping
(
file_path
):
"""加载 mapping.json 文件并打印内容."""
try
:
with
open
(
file_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
data
=
json
.
load
(
f
)
print
(
"Loaded Mapping:"
)
print
(
json
.
dumps
(
data
,
indent
=
4
,
ensure_ascii
=
False
))
# 打印映射内容
return
data
.
get
(
"view_ids"
,
{})
except
(
FileNotFoundError
,
json
.
JSONDecodeError
)
as
e
:
print
(
f
"Error loading mapping file: {e}"
)
return
{}
def
replace_identifiers_in_file
(
file_path
,
mapping
,
replacements_log
):
"""替换单个文件中的标识符,并存储替换的内容到 JSON 日志."""
with
open
(
file_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
original_text
=
f
.
read
()
print
(
f
"
\n
Processing File: {file_path}"
)
print
(
"Original Content:"
)
print
(
original_text
)
# 打印文件原始内容
modified_text
=
original_text
replacements
=
[]
# 替换所有绑定相关的引用
def
replace_binding_references
(
match
):
identifier
=
match
.
group
(
2
)
or
match
.
group
(
3
)
return
f
"{match.group(1)}.{mapping.get(identifier, identifier)}"
# 替换类似 `binding.tvTest1` 和 `it.tvTest1` 的结构
modified_text
=
re
.
sub
(
r'\b(binding|it)\.([a-zA-Z_0-9]+)\b'
,
replace_binding_references
,
modified_text
)
# 替换无前缀的变量名,如 `tvTest1` 和 `tvTest2`,根据 mapping 文件中的 view_ids 替换
def
replace_variable_names
(
match
):
identifier
=
match
.
group
(
1
)
# 使用 mapping 中的 view_ids 替换变量名
new_identifier
=
mapping
.
get
(
identifier
,
identifier
)
if
new_identifier
!=
identifier
:
replacements
.
append
({
"original"
:
identifier
,
"modified"
:
new_identifier
})
return
new_identifier
# 先提取所有引号中的内容
quoted_texts
=
re
.
findall
(
r'"([^"]*)"'
,
modified_text
)
# 将引号中的内容临时替换为占位符
placeholders
=
[
f
"__PLACEHOLDER_{i}__"
for
i
in
range
(
len
(
quoted_texts
))]
for
i
,
quoted
in
enumerate
(
quoted_texts
):
modified_text
=
modified_text
.
replace
(
f
'"{quoted}"'
,
placeholders
[
i
])
# 过滤掉方法调用中的标识符(例如 taichiPref.edit() 不替换 edit)
def
is_function_call
(
match
):
# 检查匹配的标识符后是否跟着 `()`
return
f
"{match.group(1)}()"
if
match
.
group
(
2
)
else
None
# 替换变量名,排除函数调用
modified_text
=
re
.
sub
(
r'\b('
+
'|'
.
join
(
mapping
.
keys
())
+
r')\b(?!\()'
,
replace_variable_names
,
modified_text
)
# 将占位符替换回原始引号内容
for
i
,
quoted
in
enumerate
(
quoted_texts
):
modified_text
=
modified_text
.
replace
(
placeholders
[
i
],
f
'"{quoted}"'
)
# 过滤重复的替换项:根据 (original, modified) 进行去重
seen_replacements
=
set
()
unique_replacements
=
[]
for
replacement
in
replacements
:
replacement_tuple
=
(
replacement
[
"original"
],
replacement
[
"modified"
])
if
replacement_tuple
not
in
seen_replacements
:
unique_replacements
.
append
(
replacement
)
seen_replacements
.
add
(
replacement_tuple
)
if
unique_replacements
:
replacements_log
[
file_path
]
=
unique_replacements
# 写入修改后的内容
with
open
(
file_path
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
modified_text
)
else
:
print
(
"No replacements made."
)
def
process_files_in_directory
(
directory
,
mapping
,
replacements_log
):
"""处理目录下的所有文件并打印文件路径."""
for
root
,
_
,
files
in
os
.
walk
(
directory
):
for
file
in
files
:
if
file
.
endswith
(
".kt"
):
# 仅处理 Kotlin 文件
file_path
=
os
.
path
.
join
(
root
,
file
)
replace_identifiers_in_file
(
file_path
,
mapping
,
replacements_log
)
def
solve_runapplyalso
():
# 配置路径
mapping_file
=
"mapping.json"
src_directory
=
"./src/main/java"
log_file
=
"replacement_log.json"
# 加载映射表
mapping
=
load_mapping
(
mapping_file
)
# 检查路径
if
not
os
.
path
.
exists
(
src_directory
):
print
(
f
"Source directory not found: {src_directory}"
)
elif
not
mapping
:
print
(
"Mapping file is empty or not found."
)
else
:
# 存储替换日志
replacements_log
=
{}
# 开始处理文件
process_files_in_directory
(
src_directory
,
mapping
,
replacements_log
)
# 将替换日志保存为 JSON 文件
with
open
(
log_file
,
'w'
,
encoding
=
'utf-8'
)
as
log
:
json
.
dump
(
replacements_log
,
log
,
ensure_ascii
=
False
,
indent
=
4
)
print
(
f
"
\n
Replacement completed. Log saved to {log_file}."
)
def
load_color_mapping
(
file_path
):
"""加载 mapping.json 文件并打印内容."""
try
:
with
open
(
file_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
data
=
json
.
load
(
f
)
print
(
"Loaded color Mapping:"
)
print
(
json
.
dumps
(
data
,
indent
=
4
,
ensure_ascii
=
False
))
# 打印映射内容
color_ids
=
data
.
get
(
"color_ids"
,
{})
print
(
f
"color_ids extracted: {color_ids}"
)
# 检查 color_ids 是否存在
return
color_ids
except
(
FileNotFoundError
,
json
.
JSONDecodeError
)
as
e
:
print
(
f
"Error loading mapping file: {e}"
)
return
{}
if
__name__
==
'__main__'
:
if
os
.
path
.
exists
(
'build.gradle'
):
gradle_path
=
'build.gradle'
elif
os
.
path
.
exists
(
'build.gradle.kts'
):
gradle_path
=
'../build.gradle.kts'
else
:
exit
(
'找不到 build.gradle 文件'
)
applicationId
=
re
.
search
(
'namespace .*?["
\'
](.*?)["
\'
]'
,
open
(
gradle_path
,
'r'
,
encoding
=
'utf-8'
)
.
read
())[
1
]
print
(
applicationId
)
main
()
# solve_runapplyalso()
app/word_file.json
0 → 100644
View file @
c5b183f7
This source diff could not be displayed because it is too large. You can
view the blob
instead.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment