-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
khanhtranngoccva
wants to merge
28
commits into
WSA-Community:main
Choose a base branch
from
khanhtranngoccva:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+541
−0
Open
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
c8c9cf4
Update README.md
khanhtranngoccva e93cc28
One-click script wrapper for root + Google Play
khanhtranngoccva 5798d87
One-click precompiled executable file
khanhtranngoccva d20b58a
Merge branch 'main' of https://github.com/khanhtranngoccva/WSAGAScript
khanhtranngoccva e6f1205
Update README.md
khanhtranngoccva 94fbbfa
Merge branch 'WSA-Community:main' into main
khanhtranngoccva 2a09970
Improved rollback and updatability
khanhtranngoccva a194d7e
Improved rollback and updatability
khanhtranngoccva 2d4579d
Minor fixes
khanhtranngoccva ccad8d1
Minor fixes
khanhtranngoccva d0abeb0
Uninstaller script
khanhtranngoccva 12cc3a3
Rename for easier use
khanhtranngoccva 5468e24
rename kernel to match newest commit
khanhtranngoccva 74d4b47
Script now checks for CPU architecture
khanhtranngoccva f4fc633
Minor fixes
khanhtranngoccva fa51fbb
Merge branch 'WSA-Community:main' into main
khanhtranngoccva 0cc941b
Minor fixes for kernel
khanhtranngoccva 163fc5d
Retries download if it fails for no reason
khanhtranngoccva 943cf1c
Retries download if it fails for no reason
khanhtranngoccva de2efd4
Retries download if it fails for no reason
khanhtranngoccva f8e40bd
fixes kernel folder
khanhtranngoccva 8697e2e
fixes kernel folder
khanhtranngoccva 5e8add2
Fixes weird uninstall behavior that deletes the main installation fol…
khanhtranngoccva 37d347d
Fixes stdout checking of uninstall script
khanhtranngoccva 75e04f4
Merge branch 'WSA-Community:main' into main
khanhtranngoccva bba8be8
Captcha bypass
khanhtranngoccva 73faa19
Merge remote-tracking branch 'origin/main'
khanhtranngoccva 3a9da7c
Merge branch 'WSA-Community:main' into main
khanhtranngoccva File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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): | ||
"""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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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