-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathheaderStructsToCe.py
More file actions
342 lines (305 loc) · 14.1 KB
/
Copy pathheaderStructsToCe.py
File metadata and controls
342 lines (305 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ce_structs_from_header.py
Парсер IDA-подобного дампа структур -> Cheat Engine .csx
Поддержка:
- struct __fixed Name // sizeof=0x...
- '{' / '};' с префиксом офсета (например, "00000000 {" и "00000300 };")
- Базовые типы, одиночные/двойные/многократные указатели
- Двойной указатель T **field -> Element Pointer с вложенной <Structure ...> из 100 Pointer на T
- VTable: 'ret (__cdecl|__thiscall|__stdcall|__fastcall *FuncName)(...)' -> Pointer с Description=FuncName
- Встроенные пользовательские типы (без '*') -> Array of byte до следующего поля/конца
- Паддинги 'padding byte' игнорируются
- Структуры в .csx сортируются по алфавиту
"""
import argparse
import io
import re
import sys
from xml.sax.saxutils import escape
# ---------- Регэкспы формата ----------
STRUCT_START_RE = re.compile(
r'^\s*([0-9A-Fa-f]{8})\s+struct\s+(?:__fixed\s+)?(?P<name>\w+)'
r'\s*//\s*sizeof\s*=\s*0x(?P<size>[0-9A-Fa-f]+)\s*$',
re.M
)
OPEN_BRACE_RE = re.compile(r'^\s*(?:[0-9A-Fa-f]{8}\s+)?{\s*$', re.M)
CLOSE_BRACE_RE = re.compile(r'^\s*(?:[0-9A-Fa-f]{8}\s+)?}\s*;\s*$', re.M)
PADDING_RE = re.compile(r'^\s*([0-9A-Fa-f]{8})\s*//\s*padding\s+byte\s*$', re.I | re.M)
# Обычные поля: "00000034 Type **name;"
FIELD_RE = re.compile(
r'^\s*([0-9A-Fa-f]{8})\s+'
r'(?P<type>(?:[\w:]+|\bvoid\b))\s*'
r'(?P<stars>\*+)?\s*'
r'(?P<name>\w+)\s*;'
r'(?:\s*//.*)?$',
re.M
)
# VTABLE: "00000000 rettype ( __cdecl *FuncName )( ... );"
VTABLE_RE = re.compile(
r'^\s*([0-9A-Fa-f]{8})\s+'
r'.*?\(\s*(?:__\w+\s+)?\*\s*(?P<fname>\w+)\s*\)\s*\('
r'.*?\)\s*;\s*$',
re.M
)
def parse_hex_off(s: str) -> int:
return int(s, 16)
# ---------- Карта базовых типов ----------
BASE_TYPE_MAP = {
'byte': ('Byte', 1, 'unsigned integer'),
'char': ('Byte', 1, 'unsigned integer'),
'signed char': ('Byte', 1, 'signed integer'),
'uint8_t': ('Byte', 1, 'unsigned integer'),
'int8_t': ('Byte', 1, 'signed integer'),
'short': ('2 Bytes', 2, 'signed integer'),
'unsigned short': ('2 Bytes', 2, 'unsigned integer'),
'int16_t': ('2 Bytes', 2, 'signed integer'),
'uint16_t': ('2 Bytes', 2, 'unsigned integer'),
'int': ('4 Bytes', 4, 'signed integer'),
'unsigned': ('4 Bytes', 4, 'unsigned integer'),
'unsigned int':('4 Bytes', 4, 'unsigned integer'),
'int32_t': ('4 Bytes', 4, 'signed integer'),
'uint32_t': ('4 Bytes', 4, 'unsigned integer'),
'long': ('4 Bytes', 4, 'signed integer'), # LP32/LLP64 по умолчанию
'unsigned long': ('4 Bytes', 4, 'unsigned integer'),
'long long': ('8 Bytes', 8, 'signed integer'),
'unsigned long long': ('8 Bytes', 8, 'unsigned integer'),
'int64_t': ('8 Bytes', 8, 'signed integer'),
'uint64_t': ('8 Bytes', 8, 'unsigned integer'),
'float': ('Float', 4, 'unsigned integer'),
'double': ('Double', 8, 'unsigned integer'),
}
def decide_int_display(default_display: str, name: str) -> str:
n = name.lower()
if any(k in n for k in ('flag', 'bits', 'mask', 'hex')):
return 'hexadecimal'
return default_display
# ---------- Модели ----------
class InlineStructure:
"""Вложенная структура внутри <Element> (для T **)."""
__slots__ = ('name', 'elements', 'structsize')
def __init__(self, name: str, elements: list, structsize: int):
self.name = name
self.elements = elements
self.structsize = structsize
class Element:
__slots__ = ('offset','vartype','bytesize','description','display','childstruct','inline_struct')
def __init__(self, offset:int, vartype:str, bytesize:int, description:str,
display:str='unsigned integer', childstruct:str|None=None, inline_struct:InlineStructure|None=None):
self.offset = offset
self.vartype = vartype
self.bytesize = bytesize
self.description = description
self.display = display
self.childstruct = childstruct
self.inline_struct = inline_struct
# ---------- Парсинг ----------
def collect_struct_blocks(text:str):
blocks = []
for m in STRUCT_START_RE.finditer(text):
name = m.group('name')
size = int(m.group('size'), 16)
om = OPEN_BRACE_RE.search(text, pos=m.end())
if not om: continue
cm = CLOSE_BRACE_RE.search(text, pos=om.end())
if not cm: continue
body = text[om.end():cm.start()]
blocks.append((name, size, body))
return blocks
def parse_body(body:str):
"""
Возвращает список entries.
Каждый entry — dict одного из видов:
{'kind':'vtable','off':..,'fname':..}
{'kind':'field','off':..,'type':..,'stars':..,'name':..}
Паддинги игнорируются.
"""
entries = []
for line in body.splitlines():
if PADDING_RE.match(line):
continue
mv = VTABLE_RE.match(line)
if mv:
off = parse_hex_off(mv.group(1))
fname = mv.group('fname')
entries.append({'kind':'vtable','off':off,'fname':fname})
continue
mf = FIELD_RE.match(line)
if mf:
off = parse_hex_off(mf.group(1))
basetype = mf.group('type')
stars = (mf.group('stars') or '')
name = mf.group('name')
entries.append({'kind':'field','off':off,'type':basetype,'stars':stars,'name':name})
continue
return entries
# ---------- Развёртка в CE элементы ----------
def build_elements(entries:list[dict],
struct_names:set[str],
declared_size:int,
ptr_size:int,
int_display_default:str) -> list[Element]:
entries = sorted(entries, key=lambda x: x['off'])
elems:list[Element] = []
# Для вычисления размера in-place неизвестных типов определим "следующий офсет" среди только полей
fields = sorted((e for e in entries if e['kind']=='field'), key=lambda x: x['off'])
next_off_by_off: dict[int,int] = {}
for i, f in enumerate(fields):
off = f['off']
nxt = fields[i+1]['off'] if i+1 < len(fields) else declared_size
next_off_by_off[off] = nxt
for ent in entries:
off = ent['off']
if ent['kind'] == 'vtable':
# Простой указатель на функцию, Description=имя
elems.append(Element(
offset=off,
vartype='Pointer',
bytesize=ptr_size,
description=ent['fname'],
display='unsigned integer',
childstruct=None
))
continue
# Поле
basetype = ent['type']
stars = (ent['stars'] or '').strip()
name = ent['name']
if stars:
if stars == '*':
child = basetype if basetype in struct_names else None
elems.append(Element(
offset=off,
vartype='Pointer',
bytesize=ptr_size,
description=f"{basetype} * {name}",
display='unsigned integer',
childstruct=child
))
elif stars == '**':
# Вложенная структура с 100 указателями на T
inline_name = f'Autocreated at {off:08X}'
inline_elems = []
child = basetype if basetype in struct_names else None
for i in range(100):
inline_elems.append(Element(
offset=i*ptr_size,
vartype='Pointer',
bytesize=ptr_size,
description=f"{basetype} *[{i}]",
display='unsigned integer',
childstruct=child
))
inline_struct = InlineStructure(inline_name, inline_elems, structsize=100*ptr_size)
elems.append(Element(
offset=off,
vartype='Pointer',
bytesize=ptr_size,
description=f"{basetype} ** {name}",
display='unsigned integer',
childstruct=None,
inline_struct=inline_struct
))
else:
# '***' и более — просто Pointer без ChildStruct (можно расширить по желанию)
elems.append(Element(
offset=off,
vartype='Pointer',
bytesize=ptr_size,
description=f"{basetype} {stars} {name}",
display='unsigned integer',
childstruct=None
))
else:
key = basetype.lower()
if key in BASE_TYPE_MAP:
vt, bs, dd = BASE_TYPE_MAP[key]
disp = decide_int_display(int_display_default if 'Bytes' in vt else dd, name) if 'Bytes' in vt else dd
elems.append(Element(off, vt, bs, name, disp))
else:
# неизвестный in-place тип -> Array of byte до следующего поля (или конца структуры)
next_off = next_off_by_off.get(off, declared_size)
size = max(0, next_off - off)
elems.append(Element(off, 'Array of byte', size, f"{basetype} {name}", 'unsigned integer'))
elems.sort(key=lambda e: e.offset)
return elems
# ---------- Генерация CSX ----------
def emit_element(out:io.StringIO, e:Element, indent:str=' '):
"""Пишет <Element .../>; если у элемента есть вложенная структура — пишет её внутрь."""
attrs = {
'Offset': str(e.offset),
'Vartype': e.vartype,
'Bytesize': str(e.bytesize),
'OffsetHex': f"{e.offset:08X}",
'Description': escape(e.description),
'DisplayMethod': e.display
}
child_attr = f' ChildStruct="{escape(e.childstruct)}"' if (e.vartype=='Pointer' and e.childstruct) else ''
if e.inline_struct is None:
out.write(indent + '<Element ' + ' '.join(f'{k}="{v}"' for k, v in attrs.items()) + f'{child_attr}/>\n')
else:
out.write(indent + '<Element ' + ' '.join(f'{k}="{v}"' for k, v in attrs.items()) + f'{child_attr}>\n')
s = e.inline_struct
out.write(indent + f' <Structure Name="{escape(s.name)}" AutoFill="0" AutoCreate="1" '
f'DefaultHex="0" AutoDestroy="0" DoNotSaveLocal="0" RLECompression="1" '
f'AutoCreateStructsize="{s.structsize}">\n')
out.write(indent + ' <Elements>\n')
for ie in s.elements:
emit_element(out, ie, indent=indent+' ')
out.write(indent + ' </Elements>\n')
out.write(indent + ' </Structure>\n')
out.write(indent + '</Element>\n')
def to_csx(structs:list[tuple[str,int,list[Element]]]) -> str:
# сортировка структур по алфавиту
structs = sorted(structs, key=lambda x: x[0].lower())
out = io.StringIO()
out.write('<Structures>\n')
for name, declared_size, elems in structs:
end_off = max((e.offset + e.bytesize for e in elems), default=0)
structsize = max(end_off, declared_size)
out.write(
f' <Structure Name="{escape(name)}" AutoFill="0" AutoCreate="1" '
f'DefaultHex="0" AutoDestroy="0" DoNotSaveLocal="0" RLECompression="1" '
f'AutoCreateStructsize="{structsize}">\n'
)
out.write(' <Elements>\n')
for e in elems:
emit_element(out, e, indent=' ')
out.write(' </Elements>\n')
out.write(' </Structure>\n')
out.write('</Structures>\n')
return out.getvalue()
# ---------- CLI ----------
def main():
ap = argparse.ArgumentParser(description='Генератор Cheat Engine .csx из хидера со структурами.')
ap.add_argument('input', help='Входной файл с хидером (или - для stdin)')
ap.add_argument('-o', '--output', help='Выходной .csx (по умолчанию stdout)')
ap.add_argument('--ptr-size', type=int, choices=(4, 8), default=8,
help='Размер указателя, байт (по умолчанию 8).')
ap.add_argument('--int-display', choices=('unsigned', 'signed', 'hex'),
default='unsigned',
help='Отображение 2/4/8-байтовых целых по умолчанию.')
args = ap.parse_args()
text = sys.stdin.read() if args.input == '-' else open(args.input, 'r', encoding='utf-8', errors='ignore').read()
blocks = collect_struct_blocks(text)
if not blocks:
print('Не найдено ни одной структуры. Проверь формат входа.', file=sys.stderr)
sys.exit(1)
struct_names = {name for name,_,_ in blocks}
display_map = {'unsigned': 'unsigned integer', 'signed': 'signed integer', 'hex': 'hexadecimal'}
structs_out = []
for name, declared_size, body in blocks:
entries = parse_body(body) # паддинги и лишнее отфильтруются
elems = build_elements(entries, struct_names, declared_size,
ptr_size=args.ptr_size,
int_display_default=display_map[args.int_display])
structs_out.append((name, declared_size, elems))
xml = to_csx(structs_out)
if args.output:
with open(args.output, 'w', encoding='utf-8', newline='\n') as f:
f.write(xml)
else:
sys.stdout.write(xml)
if __name__ == '__main__':
main()