"""DXBC binary dumper — parses container, signatures, SHEX instructions, and validates via D3DDisassemble.""" import struct import sys import os # DXBC opcodes (SM5) OPCODES = { 0x00: "add", 0x01: "and", 0x02: "break", 0x03: "breakc", 0x04: "call", 0x05: "callc", 0x06: "case", 0x07: "continue", 0x08: "continuec", 0x09: "cut", 0x0A: "default", 0x0B: "deriv_rtx", 0x0C: "deriv_rty", 0x0D: "discard", 0x0E: "div", 0x0F: "dp2", 0x10: "dp3", 0x11: "dp4", 0x12: "else", 0x13: "emit", 0x14: "emitthencut", 0x15: "endif", 0x16: "endloop", 0x17: "endswitch", 0x18: "eq", 0x19: "exp", 0x1A: "frc", 0x1B: "ftoi", 0x1C: "ftou", 0x1D: "ge", 0x1E: "iadd", 0x1F: "if", 0x20: "ieq", 0x21: "ige", 0x22: "ilt", 0x23: "imad", 0x24: "imax", 0x25: "imin", 0x26: "imul", 0x27: "ine", 0x28: "ineg", 0x29: "ishl", 0x2A: "ishr", 0x2B: "itof", 0x2C: "label", 0x2D: "ld", 0x2E: "ld_ms", 0x2F: "log", 0x30: "loop", 0x31: "lt", 0x32: "mad", 0x33: "min", 0x34: "max", 0x35: "customdata", 0x36: "mov", 0x37: "movc", 0x38: "mul", 0x39: "ne", 0x3A: "nop", 0x3B: "not", 0x3C: "or", 0x3D: "resinfo", 0x3E: "ret", 0x3F: "retc", 0x40: "round_ne", 0x41: "round_ni", 0x42: "round_pi", 0x43: "round_z", 0x44: "rsq", 0x45: "sample", 0x46: "sample_c", 0x47: "sample_c_lz", 0x48: "sample_l", 0x49: "sample_d", 0x4A: "sample_b", 0x4E: "sqrt", 0x4F: "switch", 0x50: "sincos", 0x55: "dcl_resource", 0x56: "dcl_constantbuffer", 0x57: "dcl_sampler", 0x58: "dcl_index_range", 0x59: "dcl_output_topology", 0x5A: "dcl_input_primitive", 0x5B: "dcl_max_output_vertex_count", 0x5C: "dcl_input", 0x5D: "dcl_input_sgv", 0x5E: "dcl_input_siv", 0x5F: "dcl_input_ps", 0x60: "dcl_input_ps_sgv", 0x61: "dcl_input_ps_siv", 0x62: "dcl_output", 0x63: "dcl_output_sgv", 0x64: "dcl_output_siv", 0x65: "dcl_temps", 0x66: "dcl_indexable_temp", 0x67: "dcl_global_flags", 0x68: "dcl_thread_group", # Extended 0x6A: "dcl_tgsm_raw", 0x6B: "dcl_tgsm_structured", 0x6C: "dcl_resource_structured", 0x6D: "dcl_resource_raw", 0x6E: "dcl_uav_typed", 0x6F: "dcl_uav_raw", 0x70: "dcl_uav_structured", 0x7E: "uge", 0x7F: "ult", 0x80: "umul", 0x81: "umad", 0x82: "umax", 0x83: "umin", 0x84: "ushr", 0x85: "utof", 0x8B: "ld_structured", 0x8C: "store_structured", 0xA8: "store_uav_typed", } OPERAND_TYPES = { 0: "r", 1: "v", 2: "o", 3: "x(indexable_temp)", 4: "l(imm32)", 5: "l(imm64)", 6: "s", 7: "t", 8: "cb", 9: "x(icb)", 10: "x(label)", 11: "x(output_depth)", 12: "null", 13: "x(rasterizer)", 14: "x(coverage)", 0x1A: "x(tgsm_stride)", } SYS_VALUES = { 0: "NONE", 1: "POSITION", 2: "CLIP_DISTANCE", 3: "CULL_DISTANCE", 4: "RENDER_TARGET_ARRAY", 5: "VIEWPORT_ARRAY", 6: "VERTEX_ID", 7: "PRIMITIVE_ID", 8: "INSTANCE_ID", 9: "IS_FRONT_FACE", 10: "SAMPLE_INDEX", 11: "FINAL_QUAD_EDGE_TESSFACTOR", 12: "FINAL_QUAD_INSIDE_TESSFACTOR", 13: "FINAL_TRI_EDGE_TESSFACTOR", 14: "FINAL_TRI_INSIDE_TESSFACTOR", 15: "FINAL_LINE_DETAIL_TESSFACTOR", 16: "FINAL_LINE_DENSITY_TESSFACTOR", 0xFFFF: "DEPTH", } COMP = "xyzw" def mask_str(m): return "".join(COMP[i] for i in range(4) if m & (1 << i)) def swizzle_str(s): return "".join(COMP[(s >> (i*2)) & 3] for i in range(4)) def decode_operand(data, pos): """Decode a DXBC operand, return (text, new_pos).""" if pos >= len(data): return "???", pos token = data[pos]; pos += 1 num_comp = token & 0x3 sel_mode = (token >> 2) & 0x3 op_type = (token >> 12) & 0xFF idx_dim = (token >> 20) & 0x3 # Extended operand? extended = (token >> 31) & 1 negate = False abs_val = False if extended and pos < len(data): ext_token = data[pos]; pos += 1 negate = bool(ext_token & (1 << 6)) abs_val = bool(ext_token & (1 << 7)) type_name = OPERAND_TYPES.get(op_type, f"type{op_type}") # Immediate values if op_type == 4: # immediate32 if num_comp == 1: # scalar if pos < len(data): val = struct.unpack_from('> 4) & 0xF comp_str = "." + mask_str(m) elif sel_mode == 1: # swizzle s = (token >> 4) & 0xFF sw = swizzle_str(s) if len(set(sw)) > 1 or sw != "xyzw": comp_str = "." + sw elif sel_mode == 2: # select1 c = (token >> 4) & 0x3 comp_str = "." + COMP[c] # Index indices = [] for d in range(idx_dim): idx_rep = (token >> (22 + d*3)) & 0x7 if idx_rep == 0: # immediate32 if pos < len(data): indices.append(str(data[pos])); pos += 1 elif idx_rep == 1: # immediate64 if pos + 1 < len(data): val = data[pos] | (data[pos+1] << 32) indices.append(str(val)); pos += 2 elif idx_rep == 2: # relative indices.append("rel") _, pos = decode_operand(data, pos) elif idx_rep == 3: # immediate32 + relative if pos < len(data): base = data[pos]; pos += 1 indices.append(f"{base}+rel") _, pos = decode_operand(data, pos) prefix = "" suffix = "" if negate: prefix = "-" if abs_val: prefix = f"|{prefix}"; suffix = "|" if op_type == 8: # cb if len(indices) == 2: return f"{prefix}cb{indices[0]}[{indices[1]}]{comp_str}{suffix}", pos return f"{prefix}cb[{','.join(indices)}]{comp_str}{suffix}", pos idx_str = "".join(f"[{i}]" if not i.isdigit() else i for i in indices) if len(indices) == 1 and indices[0].isdigit(): idx_str = indices[0] name = type_name.split("(")[0] if "(" not in type_name else type_name if op_type in (0, 1, 2, 6, 7): return f"{prefix}{type_name}{idx_str}{comp_str}{suffix}", pos elif op_type == 12: # null return "null", pos return f"{prefix}{type_name}{idx_str}{comp_str}{suffix}", pos def dump_shex(data, chunk_data): """Dump SHEX/SHDR instructions.""" words = [] for i in range(0, len(chunk_data), 4): words.append(struct.unpack_from('> 16) & 0xFFFF major = (ver >> 4) & 0xF minor = ver & 0xF types = {0xFFFF: "ps", 0xFFFE: "vs", 0x4353: "cs", 0x4753: "gs", 0x4853: "hs", 0x4453: "ds"} pt_str = types.get(prog_type, f'0x{prog_type:04x}') print(f" Version: {pt_str}_{major}_{minor}") print(f" Length: {words[1]} dwords") pos = 2 inst_num = 0 while pos < len(words): opcode_token = words[pos] opcode = opcode_token & 0x7FF length = (opcode_token >> 24) & 0x7F saturate = (opcode_token >> 13) & 1 if length == 0: # Some opcodes have 0 length meaning they're 1 dword length = 1 name = OPCODES.get(opcode, f"op_0x{opcode:02x}") # Decode operands operand_words = words[pos+1:pos+length] sat_str = "_sat" if saturate else "" raw_hex = " ".join(f"{w:08x}" for w in words[pos:pos+length]) # Try to decode operands operands = [] opos = 0 try: while opos < len(operand_words): text, new_opos = decode_operand(operand_words, opos) if new_opos == opos: break # no progress operands.append(text) opos = new_opos except: pass op_str = ", ".join(operands) if operands else "" print(f" {inst_num:4d}: {name}{sat_str} {op_str}") print(f" [{raw_hex}]") pos += length inst_num += 1 def dump_dxbc(filepath): with open(filepath, 'rb') as f: data = f.read() print(f"File: {filepath} ({len(data)} bytes)") if len(data) < 32: print("ERROR: File too small for DXBC header") return magic = data[0:4] if magic != b'DXBC': print(f"ERROR: Bad magic: {magic}") return md5 = data[4:20].hex() version = struct.unpack_from('> 8) & 0xFF name_end = name_off while name_end < len(chunk_data) and chunk_data[name_end] != 0: name_end += 1 name = chunk_data[name_off:name_end].decode('utf-8', errors='replace') sv_name = SYS_VALUES.get(sys_val, f"?{sys_val}") ctypes = {0: "unknown", 1: "uint", 2: "int", 3: "float"} ct = ctypes.get(comp_type, f"?{comp_type}") io = "v" if chunk_magic == "ISGN" else "o" print(f" [{j}] {name}{sem_idx} : {io}{reg}.{mask_str(mask)} ({ct}) sysval={sv_name} rw={mask_str(rw)}") # Validate if chunk_magic == "OSGN": for k in range(j): base_k = 8 + k * 24 reg_k = struct.unpack_from('= 8: inst_count = struct.unpack_from('") sys.exit(1) dump_dxbc(sys.argv[1])