Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Installer script that automatically installs WSA with Google Play and root as well as all prerequisites #98

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c8c9cf4
Update README.md
khanhtranngoccva Oct 31, 2021
e93cc28
One-click script wrapper for root + Google Play
khanhtranngoccva Oct 31, 2021
5798d87
One-click precompiled executable file
khanhtranngoccva Oct 31, 2021
d20b58a
Merge branch 'main' of https://github.com/khanhtranngoccva/WSAGAScript
khanhtranngoccva Oct 31, 2021
e6f1205
Update README.md
khanhtranngoccva Oct 31, 2021
94fbbfa
Merge branch 'WSA-Community:main' into main
khanhtranngoccva Nov 1, 2021
2a09970
Improved rollback and updatability
khanhtranngoccva Nov 1, 2021
a194d7e
Improved rollback and updatability
khanhtranngoccva Nov 1, 2021
2d4579d
Minor fixes
khanhtranngoccva Nov 1, 2021
ccad8d1
Minor fixes
khanhtranngoccva Nov 1, 2021
d0abeb0
Uninstaller script
khanhtranngoccva Nov 1, 2021
12cc3a3
Rename for easier use
khanhtranngoccva Nov 1, 2021
5468e24
rename kernel to match newest commit
khanhtranngoccva Nov 1, 2021
74d4b47
Script now checks for CPU architecture
khanhtranngoccva Nov 1, 2021
f4fc633
Minor fixes
khanhtranngoccva Nov 1, 2021
fa51fbb
Merge branch 'WSA-Community:main' into main
khanhtranngoccva Nov 1, 2021
0cc941b
Minor fixes for kernel
khanhtranngoccva Nov 1, 2021
163fc5d
Retries download if it fails for no reason
khanhtranngoccva Nov 1, 2021
943cf1c
Retries download if it fails for no reason
khanhtranngoccva Nov 1, 2021
de2efd4
Retries download if it fails for no reason
khanhtranngoccva Nov 1, 2021
f8e40bd
fixes kernel folder
khanhtranngoccva Nov 1, 2021
8697e2e
fixes kernel folder
khanhtranngoccva Nov 2, 2021
5e8add2
Fixes weird uninstall behavior that deletes the main installation fol…
khanhtranngoccva Nov 3, 2021
37d347d
Fixes stdout checking of uninstall script
khanhtranngoccva Nov 3, 2021
75e04f4
Merge branch 'WSA-Community:main' into main
khanhtranngoccva Nov 26, 2021
bba8be8
Captcha bypass
khanhtranngoccva Mar 26, 2022
73faa19
Merge remote-tracking branch 'origin/main'
khanhtranngoccva Mar 26, 2022
3a9da7c
Merge branch 'WSA-Community:main' into main
khanhtranngoccva May 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added WSA_OneClickRun.zip
Binary file not shown.
193 changes: 193 additions & 0 deletions functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import os
import time
import shutil
import tkinter
from tkinter.font import Font
import ctypes
import sys
from sys import exit
from urllib import request, error
from speed_downloader import speed_download


def download_url(url, root=".", filename=None):
"""Download a file from a url and place it in root.
Args:
url (str): URL to download file from
root (str): Directory to place downloaded file in
filename (str, optional): Name to save the file under. If None, use the basename of the URL
"""

root = os.path.expanduser(root)
if not filename:
filename = os.path.basename(url)
fpath = os.path.join(root, filename)

os.makedirs(root, exist_ok=True)
try:
print('Downloading ' + url + ' to ' + fpath)
request.urlretrieve(url, fpath)
except (error.URLError, IOError):
if url[:5] == 'https':
url = url.replace('https:', 'http:')
print('Failed download. Trying https -> http instead.'
' Downloading ' + url + ' to ' + fpath)
request.urlretrieve(url, fpath)


def is_admin():
"""
Checks if the user has admin permissions
:return: admin true/false
"""
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except Exception as e:
print(e)
return False


def get_admin_permission():
"""checks if the program has admin permissions, otherwise it requests admin permission once, then exits"""
if is_admin():
pass
else:
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
exit()


def ps_check_feature(feature):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should support WSL from Store as well, the component is no longer needed

"""Checks whether a Windows optional feature exists.
Returns True if the component is installed. Otherwise returns False.
Requires admin permissions."""
_ = os.popen("powershell \"Get-WindowsOptionalFeature -FeatureName {} -Online\"".format(feature)).read().replace(
" ", "")
if "State:Disabled" in _:
return False
elif "State:Enabled" in _:
return True
else:
return False


def is_wsl_framework_installed():
"""Checks if the WSL framework (excluding distros) is installed."""
return ps_check_feature("Microsoft-Windows-Subsystem-Linux") and ps_check_feature(
"VirtualMachinePlatform") and shutil.which("bash") and shutil.which("wsl")


def wsl_get_distro():
"""
Returns the output of "wsl.exe --list"
:return:
"""
__ = os.popen("wsl --list").readlines()
result = (_.replace("\x00", "") for _ in __)
result = "".join(_ for _ in result if _ != "\n" and _ != "")
return result.casefold()


class WindowError(tkinter.Tk):
"""
A window displaying errors. May not be polished yet. Especially the quit() and destroy() not working properly.
"""

def __init__(self, *messages, color="red", tx_color="white", yes_command="", yes_text="OK", no_text="Exit"):
"""
Creates the windows. Mainloop needed after initializing
:param messages: All the messages. Separate the strings to make a line break
:param color: Background color
:param tx_color: Text color
:param yes_command: Executes this command in a string.
:param yes_text: The label of the button.
"""
super().__init__()
self.title("WSAGAScript OneClickRun")
self.geometry("640x240")
self.minsize(height=240, width=640)
self.maxsize(height=240, width=640)
self.config(padx=8, pady=8, background=color)
row_weight = 10000, 1
for _, __ in enumerate(row_weight):
self.rowconfigure(_, weight=__)
column_weight = 100, 1
for _, __ in enumerate(column_weight):
self.columnconfigure(_, weight=__)
error_frame = tkinter.Frame(self, background=color)
error_frame.grid(row=0, column=0, sticky="new", columnspan=2)
for message in messages:
tkinter.Label(error_frame, text=message, font=Font(family="Arial", size=12), background=color,
fg=tx_color, wraplength=600, justify="left").pack(side="top", anchor="nw")
if yes_command:
button_accept = tkinter.Button(self, relief="raised", text=yes_text, command=self.accept_button_function)
self.yes_command = yes_command
button_accept.grid(row=1, column=0, sticky="e")
button_exit = tkinter.Button(self, relief="raised", text=no_text, command=self.destroy)
button_exit.grid(row=1, column=1, sticky="e")

def accept_button_function(self):
# self.quit()
self.destroy()
exec(f'{self.yes_command}')


def install_wsl():
"""Installs WSL and its dependencies, then reboots. Requires admin permission."""
if is_admin():
print("Installing Windows Subsystem for Linux. The system will restart in about a minute.")
os.popen(
"dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart").read()
print("Installing Virtual Machine Platform. The system will restart in about 30 seconds.")
os.popen("dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart").read()
os.popen("shutdown.exe /r /t 0")


def is_linux_enabled(distro):
"""
Checks if WSL distro is installed. If not, tries to install distro. Requires admin
If WSL isn't present, prompts to install WSL and its prerequisites.
:param distro:
:return: TRUE if the specified WSL distro has already been installed.
LINUX_INSTALLED if WSL is present but the distro is not.
WSL_NOT_INSTALLED if WSL is not present.
"""
if not is_wsl_framework_installed():
_ = WindowError("WSL framework is not installed.",
"Please save your work before clicking install. The machine will reboot.",
yes_text="Install WSL",
yes_command="install_wsl()",
no_text="Exit")
_.wait_window()
exit()
wsl_list_header = "windows subsystem for linux distributions"
wsl_no_distributions = "no installed distributions"
result = wsl_get_distro()
if distro in result and wsl_list_header in result: # checks if distro has been installed. Done
os.popen("wsl -s {}".format(distro))
return "TRUE"
elif wsl_no_distributions in result or distro not in result and wsl_list_header in result:
# checks if there is no distribution, or the distribution is not present
print("An instance of {} is being installed on your device. Please wait.".format(distro))
print("Downloading kernel.")
speed_download("https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi", "./TEMP")
os.popen("msiexec /i \"TEMP\\wsl_update_x64.msi\" /quiet").read()
print(f"Downloading {distro} system image.")
os.popen("wsl --install -d {}".format(distro)).read()
# execution of the last line stops, but installation hasn't been completed. therefore must check again.
while True: # check again for the presence of the OS before exiting
time.sleep(2)
result = wsl_get_distro()
# print(result)
if distro in result and wsl_list_header in result:
os.popen("wsl -s {}".format(distro))
break
return "LINUX_INSTALLED"


def remove(path):
""" param <path> could either be relative or absolute. """
if os.path.exists(path):
if os.path.isfile(path) or os.path.islink(path):
os.remove(path) # remove the file
elif os.path.isdir(path):
shutil.rmtree(path) # remove dir and all contains
204 changes: 204 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import platform
from functions import *
import os
import shutil
import traceback
from sys import exit
from wsa_online_link_generator import *
import fnmatch
from speed_downloader import speed_download
from xml.dom import minidom
from packaging import version
import subprocess

# URL to download WSA Script from GitHub
wsagascript_url = "https://github.com/ADeltaX/WSAGAScript/archive/refs/heads/main.zip"

# URLs to download GApps from SourceForge. Hardcoded :(
gapps_url_x64 = "https://nchc.dl.sourceforge.net/project/opengapps/x86_64/20211021/open_gapps-x86_64-11.0-pico-20211021.zip"
gapps_url_arm64 = "https://nchc.dl.sourceforge.net/project/opengapps/arm64/20211030/open_gapps-arm64-11.0-pico-20211030.zip"

# directories for system images
gapps_dir = "./TEMP/WSAGAScript-main/#GAPPS"
images_dir = "./TEMP/WSAGAScript-main/#IMAGES"

# instead of installing in a temporary folder, move everything to install_loc before installing
install_loc = "C:/Program Files/WSA_Advanced/"

# preinstalled version initialization
existing_install_version = None

supported_architecture = ["arm64", "amd64"]


def cleanup():
cur_dir = os.path.dirname(__file__)
os.chdir(cur_dir)
remove("./TEMP")


if __name__ == "__main__":
try:
# gets admin and modifies registry for developer mode, tries to enable WSL,
# and switches to the executable directory (just don't want to be execute in the wrong one,
# which may delete important stuff
get_admin_permission()
executable_dir = os.path.dirname(__file__)
# if the script executes in the shell with the location C:\,
# it will affect the folder C:\TEMP (which is NOT good)
print(f"EXECUTABLE DIRECTORY: {executable_dir}")
os.chdir(executable_dir)

cpu_arch = platform.machine()
if cpu_arch.casefold() not in supported_architecture:
WindowError("Your CPU does not support Windows Subsystem for Android.").wait_window()
exit()

print(f'WSL install status: {is_linux_enabled("debian")}')

os.popen('reg add "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock"'
' /t REG_DWORD'
' /f /v "AllowDevelopmentWithoutDevLicense" /d "1"').read()
cleanup()

# creates WSA directories
# Checks for updates and downloads the newest version of WSA.
os.makedirs("./TEMP/wsa", exist_ok=True)
os.makedirs("./TEMP/wsa_main", exist_ok=True)
os.makedirs(install_loc, exist_ok=True)

# obtains WSA package link and version
wsa_entry_result = get_wsa_entry()
if not wsa_entry_result:
wsa_archive_version = None
WindowError("No matching Windows Subsystem for Android package was found. Press ENTER to exit.")
exit()
else:
wsa_archive_url, wsa_archive_name = wsa_entry_result
wsa_archive_version = version.parse(wsa_archive_name.split("_")[1])
if not wsa_archive_version:
raise Exception("Sanity check failed. WSA archive version not found.")
try:
existing_install_version = max(map(version.parse, os.listdir(install_loc)))
# exception will be raised so that the new installation mode is used if there is nothing
if not existing_install_version:
# makes sure the version is not empty. Empty directories may cause unintentional deletions.
raise Exception("Sanity check failed. WSA existing version not found.")
print(f"Existing installation version: {existing_install_version}")
print(f'Latest version: {wsa_archive_version}')
if wsa_archive_version <= existing_install_version:
input("Windows Subsystem for Android is up-to-date. Press ENTER to exit.")
exit()
else:
print("Updating WSA...")
except ValueError:
print("New installation detected.")
speed_download(wsa_archive_url, "./TEMP", "wsa.zip")
shutil.unpack_archive("./TEMP/wsa.zip", "./TEMP/wsa", "zip")

# unpacks the correct 64-bit archive
for _ in os.listdir("./TEMP/wsa"):
if cpu_arch.casefold() == "amd64" and fnmatch.fnmatch(_, "*x64_Release*.msix"):
shutil.unpack_archive(f"./TEMP/wsa/{_}", "./TEMP/wsa_main", "zip")
break
elif cpu_arch.casefold() == "arm64" and fnmatch.fnmatch(_, "*ARM64_Release*.msix"):
shutil.unpack_archive(f"./TEMP/wsa/{_}", "./TEMP/wsa_main", "zip")
break
else:
cleanup()
WindowError("Your selected archive does not have the 64-bit MSIX bundle.").wait_window()
exit()

# removes signature from package
remove("./TEMP/wsa_main/AppxMetadata")
remove("./TEMP/wsa_main/[Content_Types].xml")
remove("./TEMP/wsa_main/AppxBlockMap.xml")
remove("./TEMP/wsa_main/AppxSignature.p7x")

# downloads WSAGAScript and extract, creates WSAGAScript-main
os.makedirs("./TEMP/WSAGAScript-main", exist_ok=True)
speed_download(wsagascript_url, "./TEMP", "WSAGAScript.zip")
shutil.unpack_archive("./TEMP/WSAGAScript.zip", "./TEMP", "zip")

# creates GAPPS and IMAGES directory in case it's missing
os.makedirs(gapps_dir, exist_ok=True)
os.makedirs(images_dir, exist_ok=True)

# downloads GApps
if cpu_arch.casefold() == "amd64":
speed_download(gapps_url_x64, gapps_dir)
else:
speed_download(gapps_url_arm64, gapps_dir)

# moves files to working directory.
for _ in os.listdir("./TEMP/wsa_main"):
if fnmatch.fnmatch(_, "*.img"):
shutil.move(f'./TEMP/wsa_main/{_}', "./TEMP/WSAGAScript-main/#IMAGES")

# executes main script
install_script = """#!/bin/bash
sudo apt update
sudo apt install unzip lzip
sudo bash ./extract_gapps_pico.sh
sudo bash ./extend_and_mount_images.sh
sudo bash ./apply.sh
sudo bash ./unmount_images.sh"""
with open("./TEMP/WSAGAScript-main/autorun.sh", "w", newline="\n") as file:
file.write(install_script)
print("ALTERING SYSTEM IMAGE. DO NOT EXIT!")
print(os.popen("bash -c 'cd ./TEMP/WSAGAScript-main; sudo bash ./autorun.sh'").read())

# copies back
for _ in os.listdir("./TEMP/WSAGAScript-main/#IMAGES"):
if fnmatch.fnmatch(_, "*.img"):
shutil.move(f'./TEMP/WSAGAScript-main/#IMAGES/{_}', "./TEMP/wsa_main")

# rooted kernel
if cpu_arch.casefold() == "amd64":
os.rename('./TEMP/WSAGAScript-main/misc/kernel-x86_64', "./TEMP/kernel")
else:
os.rename('./TEMP/WSAGAScript-main/misc/kernel-arm64', "./TEMP/kernel")
shutil.copy("./TEMP/kernel", "./TEMP/wsa_main/Tools")

# bypasses Windows 11 requirement
manifest_data = minidom.parse("./TEMP/wsa_main/AppxManifest.xml")
selected_element = manifest_data.getElementsByTagName("TargetDeviceFamily")[0]
selected_element.attributes["MinVersion"].value = "10.0.19043.1237"

with open("./TEMP/wsa_main/AppxManifest.xml", "w", encoding="utf-8") as file:
file.write(manifest_data.toxml())

# installs
new_install_location = os.path.realpath(os.path.join(install_loc, str(wsa_archive_version)))
print(f'Installing to {new_install_location}')
os.makedirs(new_install_location, exist_ok=True)

for file in os.listdir("./TEMP/wsa_main"):
shutil.move(os.path.join("./TEMP/wsa_main", file), new_install_location)
install_process = subprocess.run(f"powershell.exe Add-AppxPackage "
f"-Register '{new_install_location}\\AppXManifest.xml' "
f"-ForceTargetApplicationShutdown")

# cleans up temporary folder
print("Cleaning up temporary files.")
cleanup()

# deletes either the old or new version depending on return code
if not install_process.returncode:
if existing_install_version:
print(f"Deleting version {existing_install_version}.")
remove(os.path.join(install_loc, str(existing_install_version)))
WindowError("WSA with GApps and root access installed. Press ENTER to exit.",
color="green", tx_color="white").wait_window()
else:
remove(new_install_location)
WindowError("Package installation failed. Installation has been rolled back.").wait_window()
except Exception as e:
cleanup()
print(traceback.format_exc())
WindowError("Install failure. An exception occured. Installation has been rolled back.",
tx_color="white").wait_window()
with open("error.log", "w") as file:
print(e, file=file)
print(traceback.format_exc(), file=file)
exit()
Loading