166 lines
No EOL
5.1 KiB
Python
166 lines
No EOL
5.1 KiB
Python
from __future__ import annotations
|
|
|
|
import struct
|
|
from pathlib import Path
|
|
|
|
|
|
MAGIC_DATA_OFF = 33000
|
|
|
|
|
|
OPCODE_NAMES = {
|
|
0x81: "set_unused_field53",
|
|
0x82: "clear_unused_field53",
|
|
0x84: "set_target_objid",
|
|
0x85: "anim_walk",
|
|
0x86: "anim_run",
|
|
0x87: "anim_retreat",
|
|
0x88: "turn_left_90",
|
|
0x89: "turn_right_90",
|
|
0x8A: "fire_small_if_clear",
|
|
0x8B: "fire_large_if_clear",
|
|
0x8C: "anim_stand",
|
|
0x8D: "pathfind_home",
|
|
0x8E: "pathfind_target",
|
|
0x8F: "pathfind_midpoint",
|
|
0x92: "anim_kneel_and_fire",
|
|
0x93: "sleep_scaled",
|
|
0x94: "loiter",
|
|
0x95: "face_target",
|
|
0x96: "set_activity",
|
|
0x97: "switch_tactic",
|
|
0x98: "teleport_home",
|
|
0x99: "terminate",
|
|
0x9A: "jump_if_dist_lt_481",
|
|
0x9B: "jump_if_dist_gt_160",
|
|
0x9C: "jump_if_shot_blocked",
|
|
0x9D: "jump_if_shot_clear",
|
|
0x9E: "random_jump_nonzero",
|
|
0x9F: "loop_begin",
|
|
0xA6: "pathfind_marker_frame",
|
|
0xA7: "face_north",
|
|
0xA8: "face_south",
|
|
0xA9: "face_east",
|
|
0xAA: "face_west",
|
|
0xAB: "face_northeast",
|
|
0xAC: "face_southwest",
|
|
0xAD: "face_southeast",
|
|
0xAE: "face_northwest",
|
|
0xAF: "var_set",
|
|
0xB0: "var_add",
|
|
0xB1: "var_sub",
|
|
0xB2: "var_mul",
|
|
0xB3: "var_div",
|
|
0xB4: "var_store_curdir",
|
|
0xB5: "set_dir_raw",
|
|
0xB6: "var_store_curdir_again",
|
|
0xB7: "anim_kneeling_retreat",
|
|
0xB8: "anim_kneeling_advance",
|
|
0xB9: "anim_kneeling_slow_retreat",
|
|
0xC0: "jump",
|
|
0xC1: "loop_end",
|
|
0xFF: "flip_to_block1_restart",
|
|
}
|
|
|
|
|
|
def format_word(value: int) -> str:
|
|
if value >= MAGIC_DATA_OFF:
|
|
return f"var[{value - MAGIC_DATA_OFF}]"
|
|
return f"0x{value:04x} ({value})"
|
|
|
|
|
|
def decode_block(block: bytes, start_offset: int) -> list[str]:
|
|
pc = 0
|
|
lines: list[str] = []
|
|
|
|
def need_word(use_data: bool) -> int:
|
|
nonlocal pc
|
|
value = struct.unpack_from("<H", block, pc)[0]
|
|
pc += 2
|
|
return value
|
|
|
|
while pc < len(block):
|
|
opcode_offset = start_offset + pc
|
|
opcode = block[pc]
|
|
pc += 1
|
|
opname = OPCODE_NAMES.get(opcode, f"opcode_{opcode:02x}")
|
|
operands: list[str] = []
|
|
|
|
if opcode in {0x81, 0x84, 0x93, 0x94, 0x96, 0x97, 0x9A, 0x9B, 0x9C, 0x9D, 0xB5, 0xC0, 0x9F}:
|
|
operands.append(format_word(need_word(True)))
|
|
elif opcode == 0x9E:
|
|
operands.append(format_word(need_word(True)))
|
|
operands.append(format_word(need_word(True)))
|
|
elif opcode == 0xA6:
|
|
operands.append(f"frame={format_word(need_word(True))}")
|
|
elif opcode in {0xAF}:
|
|
operands.append(format_word(need_word(True)))
|
|
operands.append(format_word(need_word(False)))
|
|
elif opcode in {0xB0, 0xB1, 0xB2, 0xB3}:
|
|
operands.append(format_word(need_word(False)))
|
|
operands.append(format_word(need_word(True)))
|
|
elif opcode in {0xB4, 0xB6}:
|
|
operands.append(format_word(need_word(False)))
|
|
|
|
text = opname
|
|
if operands:
|
|
text += " " + ", ".join(operands)
|
|
lines.append(f"0x{opcode_offset:04x}: {text}")
|
|
|
|
return lines
|
|
|
|
|
|
def read_u32(data: bytes, offset: int) -> int:
|
|
return struct.unpack_from("<I", data, offset)[0]
|
|
|
|
|
|
def read_u16(data: bytes, offset: int) -> int:
|
|
return struct.unpack_from("<H", data, offset)[0]
|
|
|
|
|
|
def main() -> None:
|
|
path = Path("STATIC/COMBAT.DAT")
|
|
data = path.read_bytes()
|
|
|
|
print(f"file={path} size={len(data)}")
|
|
print(f"header_magic_fill={data[:0x56].hex()[:32]}...")
|
|
|
|
entries = []
|
|
for index in range(64):
|
|
off = 0x80 + index * 8
|
|
rec_off = read_u32(data, off)
|
|
rec_len = read_u32(data, off + 4)
|
|
if rec_off == 0 and rec_len == 0:
|
|
continue
|
|
entries.append((index, rec_off, rec_len))
|
|
|
|
print(f"entry_count={len(entries)}")
|
|
for index, rec_off, rec_len in entries:
|
|
rec = data[rec_off:rec_off + rec_len]
|
|
name = rec[:16].split(b"\0", 1)[0].decode("ascii")
|
|
block_offsets = [read_u16(rec, 16 + i * 2) for i in range(4)]
|
|
valid_blocks = [value for value in block_offsets if value and value < rec_len]
|
|
print(
|
|
f"[{index:02d}] off=0x{rec_off:04x} len=0x{rec_len:04x}"
|
|
f" name={name:<16} block_offsets={block_offsets}"
|
|
f" body_len={rec_len - min(valid_blocks) if valid_blocks else 0}"
|
|
)
|
|
|
|
for block_no, block_off in enumerate(block_offsets[:2]):
|
|
if not block_off or block_off >= rec_len:
|
|
continue
|
|
next_offsets = sorted(value for value in block_offsets[:2] if value > block_off and value < rec_len)
|
|
block_end = next_offsets[0] if next_offsets else rec_len
|
|
block = rec[block_off:block_end]
|
|
print(
|
|
f" block{block_no}: start=0x{block_off:04x} end=0x{block_end:04x}"
|
|
f" size={block_end - block_off:02d} bytes={block.hex(' ')}"
|
|
)
|
|
for line in decode_block(block, block_off):
|
|
print(f" {line}")
|
|
|
|
trailing_offsets = block_offsets[2:]
|
|
print(f" extra_offsets_unused={trailing_offsets}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |