Use bundled fallbacks for fresh offline installs
This commit is contained in:
+221
-50
@@ -4,6 +4,7 @@ import os
|
||||
import shutil
|
||||
import stat
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.error
|
||||
@@ -475,6 +476,96 @@ def _cache_current_managed_mpq(target_dir, mod_id, revision):
|
||||
validator=_verify_mpq,
|
||||
)
|
||||
return True
|
||||
def _runtime_base_path():
|
||||
return getattr(
|
||||
sys,
|
||||
"_MEIPASS",
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
)
|
||||
|
||||
|
||||
def _load_bundled_remote_fallback(
|
||||
package_id,
|
||||
filename=None,
|
||||
expected_revision=None,
|
||||
):
|
||||
"""Return a release-bundled remote fallback after strict integrity checks."""
|
||||
safe_id = "".join(
|
||||
ch if ch.isalnum() or ch in ("-", "_", ".") else "_"
|
||||
for ch in str(package_id)
|
||||
)
|
||||
if safe_id in ("", ".", ".."):
|
||||
return None
|
||||
|
||||
base = _runtime_base_path()
|
||||
manifest_path = os.path.join(
|
||||
base,
|
||||
"Payload",
|
||||
"Fallback",
|
||||
"remote_fallbacks.json",
|
||||
)
|
||||
try:
|
||||
with open(manifest_path, "r", encoding="utf-8") as handle:
|
||||
manifest = json.load(handle)
|
||||
except (OSError, json.JSONDecodeError, ValueError, TypeError):
|
||||
return None
|
||||
|
||||
fallbacks = manifest.get("fallbacks") if isinstance(manifest, dict) else None
|
||||
if not isinstance(fallbacks, dict):
|
||||
return None
|
||||
record = fallbacks.get(safe_id)
|
||||
if not isinstance(record, dict):
|
||||
return None
|
||||
|
||||
recorded_name = record.get("filename")
|
||||
try:
|
||||
recorded_name = _safe_cache_filename(recorded_name)
|
||||
except RemotePackageError:
|
||||
return None
|
||||
if filename is not None:
|
||||
try:
|
||||
filename = _safe_cache_filename(filename)
|
||||
except RemotePackageError:
|
||||
return None
|
||||
if recorded_name != filename:
|
||||
return None
|
||||
|
||||
revision = record.get("revision")
|
||||
if expected_revision is not None and str(revision) != str(expected_revision):
|
||||
return None
|
||||
|
||||
expected_size = record.get("size")
|
||||
expected_sha = record.get("sha256")
|
||||
if not isinstance(expected_size, int) or expected_size <= 0:
|
||||
return None
|
||||
if (
|
||||
not isinstance(expected_sha, str)
|
||||
or len(expected_sha) != 64
|
||||
or any(ch not in "0123456789abcdefABCDEF" for ch in expected_sha)
|
||||
):
|
||||
return None
|
||||
|
||||
path = os.path.join(
|
||||
base,
|
||||
"Payload",
|
||||
"Fallback",
|
||||
"Remote",
|
||||
safe_id,
|
||||
recorded_name,
|
||||
)
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
try:
|
||||
if os.path.getsize(path) != expected_size:
|
||||
return None
|
||||
if _file_sha256(path) != expected_sha.lower():
|
||||
return None
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
return path, record
|
||||
|
||||
|
||||
|
||||
def _remove_tree(path):
|
||||
if not os.path.exists(path):
|
||||
@@ -1120,19 +1211,13 @@ def install_wowpresence(target_dir, progress=None):
|
||||
return revision
|
||||
|
||||
|
||||
def install_cached_wowpresence(target_dir, progress=None):
|
||||
"""Install the last validated WowPresence package cached by this WoW install."""
|
||||
cached = _load_cached_file(
|
||||
target_dir,
|
||||
WOWPRESENCE_MANAGED_ID,
|
||||
"WowPresence.zip",
|
||||
)
|
||||
if cached is None:
|
||||
raise RemotePackageError("No validated WowPresence offline fallback is cached.")
|
||||
|
||||
zip_path, metadata = cached
|
||||
revision = str(metadata.get("revision") or "cached")
|
||||
|
||||
def _install_wowpresence_fallback_archive(
|
||||
target_dir,
|
||||
zip_path,
|
||||
revision,
|
||||
package_revision,
|
||||
progress=None,
|
||||
):
|
||||
existing_manifest = _load_managed_manifest_data(
|
||||
target_dir,
|
||||
WOWPRESENCE_MANAGED_ID,
|
||||
@@ -1152,14 +1237,13 @@ def install_cached_wowpresence(target_dir, progress=None):
|
||||
)
|
||||
|
||||
ensure_wowpresence_config(target_dir)
|
||||
extract_root = tempfile.mkdtemp(prefix="modernization_wowpresence_cache_")
|
||||
extract_root = tempfile.mkdtemp(prefix="modernization_wowpresence_fallback_")
|
||||
try:
|
||||
_emit_progress(progress, "Loading cached WowPresence fallback...", None, None)
|
||||
_safe_extract(zip_path, extract_root)
|
||||
dll_path = _find_file(extract_root, "WowPresence.dll")
|
||||
exe_path = _find_file(extract_root, "WowPresence.exe")
|
||||
_verify_x86_pe(dll_path, "cached WowPresence.dll")
|
||||
_verify_x86_pe(exe_path, "cached WowPresence.exe")
|
||||
_verify_x86_pe(dll_path, "fallback WowPresence.dll")
|
||||
_verify_x86_pe(exe_path, "fallback WowPresence.exe")
|
||||
|
||||
_install_managed_files_transactional(
|
||||
target_dir,
|
||||
@@ -1179,7 +1263,7 @@ def install_cached_wowpresence(target_dir, progress=None):
|
||||
"WowPresence.exe": _file_sha256(exe_path),
|
||||
},
|
||||
package_digest=None,
|
||||
package_revision=f"cache:{metadata.get('sha256', '')}",
|
||||
package_revision=package_revision,
|
||||
)
|
||||
finally:
|
||||
shutil.rmtree(extract_root, ignore_errors=True)
|
||||
@@ -1188,6 +1272,48 @@ def install_cached_wowpresence(target_dir, progress=None):
|
||||
return revision
|
||||
|
||||
|
||||
def install_cached_wowpresence(target_dir, progress=None):
|
||||
"""Install the last validated WowPresence package cached by this WoW install."""
|
||||
cached = _load_cached_file(
|
||||
target_dir,
|
||||
WOWPRESENCE_MANAGED_ID,
|
||||
"WowPresence.zip",
|
||||
)
|
||||
if cached is None:
|
||||
raise RemotePackageError("No validated WowPresence offline fallback is cached.")
|
||||
|
||||
zip_path, metadata = cached
|
||||
revision = str(metadata.get("revision") or "cached")
|
||||
_emit_progress(progress, "Loading cached WowPresence fallback...", None, None)
|
||||
return _install_wowpresence_fallback_archive(
|
||||
target_dir,
|
||||
zip_path,
|
||||
revision,
|
||||
f"cache:{metadata.get('sha256', '')}",
|
||||
progress=progress,
|
||||
)
|
||||
|
||||
|
||||
def install_bundled_wowpresence(target_dir, progress=None):
|
||||
"""Install the release-bundled known-good WowPresence fallback."""
|
||||
bundled = _load_bundled_remote_fallback(
|
||||
WOWPRESENCE_MANAGED_ID,
|
||||
"WowPresence.zip",
|
||||
)
|
||||
if bundled is None:
|
||||
raise RemotePackageError("Bundled WowPresence fallback is unavailable.")
|
||||
|
||||
zip_path, metadata = bundled
|
||||
revision = str(metadata.get("revision") or "bundled")
|
||||
_emit_progress(progress, "Loading bundled WowPresence fallback...", None, None)
|
||||
return _install_wowpresence_fallback_archive(
|
||||
target_dir,
|
||||
zip_path,
|
||||
revision,
|
||||
f"bundled:{metadata.get('sha256', '')}",
|
||||
progress=progress,
|
||||
)
|
||||
|
||||
def install_interact(target_dir, progress=None):
|
||||
_emit_progress(progress, "Checking Interact release...", None, None)
|
||||
release = _latest_release("lookino/Interact")
|
||||
@@ -2046,16 +2172,31 @@ def _install_remote_mpq(
|
||||
"payload.mpq",
|
||||
expected_revision=revision,
|
||||
)
|
||||
if cached is None:
|
||||
raise download_error
|
||||
source_path, _ = cached
|
||||
_verify_mpq(source_path)
|
||||
_emit_progress(
|
||||
progress,
|
||||
f"{label.replace('Downloading ', '')}: using cached offline fallback.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
if cached is not None:
|
||||
source_path, _ = cached
|
||||
_verify_mpq(source_path)
|
||||
_emit_progress(
|
||||
progress,
|
||||
f"{label.replace('Downloading ', '')}: using cached offline fallback.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
else:
|
||||
bundled = _load_bundled_remote_fallback(
|
||||
mod_id,
|
||||
"payload.mpq",
|
||||
expected_revision=revision,
|
||||
)
|
||||
if bundled is None:
|
||||
raise download_error
|
||||
source_path, _ = bundled
|
||||
_verify_mpq(source_path)
|
||||
_emit_progress(
|
||||
progress,
|
||||
f"{label.replace('Downloading ', '')}: using bundled offline fallback.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
||||
try:
|
||||
_emit_progress(progress, f"Installing {os.path.basename(destination)}...", None, None)
|
||||
@@ -2182,17 +2323,32 @@ def install_pink_herbs(target_dir, progress=None):
|
||||
return f"seacrabsam/patch-herb main@{installed_revision[:7]}"
|
||||
|
||||
cached = _load_cached_file(target_dir, mod_id, "payload.mpq")
|
||||
if cached is None:
|
||||
raise revision_error
|
||||
source_path, metadata = cached
|
||||
revision = str(metadata.get("revision") or "cached")
|
||||
_verify_mpq(source_path)
|
||||
_emit_progress(
|
||||
progress,
|
||||
"Pink Herbs: using cached offline fallback.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
if cached is not None:
|
||||
source_path, metadata = cached
|
||||
revision = str(metadata.get("revision") or "cached")
|
||||
_verify_mpq(source_path)
|
||||
_emit_progress(
|
||||
progress,
|
||||
"Pink Herbs: using cached offline fallback.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
else:
|
||||
bundled = _load_bundled_remote_fallback(
|
||||
mod_id,
|
||||
"payload.mpq",
|
||||
)
|
||||
if bundled is None:
|
||||
raise revision_error
|
||||
source_path, metadata = bundled
|
||||
revision = str(metadata.get("revision") or "bundled")
|
||||
_verify_mpq(source_path)
|
||||
_emit_progress(
|
||||
progress,
|
||||
"Pink Herbs: using bundled offline fallback.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
else:
|
||||
if installed_revision == revision:
|
||||
try:
|
||||
@@ -2252,17 +2408,32 @@ def install_pink_herbs(target_dir, progress=None):
|
||||
return f"seacrabsam/patch-herb main@{installed_revision[:7]}"
|
||||
|
||||
cached = _load_cached_file(target_dir, mod_id, "payload.mpq")
|
||||
if cached is None:
|
||||
raise download_error
|
||||
source_path, metadata = cached
|
||||
revision = str(metadata.get("revision") or revision)
|
||||
_verify_mpq(source_path)
|
||||
_emit_progress(
|
||||
progress,
|
||||
"Pink Herbs: using cached offline fallback.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
if cached is not None:
|
||||
source_path, metadata = cached
|
||||
revision = str(metadata.get("revision") or revision)
|
||||
_verify_mpq(source_path)
|
||||
_emit_progress(
|
||||
progress,
|
||||
"Pink Herbs: using cached offline fallback.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
else:
|
||||
bundled = _load_bundled_remote_fallback(
|
||||
mod_id,
|
||||
"payload.mpq",
|
||||
)
|
||||
if bundled is None:
|
||||
raise download_error
|
||||
source_path, metadata = bundled
|
||||
revision = str(metadata.get("revision") or revision)
|
||||
_verify_mpq(source_path)
|
||||
_emit_progress(
|
||||
progress,
|
||||
"Pink Herbs: using bundled offline fallback.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
||||
try:
|
||||
_migrate_legacy_pink_herbs_patch(target_dir, source_path, progress=progress)
|
||||
|
||||
Reference in New Issue
Block a user