Compare commits

..
Author SHA1 Message Date
Soul Inferno DE 218fadb6a4 Update README.md 2026-05-26 21:04:46 +02:00
Soul Inferno DE 89e3711977 Merge pull request #1 from casualsnek/main
updates
2026-02-01 19:05:41 +01:00
Soul Inferno DE 73c746fe2b Added support for multi device users
with this changes you can get all GSF IDs if you are using more than one profile on your waydroid system.

What this basically does:

- lists /data/users folders that can will be used for multiple sqlite requests
- uses android pm list users for the output to see a clean output line for name, folder id and gsf id
2025-07-29 05:24:59 +02:00
5 changed files with 146 additions and 83 deletions
+10
View File
@@ -1,3 +1,13 @@
# What's the difference from this fork ?
I added some small code lines to enable this script for beeing used with "MULTIPLE ANDROID USERS IN WAYDROID"
--> It won't work with only a single user created (maybe fixing that in the future..)
--> You need to create at least "two users" in Waydroid to be able to get the IDs for Android verifications
# Waydroid Extras Script # Waydroid Extras Script
Script to add GApps and other stuff to Waydroid! Script to add GApps and other stuff to Waydroid!
+42 -12
View File
@@ -1,21 +1,51 @@
from tools import container from tools import container
from tools.helper import shell from tools.helper import shell
from tools.logger import Logger from tools.logger import Logger
import re
class AndroidId: class AndroidId:
def get_id(self): def get_id(self):
if container.is_running(): if not container.is_running():
try:
queryout = shell(
arg="sqlite3 /data/data/com.google.android.gsf/databases/gservices.db \"select * from main where name = 'android_id';\"",
env="ANDROID_RUNTIME_ROOT=/apex/com.android.runtime ANDROID_DATA=/data ANDROID_TZDATA_ROOT=/apex/com.android.tzdata ANDROID_I18N_ROOT=/apex/com.android.i18n"
)
except:
return
else:
Logger.error("Please make sure Waydroid is running and Gapps has been installed!") Logger.error("Please make sure Waydroid is running and Gapps has been installed!")
return return
print(queryout.replace("android_id|", "").strip())
try:
# Get list of user IDs and their names using 'pm list users'
users_raw = shell(
arg="pm list users",
env="ANDROID_RUNTIME_ROOT=/apex/com.android.runtime ANDROID_DATA=/data ANDROID_TZDATA_ROOT=/apex/com.android.tzdata ANDROID_I18N_ROOT=/apex/com.android.i18n"
)
user_map = {}
for line in users_raw.strip().splitlines():
match = re.search(r'UserInfo\{(\d+):([^:}]+)', line)
if match:
user_id, username = match.groups()
user_map[user_id] = username
except Exception as e:
Logger.error(f"Failed to parse user list: {e}")
return
print("------------------------------------------------------------------------")
print(f"{'Username':<20} | {'User ID':^10} | {'GSF Android ID'}")
print("------------------------------------------------------------------------")
for user_id, username in user_map.items():
try:
db_path = f"/data/user/{user_id}/com.google.android.gsf/databases/gservices.db"
query = f"sqlite3 {db_path} \"select value from main where name = 'android_id';\""
gsf_id = shell(
arg=query,
env="ANDROID_RUNTIME_ROOT=/apex/com.android.runtime ANDROID_DATA=/data ANDROID_TZDATA_ROOT=/apex/com.android.tzdata ANDROID_I18N_ROOT=/apex/com.android.i18n"
).strip()
if gsf_id:
print(f"{username:<20} | {user_id:^10} | {gsf_id}")
else:
print(f"{username:<20} | {user_id:^10} | (not found)")
except:
print(f"{username:<20} | {user_id:^10} | (query error)")
print("------------------------------------------------------------------------")
print(" ^----- Open https://google.com/android/uncertified/?pli=1") print(" ^----- Open https://google.com/android/uncertified/?pli=1")
print(" Login with your google id then submit the form with id shown above") print(" Login with your Google ID, then submit the form with the ID(s) shown above.")
+24 -4
View File
@@ -1,9 +1,23 @@
import configparser import configparser
import os import os
import sys import sys
# import dbus
from tools.helper import run from tools.helper import run
from tools.logger import Logger from tools.logger import Logger
# def DBusContainerService(object_path="/ContainerManager", intf="id.waydro.ContainerManager"):
# return dbus.Interface(dbus.SystemBus().get_object("id.waydro.Container", object_path), intf)
# def DBusSessionService(object_path="/SessionManager", intf="id.waydro.SessionManager"):
# return dbus.Interface(dbus.SessionBus().get_object("id.waydro.Session", object_path), intf)
# def use_dbus():
# try:
# DBusContainerService()
# except:
# return False
# return True
def use_overlayfs(): def use_overlayfs():
cfg = configparser.ConfigParser() cfg = configparser.ConfigParser()
cfg_file = os.environ.get("WAYDROID_CONFIG", "/var/lib/waydroid/waydroid.cfg") cfg_file = os.environ.get("WAYDROID_CONFIG", "/var/lib/waydroid/waydroid.cfg")
@@ -20,14 +34,20 @@ def use_overlayfs():
return False return False
# def get_session():
# return DBusContainerService().GetSession()
def stop(): def stop():
# if use_dbus():
# session = DBusContainerService().GetSession()
# if session:
# DBusContainerService().Stop(False)
# else:
run(["waydroid", "container", "stop"]) run(["waydroid", "container", "stop"])
def is_running(): def is_running():
return "Session:\tRUNNING" in run(["waydroid", "status"]).stdout.decode() return "Session:\tRUNNING" in run(["waydroid", "status"]).stdout.decode()
def upgrade(): def upgrade():
try: run(["waydroid", "upgrade", "-o"], ignore=r"\[.*\] Stopping container\n\[.*\] Starting container")
run(["waydroid", "upgrade"], ignore=r"\[.*\] Stopping container\n\[.*\] Starting container")
except subprocess.CalledProcessError:
Logger.warning("Waydroid upgrade reported an error (likely system_ota), but we are continuing...")
+61 -59
View File
@@ -2,6 +2,7 @@ import gzip
import os import os
import re import re
import platform import platform
import re
import subprocess import subprocess
import sys import sys
import requests import requests
@@ -10,24 +11,27 @@ from tqdm import tqdm
import hashlib import hashlib
from typing import Optional from typing import Optional
def get_download_dir(): def get_download_dir():
if os.environ.get("XDG_CACHE_HOME"): download_loc = ""
if os.environ.get("XDG_CACHE_HOME", None) is None:
download_loc = os.path.join('/', "home", os.environ.get(
"SUDO_USER", os.environ["USER"]), ".cache", "waydroid-script", "downloads"
)
else:
download_loc = os.path.join( download_loc = os.path.join(
os.environ["XDG_CACHE_HOME"], "waydroid-script", "downloads" os.environ["XDG_CACHE_HOME"], "waydroid-script", "downloads"
) )
else:
user = os.environ.get("SUDO_USER", os.environ.get("USER", "root"))
download_loc = os.path.join("/", "home", user, ".cache", "waydroid-script", "downloads")
if not os.path.exists(download_loc): if not os.path.exists(download_loc):
os.makedirs(download_loc, exist_ok=True) os.makedirs(download_loc)
return download_loc return download_loc
# not good
def get_data_dir(): def get_data_dir():
user = os.environ.get("SUDO_USER", os.environ.get("USER", "root")) return os.path.join('/', "home", os.environ.get("SUDO_USER", os.environ["USER"]), ".local", "share", "waydroid", "data")
return os.path.join("/", "home", user, ".local", "share", "waydroid", "data")
def run(args: list, env: Optional[dict] = None, ignore: Optional[str] = None): # execute on host
def run(args: list, env: Optional[str] = None, ignore: Optional[str] = None):
result = subprocess.run( result = subprocess.run(
args=args, args=args,
env=env, env=env,
@@ -35,12 +39,11 @@ def run(args: list, env: Optional[dict] = None, ignore: Optional[str] = None):
stderr=subprocess.PIPE stderr=subprocess.PIPE
) )
# print(result.stdout.decode())
if result.stderr: if result.stderr:
error = result.stderr.decode("utf-8").strip() error = result.stderr.decode("utf-8")
if ignore and re.match(ignore, error):
if (ignore and re.search(ignore, error)) or "system_ota" in error:
return result return result
Logger.error(error) Logger.error(error)
raise subprocess.CalledProcessError( raise subprocess.CalledProcessError(
returncode=result.returncode, returncode=result.returncode,
@@ -49,58 +52,64 @@ def run(args: list, env: Optional[dict] = None, ignore: Optional[str] = None):
) )
return result return result
# execute on waydroid shell
def shell(arg: str, env: Optional[str] = None): def shell(arg: str, env: Optional[str] = None):
cmd = ["sudo", "waydroid", "shell"] a = subprocess.Popen(
process = subprocess.Popen( args=["sudo", "waydroid", "shell"],
args=cmd,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE
text=True
) )
subprocess.Popen(
args=["echo", "export BOOTCLASSPATH=/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/core-icu4j.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/framework-atb-backward-compatibility.jar:/apex/com.android.conscrypt/javalib/conscrypt.jar:/apex/com.android.media/javalib/updatable-media.jar:/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar:/apex/com.android.os.statsd/javalib/framework-statsd.jar:/apex/com.android.permission/javalib/framework-permission.jar:/apex/com.android.sdkext/javalib/framework-sdkextensions.jar:/apex/com.android.wifi/javalib/framework-wifi.jar:/apex/com.android.tethering/javalib/framework-tethering.jar"],
stdout=a.stdin,
stdin=subprocess.PIPE
).communicate()
boot_classpath = "export BOOTCLASSPATH=/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/core-icu4j.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/framework-atb-backward-compatibility.jar:/apex/com.android.conscrypt/javalib/conscrypt.jar:/apex/com.android.media/javalib/updatable-media.jar:/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar:/apex/com.android.os.statsd/javalib/framework-statsd.jar:/apex/com.android.permission/javalib/framework-permission.jar:/apex/com.android.sdkext/javalib/framework-sdkextensions.jar:/apex/com.android.wifi/javalib/framework-wifi.jar:/apex/com.android.tethering/javalib/framework-tethering.jar"
full_script = f"{boot_classpath}\n"
if env: if env:
full_script += f"{env}\n" subprocess.Popen(
full_script += f"{arg}\n" args=["echo", env],
stdout=a.stdin,
stdin=subprocess.PIPE
).communicate()
stdout, stderr = process.communicate(input=full_script) subprocess.Popen(
args=["echo", arg],
stdout=a.stdin,
stdin=subprocess.PIPE
).communicate()
if process.returncode != 0 and stderr: a.stdin.close()
Logger.error(stderr.strip()) if a.stderr.read():
Logger.error(a.stderr.read().decode('utf-8'))
raise subprocess.CalledProcessError( raise subprocess.CalledProcessError(
returncode=process.returncode, returncode=a.returncode,
cmd=cmd, cmd=a.args,
stderr=stderr.encode() stderr=a.stderr
) )
return stdout return a.stdout.read().decode("utf-8")
def download_file(url, f_name): def download_file(url, f_name):
md5 = ""
response = requests.get(url, stream=True) response = requests.get(url, stream=True)
total_size_in_bytes = int(response.headers.get('content-length', 0)) total_size_in_bytes = int(response.headers.get('content-length', 0))
block_size = 1024 block_size = 1024 # 1 Kibibyte
progress_bar = tqdm(total=total_size_in_bytes, unit='iB', unit_scale=True) progress_bar = tqdm(total=total_size_in_bytes, unit='iB', unit_scale=True)
with open(f_name, 'wb') as file: with open(f_name, 'wb') as file:
for data in response.iter_content(block_size): for data in response.iter_content(block_size):
progress_bar.update(len(data)) progress_bar.update(len(data))
file.write(data) file.write(data)
progress_bar.close() progress_bar.close()
hash_md5 = hashlib.md5()
with open(f_name, "rb") as f: with open(f_name, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""): bytes = f.read()
hash_md5.update(chunk) md5 = hashlib.md5(bytes).hexdigest()
if total_size_in_bytes != 0 and progress_bar.n != total_size_in_bytes: if total_size_in_bytes != 0 and progress_bar.n != total_size_in_bytes:
raise ValueError("Download incomplete or corrupted") raise ValueError("Something went wrong while downloading")
return md5
return hash_md5.hexdigest()
def host(): def host():
machine = platform.machine() machine = platform.machine()
mapping = { mapping = {
"i686": ("x86", 32), "i686": ("x86", 32),
"x86_64": ("x86_64", 64), "x86_64": ("x86_64", 64),
@@ -108,37 +117,30 @@ def host():
"armv7l": ("armeabi-v7a", 32), "armv7l": ("armeabi-v7a", 32),
"armv8l": ("armeabi-v7a", 32) "armv8l": ("armeabi-v7a", 32)
} }
if machine in mapping: if machine in mapping:
if machine == "x86_64": if mapping[machine] == "x86_64":
try: with open("/proc/cpuinfo") as f:
with open("/proc/cpuinfo", "r") as f:
if "sse4_2" not in f.read(): if "sse4_2" not in f.read():
Logger.warning("CPU does not support SSE4.2, falling back to x86...") Logger.warning("x86_64 CPU does not support SSE4.2, falling back to x86...")
return ("x86", 32) return ("x86", 32)
except Exception:
pass
return mapping[machine] return mapping[machine]
raise ValueError("platform.machine '" + machine + "'"
" architecture is not supported")
raise ValueError(f"Architecture '{machine}' is not supported")
def check_root(): def check_root():
if os.geteuid() != 0: if os.geteuid() != 0:
Logger.error("This script must be run as root (sudo). Aborting.") Logger.error("This script must be run as root. Aborting.")
sys.exit(1) sys.exit(1)
def backup(path): def backup(path):
if not os.path.exists(path):
return
gz_filename = path+".gz" gz_filename = path+".gz"
with open(path, "rb") as f_in: with gzip.open(gz_filename, 'wb') as f_gz:
with gzip.open(gz_filename, "wb") as f_out: with open(path, "rb") as f:
f_out.writelines(f_in) f_gz.write(f.read())
def restore(path): def restore(path):
gz_filename = path+".gz" gz_filename = path+".gz"
if not os.path.exists(gz_filename): with gzip.GzipFile(gz_filename) as f_gz:
return with open(path, "wb") as f:
with gzip.open(gz_filename, "rb") as f_in: f.writelines(f_gz)
with open(path, "wb") as f_out:
f_out.write(f_in.read())
+2 -1
View File
@@ -28,6 +28,7 @@ def resize(img_file, size):
run(["sudo", "resize2fs", img_file, size], ignore="^resize2fs \d+\.\d+\.\d (.+)\n$") run(["sudo", "resize2fs", img_file, size], ignore="^resize2fs \d+\.\d+\.\d (.+)\n$")
def get_image_dir(): def get_image_dir():
# Read waydroid config to get image location
cfg = configparser.ConfigParser() cfg = configparser.ConfigParser()
cfg_file = os.environ.get("WAYDROID_CONFIG", "/var/lib/waydroid/waydroid.cfg") cfg_file = os.environ.get("WAYDROID_CONFIG", "/var/lib/waydroid/waydroid.cfg")
if not os.path.isfile(cfg_file): if not os.path.isfile(cfg_file):
@@ -35,6 +36,6 @@ def get_image_dir():
sys.exit(1) sys.exit(1)
cfg.read(cfg_file) cfg.read(cfg_file)
if "waydroid" not in cfg: if "waydroid" not in cfg:
Logger.error("Required entry in config was not found, Cannot continue!") Logger.error("Required entry in config was not found, Cannot continue!") #magisk
sys.exit(1) sys.exit(1)
return cfg["waydroid"]["images_path"] return cfg["waydroid"]["images_path"]