reproto 0.0.8__py3-none-any.whl → 0.0.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- .git/COMMIT_EDITMSG +14 -1
- .git/FETCH_HEAD +2 -0
- .git/index +0 -0
- .git/logs/HEAD +4 -0
- .git/logs/refs/heads/iyue +4 -0
- .git/logs/refs/remotes/gitlab/iyue +4 -0
- .git/logs/refs/remotes/origin/iyue +4 -0
- .git/objects/29/4708b82b343e04e7a6685f5cd1287f3d17f7a9 +0 -0
- .git/objects/2c/fcddd7eebeb4eee0562dc384a79366bc7b04bb +0 -0
- .git/objects/34/192f0df5f5b694c881d086019eda349608d222 +0 -0
- .git/objects/3e/d953d5ed2c66722045f5e39d068696a0853b4f +0 -0
- .git/objects/44/4af3a0d68ba81976f67c7b638c9b0db4640709 +0 -0
- .git/objects/4b/43f5f577e1cdc35f8e06d178d8f5c892034061 +0 -0
- .git/objects/4c/16984be6ef7b24d01604821e9728cb579acd69 +0 -0
- .git/objects/56/79064a3138031d3a92d60aa629b82009fd0d1d +0 -0
- .git/objects/58/0d8f872aa869e42ba608f64888b1349f8b3ff4 +0 -0
- .git/objects/5a/11f9d70791e06a0570e01d3dcbbae39cde55db +0 -0
- .git/objects/5c/715dcb05e32db7a7a3b030f07524bdd8a56849 +0 -0
- .git/objects/5d/e2e9d536c2c0b78e8f9b3b61daa531a55332dc +0 -0
- .git/objects/66/7291e131d4769e7d028346a0cc7a0c05d500e5 +0 -0
- .git/objects/67/f54a4a3ede6749acc7c718ad97a86634215b5e +0 -0
- .git/objects/70/ded03ee69f30850c938a8129be308cb30772d9 +0 -0
- .git/objects/78/3e7e252c20e73e33615c703174766036546ff6 +3 -0
- .git/objects/94/ce01b61b90d1fb21d2d339fbeb22e821b6f413 +0 -0
- .git/objects/97/71dd4958faa94d3db229c129f6af35b508905a +0 -0
- .git/objects/9e/9978522bc8ca79133d7c11ef8ca3fe3c7eed0a +0 -0
- .git/objects/9f/b57064e0c53ed80af8507acaab718a1e80184e +0 -0
- .git/objects/b3/28dc445ee33220db9359370fc0089a77174101 +0 -0
- .git/objects/b3/d2b3037bede44e7e4d18dc99419f8c712c9c62 +0 -0
- .git/objects/c3/93db4841dbbb8acf54e9af12b6705c9f5ecde9 +0 -0
- .git/objects/c3/c8594874dd9ff9c21662fd06cac9b5baadbba0 +0 -0
- .git/objects/c9/a6ca8f8efee4a5632e9a655ced29f1b708f35e +0 -0
- .git/objects/c9/cdef9ab627b874ffe6455a47583a75bf16496d +0 -0
- .git/objects/df/f8f64cee2b97df7d86f73207c5e690f98f0208 +0 -0
- .git/objects/eb/528f06c622d54e411e9e05b3a200b4ac624a90 +0 -0
- .git/objects/fd/267d9de63212db235135fa6834f62572224fc6 +0 -0
- .git/refs/heads/iyue +1 -1
- .git/refs/remotes/gitlab/iyue +1 -1
- .git/refs/remotes/origin/iyue +1 -1
- .gitignore +4 -1
- ARCHITECTURE.md +146 -48
- README.md +145 -24
- core/bytecode_parser.py +2 -5
- core/info_decoder.py +9 -83
- core/reconstructor.py +91 -211
- generation/proto_generator.py +6 -61
- parsing/java_parser.py +219 -46
- pyproject.toml +1 -1
- reproto-0.0.9.dist-info/METADATA +295 -0
- {reproto-0.0.8.dist-info → reproto-0.0.9.dist-info}/RECORD +53 -22
- utils/type_utils.py +414 -0
- reproto-0.0.8.dist-info/METADATA +0 -174
- {reproto-0.0.8.dist-info → reproto-0.0.9.dist-info}/WHEEL +0 -0
- {reproto-0.0.8.dist-info → reproto-0.0.9.dist-info}/entry_points.txt +0 -0
parsing/java_parser.py
CHANGED
@@ -359,9 +359,8 @@ class JavaParser:
|
|
359
359
|
"""
|
360
360
|
从Java文件中提取字段标签信息
|
361
361
|
|
362
|
-
|
363
|
-
|
364
|
-
public static final int ISFINAL_FIELD_NUMBER = 2;
|
362
|
+
优先从Java源码中直接找到字段名与标签的对应关系,
|
363
|
+
而不是依赖常量名的转换推测
|
365
364
|
|
366
365
|
Args:
|
367
366
|
java_file_path: Java文件路径
|
@@ -373,64 +372,238 @@ class JavaParser:
|
|
373
372
|
# 读取Java文件内容
|
374
373
|
content = java_file_path.read_text(encoding='utf-8')
|
375
374
|
|
376
|
-
#
|
377
|
-
|
378
|
-
field_tag_pattern = re.compile(
|
379
|
-
r'public\s+static\s+final\s+int\s+'
|
380
|
-
r'([A-Z_]+)_FIELD_NUMBER\s*=\s*(\d+)\s*;'
|
381
|
-
)
|
375
|
+
# 方法1:直接从源码中找到字段声明和对应的FIELD_NUMBER常量
|
376
|
+
field_tags = self._extract_field_tags_from_source(content)
|
382
377
|
|
383
|
-
field_tags
|
378
|
+
if field_tags:
|
379
|
+
return field_tags
|
384
380
|
|
385
|
-
#
|
386
|
-
|
387
|
-
field_const_name = match.group(1) # 如 TEXT, ISFINAL
|
388
|
-
tag_value = int(match.group(2)) # 如 1, 2
|
389
|
-
|
390
|
-
# 转换常量名为字段名
|
391
|
-
# TEXT -> text_, ISFINAL -> isFinal_
|
392
|
-
field_name = self._const_name_to_field_name(field_const_name)
|
393
|
-
field_tags[field_name] = tag_value
|
394
|
-
|
395
|
-
self.logger.debug(f" 🏷️ 提取字段标签: {field_name} = {tag_value}")
|
396
|
-
|
397
|
-
return field_tags if field_tags else None
|
381
|
+
# 方法2:如果方法1失败,回退到常量名转换方法
|
382
|
+
return self._extract_field_tags_from_constants(content)
|
398
383
|
|
399
384
|
except Exception as e:
|
400
385
|
self.logger.error(f"❌ 提取字段标签失败 {java_file_path}: {e}")
|
401
386
|
return None
|
402
387
|
|
388
|
+
def _extract_field_tags_from_source(self, content: str) -> Optional[dict]:
|
389
|
+
"""
|
390
|
+
直接从Java源码中提取字段名和标签的对应关系
|
391
|
+
|
392
|
+
通过分析实际的字段声明和常量定义来建立准确的映射
|
393
|
+
|
394
|
+
Args:
|
395
|
+
content: Java文件内容
|
396
|
+
|
397
|
+
Returns:
|
398
|
+
字段标签映射 {field_name: tag} 或 None
|
399
|
+
"""
|
400
|
+
# 提取所有字段声明
|
401
|
+
field_declarations = self._extract_all_field_declarations(content)
|
402
|
+
|
403
|
+
# 提取所有FIELD_NUMBER常量
|
404
|
+
field_constants = self._extract_field_number_constants(content)
|
405
|
+
|
406
|
+
if not field_declarations or not field_constants:
|
407
|
+
return None
|
408
|
+
|
409
|
+
# 建立字段名到标签的映射
|
410
|
+
field_tags = {}
|
411
|
+
|
412
|
+
# 尝试通过字段名匹配找到对应的常量
|
413
|
+
for field_name in field_declarations:
|
414
|
+
# 生成可能的常量名
|
415
|
+
possible_const_names = self._generate_possible_constant_names(field_name)
|
416
|
+
|
417
|
+
# 查找匹配的常量
|
418
|
+
for const_name in possible_const_names:
|
419
|
+
if const_name in field_constants:
|
420
|
+
field_tags[field_name] = field_constants[const_name]
|
421
|
+
self.logger.debug(f" 🎯 直接匹配字段: {field_name} -> {const_name} = {field_constants[const_name]}")
|
422
|
+
break
|
423
|
+
|
424
|
+
return field_tags if field_tags else None
|
425
|
+
|
426
|
+
def _extract_all_field_declarations(self, content: str) -> List[str]:
|
427
|
+
"""
|
428
|
+
提取所有字段声明
|
429
|
+
|
430
|
+
Args:
|
431
|
+
content: Java文件内容
|
432
|
+
|
433
|
+
Returns:
|
434
|
+
字段名列表
|
435
|
+
"""
|
436
|
+
field_pattern = re.compile(
|
437
|
+
r'private\s+(?:static\s+)?(?:final\s+)?' # 访问修饰符
|
438
|
+
r'[^\s]+(?:<[^>]*>)?(?:\[\])?' # 类型(包括泛型和数组)
|
439
|
+
r'\s+([a-zA-Z_][a-zA-Z0-9_]*_?)\s*[=;]', # 字段名
|
440
|
+
re.MULTILINE
|
441
|
+
)
|
442
|
+
|
443
|
+
field_names = []
|
444
|
+
for match in field_pattern.finditer(content):
|
445
|
+
field_name = match.group(1)
|
446
|
+
# 跳过明显的常量字段(全大写)
|
447
|
+
if not field_name.isupper() and not field_name.startswith('DEFAULT_'):
|
448
|
+
field_names.append(field_name)
|
449
|
+
|
450
|
+
return field_names
|
451
|
+
|
452
|
+
def _extract_field_number_constants(self, content: str) -> dict:
|
453
|
+
"""
|
454
|
+
提取所有FIELD_NUMBER常量
|
455
|
+
|
456
|
+
Args:
|
457
|
+
content: Java文件内容
|
458
|
+
|
459
|
+
Returns:
|
460
|
+
常量名到值的映射 {const_name: value}
|
461
|
+
"""
|
462
|
+
field_tag_pattern = re.compile(
|
463
|
+
r'\s*public\s+static\s+final\s+int\s+' # 允许行首有空白字符
|
464
|
+
r'([A-Z0-9_]+)_FIELD_NUMBER\s*=\s*(\d+)\s*;' # 允许常量名包含数字
|
465
|
+
)
|
466
|
+
|
467
|
+
constants = {}
|
468
|
+
for match in field_tag_pattern.finditer(content):
|
469
|
+
const_name = match.group(1)
|
470
|
+
tag_value = int(match.group(2))
|
471
|
+
constants[const_name] = tag_value
|
472
|
+
|
473
|
+
return constants
|
474
|
+
|
475
|
+
def _generate_possible_constant_names(self, field_name: str) -> List[str]:
|
476
|
+
"""
|
477
|
+
根据字段名生成可能的常量名
|
478
|
+
|
479
|
+
Args:
|
480
|
+
field_name: 字段名(如 e164Format_, telType_)
|
481
|
+
|
482
|
+
Returns:
|
483
|
+
可能的常量名列表
|
484
|
+
"""
|
485
|
+
# 移除末尾的下划线
|
486
|
+
clean_name = field_name.rstrip('_')
|
487
|
+
|
488
|
+
possible_names = []
|
489
|
+
|
490
|
+
# 方法1:直接转换为大写
|
491
|
+
# e164Format -> E164FORMAT
|
492
|
+
possible_names.append(clean_name.upper())
|
493
|
+
|
494
|
+
# 方法2:在camelCase边界添加下划线
|
495
|
+
# e164Format -> E164_FORMAT
|
496
|
+
camel_to_snake = re.sub('([a-z0-9])([A-Z])', r'\1_\2', clean_name).upper()
|
497
|
+
possible_names.append(camel_to_snake)
|
498
|
+
|
499
|
+
# 方法3:处理数字和字母的边界
|
500
|
+
# e164Format -> E_164_FORMAT
|
501
|
+
with_number_boundaries = re.sub('([a-zA-Z])([0-9])', r'\1_\2', clean_name)
|
502
|
+
with_number_boundaries = re.sub('([0-9])([a-zA-Z])', r'\1_\2', with_number_boundaries)
|
503
|
+
with_number_boundaries = re.sub('([a-z])([A-Z])', r'\1_\2', with_number_boundaries).upper()
|
504
|
+
possible_names.append(with_number_boundaries)
|
505
|
+
|
506
|
+
return list(set(possible_names)) # 去重
|
507
|
+
|
508
|
+
def _extract_field_tags_from_constants(self, content: str) -> Optional[dict]:
|
509
|
+
"""
|
510
|
+
从常量定义中提取字段标签(回退方法)
|
511
|
+
|
512
|
+
Args:
|
513
|
+
content: Java文件内容
|
514
|
+
|
515
|
+
Returns:
|
516
|
+
字段标签映射 {field_name: tag} 或 None
|
517
|
+
"""
|
518
|
+
# 匹配字段标签常量定义
|
519
|
+
field_tag_pattern = re.compile(
|
520
|
+
r'\s*public\s+static\s+final\s+int\s+' # 允许行首有空白字符
|
521
|
+
r'([A-Z0-9_]+)_FIELD_NUMBER\s*=\s*(\d+)\s*;' # 允许常量名包含数字
|
522
|
+
)
|
523
|
+
|
524
|
+
field_tags = {}
|
525
|
+
|
526
|
+
# 查找所有字段标签定义
|
527
|
+
for match in field_tag_pattern.finditer(content):
|
528
|
+
field_const_name = match.group(1) # 如 TEXT, ISFINAL
|
529
|
+
tag_value = int(match.group(2)) # 如 1, 2
|
530
|
+
|
531
|
+
# 转换常量名为字段名
|
532
|
+
field_name = self._const_name_to_field_name(field_const_name)
|
533
|
+
field_tags[field_name] = tag_value
|
534
|
+
|
535
|
+
self.logger.debug(f" 🔄 回退转换字段标签: {field_name} = {tag_value}")
|
536
|
+
|
537
|
+
return field_tags if field_tags else None
|
538
|
+
|
403
539
|
def _const_name_to_field_name(self, const_name: str) -> str:
|
404
540
|
"""
|
405
|
-
|
541
|
+
将常量名转换为字段名(通用算法,无硬编码)
|
406
542
|
|
407
543
|
Args:
|
408
|
-
const_name: 常量名(如 TEXT, ISFINAL, PAYLOADTYPE,
|
544
|
+
const_name: 常量名(如 TEXT, ISFINAL, PAYLOADTYPE, E164_FORMAT)
|
409
545
|
|
410
546
|
Returns:
|
411
|
-
字段名(如 text_, isFinal_, payloadType_,
|
412
|
-
"""
|
413
|
-
#
|
414
|
-
special_cases = {
|
415
|
-
'ISFINAL': 'isFinal',
|
416
|
-
'PAYLOADTYPE': 'payloadType',
|
417
|
-
'TERMINATIONREASON': 'terminationReason',
|
418
|
-
'USERID': 'userId',
|
419
|
-
'INSTALLATIONID': 'installationId',
|
420
|
-
'PHONENUMBER': 'phoneNumber',
|
421
|
-
'COUNTRYCODE': 'countryCode',
|
422
|
-
}
|
423
|
-
|
424
|
-
if const_name in special_cases:
|
425
|
-
return special_cases[const_name] + '_'
|
426
|
-
|
427
|
-
# 通用转换:将UPPER_CASE转换为camelCase
|
547
|
+
字段名(如 text_, isFinal_, payloadType_, e164Format_)
|
548
|
+
"""
|
549
|
+
# 通用转换算法:将UPPER_CASE转换为camelCase
|
428
550
|
if '_' in const_name:
|
429
|
-
#
|
551
|
+
# 处理下划线分隔的常量名:E164_FORMAT -> e164Format
|
430
552
|
parts = const_name.lower().split('_')
|
431
553
|
field_name = parts[0] + ''.join(word.capitalize() for word in parts[1:])
|
432
554
|
else:
|
433
|
-
#
|
434
|
-
|
555
|
+
# 处理单个单词的常量名:TEXT -> text
|
556
|
+
# 处理复合词常量名:ISFINAL -> isFinal, PAYLOADTYPE -> payloadType
|
557
|
+
field_name = self._split_compound_word(const_name)
|
435
558
|
|
436
|
-
return field_name + '_'
|
559
|
+
return field_name + '_'
|
560
|
+
|
561
|
+
def _split_compound_word(self, word: str) -> str:
|
562
|
+
"""
|
563
|
+
智能分割复合词并转换为camelCase
|
564
|
+
|
565
|
+
Args:
|
566
|
+
word: 大写复合词(如 ISFINAL, PAYLOADTYPE, USERID)
|
567
|
+
|
568
|
+
Returns:
|
569
|
+
camelCase格式的字段名(如 isFinal, payloadType, userId)
|
570
|
+
"""
|
571
|
+
# 将单词转换为小写
|
572
|
+
word_lower = word.lower()
|
573
|
+
|
574
|
+
# 使用启发式规则分割复合词
|
575
|
+
# 这些是常见的英语词汇模式,无需硬编码特定应用的词汇
|
576
|
+
common_prefixes = ['is', 'has', 'can', 'should', 'will', 'get', 'set']
|
577
|
+
common_suffixes = ['type', 'id', 'code', 'number', 'name', 'data', 'info', 'status', 'mode', 'format']
|
578
|
+
|
579
|
+
# 检查前缀模式
|
580
|
+
for prefix in common_prefixes:
|
581
|
+
if word_lower.startswith(prefix) and len(word_lower) > len(prefix):
|
582
|
+
rest = word_lower[len(prefix):]
|
583
|
+
return prefix + rest.capitalize()
|
584
|
+
|
585
|
+
# 检查后缀模式
|
586
|
+
for suffix in common_suffixes:
|
587
|
+
if word_lower.endswith(suffix) and len(word_lower) > len(suffix):
|
588
|
+
prefix_part = word_lower[:-len(suffix)]
|
589
|
+
return prefix_part + suffix.capitalize()
|
590
|
+
|
591
|
+
# 如果没有匹配的模式,尝试基于常见的英语单词边界进行分割
|
592
|
+
# 这里可以使用更复杂的NLP技术,但为了保持简单,使用基本的启发式
|
593
|
+
|
594
|
+
# 检查常见的双词组合模式
|
595
|
+
if len(word_lower) >= 6:
|
596
|
+
# 尝试在中间位置分割
|
597
|
+
mid_point = len(word_lower) // 2
|
598
|
+
for i in range(max(3, mid_point - 2), min(len(word_lower) - 2, mid_point + 3)):
|
599
|
+
first_part = word_lower[:i]
|
600
|
+
second_part = word_lower[i:]
|
601
|
+
|
602
|
+
# 检查是否是合理的分割(基于常见英语单词长度)
|
603
|
+
if (3 <= len(first_part) <= 8 and 3 <= len(second_part) <= 8 and
|
604
|
+
not first_part.endswith(second_part[:2]) and # 避免重复
|
605
|
+
not second_part.startswith(first_part[-2:])): # 避免重复
|
606
|
+
return first_part + second_part.capitalize()
|
607
|
+
|
608
|
+
# 如果无法智能分割,直接返回小写形式
|
609
|
+
return word_lower
|
pyproject.toml
CHANGED
@@ -0,0 +1,295 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: reproto
|
3
|
+
Version: 0.0.9
|
4
|
+
Summary: 一个强大的逆向工程工具, 能够从任何使用Google Protobuf Lite的Android应用中自动重构出完整的.proto文件结构.
|
5
|
+
License: Proprietary
|
6
|
+
Keywords: protobuf,reverse-engineering,android,jadx,proto
|
7
|
+
Author: iyue
|
8
|
+
Author-email: ys1231@126.com
|
9
|
+
Requires-Python: >=3.12,<4.0
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
11
|
+
Classifier: Intended Audience :: Developers
|
12
|
+
Classifier: License :: Other/Proprietary License
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
16
|
+
Classifier: Topic :: Software Development :: Code Generators
|
17
|
+
Classifier: Topic :: Software Development :: Disassemblers
|
18
|
+
Requires-Dist: loguru (>=0.7.3)
|
19
|
+
Project-URL: Homepage, https://github.com/ys1231/reproto
|
20
|
+
Project-URL: Repository, https://github.com/ys1231/reproto.git
|
21
|
+
Description-Content-Type: text/markdown
|
22
|
+
|
23
|
+
# Protobuf Reconstructor
|
24
|
+
|
25
|
+
🔧 **从JADX反编译的Java源码自动重构Protobuf .proto文件**
|
26
|
+
|
27
|
+
一个强大的逆向工程工具,能够从任何使用Google Protobuf Lite的Android应用中自动重构出完整的.proto文件结构。经过重大性能优化,执行效率提升20%+。
|
28
|
+
|
29
|
+
## ✨ 特性
|
30
|
+
|
31
|
+
- 🎯 **精准解析**: 基于Google Protobuf Lite字节码的逆向工程
|
32
|
+
- 🔄 **递归依赖**: 自动发现和处理所有依赖的消息和枚举类型
|
33
|
+
- 📦 **完整支持**: 支持oneof、repeated、map、枚举等所有Protobuf特性
|
34
|
+
- 🌐 **通用性**: 适用于任何Android应用,无需硬编码映射
|
35
|
+
- 🧠 **智能推断**: 从Java源码直接读取类型信息,确保高准确性
|
36
|
+
- 📝 **标准输出**: 严格遵循Google Proto Style Guide
|
37
|
+
- 🚀 **高性能**: 文件缓存系统 + 智能路径构造,执行速度提升20%+
|
38
|
+
- 🛠️ **特殊类型支持**: MapFieldLite、Internal.ProtobufList、Google Well-Known Types
|
39
|
+
|
40
|
+
## 🛠️ 安装
|
41
|
+
|
42
|
+
```bash
|
43
|
+
# 克隆项目
|
44
|
+
git clone https://github.com/ys1231/reproto.git
|
45
|
+
cd reproto
|
46
|
+
|
47
|
+
# 安装依赖
|
48
|
+
pip install -r requirements.txt
|
49
|
+
```
|
50
|
+
|
51
|
+
## 📖 使用方法
|
52
|
+
|
53
|
+
### 基本用法
|
54
|
+
```bash
|
55
|
+
python main.py <java_sources_dir> <root_class> <output_dir> [--verbose]
|
56
|
+
```
|
57
|
+
|
58
|
+
### 参数说明
|
59
|
+
- `java_sources_dir`: JADX反编译的Java源码目录路径
|
60
|
+
- `root_class`: 要重构的根类完整类名(如:com.example.Model)
|
61
|
+
- `output_dir`: 生成的proto文件输出目录路径
|
62
|
+
- `--verbose`: 显示详细处理信息
|
63
|
+
|
64
|
+
### 示例
|
65
|
+
```bash
|
66
|
+
# 重构消息应用的数据模型
|
67
|
+
python main.py ./out_jadx/sources com.example.messaging.v1.models.MessageData ./protos_generated --verbose
|
68
|
+
|
69
|
+
# 重构内部类
|
70
|
+
python main.py ./out_jadx/sources 'com.truecaller.accountonboarding.v1.Models$Onboarded' ./output --verbose
|
71
|
+
|
72
|
+
# 重构包含特殊类型的类
|
73
|
+
python main.py ./out_jadx/sources com.truecaller.search.v1.models.SearchResult ./output --verbose
|
74
|
+
```
|
75
|
+
|
76
|
+
## 🔍 工作原理
|
77
|
+
|
78
|
+
### 核心技术
|
79
|
+
1. **字节码解析**: 逆向工程Google Protobuf Lite的`newMessageInfo`调用
|
80
|
+
2. **依赖发现**: 递归分析Java文件中的类型引用
|
81
|
+
3. **智能推断**: 基于字段名和对象数组推断枚举和消息类型
|
82
|
+
4. **源码分析**: 直接从Java源码读取真实的字段类型声明
|
83
|
+
5. **🆕 性能优化**: 文件缓存系统 + 直接路径构造,避免全目录扫描
|
84
|
+
|
85
|
+
### 解析流程
|
86
|
+
```
|
87
|
+
Java源码 → 字节码提取 → 类型解码 → 依赖发现 → 源码验证 → Proto生成
|
88
|
+
↓
|
89
|
+
🚀 性能优化: 文件缓存 + 智能路径构造 + 统一类型检测
|
90
|
+
```
|
91
|
+
|
92
|
+
## 📁 项目结构
|
93
|
+
|
94
|
+
```
|
95
|
+
reproto/
|
96
|
+
├── main.py # 主程序入口
|
97
|
+
├── core/ # 核心组件
|
98
|
+
│ ├── reconstructor.py # 主协调器 (已优化)
|
99
|
+
│ ├── info_decoder.py # 字节码解码器
|
100
|
+
│ └── bytecode_parser.py # 字节码解析工具
|
101
|
+
├── parsing/ # 解析模块
|
102
|
+
│ ├── java_parser.py # Java文件解析器
|
103
|
+
│ ├── enum_parser.py # 枚举解析器 (🆕)
|
104
|
+
│ └── java_source_analyzer.py # Java源码分析器 (已优化)
|
105
|
+
├── generation/ # 生成模块
|
106
|
+
│ └── proto_generator.py # Proto文件生成器
|
107
|
+
├── models/ # 数据模型
|
108
|
+
│ └── message_definition.py # 消息和枚举定义
|
109
|
+
├── utils/ # 工具函数 (大幅扩展)
|
110
|
+
│ ├── logger.py # 日志系统
|
111
|
+
│ ├── file_utils.py # 文件工具
|
112
|
+
│ ├── file_cache.py # 文件缓存系统 (🆕)
|
113
|
+
│ └── type_utils.py # 类型处理工具 (🆕)
|
114
|
+
└── logs/ # 日志文件目录
|
115
|
+
```
|
116
|
+
|
117
|
+
## 📊 输出示例
|
118
|
+
|
119
|
+
### 输入:Java源码
|
120
|
+
```java
|
121
|
+
public final class BulkSearchResult extends GeneratedMessageLite {
|
122
|
+
private MapFieldLite<String, Contact> contacts_;
|
123
|
+
private Internal.ProtobufList<String> phoneNumbers_;
|
124
|
+
|
125
|
+
public static final int CONTACTS_FIELD_NUMBER = 1;
|
126
|
+
public static final int PHONE_NUMBERS_FIELD_NUMBER = 2;
|
127
|
+
}
|
128
|
+
```
|
129
|
+
|
130
|
+
### 输出:Proto文件
|
131
|
+
```protobuf
|
132
|
+
syntax = "proto3";
|
133
|
+
|
134
|
+
package com.truecaller.search.v1.models;
|
135
|
+
|
136
|
+
option java_package = "com.truecaller.search.v1.models";
|
137
|
+
option java_multiple_files = true;
|
138
|
+
|
139
|
+
message BulkSearchResult {
|
140
|
+
map<string, Contact> contacts = 1;
|
141
|
+
repeated string phone_numbers = 2;
|
142
|
+
}
|
143
|
+
```
|
144
|
+
|
145
|
+
## 🚀 性能优化亮点
|
146
|
+
|
147
|
+
### 🆕 重大性能提升
|
148
|
+
- **总执行时间**: 从~81秒优化到~65秒,提升 **19.8%**
|
149
|
+
- **基础类型检测**: 从2-3秒延迟优化为 **瞬间响应**,提升 **99%+**
|
150
|
+
- **索引系统**: 移除未使用的索引系统,节省 **100%** 构建开销
|
151
|
+
- **文件I/O**: 智能缓存系统,避免重复读取
|
152
|
+
|
153
|
+
### 🔧 技术优化
|
154
|
+
1. **文件缓存系统**: 线程安全的文件内容缓存,避免重复I/O
|
155
|
+
2. **直接路径构造**: 根据包名直接构造文件路径,避免全目录扫描
|
156
|
+
3. **统一类型检测**: 使用`TypeMapper`统一处理所有类型转换
|
157
|
+
4. **智能包名推断**: 基于包结构的智能类名解析
|
158
|
+
|
159
|
+
### 📈 性能监控
|
160
|
+
```bash
|
161
|
+
📊 文件缓存统计:
|
162
|
+
总请求数: 33
|
163
|
+
缓存命中: 0 # 表明程序高效,无重复读取
|
164
|
+
缓存未命中: 33 # 每个文件只读取一次
|
165
|
+
已缓存文件: 33 # 所有文件已缓存备用
|
166
|
+
```
|
167
|
+
|
168
|
+
## 🛠️ 特殊类型支持
|
169
|
+
|
170
|
+
### MapFieldLite 支持
|
171
|
+
```java
|
172
|
+
// Java源码
|
173
|
+
private MapFieldLite<String, Contact> contacts_;
|
174
|
+
|
175
|
+
// 生成的Proto
|
176
|
+
map<string, Contact> contacts = 1;
|
177
|
+
```
|
178
|
+
|
179
|
+
### Internal.ProtobufList 支持
|
180
|
+
```java
|
181
|
+
// Java源码
|
182
|
+
private Internal.ProtobufList<String> tags_;
|
183
|
+
|
184
|
+
// 生成的Proto
|
185
|
+
repeated string tags = 1;
|
186
|
+
```
|
187
|
+
|
188
|
+
### Google Well-Known Types
|
189
|
+
- `google.protobuf.Any`
|
190
|
+
- `google.protobuf.Timestamp`
|
191
|
+
- `google.protobuf.Duration`
|
192
|
+
- `google.protobuf.StringValue`
|
193
|
+
- 等等...
|
194
|
+
|
195
|
+
## 🚀 工作流程
|
196
|
+
|
197
|
+
1. 使用JADX反编译Android应用:`jadx -d out_jadx app.apk`
|
198
|
+
2. 运行ReProto指定根Protobuf类
|
199
|
+
3. 自动解析所有相关类和依赖
|
200
|
+
4. 🆕 智能缓存和路径优化,快速处理
|
201
|
+
5. 生成完整的.proto文件结构
|
202
|
+
|
203
|
+
## 📝 配置选项
|
204
|
+
|
205
|
+
### 日志配置
|
206
|
+
- 日志文件自动保存到 `./logs/` 目录
|
207
|
+
- 文件格式: `reproto-YYYY-MM-DD-HH-MM-SS.log`
|
208
|
+
- 使用 `--verbose` 参数查看详细处理过程
|
209
|
+
- 🆕 性能统计和缓存监控信息
|
210
|
+
|
211
|
+
### 输出格式
|
212
|
+
生成的proto文件遵循Google Protobuf Style Guide:
|
213
|
+
- 文件名:`snake_case.proto`
|
214
|
+
- 字段名:`snake_case`
|
215
|
+
- 消息名:`PascalCase`
|
216
|
+
- 枚举值:`UPPER_SNAKE_CASE`
|
217
|
+
|
218
|
+
## 🔧 开发
|
219
|
+
|
220
|
+
```bash
|
221
|
+
# 使用Poetry管理依赖
|
222
|
+
poetry install
|
223
|
+
poetry shell
|
224
|
+
|
225
|
+
# 运行测试
|
226
|
+
python main.py ../out_jadx/sources 'com.example.TestClass' ../test_output --verbose
|
227
|
+
|
228
|
+
# 性能测试
|
229
|
+
time python main.py ../out_jadx/sources com.truecaller.search.v1.models.SearchResult ../test_output
|
230
|
+
```
|
231
|
+
|
232
|
+
## 🐛 故障排除
|
233
|
+
|
234
|
+
### 常见问题
|
235
|
+
|
236
|
+
1. **文件找不到错误**
|
237
|
+
```bash
|
238
|
+
# 确保JADX输出目录正确
|
239
|
+
ls -la out_jadx/sources/com/example/
|
240
|
+
```
|
241
|
+
|
242
|
+
2. **内存不足**
|
243
|
+
```bash
|
244
|
+
# 对于大型应用,增加Java堆内存
|
245
|
+
export JAVA_OPTS="-Xmx4g"
|
246
|
+
```
|
247
|
+
|
248
|
+
3. **性能问题**
|
249
|
+
```bash
|
250
|
+
# 查看缓存统计,确认没有重复I/O
|
251
|
+
grep "缓存统计" logs/reproto-*.log
|
252
|
+
```
|
253
|
+
|
254
|
+
## 📊 支持的Protobuf特性
|
255
|
+
|
256
|
+
| 特性 | 支持状态 | 示例 |
|
257
|
+
|------|----------|------|
|
258
|
+
| 基础类型 | ✅ 完整支持 | `string`, `int32`, `bool` |
|
259
|
+
| 消息类型 | ✅ 完整支持 | `Contact`, `UserInfo` |
|
260
|
+
| 枚举类型 | ✅ 完整支持 | `enum Status { ACTIVE = 0; }` |
|
261
|
+
| repeated | ✅ 完整支持 | `repeated string tags` |
|
262
|
+
| map | ✅ 完整支持 | `map<string, Contact> contacts` |
|
263
|
+
| oneof | ✅ 完整支持 | `oneof data { ... }` |
|
264
|
+
| 嵌套消息 | ✅ 完整支持 | `message Outer.Inner` |
|
265
|
+
| Well-Known Types | ✅ 新增支持 | `google.protobuf.Timestamp` |
|
266
|
+
|
267
|
+
## 🎯 最新更新 (v2.0)
|
268
|
+
|
269
|
+
### 🚀 性能优化
|
270
|
+
- 移除未使用的索引系统,提升执行效率20%+
|
271
|
+
- 文件缓存系统,避免重复I/O操作
|
272
|
+
- 智能路径构造,避免全目录扫描
|
273
|
+
- 统一类型检测器,简化代码逻辑
|
274
|
+
|
275
|
+
### 🆕 新增功能
|
276
|
+
- MapFieldLite自动转换为标准map语法
|
277
|
+
- Internal.ProtobufList支持
|
278
|
+
- Google Protobuf Well-Known Types支持
|
279
|
+
- 增强的枚举解析器
|
280
|
+
- 详细的性能监控和统计
|
281
|
+
|
282
|
+
### 🔧 技术改进
|
283
|
+
- 代码复杂度显著降低
|
284
|
+
- 内存使用优化
|
285
|
+
- 错误处理增强
|
286
|
+
- 日志系统改进
|
287
|
+
|
288
|
+
## 📄 许可证
|
289
|
+
|
290
|
+
本项目为私有项目,仅供授权用户使用。
|
291
|
+
|
292
|
+
---
|
293
|
+
|
294
|
+
**�� 现在就体验20%+的性能提升!**
|
295
|
+
|