#!/usr/bin/env python3 import base64 import json import os import shutil import sys import urllib.error import urllib.request from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[1] UPROJECT = PROJECT_ROOT / "FunnyGirl.uproject" DEST = PROJECT_ROOT / "Plugins" / "MobileFSR" BRANCH = os.environ.get("MOBILE_FSR_BRANCH", "5.6.1-release") TOKEN = os.environ.get("GITHUB_TOKEN", "") OWNER = "EpicGames" REPO = "UnrealEngine" SOURCE_PATH = "Engine/Plugins/Runtime/MobileFSR" API_ROOT = f"https://api.github.com/repos/{OWNER}/{REPO}/contents" def request_json(url): request = urllib.request.Request(url) request.add_header("Accept", "application/vnd.github+json") request.add_header("X-GitHub-Api-Version", "2022-11-28") if TOKEN: request.add_header("Authorization", f"Bearer {TOKEN}") try: with urllib.request.urlopen(request) as response: return json.loads(response.read().decode("utf-8")) except urllib.error.HTTPError as error: body = error.read().decode("utf-8", errors="replace") raise RuntimeError(f"GitHub API failed: HTTP {error.code}\n{body}") from error def download_file(api_url, output_path): data = request_json(api_url) encoding = data.get("encoding") content = data.get("content") if encoding != "base64" or content is None: raise RuntimeError(f"Unsupported file response for {api_url}") output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_bytes(base64.b64decode(content)) def download_dir(source_path, output_dir): url = f"{API_ROOT}/{source_path}?ref={BRANCH}" entries = request_json(url) if not isinstance(entries, list): raise RuntimeError(f"Expected directory listing for {source_path}") for entry in entries: name = entry["name"] entry_type = entry["type"] if entry_type == "dir": download_dir(entry["path"], output_dir) elif entry_type == "file": rel_path = Path(entry["path"]).relative_to(SOURCE_PATH) print(f"Downloading {rel_path}") download_file(entry["url"], output_dir / rel_path) else: print(f"Skipping {entry['path']} ({entry_type})") def enable_plugin(): data = json.loads(UPROJECT.read_text()) plugins = data.setdefault("Plugins", []) for plugin in plugins: if plugin.get("Name") == "MobileFSR": plugin["Enabled"] = True break else: plugins.append({"Name": "MobileFSR", "Enabled": True}) UPROJECT.write_text(json.dumps(data, indent="\t") + "\n") def main(): if not TOKEN: print("GITHUB_TOKEN is required for private EpicGames/UnrealEngine access.", file=sys.stderr) return 1 tmp_dest = DEST.with_name(".MobileFSR.download") shutil.rmtree(tmp_dest, ignore_errors=True) shutil.rmtree(DEST, ignore_errors=True) tmp_dest.mkdir(parents=True) print(f"Downloading MobileFSR from {OWNER}/{REPO}:{BRANCH}") download_dir(SOURCE_PATH, tmp_dest) if not (tmp_dest / "MobileFSR.uplugin").exists(): print("MobileFSR.uplugin was not downloaded; aborting.", file=sys.stderr) return 1 shutil.rmtree(DEST, ignore_errors=True) shutil.move(str(tmp_dest), str(DEST)) enable_plugin() print(f"Installed MobileFSR to {DEST}") print(f"Enabled MobileFSR in {UPROJECT}") return 0 if __name__ == "__main__": raise SystemExit(main())