Write Patch as YAML

SkillFiles & storage

Write patch analysis results, including patch_sig match VA/RVA, as a YAML file beside the binary using IDA Pro MCP. Use this skill after identifying a patch target and generating a unique signature to persist patch_name, patch_va, patch_rva, patch_sig, optional patch_sig_disp, and patch_bytes in a standardized format.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Write Patch as YAML skill

What this skill tells your AI

The instructions your AI receives, as published by hlnd2t/cs2_vibesignatures in .claude/skills/write-patch-as-yaml/SKILL.md and read by ahel’s review.

Persist a single patch analysis result to a YAML file beside the binary using IDA Pro MCP. Resolve the unique patch_sig match in the current IDB and record its VA and RVA in the output.

Prerequisites

Before using this skill, you should have:

  1. Identified the patch name and determined patch_bytes
  2. Generated a unique signature using /generate-signature-for-patch

Required Parameters

ParameterDescriptionExample
patch_nameDescriptive name of the patchServerMovementUnlock
patch_sigUnique byte signature locating the instruction to patch0F 86 AF 00 00 00 0F 57 C0 0F 2E C2
patch_bytesReplacement bytes to write at the patch locationE9 B0 00 00 00 90

The skill computes these required output fields automatically; callers do not provide them:

Output fieldDescriptionExample
patch_vaVA where patch_sig uniquely matches0x180A00E2F
patch_rvaRVA of the same signature match (patch_va - image_base)0xA00E2F

Optional Parameters

ParameterDescriptionExample
patch_sig_dispByte displacement from signature start to the target instruction. 0 or None means signature starts at the target instruction. Non-zero values support legacy or externally generated displaced signatures. (use None to omit)5

Method

mcp__ida-pro-mcp__py_eval code="""
import idaapi
import ida_bytes
import ida_segment
import os
import yaml

# === REQUIRED: Replace these values ===
patch_name = "<patch_name>"             # e.g., "ServerMovementUnlock"
patch_sig = "<patch_sig>"               # e.g., "0F 86 AF 00 00 00 0F 57 C0 0F 2E C2"
patch_bytes = "<patch_bytes>"           # e.g., "E9 B0 00 00 00 90"
# ======================================

# === OPTIONAL: Set to None to omit from output ===
patch_sig_disp = <patch_sig_disp>       # e.g., 5 or None (0 also omitted)
# =================================================

# Find the unique patch_sig match in .text. patch_va/patch_rva always point to
# the signature start; when patch_sig_disp is non-zero, the target instruction
# is at patch_va + patch_sig_disp.
tokens = patch_sig.split()
if not tokens or any(token != '??' and (len(token) != 2 or any(c not in '0123456789abcdefABCDEF' for c in token)) for token in tokens):
    raise ValueError(f"Invalid patch_sig: {patch_sig!r}")

pattern = bytes(0 if token == '??' else int(token, 16) for token in tokens)
mask = bytes(0x00 if token == '??' else 0xFF for token in tokens)

def raw_bin_search(ea, max_ea, data, data_mask, flags=0):
    if hasattr(ida_bytes, 'find_bytes'):
        return ida_bytes.find_bytes(data, ea, range_end=max_ea, mask=data_mask, flags=flags)
    return ida_bytes.bin_search(ea, max_ea, data, data_mask, len(data), flags)

text_seg = ida_segment.get_segm_by_name('.text')
if text_seg:
    search_start, search_end = text_seg.start_ea, text_seg.end_ea
else:
    search_start, search_end = idaapi.cvar.inf.min_ea, idaapi.cvar.inf.max_ea

flags = ida_bytes.BIN_SEARCH_FORWARD | ida_bytes.BIN_SEARCH_NOBREAK
matches = []
ea = raw_bin_search(search_start, search_end, pattern, mask, flags)
while ea != idaapi.BADADDR and len(matches) < 2:
    matches.append(ea)
    ea = raw_bin_search(ea + 1, search_end, pattern, mask, flags)

if len(matches) != 1:
    raise RuntimeError(f"patch_sig must match exactly once in .text, found {len(matches)} matches")

patch_va = matches[0]
patch_rva = patch_va - idaapi.get_imagebase()

# Get binary path and determine platform
input_file = idaapi.get_input_file_path()
dir_path = os.environ.get('CS2VIBE_ARTIFACT_DIR') or os.path.dirname(input_file)

if input_file.endswith('.dll'):
    platform = 'windows'
else:
    platform = 'linux'

# Build data dictionary conditionally
data = {}

data['patch_name'] = patch_name
data['patch_va'] = hex(patch_va)
data['patch_rva'] = hex(patch_rva)
data['patch_sig'] = patch_sig

if patch_sig_disp is not None and patch_sig_disp > 0:
    data['patch_sig_disp'] = patch_sig_disp

data['patch_bytes'] = patch_bytes

yaml_path = os.path.join(dir_path, f"{patch_name}.{platform}.yaml")
with open(yaml_path, 'w', encoding='utf-8') as f:
    yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
print(f"Written to: {yaml_path}")
print(f"patch_va={hex(patch_va)}, patch_rva={hex(patch_rva)}")
"""

Output File Naming Convention

The output YAML filename follows this pattern:

  • <patch_name>.<platform>.yaml

Examples:

  • server.dllServerMovementUnlock.windows.yaml
  • libserver.so / libserver.soServerMovementUnlock.linux.yaml

Output YAML Format

Full output (with a displaced signature):

patch_name: ServerMovementUnlock
patch_va: '0x180a00e2f'
patch_rva: '0xa00e2f'
patch_sig: 48 85 C0 74 ?? E8 ?? ?? ?? ?? 0F 86 AF 00 00 00
patch_sig_disp: 5
patch_bytes: E9 B0 00 00 00 90

Standard output (without backward expansion, patch_sig_disp is 0 or omitted):

patch_name: ServerMovementUnlock
patch_va: '0x180a00e2f'
patch_rva: '0xa00e2f'
patch_sig: 0F 86 AF 00 00 00 0F 57 C0 0F 2E C2
patch_bytes: E9 B0 00 00 00 90

Each field:

  • patch_name - Descriptive name of the patch
  • patch_va - VA where patch_sig uniquely matches in the current binary
  • patch_rva - RVA of the same signature match (patch_va - image_base)
  • patch_sig - Unique byte signature locating the instruction to patch
  • patch_sig_disp (optional) - Byte displacement from patch_va to the target instruction. Only present when non-zero. Runtime: scan for patch_sig, then add patch_sig_disp to get the target instruction address.
  • patch_bytes - Replacement bytes to write at the patch location

Platform Detection

The skill automatically detects the platform based on file extension:

  • .dll → Windows
  • .so → Linux

Example Usage

Standard patch (forward-only signature)

patch_name = "ServerMovementUnlock"
patch_sig = "0F 86 AF 00 00 00 0F 57 C0 0F 2E C2"
patch_bytes = "E9 B0 00 00 00 90"
patch_sig_disp = None

Legacy or externally generated displaced signature

patch_name = "DisableSteamBanCheck"
patch_sig = "48 85 C0 74 ?? E8 ?? ?? ?? ?? 0F 84 AA 00 00 00"
patch_bytes = "90 90 90 90 90 90"
patch_sig_disp = 5

NOP a call instruction

patch_name = "SkipPrecacheCall"
patch_sig = "E8 AA BB CC DD 48 8B 5C 24 30"
patch_bytes = "90 90 90 90 90"
patch_sig_disp = None

Trusted finalization

This writer produces a semantic YAML payload at the caller-provided expected artifact path. It does not own final field ordering, scalar spelling, encoding, or line endings. After runtime validation, the trusted analyzer rewrites every successful preprocessor or Agent output through the Source2 central canonicalizer; that canonical rewrite is the only byte-level trust boundary.

Notes

  • The YAML file is written to the same directory as the input binary
  • patch_sig must match exactly once in the current IDB .text segment; otherwise the skill stops without writing YAML
  • patch_va and patch_rva always identify the patch_sig match start, not the displaced target instruction
  • When patch_sig_disp is None or 0, the patch_sig_disp field is omitted from the output entirely (signature starts at the target instruction)
  • patch_sig should be a signature generated by /generate-signature-for-patch
  • patch_bytes must have the same byte count as the original instruction being patched
  • patch_sig_disp is the byte displacement from signature start to the target instruction; the current /generate-signature-for-patch normally returns 0

Signals

GitHub stars
65
Forks
10
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
write-patch-as-yaml
Source
github.com/hlnd2t/cs2_vibesignatures