Skip to content

Commit 4309f0a

Browse files
committed
Add the ability to parse and output mappings
Uses objects and inheritance instead of strings to represent types
1 parent d88a055 commit 4309f0a

File tree

8 files changed

+935
-55
lines changed

8 files changed

+935
-55
lines changed

.gitignore

+7-55
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,9 @@
1-
# Byte-compiled / optimized / DLL files
2-
__pycache__/
3-
*.py[cod]
1+
# IntelliJ
2+
.idea
3+
*.iml
44

5-
# C extensions
6-
*.so
5+
# Pip
6+
DiffUtils.egg-info
77

8-
# Distribution / packaging
9-
.Python
10-
env/
11-
build/
12-
develop-eggs/
13-
dist/
14-
downloads/
15-
eggs/
16-
.eggs/
17-
lib/
18-
lib64/
19-
parts/
20-
sdist/
21-
var/
22-
*.egg-info/
23-
.installed.cfg
24-
*.egg
25-
26-
# PyInstaller
27-
# Usually these files are written by a python script from a template
28-
# before PyInstaller builds the exe, so as to inject date/other infos into it.
29-
*.manifest
30-
*.spec
31-
32-
# Installer logs
33-
pip-log.txt
34-
pip-delete-this-directory.txt
35-
36-
# Unit test / coverage reports
37-
htmlcov/
38-
.tox/
39-
.coverage
40-
.coverage.*
41-
.cache
42-
nosetests.xml
43-
coverage.xml
44-
*,cover
45-
46-
# Translations
47-
*.mo
48-
*.pot
49-
50-
# Django stuff:
51-
*.log
52-
53-
# Sphinx documentation
54-
docs/_build/
55-
56-
# PyBuilder
57-
target/
8+
# Python
9+
*.pyc

setup.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env python
2+
3+
from setuptools import setup, test
4+
5+
setup(name='SrgLib',
6+
version='1.0.0',
7+
description='A python library for handling srg files and related stuff)',
8+
author='Techcable',
9+
author_emails='[email protected]',
10+
packages=["srg"]
11+
)

srg/__init__.py

+268
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
import re
2+
from collections import namedtuple
3+
4+
from srg.types import Type
5+
6+
7+
class MethodData:
8+
def __init__(self, type, name, args, return_type):
9+
"""
10+
Create a method data object
11+
12+
:param type: the type containing the method
13+
:param name: the name of the method
14+
:param args: the method argument types
15+
:param return_type: the method return types
16+
"""
17+
self.type = type
18+
self.name = name
19+
self.args = tuple(args)
20+
self.return_type = return_type
21+
22+
def get_internal_name(self):
23+
"""
24+
Get the internal name of this method
25+
26+
Internal mehod names are in the format ${internal type name}/${method name}
27+
28+
:return: the internal method name
29+
"""
30+
return self.type.get_internal_name() + "/" + self.name
31+
32+
def get_method_signature(self):
33+
"""
34+
Get the method signature in bytecode format
35+
36+
:return: this method's signature
37+
"""
38+
signature = "("
39+
for arg in self.args:
40+
signature += arg.get_descriptor()
41+
signature += ")"
42+
signature += self.return_type.get_descriptor()
43+
return signature
44+
45+
def __hash__(self):
46+
return hash((self.get_internal_name(), self.args, self.return_type))
47+
48+
def __eq__(self, other):
49+
return isinstance(other, MethodData)\
50+
and self.get_internal_name() == other.get_internal_name()\
51+
and self.args == other.args\
52+
and self.return_type == other.return_type
53+
54+
class FieldData:
55+
def __init__(self, type, name):
56+
"""
57+
Create field data
58+
59+
:param type: the type containing the field
60+
:param name: the name of the field
61+
"""
62+
self.type = type
63+
self.name = name
64+
65+
def get_internal_name(self):
66+
"""
67+
Get the internal name of this field
68+
69+
Internal mehod names are in the format ${internal type name}/${field name}
70+
71+
:return: the internal method name
72+
"""
73+
return self.type.get_internal_name() + "/" + self.name
74+
75+
def __hash__(self):
76+
return hash(self.get_internal_name())
77+
78+
def __eq__(self, other):
79+
return isinstance(other, FieldData) and self.get_internal_name() == other.get_internal_name()
80+
81+
class Mappings:
82+
"""
83+
Java obfuscation mappings
84+
85+
Contains the information to map one set of source identifiers to another
86+
"""
87+
88+
def __init__(self, class_mappings, field_mappings, method_mappings):
89+
"""
90+
Create a new mapping
91+
92+
:param class_mappings: a dictionary containing mappings from the old class names to the new ones
93+
:param field_mappings: a dictionary containing mappings from the old fields to the new ones
94+
:param method_mappings: a dictionary contianing mappings from the old methods to the new ones
95+
"""
96+
for original, renamed in class_mappings.items():
97+
if type(original, ) != Type:
98+
raise ValueError(original + " is not a valid type")
99+
if type(renamed) != Type:
100+
raise ValueError(renamed + " is not a valid type")
101+
for original, renamed in field_mappings.items():
102+
validate_field(original)
103+
validate_field(renamed)
104+
for original, renamed in method_mappings.items():
105+
validate_method(original)
106+
validate_method(renamed)
107+
self.classes = class_mappings
108+
self.fields = field_mappings
109+
self.methods = method_mappings
110+
111+
def get_class(self, original):
112+
"""
113+
Get the new class name
114+
115+
Returns the original name if no mapping is found
116+
117+
:param original: the original class name
118+
:return: the new class name
119+
"""
120+
renamed = self.classes[original]
121+
if renamed is None:
122+
return original
123+
else:
124+
return renamed
125+
126+
def get_method(self, original):
127+
"""
128+
Get the new method data
129+
130+
Returns the original name if no mapping is found
131+
132+
:param original: the original method data
133+
:return: the new method data
134+
"""
135+
renamed = self.methods[original]
136+
if renamed is None:
137+
return original
138+
else:
139+
return renamed
140+
141+
def get_field(self, original):
142+
"""
143+
Get the new field data
144+
145+
Returns the original name if no mapping is found
146+
147+
:param original: the original field data
148+
:return: the new field data
149+
"""
150+
renamed = self.fields[original]
151+
if renamed is None:
152+
return original
153+
else:
154+
return renamed
155+
156+
def rename_package(self, original_package, renamed_package):
157+
"""
158+
Rename all classes with the original package name
159+
160+
:param original_package: the original package name
161+
:param renamed_package: the renamed package name
162+
:return: the old mappings
163+
"""
164+
old = self.copy()
165+
for original_class, renamed_class in self.classes.items():
166+
if original_class.get_package() != original_package:
167+
continue
168+
if len(renamed_package) > 0:
169+
renamed_class = Type(renamed_package + "." + renamed_class.get_simple_name())
170+
else:
171+
renamed_class = Type(renamed_class.get_simple_name())
172+
self.classes[original_class] = renamed_class
173+
174+
return old
175+
176+
def invert(self):
177+
"""
178+
Invert the mappings, switching the original and renamed
179+
The inverted mappings are a copy of the original mappings
180+
181+
:return: the inverted mappings
182+
"""
183+
inverted_classes = {renamed: original for original, renamed in self.classes.items()}
184+
inverted_fields = {renamed: original for original, renamed in self.fields.items()}
185+
inverted_methods = {renamed: original for original, renamed in self.methods.items()}
186+
return Mappings(inverted_classes, inverted_fields, inverted_methods)
187+
188+
def copy(self):
189+
return Mappings(self.classes.copy(), self.fields.copy(), self.methods().copy())
190+
191+
192+
################
193+
## Validation ##
194+
################
195+
196+
197+
def validate_field(field):
198+
"""
199+
Asserts that the given field is valid
200+
201+
:param field: the field to check
202+
:raises ValueError: if the specified name is invalid
203+
"""
204+
if type(field) is not FieldData:
205+
raise ValueError(str(field) + " must be field data")
206+
if not is_valid_type(field.type):
207+
raise ValueError(field.type + " is not a valid class name for field: " + str(field))
208+
if not is_valid_identifier(field.name):
209+
raise ValueError(field.name + " is not a valid field name for field: " + str(field))
210+
211+
212+
def validate_method(method):
213+
"""
214+
Asserts that the given field is valid
215+
216+
:param method: the field to check
217+
:raises ValueError: if the specified name is invalid
218+
"""
219+
if type(method) is not MethodData:
220+
raise ValueError(str(method) + " must be method data")
221+
if not is_valid_type(method.type):
222+
raise ValueError(method.type + " is not a valid class name for method: " + str(method))
223+
if not is_valid_identifier(method.name):
224+
raise ValueError(method.name + " is not a valid method name for method: " + str(method))
225+
for paramType in method.args:
226+
if not is_valid_type(paramType):
227+
raise ValueError(paramType + " is not a valid paramater type for method: " + str(method))
228+
if not is_valid_type(method.return_type):
229+
raise ValueError(method.return_type + " is not a valid return type for method: " + str(method))
230+
231+
232+
# String Validator
233+
234+
_identifier_regex = re.compile("[\w_]+")
235+
_type_regex = re.compile('([\w_]+[\$\.])*([\w_]+)')
236+
_package_regex = re.compile('([\w_]+\.)*([\w_]+)')
237+
238+
239+
def is_valid_identifier(name):
240+
"""
241+
Ensures the given name is a valid java identifier
242+
243+
Java identifiers are used for field or method names
244+
245+
:param name: the name to check
246+
:return: if valid
247+
"""
248+
return _identifier_regex.match(name) is not None
249+
250+
251+
def is_valid_type(type):
252+
"""
253+
Ensures the given name is a valid class name
254+
255+
:param type: the name to validate
256+
:return: true if valid
257+
"""
258+
return isinstance(type, Type) or _type_regex.match(type) is not None
259+
260+
261+
def is_valid_package(name):
262+
"""
263+
Ensures the given package name is valid
264+
265+
:param name: the name to check
266+
:return: if valid
267+
"""
268+
return _package_regex.match(name) is not None

srg/output.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
def serialize_srg(mappings):
2+
"""
3+
A generator that serializes the lines
4+
5+
I'm not sure if this documentation is correct, since i'm not quite used to thinking of methods as object
6+
7+
:param mappings: the mappings to serialize
8+
:return: a generator that serializes the mappings
9+
:raises ValueError: if the mappings are invalid
10+
"""
11+
for original, renamed in mappings.methods.items():
12+
yield " ".join([
13+
"MD:",
14+
# Original method
15+
original.get_internal_name(),
16+
original.get_method_signature(),
17+
# Renamed method
18+
renamed.get_internal_name(),
19+
renamed.get_method_signature()
20+
])
21+
for original, renamed in mappings.fields.items():
22+
yield " ".join([
23+
"FD:",
24+
original.get_internal_name(),
25+
renamed.get_internal_name()
26+
])
27+
for original, renamed in mappings.classes.items():
28+
yield " ".join([
29+
"CL:",
30+
original.get_internal_name(),
31+
renamed.get_internal_name()
32+
])

0 commit comments

Comments
 (0)