mirror of
https://github.com/awgil/ffxiv_reverse.git
synced 2025-04-19 22:06:49 +00:00
177 lines
4.9 KiB
Python
177 lines
4.9 KiB
Python
![]() |
import idaapi
|
||
|
import ida_kernwin
|
||
|
import ida_struct
|
||
|
import ida_funcs
|
||
|
import ida_name
|
||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||
|
|
||
|
def functions():
|
||
|
start_ea = 0
|
||
|
while True:
|
||
|
nextfn = ida_funcs.get_next_func(start_ea)
|
||
|
if not nextfn:
|
||
|
break
|
||
|
start_ea = nextfn.start_ea
|
||
|
yield start_ea
|
||
|
|
||
|
def names():
|
||
|
for i in range(ida_name.get_nlist_size()):
|
||
|
yield (ida_name.get_nlist_ea(i), ida_name.get_nlist_name(i))
|
||
|
|
||
|
class dialog(QtWidgets.QDialog):
|
||
|
_layout = None
|
||
|
|
||
|
def __init__(self, name):
|
||
|
QtWidgets.QDialog.__init__(self)
|
||
|
self.setWindowTitle(name)
|
||
|
self._layout = QtWidgets.QVBoxLayout()
|
||
|
self.setLayout(self._layout)
|
||
|
|
||
|
def add_widget(self, widget):
|
||
|
self._layout.addWidget(widget)
|
||
|
return widget
|
||
|
|
||
|
def add_layout(self, layout):
|
||
|
self._layout.addLayout(layout)
|
||
|
return layout
|
||
|
|
||
|
def add_buttons(self, ok_name = 'OK'):
|
||
|
buttons = self.add_layout(QtWidgets.QHBoxLayout())
|
||
|
ok = QtWidgets.QPushButton(ok_name)
|
||
|
ok.setDefault(True)
|
||
|
ok.clicked.connect(self.accept)
|
||
|
buttons.addWidget(ok)
|
||
|
cancel = QtWidgets.QPushButton('Cancel')
|
||
|
cancel.clicked.connect(self.reject)
|
||
|
buttons.addWidget(cancel)
|
||
|
|
||
|
def run(self, ok_name = 'OK'):
|
||
|
self.add_buttons(ok_name)
|
||
|
return self.exec()
|
||
|
|
||
|
class smart_rename(idaapi.action_handler_t):
|
||
|
def __init__(self):
|
||
|
idaapi.action_handler_t.__init__(self)
|
||
|
|
||
|
def activate(self, ctx):
|
||
|
highlight = ida_kernwin.get_highlight(ida_kernwin.get_current_viewer())
|
||
|
struct = ida_struct.get_struc_id(highlight[0]) if highlight else idaapi.BADADDR
|
||
|
if struct == idaapi.BADADDR:
|
||
|
print('Select a structure to rename')
|
||
|
return 0
|
||
|
|
||
|
original_name = ida_struct.get_struc_name(struct)
|
||
|
related_names = self._related_names(original_name)
|
||
|
|
||
|
dlg = dialog('Rename class and members')
|
||
|
dlg_rename = dlg.add_widget(QtWidgets.QLineEdit(original_name))
|
||
|
|
||
|
dlg_items = dlg.add_widget(QtWidgets.QListWidget())
|
||
|
dlg_items_list = []
|
||
|
for ea, name in related_names:
|
||
|
item = QtWidgets.QListWidgetItem(name, dlg_items)
|
||
|
item.setData(QtCore.Qt.UserRole, ea)
|
||
|
item.setCheckState(QtCore.Qt.Checked)
|
||
|
dlg_items_list.append(item)
|
||
|
|
||
|
if dlg.run('Rename') == 0:
|
||
|
return 0
|
||
|
|
||
|
new_name = dlg_rename.text()
|
||
|
if new_name == original_name:
|
||
|
print('Name is not changed, nothing to do...')
|
||
|
return 0
|
||
|
|
||
|
print(f'Renaming struct {original_name} to {new_name}')
|
||
|
ida_struct.set_struc_name(struct, new_name)
|
||
|
for item in dlg_items_list:
|
||
|
if item.checkState() == QtCore.Qt.Checked:
|
||
|
ea = item.data(QtCore.Qt.UserRole)
|
||
|
rel_ori_name = item.text()
|
||
|
rel_new_name = rel_ori_name.replace(original_name, new_name)
|
||
|
print(f'Renaming related {rel_ori_name} to {rel_new_name}')
|
||
|
ida_name.set_name(ea, rel_new_name)
|
||
|
return 1
|
||
|
|
||
|
def update(self, ctx):
|
||
|
return idaapi.AST_ENABLE_ALWAYS
|
||
|
|
||
|
def _related_names(self, struct_name):
|
||
|
return [(ea, name) for ea, name in names() if self._is_related_name(name, struct_name)]
|
||
|
|
||
|
def _is_related_name(self, name, struct_name):
|
||
|
adj_name = name[5:] if name.startswith('vtbl_') else name
|
||
|
pos = adj_name.find(struct_name)
|
||
|
if pos < 0:
|
||
|
return False
|
||
|
if pos > 0 and adj_name[pos-1] != ':' and adj_name[pos-1] != '.':
|
||
|
return False
|
||
|
pos += len(struct_name)
|
||
|
if pos > len(adj_name):
|
||
|
return False
|
||
|
return pos == len(adj_name) or adj_name[pos] == ':' or adj_name[pos] == '.'
|
||
|
|
||
|
class vtable_sync(idaapi.action_handler_t):
|
||
|
def __init__(self):
|
||
|
idaapi.action_handler_t.__init__(self)
|
||
|
|
||
|
def activate(self, ctx):
|
||
|
ea = ida_kernwin.get_screen_ea()
|
||
|
name = ida_name.get_name(ea)
|
||
|
if not name.startswith('vtbl_'):
|
||
|
print('Place cursor at the beginning of a virtual table and make sure it is called vtbl_ClassName')
|
||
|
return 0
|
||
|
|
||
|
name = name[5:]
|
||
|
vt_struct = ida_struct.get_struc_id(name + '_vtbl')
|
||
|
if vt_struct != idaapi.BADADDR:
|
||
|
return self._sync_vtable(ea, name, vt_struct)
|
||
|
else:
|
||
|
return self._create_vtable(ea, name)
|
||
|
|
||
|
def update(self, ctx):
|
||
|
return idaapi.AST_ENABLE_ALWAYS
|
||
|
|
||
|
def _sync_vtable(self, ea, classname, vtstruct):
|
||
|
print('todo')
|
||
|
return 0
|
||
|
|
||
|
def _create_vtable(self, ea, classname):
|
||
|
next_ea = min([addr for addr, name in names() if addr > ea])
|
||
|
size = int((next_ea - ea) / 8)
|
||
|
|
||
|
dlg = dialog(f'Create vtable for {classname}')
|
||
|
|
||
|
if dlg.run('Create') == 0:
|
||
|
return 0
|
||
|
|
||
|
print(f'creating: max {size} entries')
|
||
|
return 0
|
||
|
|
||
|
class ffutil(idaapi.plugin_t):
|
||
|
flags = idaapi.PLUGIN_UNL
|
||
|
comment = 'A bunch of utilities to simplify reversing FF14'
|
||
|
help = ''
|
||
|
wanted_name = 'ffutil'
|
||
|
wanted_hotkey = 'Ctrl+Alt+M'
|
||
|
|
||
|
def init(self):
|
||
|
print('ffutil init')
|
||
|
self._register_action('smart_rename', 'Rename class and all methods', smart_rename(), 'Ctrl+Shift+N')
|
||
|
self._register_action('vtable_sync', 'Sync vtable definitions and signatures', vtable_sync(), 'Ctrl+Shift+V')
|
||
|
return idaapi.PLUGIN_KEEP
|
||
|
|
||
|
def run(self, arg=None):
|
||
|
print('ffutil run')
|
||
|
|
||
|
def term(self):
|
||
|
print('ffutil term')
|
||
|
|
||
|
def _register_action(self, name, label, handler, shortcut):
|
||
|
deco_name = 'ffutil:' + name
|
||
|
idaapi.unregister_action(deco_name)
|
||
|
idaapi.register_action(idaapi.action_desc_t(deco_name, label, handler, shortcut))
|
||
|
|
||
|
def PLUGIN_ENTRY():
|
||
|
return ffutil()
|