generate.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. #!/usr/bin/env python3
  2. # Generates a dummy resource.json file and corresponding .zip archives
  3. import filecmp
  4. import hashlib # md5
  5. import math
  6. import io
  7. import json
  8. import re # regex
  9. import shutil # rmtree, copy2
  10. import os
  11. import zipfile
  12. SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
  13. os.chdir(SCRIPT_DIR)
  14. LATEST = "game_1.9.1" # either "game_1.9.0" or "game_1.9.1"
  15. LANG_PACKS_DIR = "GenshinImpact_Data/StreamingAssets/AudioAssets"
  16. LANG_CODE_TO_DIR = {
  17. "en-us": "English(US)",
  18. "ja-jp": "Japanese",
  19. "ko-kr": "Korean",
  20. "zh-cn": "Chinese"
  21. }
  22. # Destination to put generated *.zip archives
  23. ZIP_OUTPUT_DIR = os.path.join(SCRIPT_DIR, "archives")
  24. os.makedirs(ZIP_OUTPUT_DIR, exist_ok=True)
  25. regex_get_version = re.compile(r'^[a-z]+_(\d+\.\d+\.\d+)')
  26. def may_bundle_file(fullpath, langdir):
  27. if "pkg_version" in fullpath:
  28. return False
  29. if langdir:
  30. # Voiceover pack files
  31. return (os.path.join(LANG_PACKS_DIR, langdir) in fullpath)
  32. else:
  33. # Main archive contents without voiceover files
  34. return (LANG_PACKS_DIR not in fullpath)
  35. def make_pkg_version(path, langdir):
  36. if langdir:
  37. outpath = os.path.join(path, "Audio_{}_pkg_version".format(langdir))
  38. else:
  39. outpath = os.path.join(path, "pkg_version")
  40. fh = io.open(outpath, "w")
  41. for root, dirs, files in os.walk(path):
  42. for file in files:
  43. fullpath = os.path.join(root, file)
  44. if not may_bundle_file(fullpath, langdir):
  45. continue
  46. shortpath = os.path.relpath(fullpath, path)
  47. fh.write(json.dumps({
  48. "remoteName": shortpath,
  49. "md5": hashlib.md5(io.open(fullpath,'rb').read()).hexdigest(),
  50. "fileSize": os.path.getsize(fullpath)
  51. }))
  52. fh.write("\r\n")
  53. print("Created pkg_version for {}".format(path))
  54. return outpath
  55. # ----------------- Diffs
  56. def make_deletefiles(dir_old, dir_new, dir_out, langdir):
  57. fh = io.open(os.path.join(dir_out, "deletefiles.txt"), "w")
  58. for root, dirs, files in os.walk(dir_old):
  59. for file in files:
  60. fullpath = os.path.join(root, file)
  61. if not may_bundle_file(fullpath, langdir):
  62. continue
  63. shortpath = os.path.relpath(fullpath, dir_old)
  64. if os.path.isfile(os.path.join(dir_new, shortpath)):
  65. continue # Does still exist
  66. fh.write(shortpath)
  67. fh.write("\r\n")
  68. print("Created deletefiles for {}".format(os.path.basename(dir_out)))
  69. return
  70. def make_hdifffiles(dir_old, dir_new, dir_out, langdir):
  71. # TODO:Implement hdifffiles.txt generation
  72. raise "Not implemented"
  73. def copy_diff_files(dir_old, dir_new, dir_out, langdir):
  74. for root, dirs, files in os.walk(dir_new):
  75. for file in files:
  76. fullpath = os.path.join(root, file)
  77. if not may_bundle_file(fullpath, langdir):
  78. continue
  79. # Path relative to the installation directory
  80. shortpath = os.path.relpath(fullpath, dir_new)
  81. path_old = os.path.join(dir_old, shortpath)
  82. if os.path.isfile(path_old):
  83. if filecmp.cmp(path_old, fullpath, shallow=False):
  84. continue # Same file, unchanged.
  85. dstpath = os.path.join(dir_out, shortpath)
  86. os.makedirs(os.path.dirname(dstpath), exist_ok=True)
  87. shutil.copy2(fullpath, dstpath)
  88. print("Created diff dir for {}".format(os.path.basename(dir_out)))
  89. return
  90. # Copy diff files and zip them
  91. def make_diff_zip(dir_old, dir_new, langcode):
  92. ver_old = regex_get_version.match(dir_old).groups(1)[0] # 1.8.0
  93. ver_new = regex_get_version.match(dir_new).groups(1)[0] # 1.9.0
  94. diffname = "{}_{}_{}_hdiff_RANDOMVALUE".format(langcode or "diff", ver_old, ver_new)
  95. dir_out = os.path.join(ZIP_OUTPUT_DIR, diffname)
  96. # Remove old instances
  97. if os.path.isdir(dir_out):
  98. shutil.rmtree(dir_out)
  99. os.mkdir(dir_out)
  100. # Create update files and zip
  101. langdir = LANG_CODE_TO_DIR[langcode] if langcode else None
  102. copy_diff_files(dir_old, dir_new, dir_out, langdir)
  103. # Only add deletefiles and pkg_version when there are changed files
  104. if os.listdir(dir_out):
  105. make_deletefiles(dir_old, dir_new, dir_out, langdir)
  106. shutil.copy2(make_pkg_version(dir_new, langdir), dir_out)
  107. # Just pack everything in the directory
  108. return make_dir_zip(dir_out, diffname + ".zip", langdir)
  109. # For the first time install
  110. def make_lang_zip(path, langcode):
  111. ver = regex_get_version.match(path).groups(1)[0] # 1.9.0
  112. langdir = LANG_CODE_TO_DIR[langcode]
  113. outname = "Audio_{}_{}".format(langdir, ver)
  114. return make_dir_zip(path, outname + ".zip", langdir)
  115. # ----------------- ZIP archive creation
  116. # Splits the given file into two parts
  117. def make_zip_segments(zippath):
  118. chunksize = math.ceil(os.path.getsize(zippath) / 2)
  119. i = 0
  120. fh = io.open(zippath, "rb")
  121. while True:
  122. data = fh.read(chunksize)
  123. if not data:
  124. break # done
  125. sh = io.open(zippath + ".{:03d}".format(i + 1), "wb")
  126. sh.write(data)
  127. i = i + 1
  128. print("Split {} into {} parts".format(os.path.basename(zippath), i))
  129. return
  130. # Create archive from directory (all contents)
  131. def make_dir_zip(path, outname, langdir):
  132. zippath = os.path.join(ZIP_OUTPUT_DIR, outname)
  133. zh = zipfile.ZipFile(zippath, "w", zipfile.ZIP_DEFLATED)
  134. for root, dirs, files in os.walk(path):
  135. for file in files:
  136. fullpath = os.path.join(root, file)
  137. if not may_bundle_file(fullpath, langdir):
  138. continue
  139. shortpath = os.path.relpath(os.path.join(root, file), path)
  140. zh.write(
  141. os.path.join(root, file),
  142. shortpath
  143. )
  144. filename = ""
  145. if langdir:
  146. filename = "Audio_" + langdir + "_pkg_version"
  147. else:
  148. filename = "pkg_version"
  149. fullpath = os.path.join(path, filename)
  150. if os.path.isfile(fullpath):
  151. zh.write(fullpath, filename)
  152. if langdir:
  153. # Previously excluded
  154. filename = "deletefiles.txt"
  155. fullpath = os.path.join(path, filename)
  156. if os.path.isfile(fullpath):
  157. zh.write(fullpath, filename)
  158. print("Created archive {}".format(outname))
  159. return zippath
  160. # ----------------- JSON creation
  161. def save_json_file(fields):
  162. fh = io.open("resource.json", "w")
  163. fh.write(json.dumps(fields))
  164. print("Saved resource.json")
  165. def dict_add_game_entry(ref, zip_path, decompressed_path):
  166. if not decompressed_path:
  167. # diff listing
  168. ref.append({})
  169. ref = ref[len(ref) - 1]
  170. filename = os.path.basename(zip_path)
  171. ref["name"] = filename
  172. m = regex_get_version.match(filename)
  173. ref["version"] = m.group(1)
  174. md5hash = hashlib.md5()
  175. if decompressed_path:
  176. relpath = os.path.relpath(decompressed_path, SCRIPT_DIR)
  177. ref["decompressed_path"] = "http://0.0.0.0:8000/" + relpath
  178. md5hash = hashlib.md5()
  179. filesize = dict_add_archive(ref, zip_path, None)
  180. ref["size"] = str(math.floor(filesize * 1.654321)) # installed size
  181. ref["package_size"] = str(filesize) # archive size
  182. ref["voice_packs"] = []
  183. return ref
  184. def dict_add_voice_pack_entry(ref, zip_path, langcode):
  185. ref.append({})
  186. ref = ref[len(ref) - 1]
  187. ref["language"] = langcode
  188. ref["name"] = "" # only poopulated in diffs
  189. md5hash = hashlib.md5()
  190. filesize = dict_add_archive(ref, zip_path, md5hash)
  191. ref["md5"] = md5hash.hexdigest()
  192. ref["size"] = str(math.floor(filesize * 1.654321)) # installed size
  193. ref["package_size"] = str(filesize) # archive size
  194. return
  195. # Returns the total file size
  196. def dict_add_archive(ref, zip_path, md5hash):
  197. filesize = 0
  198. if not md5hash:
  199. md5hash = hashlib.md5()
  200. # Try all available segments. Sum up all files and update the md5 hash
  201. sref = []
  202. i = 0
  203. totalsize = 0
  204. while True:
  205. segname = zip_path + ".{:03d}".format(i + 1)
  206. if not os.path.isfile(segname):
  207. break
  208. sref.append({})
  209. totalsize = totalsize + dict_add_archive(sref[i], segname, md5hash)
  210. i = i + 1
  211. if i > 0:
  212. ref["segments"] = sref
  213. ref["md5"] = md5hash.hexdigest()
  214. ref["package_size"] = "0" # same as sent by server
  215. return totalsize
  216. # Found no segments. Try normal archive
  217. if os.path.isfile(zip_path):
  218. # Single archive (segmented or normal)
  219. relpath = os.path.relpath(zip_path, SCRIPT_DIR)
  220. ref["path"] = "http://0.0.0.0:8000/" + relpath
  221. data = io.open(zip_path, 'rb').read()
  222. md5hash.update(data) # total md5 hash for segments
  223. ref["md5"] = hashlib.md5(data).hexdigest() # for this archive only
  224. ref["package_size"] = "0" # same as sent by server
  225. return len(data)
  226. raise "Cannot find archive for {}".format(zip_path)
  227. # ----------------- Main program
  228. fields = {
  229. "retcode": 0,
  230. "message": "OK",
  231. "data": {
  232. "game": {
  233. "latest": {},
  234. "diffs": []
  235. },
  236. "plugin": {
  237. "plugins": [],
  238. "version": 0
  239. },
  240. "pre_download_game": {
  241. "latest": {},
  242. "diffs": []
  243. }
  244. }
  245. }
  246. HAVE_PREDOWNLOAD = True
  247. if not HAVE_PREDOWNLOAD:
  248. fields["data"]["pre_download_game"] = None
  249. # Main game archive
  250. make_pkg_version(LATEST, None)
  251. zip190_path = make_dir_zip(LATEST, LATEST + ".zip", None)
  252. make_zip_segments(zip190_path)
  253. ref_game = dict_add_game_entry(fields["data"]["game"]["latest"], zip190_path, LATEST)
  254. # Main voiceover packs
  255. for k in LANG_CODE_TO_DIR:
  256. zippath = make_lang_zip(LATEST, k)
  257. dict_add_voice_pack_entry(ref_game["voice_packs"], zippath, k)
  258. # 1.8.0 -> 1.9.0 diff
  259. diff_path = make_diff_zip("game_1.8.0", "game_1.9.0", None)
  260. ref_diff = dict_add_game_entry(fields["data"]["game"]["diffs"], diff_path, None)
  261. # 1.8.0 -> 1.9.0 voiceover diffs
  262. for k in LANG_CODE_TO_DIR:
  263. diff_path = make_diff_zip("game_1.8.0", "game_1.9.0", k)
  264. dict_add_voice_pack_entry(ref_diff["voice_packs"], diff_path, k)
  265. if LATEST == "game_1.9.1":
  266. # 1.9.0 -> 1.9.1 diff
  267. diff_path = make_diff_zip("game_1.9.0", "game_1.9.1", None)
  268. ref_diff = dict_add_game_entry(fields["data"]["game"]["diffs"], diff_path, None)
  269. # 1.9.0 -> 1.9.1 voiceover diffs
  270. for k in LANG_CODE_TO_DIR:
  271. diff_path = make_diff_zip("game_1.9.0", "game_1.9.1", k)
  272. dict_add_voice_pack_entry(ref_diff["voice_packs"], diff_path, k)
  273. save_json_file(fields)