diff --git a/.github/workflows/update-fexcore.yml b/.github/workflows/update-fexcore.yml
index f10b7c8f3a..71dcfc3686 100644
--- a/.github/workflows/update-fexcore.yml
+++ b/.github/workflows/update-fexcore.yml
@@ -22,57 +22,8 @@ jobs:
- name: Find latest FEXCore .wcp
id: check
env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- # Fetch the file listing from the upstream repo via the GitHub API.
- # Authenticate to avoid anonymous rate-limit (60 req/hr).
- RESPONSE=$(curl -sf \
- -H "Accept: application/vnd.github.v3+json" \
- -H "Authorization: Bearer $GH_TOKEN" \
- "https://api.github.com/repos/StevenMXZ/Winlator-Contents/contents/FEXCore")
-
- # Pick the file with the highest YYMM version.
- # Only considers files whose names start with 4 digits (e.g. 2604.wcp, 2508.1.wcp).
- LATEST=$(echo "$RESPONSE" | python3 - <<'EOF'
-import sys, json, re
-
-data = json.loads(sys.stdin.read())
-files = [f["name"] for f in data if f["type"] == "file" and f["name"].endswith(".wcp")]
-
-best = None
-best_ver = (-1, -1)
-for name in files:
- m = re.match(r'^(\d{4})(?:\.(\d+))?\.wcp$', name)
- if m:
- ver = (int(m.group(1)), int(m.group(2) or 0))
- if ver > best_ver:
- best_ver = ver
- best = name
-
-print(best or "")
-EOF
-)
-
- if [[ -z "$LATEST" ]]; then
- echo "Could not determine latest .wcp file." >&2
- exit 1
- fi
-
- # Extract the YYMM version (first four digits) for the output filename.
- VERSION=$(echo "$LATEST" | grep -oP '^\d{4}')
- TZST_PATH="app/src/main/assets/fexcore/fexcore-${VERSION}.tzst"
-
- echo "LATEST_FILE=$LATEST" >> "$GITHUB_OUTPUT"
- echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
- echo "TZST_PATH=$TZST_PATH" >> "$GITHUB_OUTPUT"
-
- if [[ -f "$TZST_PATH" ]]; then
- echo "ALREADY_EXISTS=true" >> "$GITHUB_OUTPUT"
- echo "fexcore-${VERSION}.tzst already present — nothing to do."
- else
- echo "ALREADY_EXISTS=false" >> "$GITHUB_OUTPUT"
- echo "New release found: $LATEST → $TZST_PATH"
- fi
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: python3 tools/check-latest-fexcore.py
- name: Download ${{ steps.check.outputs.LATEST_FILE }}
if: steps.check.outputs.ALREADY_EXISTS == 'false'
@@ -89,38 +40,9 @@ EOF
- name: Update arrays.xml
if: steps.check.outputs.ALREADY_EXISTS == 'false'
run: |
- VERSION="${{ steps.check.outputs.VERSION }}"
- ARRAYS_XML="app/src/main/res/values/arrays.xml"
-
- # Check if this version is already listed in arrays.xml.
- if grep -qF "- ${VERSION}
" "$ARRAYS_XML"; then
- echo "Version $VERSION already present in arrays.xml — skipping XML update."
- else
- # Insert the new version as the last - of fexcore_version_entries.
- # We find the closing that follows the fexcore_version_entries
- # block and insert directly before it.
- python3 - "$ARRAYS_XML" "$VERSION" <<'PYEOF'
- import sys, re
-
- path, version = sys.argv[1], sys.argv[2]
- with open(path, "r", encoding="utf-8") as f:
- content = f.read()
-
- # Find the fexcore_version_entries block and append the new item before its closing tag.
- pattern = r'(name="fexcore_version_entries".*?)()'
- replacement = r'\g<1>
- ' + version + r'
\n \g<2>'
- new_content, count = re.subn(pattern, replacement, content, count=1, flags=re.DOTALL)
-
- if count == 0:
- print("ERROR: fexcore_version_entries array not found in arrays.xml", file=sys.stderr)
- sys.exit(1)
-
- with open(path, "w", encoding="utf-8") as f:
- f.write(new_content)
-
- print(f"Appended - {version}
to fexcore_version_entries.")
- PYEOF
- fi
+ python3 tools/update-arrays-xml.py \
+ app/src/main/res/values/arrays.xml \
+ "${{ steps.check.outputs.VERSION }}"
- name: Create PR branch, commit, and open PR
if: steps.check.outputs.ALREADY_EXISTS == 'false'
diff --git a/tools/check-latest-fexcore.py b/tools/check-latest-fexcore.py
new file mode 100644
index 0000000000..ced25a63f7
--- /dev/null
+++ b/tools/check-latest-fexcore.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+"""
+check-latest-fexcore.py
+Queries the StevenMXZ/Winlator-Contents FEXCore directory and prints the
+latest .wcp filename (by YYMM version number).
+
+Usage:
+ python3 tools/check-latest-fexcore.py [--token ]
+
+A token is optional but recommended to avoid the 60 req/hr anonymous rate limit.
+It can also be supplied via the GITHUB_TOKEN environment variable.
+"""
+
+import argparse
+import json
+import os
+import re
+import subprocess
+import sys
+
+API_URL = "https://api.github.com/repos/StevenMXZ/Winlator-Contents/contents/FEXCore"
+
+
+def fetch_listing(token: str | None) -> list[dict]:
+ cmd = [
+ "curl", "-sf",
+ "-H", "Accept: application/vnd.github.v3+json",
+ ]
+ if token:
+ cmd += ["-H", f"Authorization: Bearer {token}"]
+ cmd.append(API_URL)
+
+ result = subprocess.run(cmd, capture_output=True, text=True)
+ if result.returncode != 0:
+ print(f"curl error: {result.stderr.strip()}", file=sys.stderr)
+ sys.exit(1)
+ return json.loads(result.stdout)
+
+
+def pick_latest(entries: list[dict]) -> tuple[str | None, tuple[int, int]]:
+ best_name = None
+ best_ver: tuple[int, int] = (-1, -1)
+ for entry in entries:
+ if entry.get("type") != "file":
+ continue
+ name = entry["name"]
+ if not name.endswith(".wcp"):
+ continue
+ m = re.match(r"^(\d{4})(?:\.(\d+))?\.wcp$", name)
+ if m:
+ ver = (int(m.group(1)), int(m.group(2) or 0))
+ if ver > best_ver:
+ best_ver = ver
+ best_name = name
+ return best_name, best_ver
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description="Find the latest FEXCore .wcp release.")
+ parser.add_argument("--token", default=os.environ.get("GITHUB_TOKEN"), help="GitHub token (or set GITHUB_TOKEN)")
+ parser.add_argument(
+ "--gha-output",
+ metavar="FILE",
+ default=os.environ.get("GITHUB_OUTPUT"),
+ help="Append key=value pairs to this file (GitHub Actions $GITHUB_OUTPUT). "
+ "Automatically set when GITHUB_OUTPUT env var is present.",
+ )
+ args = parser.parse_args()
+
+ if not args.gha_output:
+ # Human-readable mode
+ print(f"Querying {API_URL} ...")
+
+ entries = fetch_listing(args.token)
+
+ all_wcp = [e["name"] for e in entries if e.get("type") == "file" and e["name"].endswith(".wcp")]
+
+ latest, ver = pick_latest(entries)
+ if not latest:
+ print("ERROR: Could not determine latest versioned .wcp file.", file=sys.stderr)
+ sys.exit(1)
+
+ version = latest[:4] # first four digits = YYMM
+ tzst_path = f"app/src/main/assets/fexcore/fexcore-{version}.tzst"
+ download_url = f"https://raw.githubusercontent.com/StevenMXZ/Winlator-Contents/main/FEXCore/{latest}"
+ already_exists = os.path.isfile(tzst_path)
+
+ if args.gha_output:
+ # GitHub Actions mode: write outputs, print minimal log to stdout
+ with open(args.gha_output, "a", encoding="utf-8") as f:
+ f.write(f"LATEST_FILE={latest}\n")
+ f.write(f"VERSION={version}\n")
+ f.write(f"TZST_PATH={tzst_path}\n")
+ f.write(f"ALREADY_EXISTS={'true' if already_exists else 'false'}\n")
+ if already_exists:
+ print(f"fexcore-{version}.tzst already present — nothing to do.")
+ else:
+ print(f"New release found: {latest} → {tzst_path}")
+ else:
+ # Human-readable mode
+ print(f"\nAll .wcp files found ({len(all_wcp)}):")
+ for name in sorted(all_wcp):
+ print(f" {name}")
+ print(f"\nLatest : {latest} (parsed version {ver[0]}.{ver[1]})")
+ print(f"VERSION: {version}")
+ print(f"Output : {tzst_path}")
+ print(f"Already exists: {already_exists}")
+ print(f"Download URL: {download_url}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/update-arrays-xml.py b/tools/update-arrays-xml.py
new file mode 100644
index 0000000000..5454fa4c5e
--- /dev/null
+++ b/tools/update-arrays-xml.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+"""
+update-arrays-xml.py
+Appends a new version entry to the fexcore_version_entries array in arrays.xml.
+
+Usage:
+ python3 tools/update-arrays-xml.py
+ e.g. python3 tools/update-arrays-xml.py app/src/main/res/values/arrays.xml 2604
+"""
+
+import re
+import sys
+
+
+def main() -> None:
+ if len(sys.argv) != 3:
+ print(f"Usage: {sys.argv[0]} ", file=sys.stderr)
+ sys.exit(1)
+
+ path, version = sys.argv[1], sys.argv[2]
+
+ with open(path, "r", encoding="utf-8") as f:
+ content = f.read()
+
+ # Idempotency check
+ if f"- {version}
" in content:
+ print(f"Version {version} already present in arrays.xml — nothing to do.")
+ sys.exit(0)
+
+ # Find the fexcore_version_entries block and append the new item before its closing tag.
+ pattern = r'(name="fexcore_version_entries".*?)()'
+ replacement = r'\g<1> - ' + version + r'
\n \g<2>'
+ new_content, count = re.subn(pattern, replacement, content, count=1, flags=re.DOTALL)
+
+ if count == 0:
+ print("ERROR: fexcore_version_entries array not found in arrays.xml", file=sys.stderr)
+ sys.exit(1)
+
+ with open(path, "w", encoding="utf-8") as f:
+ f.write(new_content)
+
+ print(f"Appended - {version}
to fexcore_version_entries.")
+
+
+if __name__ == "__main__":
+ main()