tarted work on restoring Apple @e support.

This commit is contained in:
Storm Dragon
2026-04-28 19:20:12 -04:00
parent 1063f21c43
commit b6d77279f7
7 changed files with 707 additions and 0 deletions
@@ -79,6 +79,33 @@ download_with_fallback() {
return 1
}
internet_available() {
local targetUrl
local targetUrls=(
"https://www.gnu.org/"
"https://1.1.1.1/"
)
for targetUrl in "${targetUrls[@]}"; do
if curl -fsS --connect-timeout 5 --max-time 8 "$targetUrl" > /dev/null 2>&1; then
return 0
fi
done
return 1
}
require_internet_connection() {
local message="No internet connection detected. Configure a connection from System Settings, Internet Configuration, then try again."
if internet_available; then
return 0
fi
ui_msgbox "Game Installer" "Game Installer" "$message"
return 1
}
get_remote_file_size() {
local sourceUrl="$1"
local headerOutput
@@ -69,6 +69,8 @@ trap finish_logging EXIT
# shellcheck disable=SC1091
source "${scriptDir}/game_install_functions.sh"
require_internet_connection
speak "Installing ${gameName}"
# shellcheck source=/dev/null
source "${installerPath}"
+151
View File
@@ -7,9 +7,157 @@ import os
import sys
import time
import curses
import hashlib
import shutil
import subprocess
import tempfile
import zipfile
from pathlib import Path
import speechd # Python bindings for Speech Dispatcher
from stormux_speech_settings import load_speech_settings, save_speech_settings
APPLE2E_CID = "QmY9fUfpexakGuq6MGn3pSLpNJxy8jW7rU79GGywBSekHY"
APPLE2E_ZIP_NAME = "apple2e.zip"
APPLE2E_ZIP_SHA256 = "5632c11d86c173f9e2a686e31542c16aafd1cc9ef3b6a3bcb5ff44a19bdd1195"
APPLE2E_SUPPORT_DIR = Path.home() / ".local" / "games" / "apple2e"
APPLE2E_CACHE_DIR = Path.home() / ".cache" / "stormux"
APPLE2E_BOOT_DISK = "Echo II - Textalker DOS 3.3.dsk"
def speak_message(message, wait=False):
flag = "-Cw" if wait else "-C"
subprocess.run(["spd-say", flag, message], check=False)
def internet_available():
"""Return True when outbound HTTPS appears available."""
targets = ("https://www.gnu.org/", "https://1.1.1.1/")
for target in targets:
result = subprocess.run(
["curl", "-fsS", "--connect-timeout", "5", "--max-time", "8", target],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
if result.returncode == 0:
return True
return False
def apple_support_url():
gateway = os.environ.get("ipfsGateway", "https://ipfs.stormux.org")
return f"{gateway}/ipfs/{APPLE2E_CID}?filename={APPLE2E_ZIP_NAME}"
def sha256_file(filePath):
digest = hashlib.sha256()
with open(filePath, "rb") as fileHandle:
for chunk in iter(lambda: fileHandle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def download_file(sourceUrl, outputPath):
subprocess.run(
[
"curl",
"--fail",
"--show-error",
"--location",
"--connect-timeout",
"20",
"--retry",
"3",
"--retry-delay",
"2",
"--output",
str(outputPath),
sourceUrl,
],
check=True,
)
def apple_support_installed(supportDir=APPLE2E_SUPPORT_DIR):
supportDir = Path(supportDir)
return (
(supportDir / APPLE2E_BOOT_DISK).is_file()
and (supportDir / "disks").is_dir()
and any((supportDir / "disks").glob("*.dsk"))
)
def validate_zip_member(memberName):
memberPath = Path(memberName)
return (
not memberPath.is_absolute()
and ".." not in memberPath.parts
and len(memberPath.parts) >= 1
and memberPath.parts[0] == "apple2e"
)
def extract_apple_support_archive(zipPath, supportDir):
supportDir = Path(supportDir)
gamesDir = supportDir.parent
gamesDir.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(zipPath) as archive:
memberNames = archive.namelist()
if not all(validate_zip_member(name) for name in memberNames):
raise RuntimeError("Apple 2e support archive contains unsafe paths.")
if f"apple2e/{APPLE2E_BOOT_DISK}" not in memberNames:
raise RuntimeError("Apple 2e support archive is missing the boot disk.")
if not any(name.startswith("apple2e/disks/") and name.lower().endswith(".dsk") for name in memberNames):
raise RuntimeError("Apple 2e support archive is missing disk images.")
tempRoot = Path(tempfile.mkdtemp(prefix="apple2e_extract_", dir=str(gamesDir)))
try:
archive.extractall(tempRoot)
extractedDir = tempRoot / "apple2e"
if supportDir.exists():
shutil.rmtree(supportDir)
shutil.move(str(extractedDir), str(supportDir))
(supportDir / ".stormux-installed").write_text("installed\n")
finally:
shutil.rmtree(tempRoot, ignore_errors=True)
def ensure_apple_support_files(
supportDir=APPLE2E_SUPPORT_DIR,
cacheDir=APPLE2E_CACHE_DIR,
speak=speak_message,
):
supportDir = Path(supportDir)
cacheDir = Path(cacheDir)
if apple_support_installed(supportDir):
return True
if not internet_available():
message = (
"No internet connection detected. Configure a connection from "
"System Settings, Internet Configuration, then open Apple 2e again."
)
print(message)
speak(message, True)
return False
cacheDir.mkdir(parents=True, exist_ok=True)
zipPath = cacheDir / APPLE2E_ZIP_NAME
speak("Apple 2e support files are not installed. Downloading them now.", True)
download_file(apple_support_url(), zipPath)
if sha256_file(zipPath) != APPLE2E_ZIP_SHA256:
zipPath.unlink(missing_ok=True)
raise RuntimeError("Apple 2e support archive failed checksum verification.")
extract_apple_support_archive(zipPath, supportDir)
speak("Apple 2e support files installed.", True)
return True
class VoicedDiskMenu:
def __init__(self, title="Apple 2e Disk Menu"):
self.title = title
@@ -363,6 +511,9 @@ class VoicedDiskMenu:
# Run the menu
if __name__ == "__main__":
if not ensure_apple_support_files():
sys.exit(1)
# Create the menu
menu = VoicedDiskMenu()
@@ -33,6 +33,31 @@ progress_beep() {
fi
}
internet_available() {
local targetUrl
local targetUrls=(
"https://www.gnu.org/"
"https://1.1.1.1/"
)
for targetUrl in "${targetUrls[@]}"; do
if curl -fsS --connect-timeout 5 --max-time 8 "$targetUrl" > /dev/null 2>&1; then
return 0
fi
done
return 1
}
require_internet_connection() {
if internet_available; then
return 0
fi
speak "No internet connection detected. Configure a connection from System Settings, Internet Configuration, then try again."
return 1
}
# Download with progress feedback
download_with_progress() {
local url="$1"
@@ -192,6 +217,8 @@ auto_install() {
return 0
fi
require_internet_connection || return 1
# Download and install
local temp_zip="/tmp/${directory}_download.zip"
if download_with_progress "$url" "$temp_zip" "$name"; then
@@ -233,6 +260,8 @@ auto_install() {
return 0
fi
require_internet_connection || return 1
# Install package
if install_system_package "$package" "$name"; then
speak "Installation complete"