Created
April 9, 2026 22:19
-
-
Save Mart-Bogdan/99589167ff64c54fc72d971d232463c2 to your computer and use it in GitHub Desktop.
Converts UE stack traces (plain or LLDB format) to Rider-compatible format
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
| #!/usr/bin/env -S uv run --script | |
| # /// script | |
| # requires-python = ">=3.11" | |
| # dependencies = [ | |
| # "pyperclip", | |
| # ] | |
| # /// | |
| """ | |
| Unreal Engine Stack Trace Converter | |
| Converts UE stack traces (plain or LLDB format) to Rider-compatible format. | |
| Usage: | |
| # From file: | |
| uv run ue_stack_converter.py stacktrace.txt | |
| # From clipboard (read input AND copy result back to clipboard): | |
| uv run ue_stack_converter.py --clipboard | |
| # From stdin: | |
| cat stacktrace.txt | uv run ue_stack_converter.py - | |
| # Direct string argument: | |
| uv run ue_stack_converter.py --text "GitSourceControlUtils::UpdateChangelistStateByCommand() GitSourceControlUtils.cpp:1636" | |
| # Save output to file: | |
| uv run ue_stack_converter.py stacktrace.txt -o converted.txt | |
| """ | |
| import re | |
| import sys | |
| import argparse | |
| def convert_plain_frame(line: str) -> str | None: | |
| """ | |
| Converts a plain UE stack trace line like: | |
| GitSourceControlUtils::UpdateChangelistStateByCommand() GitSourceControlUtils.cpp:1636 | |
| To: | |
| at GitSourceControlUtils::UpdateChangelistStateByCommand() in C:\\GitSourceControlUtils.cpp:line 1636 | |
| """ | |
| pattern = r'^(\[Inlined\]\s+)?(.+?)\s+([\w.]+\.(?:cpp|h|hpp|inl|c))\s*:\s*(\d+)\s*$' | |
| m = re.match(pattern, line.strip()) | |
| if not m: | |
| return None | |
| inlined_prefix = m.group(1) or '' | |
| func = m.group(2).strip() | |
| filename = m.group(3) | |
| lineno = m.group(4) | |
| inlined_note = '[Inlined] ' if inlined_prefix else '' | |
| return f' at {inlined_note}{func} in C:\\{filename}:line {lineno}' | |
| def convert_lldb_frame(line: str) -> str | None: | |
| """ | |
| Rewrites only the trailing `at File.cpp:N` part of an LLDB frame line. | |
| Everything else (frame number, address, DLL, function, parameter values) is kept verbatim. | |
| Input: | |
| frame #1: 0x00007ffb UnrealEditor.dll`Foo::Bar(x=1) at Foo.cpp:42 | |
| Output: | |
| frame #1: 0x00007ffb UnrealEditor.dll`Foo::Bar(x=1) at C:\Foo.cpp:line 42 | |
| """ | |
| pattern = r'(.*?\bat\s)([\w.]+\.(?:cpp|h|hpp|inl|c)):(\d+)\s*$' | |
| m = re.match(pattern, line) | |
| if not m: | |
| return None | |
| prefix = m.group(1) | |
| filename = m.group(2) | |
| lineno = m.group(3) | |
| return f'{prefix}C:\\{filename}:line {lineno}' | |
| def is_lldb_format(text: str) -> bool: | |
| """Detect if the input looks like LLDB format.""" | |
| return bool(re.search(r'frame\s+#\d+:', text)) | |
| def convert_stack_trace(text: str) -> str: | |
| """Convert a full stack trace (auto-detects plain UE vs LLDB format).""" | |
| lines = text.splitlines() | |
| output_lines = [] | |
| lldb_mode = is_lldb_format(text) | |
| for line in lines: | |
| if not line.strip(): | |
| output_lines.append('') | |
| continue | |
| if lldb_mode: | |
| converted = convert_lldb_frame(line) | |
| if converted: | |
| output_lines.append(converted) | |
| else: | |
| output_lines.append(line) | |
| else: | |
| converted = convert_plain_frame(line) | |
| if converted: | |
| output_lines.append(converted) | |
| else: | |
| output_lines.append(line) | |
| return '\n'.join(output_lines) | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description='Convert Unreal Engine stack traces to Rider-compatible format.' | |
| ) | |
| input_group = parser.add_mutually_exclusive_group() | |
| input_group.add_argument( | |
| 'file', | |
| nargs='?', | |
| help='Input file path (use - for stdin)', | |
| ) | |
| input_group.add_argument( | |
| '--text', '-t', | |
| help='Stack trace text provided directly as argument', | |
| ) | |
| input_group.add_argument( | |
| '--clipboard', '-c', | |
| action='store_true', | |
| help='Read stack trace from clipboard and copy result back to clipboard', | |
| ) | |
| parser.add_argument( | |
| '--output', '-o', | |
| help='Output file path (default: print to stdout)', | |
| ) | |
| args = parser.parse_args() | |
| # --- Read input --- | |
| if args.clipboard: | |
| import pyperclip | |
| raw = pyperclip.paste() | |
| elif args.text: | |
| raw = args.text | |
| elif args.file == '-' or (not args.file and not sys.stdin.isatty()): | |
| raw = sys.stdin.read() | |
| elif args.file: | |
| try: | |
| with open(args.file, 'r', encoding='utf-8') as f: | |
| raw = f.read() | |
| except FileNotFoundError: | |
| print(f'Error: file not found: {args.file}', file=sys.stderr) | |
| sys.exit(1) | |
| else: | |
| print('Paste your stack trace below, then press Ctrl+D (Unix) or Ctrl+Z+Enter (Windows):') | |
| raw = sys.stdin.read() | |
| if not raw.strip(): | |
| print('Error: empty input.', file=sys.stderr) | |
| sys.exit(1) | |
| # --- Convert --- | |
| result = convert_stack_trace(raw) | |
| # --- Output --- | |
| if args.output: | |
| with open(args.output, 'w', encoding='utf-8') as f: | |
| f.write(result) | |
| print(f'Converted stack trace written to: {args.output}') | |
| else: | |
| print(result) | |
| if args.clipboard: | |
| import pyperclip | |
| pyperclip.copy(result) | |
| print('\n(Result copied to clipboard)', file=sys.stderr) | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment