164 lines
5.9 KiB
Python
164 lines
5.9 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import tomllib
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from .beta_release_lib import (
|
|
create_zip,
|
|
create_tar_gz,
|
|
copy_classified_tree,
|
|
load_path_rules,
|
|
load_side_manifest,
|
|
sanitize_client_stage,
|
|
validate_complete_mapping,
|
|
validate_client_stage,
|
|
validate_server_stage,
|
|
write_keybind_options,
|
|
)
|
|
except ImportError:
|
|
from beta_release_lib import (
|
|
create_zip,
|
|
create_tar_gz,
|
|
copy_classified_tree,
|
|
load_path_rules,
|
|
load_side_manifest,
|
|
sanitize_client_stage,
|
|
validate_complete_mapping,
|
|
validate_client_stage,
|
|
validate_server_stage,
|
|
write_keybind_options,
|
|
)
|
|
|
|
|
|
def version_directory(output_root: Path, version: str) -> Path:
|
|
resolved_root = output_root.resolve()
|
|
candidate = (resolved_root / version).resolve()
|
|
if candidate.parent != resolved_root:
|
|
raise ValueError(f"invalid release version: {version}")
|
|
return candidate
|
|
|
|
|
|
def source_mod_names(source: Path) -> set[str]:
|
|
return {path.name for path in (source / "minecraft/mods").glob("*.jar")}
|
|
|
|
|
|
def forge_request(url: str) -> urllib.request.Request:
|
|
return urllib.request.Request(
|
|
url,
|
|
headers={"User-Agent": "EscapeFromFuncraftBetaBuilder/1.0"},
|
|
)
|
|
|
|
|
|
def forge_install_env(compatibility_library_path: Path | None) -> dict[str, str] | None:
|
|
if compatibility_library_path is None:
|
|
return None
|
|
env = os.environ.copy()
|
|
existing = env.get("LD_LIBRARY_PATH")
|
|
env["LD_LIBRARY_PATH"] = (
|
|
f"{compatibility_library_path}:{existing}" if existing else str(compatibility_library_path)
|
|
)
|
|
return env
|
|
|
|
|
|
def download_forge_installer(
|
|
server_stage: Path, compatibility_library_path: Path | None = None
|
|
) -> None:
|
|
version = "1.20.1-47.4.10"
|
|
base = f"https://maven.minecraftforge.net/net/minecraftforge/forge/{version}/forge-{version}-installer.jar"
|
|
installer = server_stage / f"forge-{version}-installer.jar"
|
|
expected_sha1 = (
|
|
urllib.request.urlopen(forge_request(base + ".sha1"), timeout=30)
|
|
.read()
|
|
.decode("utf-8")
|
|
.strip()
|
|
.split()[0]
|
|
)
|
|
with urllib.request.urlopen(forge_request(base), timeout=60) as response:
|
|
installer.write_bytes(response.read())
|
|
import hashlib
|
|
|
|
actual_sha1 = hashlib.sha1(installer.read_bytes()).hexdigest()
|
|
if actual_sha1 != expected_sha1:
|
|
raise ValueError(f"Forge installer checksum mismatch: {actual_sha1} != {expected_sha1}")
|
|
subprocess.run(
|
|
["java", "-jar", installer.name, "--installServer", "."],
|
|
cwd=server_stage,
|
|
check=True,
|
|
env=forge_install_env(compatibility_library_path),
|
|
)
|
|
installer.unlink()
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--source", required=True, type=Path)
|
|
parser.add_argument("--client-output", required=True, type=Path)
|
|
parser.add_argument("--server-output", required=True, type=Path)
|
|
parser.add_argument("--version", required=True)
|
|
parser.add_argument("--manifest", type=Path, default=Path("packaging/mod-classification.toml"))
|
|
parser.add_argument("--path-manifest", type=Path, default=Path("packaging/path-classification.toml"))
|
|
parser.add_argument("--release-config", type=Path, default=Path("packaging/beta-release.toml"))
|
|
parser.add_argument(
|
|
"--forge-installer-lib-path",
|
|
type=Path,
|
|
help="directory containing a zlib implementation compatible with Forge's processors",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
source = args.source.resolve()
|
|
client_version_root = version_directory(args.client_output, args.version)
|
|
server_version_root = version_directory(args.server_output, args.version)
|
|
for root in (client_version_root, server_version_root):
|
|
if root.exists():
|
|
shutil.rmtree(root)
|
|
|
|
client_stage = client_version_root / "client-stage"
|
|
server_stage = server_version_root / "server-stage"
|
|
mod_map = load_side_manifest(args.manifest)
|
|
validate_complete_mapping(source_mod_names(source), mod_map)
|
|
path_rules = load_path_rules(args.path_manifest)
|
|
release = tomllib.loads(args.release_config.read_text(encoding="utf-8"))
|
|
world_source = source / release["source"]["world"]
|
|
world_name = world_source.name
|
|
copy_classified_tree(source, client_stage, "client", mod_map, path_rules)
|
|
copy_classified_tree(source, server_stage, "server", mod_map, path_rules)
|
|
write_keybind_options(source / "minecraft/options.txt", client_stage / "minecraft/options.txt")
|
|
sanitize_client_stage(client_stage)
|
|
validate_client_stage(client_stage, mod_map)
|
|
client_archive = client_version_root / f"escape-from-funcraft-client-{args.version}.zip"
|
|
digest = create_zip(client_stage, client_archive)
|
|
(client_version_root / "SHA256SUMS").write_text(
|
|
f"{digest} {client_archive.name}\n", encoding="utf-8"
|
|
)
|
|
shutil.copytree(world_source, server_stage / world_name)
|
|
(server_stage / "eula.txt").write_text("eula=false\n", encoding="utf-8")
|
|
(server_stage / "server.properties").write_text(
|
|
"\n".join(
|
|
(
|
|
f"level-name={world_name}",
|
|
"online-mode=true",
|
|
"enable-command-block=true",
|
|
"spawn-protection=0",
|
|
"",
|
|
)
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
validate_server_stage(server_stage, mod_map, world_name)
|
|
download_forge_installer(server_stage, args.forge_installer_lib_path)
|
|
server_archive = server_version_root / f"escape-from-funcraft-server-{args.version}.tar.gz"
|
|
server_digest = create_tar_gz(server_stage, server_archive)
|
|
(server_version_root / "SHA256SUMS").write_text(
|
|
f"{server_digest} {server_archive.name}\n", encoding="utf-8"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|