mid-kid
2 years ago
1 changed files with 259 additions and 0 deletions
@ -0,0 +1,259 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
# vim:set tabstop=4 shiftwidth=4 expandtab smarttab |
||||
|
|
||||
|
from sys import argv, exit, stderr |
||||
|
from struct import unpack, calcsize |
||||
|
from enum import Enum |
||||
|
|
||||
|
class nodetype(Enum): |
||||
|
REPT = 0 |
||||
|
FILE = 1 |
||||
|
MACRO = 2 |
||||
|
|
||||
|
class symtype(Enum): |
||||
|
LOCAL = 0 |
||||
|
IMPORT = 1 |
||||
|
EXPORT = 2 |
||||
|
|
||||
|
class sectype(Enum): |
||||
|
WRAM0 = 0 |
||||
|
VRAM = 1 |
||||
|
ROMX = 2 |
||||
|
ROM0 = 3 |
||||
|
HRAM = 4 |
||||
|
WRAMX = 5 |
||||
|
SRAM = 6 |
||||
|
OAM = 7 |
||||
|
|
||||
|
class patchtype(Enum): |
||||
|
BYTE = 0 |
||||
|
WORD = 1 |
||||
|
LONG = 2 |
||||
|
JR = 3 |
||||
|
|
||||
|
class asserttype(Enum): |
||||
|
WARN = 0 |
||||
|
ERROR = 1 |
||||
|
FAIL = 2 |
||||
|
|
||||
|
def unpack_file(fmt, file): |
||||
|
size = calcsize(fmt) |
||||
|
return unpack(fmt, file.read(size)) |
||||
|
|
||||
|
def read_string(file): |
||||
|
buf = bytearray() |
||||
|
while True: |
||||
|
b = file.read(1) |
||||
|
if b is None or b == b'\0': |
||||
|
return buf.decode() |
||||
|
else: |
||||
|
buf += b |
||||
|
|
||||
|
def parse_object(filename): |
||||
|
file = open(filename, "rb") |
||||
|
|
||||
|
obj = { |
||||
|
"nodes": [], |
||||
|
"symbols": [], |
||||
|
"sections": [], |
||||
|
"assertions": [], |
||||
|
} |
||||
|
|
||||
|
magic = unpack_file("4s", file)[0] |
||||
|
if magic == b'RGB6': |
||||
|
obj_ver = 6 |
||||
|
elif magic == b'RGB9': |
||||
|
obj_ver = 10 + unpack_file("<I", file)[0] |
||||
|
else: |
||||
|
print("Error: File '%s' is of an unknown format." % filename, file=stderr) |
||||
|
return None |
||||
|
|
||||
|
if obj_ver not in [6, 10, 11, 13, 14, 16]: |
||||
|
print("Error: File '%s' is of an unknown format: %d" % (filename, obj_ver), file=stderr) |
||||
|
return None |
||||
|
|
||||
|
obj["version"] = obj_ver |
||||
|
|
||||
|
num_symbols, num_sections = unpack_file("<II", file) |
||||
|
|
||||
|
num_nodes = 0 |
||||
|
if obj_ver >= 16: |
||||
|
num_nodes = unpack_file("<I", file)[0] |
||||
|
|
||||
|
for x in range(num_nodes): |
||||
|
node = {} |
||||
|
|
||||
|
node["parent_id"], node["parent_line"] = unpack_file("<II", file) |
||||
|
node["type"] = nodetype(unpack_file("<B", file)[0]) |
||||
|
if node["type"] != nodetype.REPT: |
||||
|
node["name"] = read_string(file) |
||||
|
else: |
||||
|
depth = unpack_file("<I", file)[0] |
||||
|
node["depth"] = [unpack_file("<I", file) for x in range(depth)] |
||||
|
obj["nodes"].append(node) |
||||
|
|
||||
|
for x in range(num_symbols): |
||||
|
symbol = {} |
||||
|
|
||||
|
symbol["name"] = read_string(file) |
||||
|
symbol["type"] = symtype(unpack_file("<B", file)[0]) |
||||
|
if symbol["type"] != symtype.IMPORT: |
||||
|
if obj_ver >= 16: |
||||
|
node = unpack_file("<I", file)[0] |
||||
|
symbol["node"] = node |
||||
|
else: |
||||
|
symbol["filename"] = read_string(file) |
||||
|
symbol["line_num"], symbol["section"], symbol["value"] = unpack_file("<III", file) |
||||
|
|
||||
|
obj["symbols"].append(symbol) |
||||
|
|
||||
|
for x in range(num_sections): |
||||
|
section = {} |
||||
|
|
||||
|
section["name"] = read_string(file) |
||||
|
size, type, section["org"], section["bank"] = unpack_file("<IBII", file) |
||||
|
section["type"] = sectype(type) |
||||
|
|
||||
|
if obj_ver >= 14: |
||||
|
section["align"], section["offset"] = unpack_file("<BI", file) |
||||
|
else: |
||||
|
section["align"] = unpack_file("<I", file)[0] |
||||
|
|
||||
|
if section["type"] == sectype.ROMX or section["type"] == sectype.ROM0: |
||||
|
section["data"] = file.read(size) |
||||
|
|
||||
|
section["patches"] = [] |
||||
|
num_patches = unpack_file("<I", file)[0] |
||||
|
for x in range(num_patches): |
||||
|
patch = {} |
||||
|
|
||||
|
if obj_ver >= 16: |
||||
|
node = unpack_file("<I", file)[0] |
||||
|
patch["node"] = node |
||||
|
else: |
||||
|
patch["filename"] = read_string(file) |
||||
|
if obj_ver < 11 or obj_ver >= 16: |
||||
|
patch["line"] = unpack_file("<I", file)[0] |
||||
|
patch["offset"] = unpack_file("<I", file)[0] |
||||
|
if obj_ver >= 14: |
||||
|
patch["pcsectionid"], patch["pcoffset"] = unpack_file("<II", file) |
||||
|
type, rpn_size = unpack_file("<BI", file) |
||||
|
patch["type"] = patchtype(type) |
||||
|
patch["rpn"] = file.read(rpn_size) |
||||
|
|
||||
|
section["patches"].append(patch) |
||||
|
|
||||
|
obj["sections"].append(section) |
||||
|
|
||||
|
num_assertions = 0 |
||||
|
if obj_ver >= 13: |
||||
|
num_assertions = unpack_file("<I", file)[0] |
||||
|
|
||||
|
for x in range(num_assertions): |
||||
|
assertion = {} |
||||
|
|
||||
|
if obj_ver >= 16: |
||||
|
node = unpack_file("<I", file)[0] |
||||
|
assertion["node"] = node |
||||
|
assertion["line"] = unpack_file("<I", file)[0] |
||||
|
else: |
||||
|
assertion["filename"] = read_string(file) |
||||
|
assertion["offset"] = unpack_file("<I", file)[0] |
||||
|
if obj_ver >= 14: |
||||
|
assertion["section"], assertion["pcoffset"] = unpack_file("<II") |
||||
|
type, rpn_size = unpack_file("<BI", file) |
||||
|
assertion["type"] = asserttype(type) |
||||
|
assertion["rpn"] = file.read(rpn_size) |
||||
|
assertion["message"] = read_string(file) |
||||
|
|
||||
|
obj["assertions"].append(assertion) |
||||
|
|
||||
|
return obj |
||||
|
|
||||
|
|
||||
|
if len(argv) <= 1: |
||||
|
print("Usage: %s [-D] <objects.o...>" % argv[0], file=stderr) |
||||
|
exit(1) |
||||
|
|
||||
|
argi = 1 |
||||
|
|
||||
|
just_dump = False |
||||
|
if argv[argi] == "-D": |
||||
|
just_dump = True |
||||
|
argi += 1 |
||||
|
if argv[argi] == "--": |
||||
|
argi += 1 |
||||
|
|
||||
|
globalsyms = {} |
||||
|
|
||||
|
for filename in argv[argi:]: |
||||
|
print(filename, file=stderr) |
||||
|
|
||||
|
obj = parse_object(filename) |
||||
|
if obj is None: |
||||
|
exit(1) |
||||
|
|
||||
|
if just_dump: |
||||
|
for symbol in obj["symbols"]: |
||||
|
print(symbol["name"]) |
||||
|
continue |
||||
|
|
||||
|
localsyms = {} |
||||
|
|
||||
|
imports = 0 |
||||
|
exports = 0 |
||||
|
for symbol in obj["symbols"]: |
||||
|
if symbol["type"] == symtype.LOCAL: |
||||
|
if symbol["name"] not in localsyms: |
||||
|
localsyms[symbol["name"]] = 0 |
||||
|
elif symbol["type"] == symtype.IMPORT: |
||||
|
imports += 1 |
||||
|
if symbol["name"] not in globalsyms: |
||||
|
globalsyms[symbol["name"]] = 0 |
||||
|
elif symbol["type"] == symtype.EXPORT: |
||||
|
exports += 1 |
||||
|
if symbol["name"] not in globalsyms: |
||||
|
globalsyms[symbol["name"]] = 0 |
||||
|
|
||||
|
print("Locals:", len(localsyms), file=stderr) |
||||
|
print("Imports:", imports, file=stderr) |
||||
|
print("Exports:", exports, file=stderr) |
||||
|
|
||||
|
def parse_rpn(rpn): |
||||
|
pos = 0 |
||||
|
while pos < len(rpn): |
||||
|
if rpn[pos] == 0x51: |
||||
|
pos += 1 |
||||
|
while rpn[pos] != 0: |
||||
|
pos += 1 |
||||
|
pos += 1 |
||||
|
elif rpn[pos] == 0x80: |
||||
|
pos += 5 |
||||
|
elif rpn[pos] == 0x50 or rpn[pos] == 0x81: |
||||
|
symbol_id = unpack("<I", rpn[pos + 1:pos + 5])[0] |
||||
|
if symbol_id != 0xffffffff: |
||||
|
symbol = obj["symbols"][symbol_id] |
||||
|
if symbol["type"] == symtype.LOCAL: |
||||
|
localsyms[symbol["name"]] += 1 |
||||
|
else: |
||||
|
globalsyms[symbol["name"]] += 1 |
||||
|
pos += 5 |
||||
|
else: |
||||
|
pos += 1 |
||||
|
|
||||
|
for section in obj["sections"]: |
||||
|
if section["type"] == sectype.ROMX or section["type"] == sectype.ROM0: |
||||
|
for patch in section["patches"]: |
||||
|
parse_rpn(patch["rpn"]) |
||||
|
for assertion in obj["assertions"]: |
||||
|
parse_rpn(assertion["rpn"]) |
||||
|
|
||||
|
print(file=stderr) |
||||
|
|
||||
|
if just_dump: |
||||
|
exit() |
||||
|
|
||||
|
print("Unreferenced globals:", file=stderr) |
||||
|
for symbol in globalsyms: |
||||
|
if globalsyms[symbol] == 0: |
||||
|
print(symbol) |
Loading…
Reference in new issue