Skip to content

Instantly share code, notes, and snippets.

@EthanArbuckle
Created May 9, 2025 13:13
Show Gist options
  • Save EthanArbuckle/7313c2fa191f7c498128fe4bbac19a10 to your computer and use it in GitHub Desktop.
Save EthanArbuckle/7313c2fa191f7c498128fe4bbac19a10 to your computer and use it in GitHub Desktop.
cli frontend for hexrays.cfg config flags
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