874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
171 lines
6.8 KiB
Python
171 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Launch the vanilla 26.3 client offline (no Microsoft account, no auth
|
|
servers: --offlineDeveloperMode) and optionally join a server straight away
|
|
(--quickPlayMultiplayer). Meant to run on a virtual screen:
|
|
|
|
vscreen start python3 tools/vanilla_client.py --name Guest --join localhost:25565
|
|
|
|
On Linux (x86_64) and on macOS (Apple silicon, from the logged-in screen session), from the files
|
|
tools/lab.py prepared on that machine. On macOS vsync is off: with it on, a window nobody can see (a
|
|
locked session, or covered) blocks in SDL_GL_SwapWindow and the client stops before it joins.
|
|
|
|
Sound goes to OpenAL's null backend. The game directory gets an options.txt
|
|
that skips the first-run screens, keeps the game running unfocused and
|
|
renders few chunks, so a harness can drive it with vscreen keys.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from . import lab
|
|
except ImportError:
|
|
import lab
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
GAME = lab.GAME
|
|
MAC = sys.platform == "darwin"
|
|
# The natives of this machine's platform, and the shared libraries in them.
|
|
NATIVES, LIBRARY_SUFFIX = ("natives-macos-arm64.jar", ".dylib") if MAC else ("natives-linux.jar", ".so")
|
|
|
|
OPTIONS = {
|
|
"onboardAccessibility": "false",
|
|
"skipMultiplayerWarning": "true",
|
|
"joinedFirstServer": "true",
|
|
"tutorialStep": "none",
|
|
"pauseOnLostFocus": "false",
|
|
"renderDistance": "6",
|
|
"simulationDistance": "6",
|
|
"maxFps": "30",
|
|
"narrator": "0",
|
|
"soundCategory_master": "0.0",
|
|
"realmsNotifications": "false",
|
|
"hideServerAddress": "false",
|
|
"autoJump": "false",
|
|
"chatLinksPrompt": "false",
|
|
"onlyShowSecureChat": "false",
|
|
"rawMouseInput": "false",
|
|
}
|
|
if sys.platform == "darwin":
|
|
OPTIONS["enableVsync"] = "false"
|
|
|
|
|
|
def host_library(path):
|
|
name = Path(path).name
|
|
if "natives-" in name:
|
|
return name.endswith(NATIVES)
|
|
if MAC:
|
|
return "netty-transport-native" not in name or "linux" not in name
|
|
if "netty-transport-native" in name and ("osx" in name or "aarch_64" in name):
|
|
return False
|
|
return "java-objc-bridge" not in name
|
|
|
|
|
|
def classpath():
|
|
prepared = json.loads((GAME / "prepared.json").read_text())
|
|
jars = [ROOT / p for p in prepared["libraries"]["client"] if host_library(p)]
|
|
return [GAME / "jars/client.jar", *jars]
|
|
|
|
|
|
def extract_natives(jars, target):
|
|
target.mkdir(parents=True, exist_ok=True)
|
|
for jar in jars:
|
|
if not jar.name.endswith(NATIVES):
|
|
continue
|
|
with zipfile.ZipFile(jar) as archive:
|
|
for name in archive.namelist():
|
|
if name.endswith(LIBRARY_SUFFIX) and (MAC or ("arm" not in name and "aarch" not in name)):
|
|
out = target / Path(name).name
|
|
if not out.exists():
|
|
out.write_bytes(archive.read(name))
|
|
|
|
|
|
def write_options(game_dir, extra):
|
|
path = game_dir / "options.txt"
|
|
current = {}
|
|
if path.exists():
|
|
for line in path.read_text().splitlines():
|
|
if ":" in line:
|
|
k, v = line.split(":", 1)
|
|
current[k] = v
|
|
current.update(OPTIONS)
|
|
current.update(extra)
|
|
path.write_text("".join(f"{k}:{v}\n" for k, v in current.items()))
|
|
|
|
|
|
def environment():
|
|
env = dict(os.environ)
|
|
libs = [p for p in (os.environ.get("NIX_LD_LIBRARY_PATH", ""), "/run/opengl-driver/lib",
|
|
"/run/current-system/sw/share/nix-ld/lib") if p]
|
|
env["LD_LIBRARY_PATH"] = ":".join(libs + [env.get("LD_LIBRARY_PATH", "")]).strip(":")
|
|
env["ALSOFT_DRIVERS"] = "null"
|
|
return env
|
|
|
|
|
|
def command(args):
|
|
jars = classpath()
|
|
natives = args.game_dir / "natives"
|
|
extract_natives(jars, natives)
|
|
assets = args.assets or (args.game_dir / "assets")
|
|
(assets / "indexes").mkdir(parents=True, exist_ok=True)
|
|
cmd = ["java", f"-Xmx{args.heap}", *(["-XstartOnFirstThread"] if MAC else []), "-XX:StackShadowPages=32",
|
|
"--enable-native-access=ALL-UNNAMED",
|
|
"--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED",
|
|
f"-Djava.library.path={natives}", f"-Dorg.lwjgl.system.SharedLibraryExtractPath={natives}",
|
|
f"-Dio.netty.native.workdir={natives}",
|
|
"-cp", os.pathsep.join(map(str, jars)), "net.minecraft.client.main.Main",
|
|
"--username", args.name, "--uuid", lab.offline_uuid(args.name).hex,
|
|
"--accessToken", "0", "--offlineDeveloperMode",
|
|
"--version", lab.VERSION, "--gameDir", str(args.game_dir),
|
|
"--assetsDir", str(assets), "--assetIndex", json.loads((GAME / "manifest.json").read_text())["assetIndex"]["id"],
|
|
"--width", str(args.width), "--height", str(args.height)]
|
|
if args.join:
|
|
cmd += ["--quickPlayMultiplayer", args.join]
|
|
if args.world:
|
|
cmd += ["--quickPlaySingleplayer", args.world]
|
|
if args.lan_port is not None:
|
|
# The vanilla client as reference LAN host (OfflineLanHost).
|
|
cp = cmd.index("-cp") + 1
|
|
cmd[cp] = os.pathsep.join([str(args.classes), cmd[cp]])
|
|
main = cmd.index("net.minecraft.client.main.Main")
|
|
cmd[main:main + 1] = [f"-Dhorizon.lanPort={args.lan_port}", "dev.horizonlab.parity.OfflineLanHost"]
|
|
if args.backend:
|
|
cmd += ["--graphicsBackend", args.backend]
|
|
return cmd
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--name", default="Guest")
|
|
parser.add_argument("--join", help="HOST:PORT to join on start")
|
|
parser.add_argument("--world", help="open this saves/ folder in singleplayer on start")
|
|
parser.add_argument("--game-dir", type=Path, default=ROOT / ".local/vanilla-client")
|
|
parser.add_argument("--assets", type=Path)
|
|
parser.add_argument("--heap", default="3G")
|
|
parser.add_argument("--width", type=int, default=1280)
|
|
parser.add_argument("--height", type=int, default=800)
|
|
parser.add_argument("--lan-port", type=int,
|
|
help="open the --world to LAN on this port without authentication (needs --classes)")
|
|
parser.add_argument("--classes", type=Path, default=ROOT / ".local/mp/oracle",
|
|
help="compiled OfflineLanHost (mods/horizon/src/test/java)")
|
|
parser.add_argument("--backend", help="graphics backend (opengl or vulkan)")
|
|
parser.add_argument("--option", action="append", default=[], help="KEY:VALUE for options.txt")
|
|
parser.add_argument("--print", action="store_true", help="print the command and exit")
|
|
args = parser.parse_args()
|
|
args.game_dir = args.game_dir.resolve()
|
|
args.game_dir.mkdir(parents=True, exist_ok=True)
|
|
write_options(args.game_dir, dict(o.split(":", 1) for o in args.option))
|
|
cmd = command(args)
|
|
if args.print:
|
|
print(" ".join(cmd))
|
|
return
|
|
sys.exit(subprocess.run(cmd, cwd=args.game_dir, env=environment()).returncode)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|