Thar0

z64compress_wrapper.py

Jul 12th, 2021 (edited)
1,623
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.09 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. #
  3. #   z64compress wrapper for decomp projects
  4. #     https://github.com/z64me/z64compress
  5. #   Arguments: <rom in> <rom out> <elf> <spec> [--cache [cache directory]] [--threads [num threads]] [--mb [target rom size]] [--matching]
  6. #   Example Makefile usage:
  7. #     python3 tools/z64compress_wrapper.py --matching --threads $(shell nproc) $< $@ $(ELF) build/$(SPEC)
  8. #
  9.  
  10. import argparse, itertools, subprocess, sys
  11.  
  12. from elftools.elf.elffile import ELFFile
  13. from elftools.elf.sections import SymbolTableSection
  14.  
  15. # Args from command line
  16. parser = argparse.ArgumentParser(description="Compress rom produced by the OoT and MM Decomp projects")
  17.  
  18. parser.add_argument("in_rom", help="uncompressed input rom filename")
  19. parser.add_argument("out_rom", help="compressed output rom filename")
  20. parser.add_argument("elf", help="path to the uncompressed rom elf file")
  21. parser.add_argument("spec", help="path to processed spec file")
  22. parser.add_argument("--cache", help="cache directory")
  23. parser.add_argument("--threads", help="number of threads to run compression on, 0 disables multithreading")
  24. parser.add_argument("--mb", help="compressed rom size in MB, default is the smallest multiple of 8mb fitting the whole rom")
  25. parser.add_argument("--matching", help="matching compression, forfeits some useful optimizations", action="store_true")
  26. parser.add_argument("--stderr", help="z64compress will write its output messages to stderr instead of stdout", action="store_true")
  27.  
  28. args = parser.parse_args()
  29.  
  30. IN_ROM = args.in_rom
  31. OUT_ROM = args.out_rom
  32.  
  33. elf_path = args.elf
  34.  
  35. CACHE_DIR = args.cache
  36. N_THREADS = int(args.threads or 0)
  37. MB = args.mb
  38. MATCHING = args.matching
  39. STDOUT = not args.stderr
  40.  
  41. # Get segments to compress
  42.  
  43. spec = ""
  44. with open(args.spec, "r") as infile:
  45.     spec = infile.read()
  46.  
  47. def ranges(i):
  48.     for _, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
  49.         b = list(b)
  50.         yield b[0][1], b[-1][1]
  51.  
  52. compress_segments = []
  53.  
  54. shift = 0
  55. for i,segment in enumerate(spec.split("beginseg\n")[1:],0):
  56.     directives = segment.split("endseg")[0].split("\n")
  57.  
  58.     for directive in directives:
  59.         directive = directive.strip()
  60.         if directive.startswith("flags") and "NOLOAD" in directive:
  61.             shift += 1
  62.         elif directive.startswith("compress"):
  63.             compress_segments.append(i - shift)
  64.  
  65. compress_segments = list(ranges(compress_segments))
  66. COMPRESS_INDICES = ",".join([f"{start}-{end}" if start != end else f"{start}" for start,end in compress_segments])
  67.  
  68. # Find dmadata
  69.  
  70. def get_dmadata_start_len():
  71.     dmadata_start = -1
  72.     dmadata_end = -1
  73.  
  74.     with open(elf_path, "rb") as elf_file:
  75.         elf = ELFFile(elf_file)
  76.  
  77.         for section in elf.iter_sections():
  78.             if not isinstance(section, SymbolTableSection):
  79.                 continue
  80.  
  81.             for sym in section.iter_symbols():
  82.                 if sym.name == "_dmadataSegmentRomStart":
  83.                     dmadata_start = sym['st_value']
  84.                 elif sym.name == "_dmadataSegmentRomEnd":
  85.                     dmadata_end = sym['st_value']
  86.                 if dmadata_start != -1 and dmadata_end != -1:
  87.                     break
  88.  
  89.         assert dmadata_start != -1, "_dmadataSegmentRomStart symbol not found in supplied ELF"
  90.         assert dmadata_end != -1, "_dmadataSegmentRomEnd symbol not found in supplied ELF"
  91.  
  92.     return dmadata_start, (dmadata_end - dmadata_start)//0x10
  93.  
  94. DMADATA_ADDR, DMADATA_COUNT = get_dmadata_start_len()
  95.  
  96. # Run
  97.  
  98. cmd = f"./tools/z64compress/z64compress \
  99. --in {IN_ROM} \
  100. --out {OUT_ROM}\
  101. {' --matching' if MATCHING else ''}\
  102. {f' --mb {MB}' if MB is not None else ''} \
  103. --codec yaz\
  104. {f' --cache {CACHE_DIR}' if CACHE_DIR is not None else ''} \
  105. --dma 0x{DMADATA_ADDR:X},{DMADATA_COUNT} \
  106. --compress {COMPRESS_INDICES}\
  107. {f' --threads {N_THREADS}' if N_THREADS > 0 else ''}\
  108. {f' --only-stdout' if STDOUT else ''}"
  109.  
  110. print(cmd)
  111. try:
  112.     subprocess.check_call(cmd, shell=True)
  113. except subprocess.CalledProcessError as e:
  114.     # Return the same error code for the wrapper if z64compress fails
  115.     sys.exit(e.returncode)
  116.  
Advertisement
Add Comment
Please, Sign In to add comment