1
Fork 0
mirror of https://github.com/awgil/ffxiv_reverse.git synced 2025-04-22 14:57:45 +00:00
ffxiv_reverse/idbtoolkit/ii.py
2023-05-08 23:23:04 +01:00

209 lines
7.9 KiB
Python

# This is a wrapper for IDA API, it is quite inconvenient to use...
import idaapi
import ida_bytes
import ida_enum
import ida_funcs
import ida_lines
import ida_nalt
import ida_name
import ida_struct
import ida_typeinf
import ida_xref
import ida_ua
import datetime
from contextlib import contextmanager
# enumerate (ea, name) tuples for all global names in database
def enumerate_names():
for i in range(ida_name.get_nlist_size()):
yield (ida_name.get_nlist_ea(i), ida_name.get_nlist_name(i))
# find EA of a global name; return idaapi.BADADDR on failure
def find_global_name_ea(name):
return ida_name.get_name_ea(idaapi.BADADDR, name)
# check whether we have a data offset at given EA
def ea_has_data_offset(ea):
flags = ida_bytes.get_full_flags(ea)
return (flags & ida_bytes.FF_DATA) != 0 and ida_bytes.is_off0(flags)
# get instruction at EA; return None or insn_t instance
def decode_instruction(ea):
insn = ida_ua.insn_t()
ilen = ida_ua.decode_insn(insn, ea)
return insn if ilen > 0 else None
# get instruction's operand as immediate
def get_instruction_operand_immediate(ea, opIndex):
insn = decode_instruction(ea)
return insn.ops[opIndex].addr if insn else None
# given EA of a call instruction, return EAs of instructions setting arguments in proper order
def get_call_argument_assignment_eas(call_ea):
return idaapi.get_arg_addrs(call_ea)
# enumerate xrefs to address
def enumerate_xrefs_to(ea):
addr = ida_xref.get_first_cref_to(ea)
while addr != idaapi.BADADDR:
yield addr
addr = ida_xref.get_next_cref_to(ea, addr)
# create typeinfo from cdecl; return None on failure
# cdecl should not have a trailing semicolon, it is added automatically
def parse_cdecl(cdecl):
tif = ida_typeinf.tinfo_t()
return tif if ida_typeinf.parse_decl(tif, None, cdecl + ';', 0) == '' else None
# speed up mass creation of enums or structs
@contextmanager
def mass_type_updater(utpFlag):
print(f'{datetime.datetime.now()} Starting mass type updates: {utpFlag}')
ida_typeinf.begin_type_updating(utpFlag)
try:
yield None
finally:
print(f'{datetime.datetime.now()} Comitting mass type updates: {utpFlag}')
ida_typeinf.end_type_updating(utpFlag)
print(f'{datetime.datetime.now()} Done with mass type updates: {utpFlag}')
# create enum type; returns idaapi.BADADDR on failure
def add_enum(name, bitfield, signed, width):
# flags seem to be undocumented, here's what I found:
# signed is 0x20000
# hexa is FF_0NUMH | FF_1NUMH (0x1100000), decimal is FF_0NUMD | FF_1NUMD (0x2200000), octal is FF_0NUMO | FF_1NUMO (0x7700000), binary is FF_0NUMB | FF_1NUMB (0x6600000), character is FF_0CHAR | FF_1CHAR (0x3300000)
flags = 0x1100000
if signed:
flags |= 0x20000
eid = ida_enum.add_enum(idaapi.BADADDR, name, flags)
if eid != idaapi.BADADDR:
ida_enum.set_enum_bf(eid, bitfield)
ida_enum.set_enum_width(eid, width)
return eid
# get structure by name
def get_struct_by_name(name):
return ida_struct.get_struc(ida_struct.get_struc_id(name))
# create struct type; returns struc_t or None on failure
def add_struct(name, isUnion = False):
sid = ida_struct.add_struc(idaapi.BADADDR, name, isUnion)
return ida_struct.get_struc(sid) if sid != idaapi.BADADDR else None
# create structure member of primitive types
def add_struct_member_primitive(s, offset, name, flag, size):
return ida_struct.add_struc_member(s, name, offset, ida_bytes.FF_DATA | flag, None, size) == 0
def add_struct_member_byte(s, offset, name, arraySize = 1):
return add_struct_member_primitive(s, offset, name, ida_bytes.FF_BYTE, arraySize)
def add_struct_member_word(s, offset, name, arraySize = 1):
return add_struct_member_primitive(s, offset, name, ida_bytes.FF_WORD, arraySize * 2)
def add_struct_member_dword(s, offset, name, arraySize = 1):
return add_struct_member_primitive(s, offset, name, ida_bytes.FF_DWORD, arraySize * 4)
def add_struct_member_qword(s, offset, name, arraySize = 1):
return add_struct_member_primitive(s, offset, name, ida_bytes.FF_QWORD, arraySize * 8)
# create structure member of pointer type
def add_struct_member_ptr(s, offset, name, arraySize = 1):
opinfo = ida_nalt.opinfo_t()
opinfo.ri = ida_nalt.refinfo_t()
opinfo.ri.base = opinfo.ri.target = idaapi.BADADDR
opinfo.ri.flags = ida_nalt.REF_OFF64
return ida_struct.add_struc_member(s, name, offset, ida_bytes.FF_DATA | ida_bytes.FF_QWORD | ida_bytes.FF_0OFF | ida_bytes.FF_1OFF, opinfo, arraySize * 8) == 0
# create structure member of enumeration type
def add_struct_member_enum(s, offset, name, type, arraySize = 1):
opinfo = ida_nalt.opinfo_t()
opinfo.tid = ida_enum.get_enum(type)
if opinfo.tid == idaapi.BADADDR:
return False
w = ida_enum.get_enum_width(opinfo.tid)
if w == 1:
flag = ida_bytes.FF_BYTE
elif w == 2:
flag = ida_bytes.FF_WORD
elif w == 8:
flag = ida_bytes.FF_QWORD
else:
flag = ida_bytes.FF_DWORD # default
return ida_struct.add_struc_member(s, name, offset, ida_bytes.FF_DATA | flag | ida_bytes.FF_0ENUM | ida_bytes.FF_1ENUM, opinfo, w * arraySize) == 0
# create structure member that is an instance of a different structure; returns success
def add_struct_member_substruct(s, offset, name, type, arraySize = 1):
opinfo = ida_nalt.opinfo_t()
opinfo.tid = ida_struct.get_struc_id(type)
size = ida_struct.get_struc_size(opinfo.tid) if opinfo.tid != idaapi.BADADDR else 0
if size == 0: # note: adding 0-sized struct field leads to problems
return False
return ida_struct.add_struc_member(s, name, offset, ida_bytes.FF_DATA | ida_bytes.FF_STRUCT, opinfo, size * arraySize) == 0
# create base class field for a structure
def add_struct_baseclass(s, type):
offset = ida_struct.get_struc_size(s)
success = add_struct_member_substruct(s, offset, f'baseclass_{hex(offset)[2:]}', type)
if success:
ida_struct.get_member(s, offset).props |= ida_struct.MF_BASECLASS
# TODO: we might need to call save_struc here...
return success
# create structure member of custom type
def add_struct_member_typed(s, offset, name, type):
return add_struct_member_byte(s, offset, name) and set_struct_member_by_offset_type(s, offset, type)
def get_struct_member_tinfo(m):
tif = ida_typeinf.tinfo_t()
return tif if ida_struct.get_member_tinfo(tif, m) else None
# set structure member type info
def set_struct_member_tinfo(s, m, tif):
# note: SET_MEMTI_* flags
return ida_struct.set_member_tinfo(s, m, 0, tif, 0) > 0
# set structure member type from string
def set_struct_member_type(s, m, type):
tif = parse_cdecl(type)
return set_struct_member_tinfo(s, m, tif) if tif else False
# set structure member type
def set_struct_member_by_offset_type(s, offset, type):
m = ida_struct.get_member(s, offset)
return set_struct_member_type(s, m, type) if m else False
# add comment; if one already exists, append as new line (unless existing comment already contains what we're trying to add)
def add_comment_enum(id, comment, repeatable = False):
existing = ida_enum.get_enum_cmt(id, repeatable)
if not existing or comment not in existing:
ida_enum.set_enum_cmt(id, f'{existing}\n{comment}' if existing else comment, repeatable)
def add_comment_func(func, comment, repeatable = False):
existing = ida_funcs.get_func_cmt(func, repeatable)
if not existing or comment not in existing:
ida_funcs.set_func_cmt(func, f'{existing}\n{comment}' if existing else comment, repeatable)
def add_comment_inline(ea, comment, repeatable = False):
existing = ida_bytes.get_cmt(ea, repeatable)
if not existing or comment not in existing:
ida_bytes.set_cmt(ea, f'{existing}\n{comment}' if existing else comment, repeatable)
def add_comment_outline(ea, comment, posterior = False):
line = ida_lines.E_NEXT if posterior else ida_lines.E_PREV
while True:
existing = ida_lines.get_extra_cmt(ea, line)
if not existing:
ida_lines.add_extra_cmt(ea, not posterior, comment)
return
elif comment in existing:
return
else:
line += 1
# func or outline
def add_comment_ea_auto(ea, comment):
func = ida_funcs.get_func(ea)
if func:
add_comment_func(func, comment)
else:
add_comment_outline(ea, comment)