1
Fork 0
mirror of https://github.com/awgil/ffxiv_reverse.git synced 2025-04-19 22:06:49 +00:00
ffxiv_reverse/idaplugins/ffutil.py

177 lines
4.9 KiB
Python
Raw Normal View History

2023-02-06 20:27:42 +02:00
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()