Advertisement
BrinkerVII

Universal Wine-TKG build for Grapejuice

Sep 6th, 2021 (edited)
113,092
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.91 KB | None | 0 0
  1. #!/usr/bin/python3
  2.  
  3. #
  4. # This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
  5. # https://creativecommons.org/licenses/by-nc-nd/4.0/
  6. #
  7.  
  8. import hashlib
  9. import json
  10. import os
  11. import re
  12. import subprocess
  13. import sys
  14. from pathlib import Path
  15. from typing import Union, List
  16.  
  17. WINE_VERSIONS = {
  18.     "6.16.r3.gf3b03ce5": {
  19.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/884173115398176768/debuntu-wine-tkg-staging-fsync-git-6.16.r3.gf3b03ce5.7z",
  20.         "hash": {
  21.             "type": "sha512",
  22.             "value": "e0e1bc6ecc0226f2a2f8e1ddbff28d36b5836c2ff2e50f733fbb86fcaacb50097f6f47281801c91b8412801a993a354b4aca24978eb2d86e7f64ee2fcf543b43"
  23.         }
  24.     },
  25.     "6.17.r0.g5f19a815": {
  26.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/886180600703250453/debuntu-wine-tkg-staging-fsync-git-6.17.r0.g5f19a815.7z",
  27.         "hash": {
  28.             "type": "sha512",
  29.             "value": "bd64593b2f3a01942bc5c6c5ee6aa654f5cce30c76e4a83397611286cd74feaf7ba8fb6fba99f145bb4d9eed523a1d007cc4d845d5324b07a40700dcf1655a3b"
  30.         }
  31.     },
  32.     "6.18.r0.gf8851f16": {
  33.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/891630589671796786/debuntu-wine-tkg-staging-fsync-git-6.18.r0.gf8851f16.7z",
  34.         "hash": {
  35.             "type": "sha512",
  36.             "value": "223f81f559e84f1b1915bdb71470dfed2648ddf60650e9c1ee6680f325cd7ebf73b1b98e0ab3d3348a069bba36775acffd157403aa453a3bf14f268500ed0dbe"
  37.         }
  38.     },
  39.     "6.20.r0.g3fb6eb99": {
  40.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/901364156597428274/debuntu-wine-tkg-staging-fsync-git-6.20.r0.g3fb6eb99.7z",
  41.         "hash": {
  42.             "type": "sha512",
  43.             "value": "8a20ccf11ae4e63c9bf382437e8dad7bc02041884784624b84152b93beb849463276e4503a674339586a4a75a541e6c49b2aab0110a13c80ce36249bc124e73e"
  44.         }
  45.     },
  46.     "6.22.r0.ga703038b": {
  47.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/912141274805575710/debuntu-wine-tkg-staging-fsync-git-6.22.r0.ga703038b.7z",
  48.         "hash": {
  49.             "type": "sha512",
  50.             "value": "88112abe39c03a76dbfb8c64e0f48d02576c6920114e46141d096f947e5f1c774051022fd6d0101e333cd30de810b4d3435d66184fb752da4870954e8c419cac"
  51.         }
  52.     },
  53.     "7.0rc1.r0.g544f90da": {
  54.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/920772054784016444/debuntu-wine-tkg-staging-fsync-git-7.0rc1.r0.g544f90da.7z",
  55.         "hash": {
  56.             "type": "sha512",
  57.             "value": "ca1da05759622762e1e54bae1d067387aa98fa31073f6b52c51f76a6f5c23f1b7d03fdb6dfb35d2c29609a5fc19e0a64c6f034fc67b47d6f4094ad529d97641a"
  58.         }
  59.     },
  60.     "7.0rc2.r0.g8f579c4e": {
  61.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/922274716096528494/debuntu-wine-tkg-staging-fsync-git-7.0rc2.r0.g8f579c4e.7z",
  62.         "hash": {
  63.             "type": "sha512",
  64.             "value": "66d32f90af8f5de11ace6b930dd5a989a8f0a5d9e09244da81adfad2bb68bbe596e8259ecec49682c34fcaa03ce2e98501b05c35a8a1da7d9881c97599eb7fbe"
  65.         }
  66.     },
  67.     "7.0rc4.r1.g98c906f8": {
  68.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/928092680603435029/debuntu-wine-tkg-staging-fsync-git-7.0rc4.r1.g98c906f8.7z",
  69.         "hash": {
  70.             "type": "sha512",
  71.             "value": "2451843024f195295a73e656cb3e683de4a1eb94765a70d9c59be3de3580652dc11bb0bc8080b7f63238c0c14c763f2af62b6a270726fa6441e35e84634dee7b"
  72.         }
  73.     },
  74.     "7.0rc6.r0.g0111d074": {
  75.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/932136639168380928/debuntu-wine-tkg-staging-fsync-git-7.0rc6.r0.g0111d074.7z",
  76.         "hash": {
  77.             "type": "sha512",
  78.             "value": "3e30e65abeee8f15b5c7547fb4232b611be27aa6505746cd25b4cc24b389b097443a5446b4d52c2d8ea4dd1415c09262f3595a1f03375d0102c4e47a81a7b5ea"
  79.         }
  80.     },
  81.     "7.0.r1.g95bf6698": {
  82.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/934769842832146494/debuntu-wine-tkg-staging-fsync-git-7.0.r1.g95bf6698.7z",
  83.         "hash": {
  84.             "type": "sha512",
  85.             "value": "f28fe737a358b5a9b6d89e3baef85e6168a23598eb327c378a2a284bc2fd959331e8637a64db59be75db35d79ab815e2688055d3de5544c9b653e5907389a2b6"
  86.         }
  87.     },
  88.     "7.1.r2.gc437a01e": {
  89.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/937332523380379678/debuntu-wine-tkg-staging-fsync-git-7.1.r2.gc437a01e.7z",
  90.         "hash": {
  91.             "type": "sha512",
  92.             "value": "9b0da4604525b88f2ed353780211a4d78c464326cffdd2d7dfdc5d487a3c9631de9f0197c2f63bd9c1475e5e48ff7d9783e9648582aa8dc29b58d6e62f617c49"
  93.         }
  94.     },
  95.     "7.2.r0.g68441b1d": {
  96.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/943053350528778240/debuntu-wine-tkg-staging-fsync-git-7.2.r0.g68441b1d.7z",
  97.         "hash": {
  98.             "type": "sha512",
  99.             "value": "78a5227807eb1af4656f4e31f2989c87eabd4754319f44d8b4b9db02a34b75569c2a0b401e5378eb215b56324f8cd8a7f7661f9e5a82bb33d667542f5b838ec6"
  100.         }
  101.     },
  102.     "7.6.r14.g6b4b9f1b": {
  103.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/967108819849199667/debuntu-wine-tkg-staging-fsync-git-7.6.r14.g6b4b9f1b.7z",
  104.         "hash": {
  105.             "type": "sha512",
  106.             "value": "eaa52d360e92eb7715b0db8b414ba0ba15b4cba8e168355ada5d171d6c0f47b4ef8f85fe433e39a152d239b9d03fbe4a9c11851c86415d65c4cd2658560faf49"
  107.         }
  108.     },
  109.     "7.7.r0.g9df73ee3": {
  110.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/967687344796893285/debuntu-wine-tkg-staging-fsync-git-7.7.r0.g9df73ee3.7z",
  111.         "hash": {
  112.             "type": "sha512",
  113.             "value": "019ce0d97f7b6f504db51954bc79761d078062c1a7b69f349de380b2a9c9081de11b6f1cabe9665cbcee28ebb9a20cd57519bc0dc795401d80a6b55a04efe3ed"
  114.         }
  115.     },
  116.     "7.20.r1.gbd2608b1": {
  117.         "url": "https://cdn.discordapp.com/attachments/858117357897121822/1038216311198715934/debuntu-wine-tkg-staging-fsync-git-7.20.r1.gbd2608b1.7z",
  118.         "hash": {
  119.             "type": "sha512",
  120.             "value": "c992cf277feae8a64969371c6c46d3e15a742791ba100092f6deec2e7048c322b9f1bc0f807421659d4f50591bd70b84e5521b9f61c47842bcb63b06edb6d80f"
  121.         }
  122.     }
  123. }
  124.  
  125.  
  126. def use_version():
  127.     return "7.20.r1.gbd2608b1"
  128.  
  129.  
  130. class bcolors:
  131.     HEADER = '\033[95m'
  132.     OKBLUE = '\033[94m'
  133.     OKCYAN = '\033[96m'
  134.     OKGREEN = '\033[92m'
  135.     WARNING = '\033[93m'
  136.     FAIL = '\033[91m'
  137.     ENDC = '\033[0m'
  138.     BOLD = '\033[1m'
  139.     UNDERLINE = '\033[4m'
  140.  
  141.  
  142. def info(msg: str):
  143.     print(f"{bcolors.BOLD}>> {bcolors.OKBLUE}{msg}{bcolors.ENDC}{bcolors.ENDC}")
  144.  
  145.  
  146. def warn(msg: str):
  147.     print(f"{bcolors.BOLD}>> {bcolors.WARNING}{msg}{bcolors.ENDC}{bcolors.ENDC}")
  148.  
  149.  
  150. def success_message(msg: str):
  151.     print(f"{bcolors.BOLD}>>> {bcolors.OKGREEN}{msg}{bcolors.ENDC}{bcolors.ENDC}")
  152.  
  153.  
  154. def error_out(msg: str):
  155.     print(
  156.         f"{bcolors.BOLD}>>> {bcolors.FAIL}{msg}{bcolors.ENDC}{bcolors.ENDC}",
  157.         file=sys.stderr
  158.     )
  159.     print(
  160.         f"{bcolors.BOLD}>>> {bcolors.FAIL}Quitting script due to an error.{bcolors.ENDC}{bcolors.ENDC}",
  161.         file=sys.stderr
  162.     )
  163.     sys.exit(-1)
  164.  
  165.  
  166. def info_on_call(message: str):
  167.     def decorator(fn):
  168.         def wrapper(*args, **kwargs):
  169.             args_as_string = json.dumps(list(map(str, args)))
  170.             kwargs_as_string = json.dumps(dict(zip(map(str, kwargs.keys()), map(str, kwargs.values()))))
  171.  
  172.             info(f"* {message} :: {bcolors.WARNING}{args_as_string} :: {kwargs_as_string}{bcolors.ENDC}")
  173.             return_value = fn(*args, **kwargs)
  174.  
  175.             info(f"* {bcolors.OKGREEN}{str(return_value)}{bcolors.ENDC}")
  176.  
  177.             return return_value
  178.  
  179.         return wrapper
  180.  
  181.     return decorator
  182.  
  183.  
  184. def download_data():
  185.     return WINE_VERSIONS.get(use_version())
  186.  
  187.  
  188. def download_url():
  189.     return download_data()["url"]
  190.  
  191.  
  192. def filename():
  193.     return download_url().split("/")[-1]
  194.  
  195.  
  196. def download_hash_algorithm():
  197.     return download_data()["hash"]["type"]
  198.  
  199.  
  200. def download_hash_value():
  201.     return download_data()["hash"]["value"]
  202.  
  203.  
  204. def download_path() -> Path:
  205.     return Path("/", "tmp", filename())
  206.  
  207.  
  208. def share_grapejuice_path() -> Path:
  209.     return Path(os.environ["HOME"]).resolve() / ".local" / "share" / "grapejuice"
  210.  
  211.  
  212. def grapejuice_user_path() -> Path:
  213.     return share_grapejuice_path() / "user"
  214.  
  215.  
  216. def wine_target_path() -> Path:
  217.     p = grapejuice_user_path() / "wine-download"
  218.     p.mkdir(parents=True, exist_ok=True)
  219.  
  220.     return p
  221.  
  222.  
  223. def find_latest_previous_wine_server() -> Union[Path, None]:
  224.     candidates = list(
  225.         sorted(
  226.             filter(
  227.                 lambda p: f"bin{os.sep}wineserver" in str(p),
  228.                 wine_target_path().rglob("wineserver")
  229.             )
  230.         )
  231.     )
  232.  
  233.     if len(candidates) == 0:
  234.         return None
  235.  
  236.     return candidates[-1]
  237.  
  238.  
  239. def grapejuice_settings_path() -> Path:
  240.     return Path(os.environ["HOME"]).resolve() / ".config" / "brinkervii" / "grapejuice" / "user_settings.json"
  241.  
  242.  
  243. @info_on_call("Hashing file")
  244. def hash_file(path: Path, algorithm: str, block_size: int = 4096) -> str:
  245.     h = hashlib.new(algorithm)
  246.  
  247.     with path.open("rb") as fp:
  248.         data = True
  249.  
  250.         while data:
  251.             data = fp.read(block_size)
  252.             h.update(data)
  253.  
  254.     return h.hexdigest().lower().strip()
  255.  
  256.  
  257. @info_on_call("Locating system binary")
  258. def which(binary_name: str, path_extra: List[Union[Path, str]] = None) -> Union[Path, None]:
  259.     path = list(map(str, [] if path_extra is None else path_extra))
  260.     path.extend(os.environ.get("PATH", "").split(":"))
  261.  
  262.     for d in path:
  263.         d = d.strip()
  264.         if not d:
  265.             continue
  266.  
  267.         d = Path(d).resolve()
  268.         file = d / binary_name
  269.  
  270.         if file.exists() and (file.is_file() or file.is_symlink()):
  271.             return file
  272.  
  273.     return None
  274.  
  275.  
  276. def stop_wine_server():
  277.     info("Stopping Wine server")
  278.  
  279.     prefixes_directory = share_grapejuice_path() / "prefixes"
  280.  
  281.     if prefixes_directory.exists():
  282.         info("Using Grapejuice 4+ prefix configuration")
  283.         prefix_list = list(filter(Path.is_dir, prefixes_directory.glob("*")))
  284.  
  285.     else:
  286.         info("Using Grapejuice <= 3 prefix directory")
  287.         prefix_list = [share_grapejuice_path() / "wineprefix"]
  288.  
  289.     for prefix_path in prefix_list:
  290.         wine_server_path = find_latest_previous_wine_server() or which("wineserver")
  291.         if wine_server_path is None:
  292.             info("Could not find wineserver, so it is not being stopped")
  293.             return
  294.  
  295.         if prefix_path.exists() and prefix_path.is_dir():
  296.             try:
  297.                 subprocess.call([str(wine_server_path), "-k"], env={"WINEPREFIX": str(prefix_path)})
  298.  
  299.             except Exception as e:
  300.                 warn(f"Failed to stop wineserver: {str(e)}")
  301.                 pass
  302.  
  303.  
  304. def wget_bin():
  305.     p = which("wget")
  306.     if p is None:
  307.         error_out("The 'wget' binary is not present on your system, please install wget.")
  308.  
  309.     return p
  310.  
  311.  
  312. @info_on_call("Downloading file using wget")
  313. def wget(url: str, download_location: Path, hash_algorithm: str, file_hash: str):
  314.     if download_location.exists():
  315.         if download_location.is_dir():
  316.             error_out(f"The wine download location {download_location} is a directory and cannot be written to. "
  317.                       f"Please move or delete the directory.")
  318.  
  319.         else:
  320.             hash_value = hash_file(download_location, hash_algorithm)
  321.             if hash_value == file_hash:
  322.                 return
  323.  
  324.             else:
  325.                 warn(f"File exists at {download_location}, but the file hash does not match. Attempting redownload.")
  326.  
  327.     subprocess.check_call([str(wget_bin()), url, "-O", str(download_location)])
  328.  
  329.     hash_value = hash_file(download_location, hash_algorithm)
  330.  
  331.     if hash_value != file_hash:
  332.         error_out(f"The Wine build was downloaded to {download_location}. However, the file hash does not match. "
  333.                   f"Please make sure the download has finished and that your internet connection is secure!")
  334.  
  335.  
  336. def seven_zip_bin() -> Path:
  337.     p = which("7z") or which("7za")
  338.  
  339.     if p is None:
  340.         error_out("The '7z' binary is not present on your system, please install 7z (the package is called 'p7zip' or "
  341.                   "'p7zip-full' on most distributions).")
  342.  
  343.     return p
  344.  
  345.  
  346. @info_on_call("Locating file in 7z archive")
  347. def find_wine_binary_in_7z(source: Path) -> str:
  348.     listing = subprocess.check_output([str(seven_zip_bin()), "l", str(source)]).decode("UTF-8")
  349.  
  350.     candidates = list(filter(lambda s: s.strip("\r").strip().endswith("bin/wine"), listing.split("\n")))
  351.     assert len(candidates) == 1, "Invalid archive, invalid number of wine binary candidates"
  352.  
  353.     m = re.split(r"\s+", candidates[0])
  354.     p = m[-1]
  355.  
  356.     assert p.endswith("bin/wine"), "Got an invalid wine binary path from archive"
  357.  
  358.     return p
  359.  
  360.  
  361. @info_on_call("Unarchiving 7z archive")
  362. def unarchive_7z(source: Path, target: Path):
  363.     subprocess.check_call([str(seven_zip_bin()), "x", "-y", "-o" + str(target), str(source)])
  364.  
  365.  
  366. @info_on_call("Updating Grapejuice settings")
  367. def update_grapejuice_settings(wine_binary_path: str):
  368.     if not grapejuice_settings_path().exists():
  369.         error_out("The Grapejuice settings file does not exist. Please open and close Grapejuice one time.")
  370.  
  371.     with grapejuice_settings_path().open("r") as fp:
  372.         data = json.load(fp)
  373.  
  374.     full_wine_binary_path = wine_target_path() / wine_binary_path
  375.     settings_version = next(
  376.         filter(
  377.             None,
  378.             [
  379.                 data.get("__version__", None),
  380.                 data.get("version", None)
  381.             ]
  382.         ),
  383.         0
  384.     )
  385.  
  386.     if settings_version >= 2:
  387.         info("Got Grapejuice 4+ user_settings")
  388.         wine_home = full_wine_binary_path.parent.parent
  389.         wine_bin = wine_home / "bin"
  390.  
  391.         if not wine_bin.exists():
  392.             error_out(f"Invalid wine_home: {wine_home}")
  393.  
  394.         for prefix in data.get("wineprefixes", []):
  395.             prefix["wine_home"] = str(wine_home)
  396.  
  397.         data["default_wine_home"] = str(wine_home)
  398.  
  399.     else:
  400.         info("Got Grapejuice < 4 user_settings")
  401.         data["wine_binary"] = str(full_wine_binary_path)
  402.  
  403.     with grapejuice_settings_path().open("w") as fp:
  404.         json.dump(data, fp, indent=2)
  405.  
  406.  
  407. def main():
  408.     info("Starting installation process")
  409.     stop_wine_server()
  410.  
  411.     download_location = download_path()
  412.     wget(download_url(), download_location, download_hash_algorithm(), download_hash_value())
  413.  
  414.     info("Processing archive")
  415.     wine_binary_path = find_wine_binary_in_7z(download_location)
  416.     unarchive_7z(download_location, wine_target_path())
  417.  
  418.     update_grapejuice_settings(wine_binary_path)
  419.  
  420.     stop_wine_server()
  421.     info("Done")
  422.  
  423.     success_message("Wine-TKG installation has succeeded!")
  424.  
  425.  
  426. if __name__ == '__main__':
  427.     main()
  428.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement