Files
not-zune/scripts/disassemble_zune.py
John Smith 553a1a256a feat(ui): disassemble Zune 4.8, extract authentic Iris UIX assets, and rebuild UI
- Download and disassemble official ZunePackage.exe redistributable
- Decompile ZuneShell.dll and extract all 1,857 assets from ZuneShellResources.dll
- Ingest authentic 32-bit RGBA transport, rating, window, branding PNGs, and Segoe Zune TrueType fonts
- Rebuild MainShellView, QuickplayView, NowPlayingView, and DeviceView with authentic Zune assets
- Enforce exact Zune Iris minimum window constraints (734x500)
- Add Avalonia Headless testing support for view instantiation
- Author detailed technical analysis docs/disassembly/zune_uix_analysis.md
2026-09-06 20:36:19 -04:00

148 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""
Zune Package Downloader & Iris Desktop Player Disassembler
Downloads ZunePackage.exe, unpacks the installer payloads, decompiles
ZuneShell.dll and extracts authentic Iris UIX markup, styles, and assets.
"""
import os
import sys
import glob
import shutil
import subprocess
import urllib.request
DOWNLOAD_URL = "https://files1.majorgeeks.com/0b93caee71a9d214d0bbbc5622ea29507e3b8a7a/internet/ZunePackage.exe"
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "tools", "disassembly"))
PKG_PATH = os.path.join(BASE_DIR, "ZunePackage.exe")
PKG_DIR = os.path.join(BASE_DIR, "package")
MSI_DIR = os.path.join(BASE_DIR, "msi")
DECOMPILED_DIR = os.path.join(BASE_DIR, "zuneshell")
UIX_DIR = os.path.join(BASE_DIR, "uix")
ASSETS_DIR = os.path.join(BASE_DIR, "assets")
def run(cmd, cwd=None):
print(f"[*] Running: {' '.join(cmd) if isinstance(cmd, list) else cmd}")
res = subprocess.run(cmd, shell=isinstance(cmd, str), cwd=cwd, capture_output=True, text=True)
if res.returncode != 0:
print(f"[!] Warning (exit {res.returncode}): {res.stderr[:300]}")
return res
def download_package():
os.makedirs(BASE_DIR, exist_ok=True)
if os.path.exists(PKG_PATH) and os.path.getsize(PKG_PATH) > 100 * 1024 * 1024:
print(f"[+] ZunePackage.exe already downloaded ({os.path.getsize(PKG_PATH)} bytes).")
return
print(f"[+] Downloading ZunePackage.exe from {DOWNLOAD_URL}...")
curl_cmd = [
"curl", "-L", "-C", "-",
"--retry", "3",
"-A", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"-o", PKG_PATH,
DOWNLOAD_URL
]
res = run(curl_cmd)
if not os.path.exists(PKG_PATH) or os.path.getsize(PKG_PATH) < 100 * 1024 * 1024:
# Fallback to python urllib with stream
print("[*] Curl failed or returned small file, trying urllib stream...")
req = urllib.request.Request(DOWNLOAD_URL, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req) as resp, open(PKG_PATH, "wb") as out_file:
shutil.copyfileobj(resp, out_file)
print(f"[+] Download complete: {os.path.getsize(PKG_PATH)} bytes.")
def unpack_package():
os.makedirs(PKG_DIR, exist_ok=True)
print(f"[+] Unpacking ZunePackage.exe into {PKG_DIR}...")
run(["7z", "x", "-y", f"-o{PKG_DIR}", PKG_PATH])
def unpack_msi():
os.makedirs(MSI_DIR, exist_ok=True)
msi_files = glob.glob(os.path.join(PKG_DIR, "**", "*.msi"), recursive=True)
print(f"[+] Found {len(msi_files)} MSI installers: {msi_files}")
# Prioritize Zune-x86.msi or Zune-x64.msi
zune_msi = None
for msi in msi_files:
if "Zune-x86.msi" in msi or "Zune-x64.msi" in msi or "zune.msi" in msi.lower():
zune_msi = msi
break
if not zune_msi and msi_files:
zune_msi = msi_files[0]
if zune_msi:
print(f"[+] Extracting primary MSI: {zune_msi}...")
run(["7z", "x", "-y", f"-o{MSI_DIR}", zune_msi])
else:
print("[!] No MSI found directly, checking for CAB archives...")
cabs = glob.glob(os.path.join(PKG_DIR, "**", "*.cab"), recursive=True)
for cab in cabs:
run(["7z", "x", "-y", f"-o{MSI_DIR}", cab])
def find_binary(name):
for root, _, files in os.walk(BASE_DIR):
for f in files:
if f.lower() == name.lower():
return os.path.join(root, f)
return None
def decompile_zune_shell():
os.makedirs(DECOMPILED_DIR, exist_ok=True)
os.makedirs(UIX_DIR, exist_ok=True)
os.makedirs(ASSETS_DIR, exist_ok=True)
zune_shell = find_binary("ZuneShell.dll")
if not zune_shell:
print("[!] Could not locate ZuneShell.dll in extracted files!")
return
print(f"[+] Found ZuneShell.dll: {zune_shell} ({os.path.getsize(zune_shell)} bytes)")
# 1. Use 7z to directly extract resources from PE dll if possible
pe_res_dir = os.path.join(BASE_DIR, "zuneshell_resources")
os.makedirs(pe_res_dir, exist_ok=True)
run(["7z", "x", "-y", f"-o{pe_res_dir}", zune_shell])
# 2. Use ilspycmd to decompile project and dump all resources
ilspy = os.path.expanduser("~/.dotnet/tools/ilspycmd")
if os.path.exists(ilspy):
print(f"[+] Decompiling ZuneShell.dll with ilspycmd into {DECOMPILED_DIR}...")
run([ilspy, "-p", "-o", DECOMPILED_DIR, zune_shell])
# Also list resources
res_list = run([ilspy, "--list-resources", zune_shell])
with open(os.path.join(BASE_DIR, "resources_list.txt"), "w") as f:
f.write(res_list.stdout)
else:
print("[!] ilspycmd not found, relying on 7z PE extraction.")
# 3. Collect all .uix files and graphics
uix_count = 0
asset_count = 0
for root, _, files in os.walk(BASE_DIR):
if root.startswith(UIX_DIR) or root.startswith(ASSETS_DIR):
continue
for f in files:
ext = os.path.splitext(f)[1].lower()
src = os.path.join(root, f)
if ext == ".uix":
dst = os.path.join(UIX_DIR, f)
if not os.path.exists(dst):
shutil.copy2(src, dst)
uix_count += 1
elif ext in [".png", ".jpg", ".jpeg", ".ico", ".cur", ".bmp"]:
dst = os.path.join(ASSETS_DIR, f)
if not os.path.exists(dst):
shutil.copy2(src, dst)
asset_count += 1
print(f"[+] Extraction complete!")
print(f" Total .uix markup files extracted: {len(os.listdir(UIX_DIR))}")
print(f" Total graphic assets extracted: {len(os.listdir(ASSETS_DIR))}")
if __name__ == "__main__":
download_package()
unpack_package()
unpack_msi()
decompile_zune_shell()