Super Mario 64 OpenGL port for PC. Mirror of https://github.com/sm64pc/sm64pc https://github.com/sm64pc/sm64pc
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

281 lines
9.2 KiB

  1. #!/usr/bin/env python3
  2. import sys
  3. import os
  4. import json
  5. def read_asset_map():
  6. with open("assets.json") as f:
  7. ret = json.load(f)
  8. return ret
  9. def read_local_asset_list(f):
  10. if f is None:
  11. return []
  12. ret = []
  13. for line in f:
  14. ret.append(line.strip())
  15. return ret
  16. def asset_needs_update(asset, version):
  17. if version <= 5 and asset == "textures/spooky/bbh_textures.00800.rgba16.png":
  18. return True
  19. if version <= 4 and asset in ["textures/mountain/ttm_textures.01800.rgba16.png", "textures/mountain/ttm_textures.05800.rgba16.png"]:
  20. return True
  21. if version <= 3 and asset == "textures/cave/hmc_textures.01800.rgba16.png":
  22. return True
  23. if version <= 2 and asset == "textures/inside/inside_castle_textures.09000.rgba16.png":
  24. return True
  25. if version <= 1 and asset.endswith(".m64"):
  26. return True
  27. if version <= 0 and asset.endswith(".aiff"):
  28. return True
  29. return False
  30. def remove_file(fname):
  31. os.remove(fname)
  32. print("deleting", fname)
  33. try:
  34. os.removedirs(os.path.dirname(fname))
  35. except OSError:
  36. pass
  37. def clean_assets(local_asset_file):
  38. assets = set(read_asset_map().keys())
  39. assets.update(read_local_asset_list(local_asset_file))
  40. for fname in list(assets) + [".assets-local.txt"]:
  41. if fname.startswith("@"):
  42. continue
  43. try:
  44. remove_file(fname)
  45. except FileNotFoundError:
  46. pass
  47. def main():
  48. # In case we ever need to change formats of generated files, we keep a
  49. # revision ID in the local asset file.
  50. new_version = 6
  51. try:
  52. local_asset_file = open(".assets-local.txt")
  53. local_asset_file.readline()
  54. local_version = int(local_asset_file.readline().strip())
  55. except Exception:
  56. local_asset_file = None
  57. local_version = -1
  58. langs = sys.argv[1:]
  59. if langs == ["--clean"]:
  60. clean_assets(local_asset_file)
  61. sys.exit(0)
  62. all_langs = ["jp", "us", "eu", "sh"]
  63. if not langs or not all(a in all_langs for a in langs):
  64. langs_str = " ".join("[" + lang + "]" for lang in all_langs)
  65. print("Usage: " + sys.argv[0] + " " + langs_str)
  66. print("For each version, baserom.<version>.z64 must exist")
  67. sys.exit(1)
  68. asset_map = read_asset_map()
  69. all_assets = []
  70. any_missing_assets = False
  71. for asset, data in asset_map.items():
  72. if asset.startswith("@"):
  73. continue
  74. if os.path.isfile(asset):
  75. all_assets.append((asset, data, True))
  76. else:
  77. all_assets.append((asset, data, False))
  78. if not any_missing_assets and any(lang in data[-1] for lang in langs):
  79. any_missing_assets = True
  80. if not any_missing_assets and local_version == new_version:
  81. # Nothing to do, no need to read a ROM. For efficiency we don't check
  82. # the list of old assets either.
  83. return
  84. # Late imports (to optimize startup perf)
  85. import subprocess
  86. import hashlib
  87. import tempfile
  88. from collections import defaultdict
  89. new_assets = {a[0] for a in all_assets}
  90. previous_assets = read_local_asset_list(local_asset_file)
  91. if local_version == -1:
  92. # If we have no local asset file, we assume that files are version
  93. # controlled and thus up to date.
  94. local_version = new_version
  95. # Create work list
  96. todo = defaultdict(lambda: [])
  97. for (asset, data, exists) in all_assets:
  98. # Leave existing assets alone if they have a compatible version.
  99. if exists and not asset_needs_update(asset, local_version):
  100. continue
  101. meta = data[:-2]
  102. size, positions = data[-2:]
  103. for lang, pos in positions.items():
  104. mio0 = None if len(pos) == 1 else pos[0]
  105. pos = pos[-1]
  106. if lang in langs:
  107. todo[(lang, mio0)].append((asset, pos, size, meta))
  108. break
  109. # Load ROMs
  110. roms = {}
  111. for lang in langs:
  112. fname = "baserom." + lang + ".z64"
  113. try:
  114. with open(fname, "rb") as f:
  115. roms[lang] = f.read()
  116. except:
  117. print("Failed to open " + fname + ". Please ensure it exists!")
  118. sys.exit(1)
  119. sha1 = hashlib.sha1(roms[lang]).hexdigest()
  120. with open("sm64." + lang + ".sha1", "r") as f:
  121. expected_sha1 = f.read().split()[0]
  122. if sha1 != expected_sha1:
  123. print(
  124. fname
  125. + " has the wrong hash! Found "
  126. + sha1
  127. + ", expected "
  128. + expected_sha1
  129. )
  130. sys.exit(1)
  131. # Make sure tools exist
  132. subprocess.check_call(
  133. ["make", "-s", "-C", "tools/", "n64graphics", "skyconv", "mio0", "aifc_decode"]
  134. )
  135. # Go through the assets in roughly alphabetical order (but assets in the same
  136. # mio0 file still go together).
  137. keys = sorted(list(todo.keys()), key=lambda k: todo[k][0][0])
  138. # Import new assets
  139. for key in keys:
  140. assets = todo[key]
  141. lang, mio0 = key
  142. if mio0 == "@sound":
  143. with tempfile.NamedTemporaryFile(prefix="ctl", delete=False) as ctl_file:
  144. with tempfile.NamedTemporaryFile(prefix="tbl", delete=False) as tbl_file:
  145. rom = roms[lang]
  146. size, locs = asset_map["@sound ctl " + lang]
  147. offset = locs[lang][0]
  148. ctl_file.write(rom[offset : offset + size])
  149. ctl_file.close()
  150. size, locs = asset_map["@sound tbl " + lang]
  151. offset = locs[lang][0]
  152. tbl_file.write(rom[offset : offset + size])
  153. tbl_file.close()
  154. args = [
  155. "python3",
  156. "tools/disassemble_sound.py",
  157. ctl_file.name,
  158. tbl_file.name,
  159. "--only-samples",
  160. ]
  161. for (asset, pos, size, meta) in assets:
  162. print("extracting", asset)
  163. args.append(asset + ":" + str(pos))
  164. try:
  165. subprocess.run(args, check=True)
  166. finally:
  167. os.unlink(ctl_file.name)
  168. os.unlink(tbl_file.name)
  169. continue
  170. if mio0 is not None:
  171. image = subprocess.run(
  172. [
  173. "./tools/mio0",
  174. "-d",
  175. "-o",
  176. str(mio0),
  177. "baserom." + lang + ".z64",
  178. "-",
  179. ],
  180. check=True,
  181. stdout=subprocess.PIPE,
  182. ).stdout
  183. else:
  184. image = roms[lang]
  185. for (asset, pos, size, meta) in assets:
  186. print("extracting", asset)
  187. input = image[pos : pos + size]
  188. os.makedirs(os.path.dirname(asset), exist_ok=True)
  189. if asset.endswith(".png"):
  190. with tempfile.NamedTemporaryFile(prefix="asset", delete=False) as png_file:
  191. png_file.write(input)
  192. png_file.flush()
  193. if asset.startswith("textures/skyboxes/") or asset.startswith("levels/ending/cake"):
  194. if asset.startswith("textures/skyboxes/"):
  195. imagetype = "sky"
  196. else:
  197. imagetype = "cake" + ("-eu" if "eu" in asset else "")
  198. subprocess.run(
  199. [
  200. "./tools/skyconv",
  201. "--type",
  202. imagetype,
  203. "--combine",
  204. png_file.name,
  205. asset,
  206. ],
  207. check=True,
  208. )
  209. else:
  210. w, h = meta
  211. fmt = asset.split(".")[-2]
  212. subprocess.run(
  213. [
  214. "./tools/n64graphics",
  215. "-e",
  216. png_file.name,
  217. "-g",
  218. asset,
  219. "-f",
  220. fmt,
  221. "-w",
  222. str(w),
  223. "-h",
  224. str(h),
  225. ],
  226. check=True,
  227. )
  228. else:
  229. with open(asset, "wb") as f:
  230. f.write(input)
  231. # Remove old assets
  232. for asset in previous_assets:
  233. if asset not in new_assets:
  234. try:
  235. remove_file(asset)
  236. except FileNotFoundError:
  237. pass
  238. # Replace the asset list
  239. output = "\n".join(
  240. [
  241. "# This file tracks the assets currently extracted by extract_assets.py.",
  242. str(new_version),
  243. *sorted(list(new_assets)),
  244. "",
  245. ]
  246. )
  247. with open(".assets-local.txt", "w") as f:
  248. f.write(output)
  249. main()