Created
May 9, 2025 13:13
-
-
Save EthanArbuckle/7313c2fa191f7c498128fe4bbac19a10 to your computer and use it in GitHub Desktop.
cli frontend for hexrays.cfg config flags
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import enum | |
import sys | |
from typing import Type | |
class HexraysFlag(enum.IntFlag): | |
JUMPOUT_HELPERS = 0x000001 # Generate JUMPOUT() function | |
DISPLAY_CASTS = 0x000002 # Print type casts | |
HIDE_UNORDERED = 0x000004 # Hide unordered fpval comparisons | |
SSE_INTRINSICS = 0x000008 # Generate SSE intrinsic functions | |
IGNORE_OVERLAPS = 0x000010 # Warning for overlapping lvars | |
FAST_STRUCTURAL = 0x000020 # Fast structural analysis | |
CONST_STRINGS = 0x000040 # Print strings only from read-only memory | |
SIGNBIT_CHECKS = 0x000080 # Convert signed comparisons to bit checks | |
UNMERGE_TAILS = 0x000100 # Reduce gotos by duplicating code | |
KEEP_CURLIES = 0x000200 # Keep curly braces for single-statement | |
DEL_ADDR_CMPS = 0x000400 # Optimize away address comparisons | |
SHOW_CSTR_CASTS = 0x000800 # Print casts from string literals | |
ESC_CLOSES_VIEW = 0x001000 # ESC closes the view | |
SPOIL_FLAGREGS = 0x002000 # All functions spoil flags | |
KEEP_INDIRECT_READS = 0x004000 # Keep indirect memory reads | |
KEEP_EH_CODE = 0x008000 # Keep exception handling code | |
SHOW_PAC_INSNS = 0x010000 # Show pointer authentication instructions | |
KEEP_POTENTIAL_DIV0 = 0x020000 # Keep potential div by zero | |
MIPS_ADDSUB_TRAP = 0x040000 # Generate integer overflow trap | |
MIPS_IGN_DIV0_TRAP = 0x080000 # Ignore division by zero trap | |
HONEST_READFLAGS = 0x200000 # Consider __readflags as using CPU flags | |
NON_FATAL_INTERR = 0x400000 # Allow decompile after internal error | |
SINGLE_LINE_PROTO = 0x800000 # Force single-line function declarations | |
DECOMPILE_LIBFUNCS = 0x1000000 # Decompile library functions | |
PROP_VOLATILE_LDX = 0x2000000 # Propagate ldx without volatile check | |
@classmethod | |
def get_individual_flags(cls: Type["HexraysFlag"]) -> list["HexraysFlag"]: | |
return [member for member in cls if member.value != 0 and (member.value & (member.value - 1)) == 0] | |
class HexraysConfig: | |
_value: int | |
def __init__(self, config: int | HexraysFlag = 0) -> None: | |
if isinstance(config, HexraysFlag): | |
self._value = config.value | |
elif isinstance(config, int): | |
self._value = config | |
else: | |
raise TypeError(f"config must be an int or HexraysFlag instance, got {type(config).__name__}") | |
def _resolve_flag(self, flag_input: str | HexraysFlag) -> HexraysFlag: | |
if isinstance(flag_input, HexraysFlag): | |
return flag_input | |
if isinstance(flag_input, str): | |
try: | |
return HexraysFlag[flag_input.upper()] | |
except KeyError: | |
valid_flags = ", ".join(f.name for f in HexraysFlag.get_individual_flags() if f.name) | |
raise ValueError(f"Unknown '{flag_input}'. Available flags are: {valid_flags}") | |
raise TypeError(f"Flag must be a string name or HexraysFlag member, not {type(flag_input).__name__}") | |
def enable(self, *flags: str | HexraysFlag) -> "HexraysConfig": | |
for flag_input in flags: | |
flag_member = self._resolve_flag(flag_input) | |
self._value |= flag_member.value | |
return self | |
def disable(self, *flags: str | HexraysFlag) -> "HexraysConfig": | |
for flag_input in flags: | |
flag_member = self._resolve_flag(flag_input) | |
self._value &= ~flag_member.value | |
return self | |
def is_enabled(self, flag_input: str | HexraysFlag) -> bool: | |
flag_member = self._resolve_flag(flag_input) | |
if flag_member.value == 0: | |
return self._value == 0 | |
return (self._value & flag_member.value) == flag_member.value | |
def get_enabled_flag_names(self) -> list[str]: | |
names: list[str] = [] | |
if self._value == 0: | |
return [] | |
for flag_member in HexraysFlag.get_individual_flags(): | |
if (self._value & flag_member.value) == flag_member.value and flag_member.name: | |
names.append(flag_member.name) | |
return names | |
def to_hex_string(self) -> str: | |
return f"0x{self._value:08X}" | |
@property | |
def int_value(self) -> int: | |
return self._value | |
@classmethod | |
def available_flags(cls) -> dict[str, int]: | |
return {member.name: member.value for member in HexraysFlag.get_individual_flags() if member.name} | |
def __repr__(self) -> str: | |
return f"HexraysConfig('{self.to_hex_string()}')" | |
def manual_config() -> None: | |
config = HexraysConfig(0x0) | |
config.enable("JUMPOUT_HELPERS") | |
config.enable("DISPLAY_CASTS") | |
config.enable("HIDE_UNORDERED") | |
config.enable("SSE_INTRINSICS") | |
# config.enable("IGNORE_OVERLAPS") | |
config.enable("FAST_STRUCTURAL") | |
config.enable("CONST_STRINGS") | |
config.enable("SIGNBIT_CHECKS") | |
config.enable("UNMERGE_TAILS") | |
config.enable("KEEP_CURLIES") | |
config.enable("DEL_ADDR_CMPS") | |
config.enable("SHOW_CSTR_CASTS") | |
# config.enable("ESC_CLOSES_VIEW") | |
# config.enable("SPOIL_FLAGREGS") | |
config.enable("KEEP_INDIRECT_READS") | |
config.enable("KEEP_EH_CODE") | |
# config.enable("SHOW_PAC_INSNS") | |
# config.enable("KEEP_POTENTIAL_DIV0") | |
config.enable("MIPS_ADDSUB_TRAP") | |
config.enable("MIPS_IGN_DIV0_TRAP") | |
# config.enable("HONEST_READFLAGS") | |
# config.enable("NON_FATAL_INTERR") | |
config.enable("SINGLE_LINE_PROTO") | |
config.enable("DECOMPILE_LIBFUNCS") | |
# config.enable("PROP_VOLATILE_LDX") | |
print("Config value:", config.to_hex_string()) | |
def main_cli() -> None: | |
parser = argparse.ArgumentParser( | |
description="Manage HexRays decompiler configuration flags", formatter_class=argparse.RawTextHelpFormatter | |
) | |
parser.add_argument( | |
"-l", | |
"--list-available", | |
action="store_true", | |
help="List all available flag names and their hex values, then exit", | |
) | |
parser.add_argument( | |
"-c", | |
"--config", | |
type=str, | |
default="0", | |
help="Initial config value (e.g., 0x8831FF or decimal). Defaults to 0", | |
) | |
parser.add_argument( | |
"-e", | |
"--enable", | |
nargs="+", | |
metavar="FLAG_NAME", | |
default=[], | |
help="One or more flag names to enable (case-insensitive)", | |
) | |
parser.add_argument( | |
"-d", | |
"--disable", | |
nargs="+", | |
metavar="FLAG_NAME", | |
default=[], | |
help="One or more flag names to disable (case-insensitive)", | |
) | |
args = parser.parse_args() | |
if args.list_available: | |
print("Config flags (name: hex_value):") | |
max_name_len = max(len(name) for name in HexraysConfig.available_flags()) | |
for name, value in sorted(HexraysConfig.available_flags().items()): | |
print(f" {name:<{max_name_len}} : 0x{value:08X}") | |
exit(0) | |
try: | |
current_config_value = int(args.config, 0) | |
except ValueError: | |
parser.error(f"Invalid format for --config: '{args.config}'. Must be decimal or hex (e.g., 0xABC)") | |
exit(1) | |
config = HexraysConfig(current_config_value) | |
if args.enable: | |
config.enable(*args.enable) | |
if args.disable: | |
config.disable(*args.disable) | |
print(f"\nConfig value: {config.to_hex_string()}") | |
enabled_flags = config.get_enabled_flag_names() | |
if enabled_flags: | |
print("\nEnabled flags:") | |
for flag_name in sorted(enabled_flags): | |
print(f"- {flag_name}") | |
else: | |
print("\nNo flags are enabled") | |
if __name__ == "__main__": | |
# manual_config() | |
main_cli() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment