tools: one copy of the helpers harnesses share, in lab.py

free_port (7 copies), offline_uuid (4), varint and parse_varint (3 and 2),
dbits (6), fbits (4) and settled (2) move into lab.py; every caller uses
lab's. The other server harnesses keep importing free_port from
test_dedicated_server_parity, which now takes lab's. Imports the moved
code left unused go too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ludovic Marie
2026-10-23 14:46:56 +02:00
parent 2439475716
commit 3960c09159
20 changed files with 189 additions and 317 deletions
+5 -3
View File
@@ -23,6 +23,8 @@ import threading
import time
from pathlib import Path
import lab
BX, BZ, SIZE = 144, 144, 96 # south-west corner and size of the platforms (inside the spawn chunks)
LAYERS = list(range(100, 245, 16)) # platform y levels: one chunk section each, the last one reaches y = 255
UUID_HI = 0x6000
@@ -1743,7 +1745,7 @@ LAST = {}
def populate(server, Bot, port, protocol, name, problems):
"""Build the whole 1.12.2 scene, save it for good and return the (still joined) bot."""
harness = sys.modules[Bot.__module__]
Player = make_player(Bot, harness.varint, harness.parse_varint, harness.mcstring)
Player = make_player(Bot, lab.varint, lab.parse_varint, harness.mcstring)
set_protocol(protocol)
say = lambda cmds, **kw: batch(server, cmds, problems, **kw)
if ERA < 12:
@@ -1874,7 +1876,7 @@ def build_flat(h, force=False):
return []
work = h.LOCAL / 'gen' / (version + '-flat')
shutil.rmtree(work, ignore_errors=True)
port = h.free_port()
port = lab.free_port()
class FlatServer(h.Server):
def __init__(self, directory): # as Server.__init__, with a flat world
@@ -1900,7 +1902,7 @@ def build_flat(h, force=False):
# A flat world's spawn is found far away; make it (64, 64), and let a bot visit the scene's area so that its chunks
# are loaded (they stay loaded, being within the spawn radius).
batch(server, ['setworldspawn 64 4 64'], problems)
Player = make_player(h.Bot, h.varint, h.parse_varint, h.mcstring)
Player = make_player(h.Bot, lab.varint, lab.parse_varint, h.mcstring)
visitor = Player(port, protocol, h.BOT)
time.sleep(2)
for x, z in ((24, 24), (24, 72), (24, 104), (72, 24), (72, 72), (72, 104), (104, 24), (104, 72), (104, 104), (72, 123), (-8, 63), (32, 103)):
+16 -51
View File
@@ -23,7 +23,6 @@ import sys
import threading
import time
import urllib.request
import uuid
import zipfile
import zlib
from pathlib import Path
@@ -31,6 +30,7 @@ from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import datafix_scene as scene # noqa: E402
import datafix_scene_12 as scene12 # noqa: E402
import lab # noqa: E402
ROOT = Path(__file__).resolve().parent.parent
LOCAL = ROOT / '.local' / 'datafix'
@@ -159,12 +159,6 @@ def install_runtime(pin, home):
target.symlink_to(to)
def free_port():
with socket.socket() as s:
s.bind(('127.0.0.1', 0))
return s.getsockname()[1]
class Server:
def __init__(self, directory, jar, port, packs=(), java='java', props_override=None):
directory.mkdir(parents=True, exist_ok=True)
@@ -230,38 +224,9 @@ def failed(lines):
# ------------------------------------------------------------------ minimal bot
def varint(v):
out = b''
v &= 0xFFFFFFFF
while True:
b = v & 0x7F
v >>= 7
out += bytes([b | (0x80 if v else 0)])
if not v:
return out
def mcstring(s):
data = s.encode()
return varint(len(data)) + data
def parse_varint(data, pos):
value = shift = 0
while True:
b = data[pos]
pos += 1
value |= (b & 0x7F) << shift
shift += 7
if not b & 0x80:
return pos, value
def offline_uuid(name):
digest = bytearray(hashlib.md5(('OfflinePlayer:' + name).encode()).digest())
digest[6] = (digest[6] & 0x0F) | 0x30
digest[8] = (digest[8] & 0x3F) | 0x80
return uuid.UUID(bytes=bytes(digest))
return lab.varint(len(data)) + data
class Bot:
@@ -273,21 +238,21 @@ class Bot:
self.buffer = b''
self.protocol = protocol
self.stop = threading.Event()
self.send(0, varint(protocol) + mcstring('127.0.0.1') + struct.pack('>H', port) + varint(2))
self.send(0, lab.varint(protocol) + mcstring('127.0.0.1') + struct.pack('>H', port) + lab.varint(2))
if protocol <= 758: # 1.18.2: the name only
self.send(0, mcstring(name))
elif protocol == 759: # 1.19: no signature data
self.send(0, mcstring(name) + b'\x00')
elif protocol == 760: # 1.19.2: no signature data, optional UUID
self.send(0, mcstring(name) + b'\x00' + b'\x01' + offline_uuid(name).bytes)
self.send(0, mcstring(name) + b'\x00' + b'\x01' + lab.offline_uuid(name).bytes)
elif protocol <= 763: # 1.19.3 - 1.20.1: optional UUID
self.send(0, mcstring(name) + b'\x01' + offline_uuid(name).bytes)
self.send(0, mcstring(name) + b'\x01' + lab.offline_uuid(name).bytes)
else:
self.send(0, mcstring(name) + offline_uuid(name).bytes)
self.send(0, mcstring(name) + lab.offline_uuid(name).bytes)
while True:
pid, data = self.receive()
if pid == 3:
self.threshold = parse_varint(data, 0)[1]
self.threshold = lab.parse_varint(data, 0)[1]
elif pid == 2:
break
elif pid == 0:
@@ -312,15 +277,15 @@ class Bot:
elif pid == disconnect:
raise RuntimeError(f'disconnected during configuration: {data!r}')
elif not old and self.known_packs(data):
self.send(7, varint(0)) # claim no packs: the server sends full registries
self.send(7, lab.varint(0)) # claim no packs: the server sends full registries
@staticmethod
def known_packs(data):
try:
pos, count = parse_varint(data, 0)
pos, count = lab.parse_varint(data, 0)
if count < 1:
return False
pos, n = parse_varint(data, pos)
pos, n = lab.parse_varint(data, pos)
return data[pos:pos + n] == b'minecraft'
except IndexError:
return False
@@ -359,18 +324,18 @@ class Bot:
return value
def send(self, packet_id, payload=b''):
body = varint(packet_id) + payload
body = lab.varint(packet_id) + payload
if self.threshold >= 0:
body = varint(len(body)) + zlib.compress(body) if len(body) >= self.threshold else varint(0) + body
self.sock.sendall(varint(len(body)) + body)
body = lab.varint(len(body)) + zlib.compress(body) if len(body) >= self.threshold else lab.varint(0) + body
self.sock.sendall(lab.varint(len(body)) + body)
def receive(self):
length = self.read_varint()
body = self.read_exact(length)
if self.threshold >= 0:
pos, size = parse_varint(body, 0)
pos, size = lab.parse_varint(body, 0)
body = zlib.decompress(body[pos:]) if size else body[pos:]
pos, packet_id = parse_varint(body, 0)
pos, packet_id = lab.parse_varint(body, 0)
return packet_id, body[pos:]
@@ -428,7 +393,7 @@ def build(name, force=False):
era = scene.era(data_version)
work = LOCAL / 'gen' / name
shutil.rmtree(work, ignore_errors=True)
port = free_port()
port = lab.free_port()
server = Server(work, jar, port, scene.experiments(data_version), java(version), props)
problems = []
+64 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
"""Pinned Java Edition import/decompilation. Game files come from the user's own
copy (their official launcher install and the server jar they downloaded); all
generated files stay in .local/."""
generated files stay in .local/. Also the small helpers the harnesses share
(free ports, offline UUIDs, VarInts, float bits)."""
import argparse
from collections import Counter
@@ -12,11 +13,13 @@ import platform
from pathlib import Path, PurePosixPath
import re
import shutil
import socket
import struct
import subprocess
import sys
import tempfile
import urllib.request
import uuid
import zipfile
ROOT = Path(__file__).resolve().parents[1]
@@ -61,6 +64,66 @@ def digest(path, algorithm):
return hashlib.file_digest(stream, algorithm).hexdigest()
def free_port():
"""A free local TCP port, for a server a harness starts."""
with socket.socket() as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]
def offline_uuid(name):
"""UUIDUtil.createOfflinePlayerUUID: the UUID an offline-mode server gives a player name."""
digest = bytearray(hashlib.md5(("OfflinePlayer:" + name).encode()).digest())
digest[6] = (digest[6] & 0x0F) | 0x30
digest[8] = (digest[8] & 0x3F) | 0x80
return uuid.UUID(bytes=bytes(digest))
def varint(value):
"""FriendlyByteBuf.writeVarInt: the int's 32 bits, 7 at a time."""
out = b""
value &= 0xFFFFFFFF
while True:
b = value & 0x7F
value >>= 7
out += bytes([b | (0x80 if value else 0)])
if not value:
return out
def parse_varint(data, pos):
"""The VarInt at data[pos]: (the position after it, its unsigned value)."""
value = shift = 0
while True:
b = data[pos]
pos += 1
value |= (b & 0x7F) << shift
shift += 7
if not b & 0x80:
return pos, value
def fbits(value):
"""A float's IEEE 754 bits in hex, as the Java oracles print them."""
return struct.pack(">f", value).hex()
def dbits(value):
"""A double's IEEE 754 bits in hex, as the Java oracles print them."""
return struct.pack(">d", value).hex()
def settled(lines):
"""One player's lines of one step, in order, with the waypoint packets reduced to the last for each waypoint:
what the locator bar shows once the step is over. Arriving in a level, vanilla sends the same waypoint more
than once on the way there; the packet by packet order is test_locator_bar_parity's."""
def waypoint(line):
parts = line.split(" ")
return parts[3] if len(parts) > 3 and parts[2] in ("track", "untrack", "update") else None
last = {waypoint(l): i for i, l in enumerate(lines) if waypoint(l)}
return [l for i, l in enumerate(lines) if not waypoint(l) or last[waypoint(l)] == i]
def download(url, destination, checksum, algorithm="sha1"):
"""Never promote an unverified or partial download into the artifact cache."""
destination.parent.mkdir(parents=True, exist_ok=True)
+1 -8
View File
@@ -45,7 +45,6 @@ from pathlib import Path
import platform
import re
import shutil
import socket
import statistics
import subprocess
import sys
@@ -81,12 +80,6 @@ PROPERTIES = {
}
def free_port():
with socket.socket() as s:
s.bind(('127.0.0.1', 0))
return s.getsockname()[1]
def cpu_seconds(text):
"""ps's cumulative CPU time: [[DD-]HH:]MM:SS[.ss]."""
days, _, rest = text.rpartition('-')
@@ -282,7 +275,7 @@ def run_once(kind, bots, behaviour, args, out):
directory = out / name
shutil.rmtree(directory, ignore_errors=True)
directory.mkdir(parents=True)
port = free_port()
port = lab.free_port()
props = dict(PROPERTIES, **{'server-port': str(port), 'level-seed': str(args.seed)})
(directory / 'server.properties').write_text(''.join(f'{k}={v}\n' for k, v in props.items()))
(directory / 'eula.txt').write_text('eula=true\n')
+5 -9
View File
@@ -17,16 +17,12 @@ playing instance and the subtitle notifications. Horizon's `Ambience` and
"""
import json
import random
import struct
try:
from . import audio_harness as h
from . import audio_harness as h, lab
except ImportError:
import audio_harness as h
def dbits(v):
return struct.pack('>d', v).hex()
import lab
def settings():
@@ -54,11 +50,11 @@ class Script:
self.lines.append(' '.join(map(str, parts)))
def sounds(self, value):
mood = '-' if value['mood'] is None else '{},{},{},{}'.format(value['mood'][0], value['mood'][1], value['mood'][2], dbits(value['mood'][3]))
self.add('sounds', value['loop'] or '-', mood, *[f'{s},{dbits(c)}' for s, c in value['additions']])
mood = '-' if value['mood'] is None else '{},{},{},{}'.format(value['mood'][0], value['mood'][1], value['mood'][2], lab.dbits(value['mood'][3]))
self.add('sounds', value['loop'] or '-', mood, *[f'{s},{lab.dbits(c)}' for s, c in value['additions']])
def player(self, at=(0.5, 64.0, 0.5), eye=1.62, water=0, spectator=0, removed=0, mode=0, height=1.8, columns=()):
self.add('player', *map(dbits, at), h.bits(eye), water, spectator, removed, mode, h.bits(height),
self.add('player', *map(lab.dbits, at), h.bits(eye), water, spectator, removed, mode, h.bits(height),
*[f'{x},{y},{z},{d}' for x, y, z, d in columns])
def tick(self, n=1):
+12 -22
View File
@@ -8,11 +8,9 @@ overhangs approached from every angle with varied speeds, input vectors,
sizes, jump boost and the canAutoJump preconditions."""
import json
import math
import os
from pathlib import Path
import platform
import random
import struct
import subprocess
try:
@@ -23,14 +21,6 @@ except ImportError:
import test_native_client as client
def fbits(v):
return struct.pack('>f', v).hex()
def dbits(v):
return struct.pack('>d', v).hex()
BLOCKS = [
('stone', {}), ('stone', {}), ('stone', {}),
('stone_slab', {'type': 'bottom'}), ('stone_slab', {'type': 'top'}), ('stone_slab', {'type': 'double'}),
@@ -64,12 +54,12 @@ DIMS = [(0.6, 1.8), (0.6, 1.8), (0.6, 1.5), (0.6, 0.6), (0.3, 0.9), (1.2, 3.6),
def player(rng, x, y, z, yaw):
w, h = rng.choice(DIMS)
return dict(pos=[dbits(x), dbits(y), dbits(z)], ground=rng.random() < 0.9, shift=rng.random() < 0.08,
yr=fbits(yaw), xr=fbits(rng.choice([0.0, 30.0, -45.0, 90.0, rng.uniform(-90, 90)])),
speed=fbits(rng.choice([0.1, 0.13000001, 0.03, 0.0, 0.2, rng.uniform(0, 0.3)])),
move=[fbits(v) for v in rng.choice([(0.0, 1.0), (0.0, 1.0), (1.0, 0.0), (-1.0, 0.0), (0.70710677, 0.70710677),
return dict(pos=[lab.dbits(x), lab.dbits(y), lab.dbits(z)], ground=rng.random() < 0.9, shift=rng.random() < 0.08,
yr=lab.fbits(yaw), xr=lab.fbits(rng.choice([0.0, 30.0, -45.0, 90.0, rng.uniform(-90, 90)])),
speed=lab.fbits(rng.choice([0.1, 0.13000001, 0.03, 0.0, 0.2, rng.uniform(0, 0.3)])),
move=[lab.fbits(v) for v in rng.choice([(0.0, 1.0), (0.0, 1.0), (1.0, 0.0), (-1.0, 0.0), (0.70710677, 0.70710677),
(0.0, -1.0), (0.0, 0.0), (0.3, 0.3)])],
width=fbits(w), height=fbits(h), boost=rng.choice([None] * 6 + [0, 1, 4, 255]),
width=lab.fbits(w), height=lab.fbits(h), boost=rng.choice([None] * 6 + [0, 1, 4, 255]),
enabled=rng.random() < 0.93, time=rng.choice([0] * 12 + [1, -3, 2]))
@@ -86,18 +76,18 @@ def cases(rng):
x = ox + 0.5 - dx * 1.1
z = oz + 0.5 - dz * 1.1
p = player(rng, x, float(oy), z, yaw)
p.update(ground=True, shift=False, enabled=True, time=0, boost=None, move=[fbits(0.0), fbits(1.0)],
width=fbits(0.6), height=fbits(1.8), xr=fbits(0.0))
yield dict(blocks=blocks, player=p, xa=fbits(dx * step), za=fbits(dz * step))
p.update(ground=True, shift=False, enabled=True, time=0, boost=None, move=[lab.fbits(0.0), lab.fbits(1.0)],
width=lab.fbits(0.6), height=lab.fbits(1.8), xr=lab.fbits(0.0))
yield dict(blocks=blocks, player=p, xa=lab.fbits(dx * step), za=lab.fbits(dz * step))
# Ceilings above the player and above the step.
for ceiling in range(2, 6):
for step_height in (0, 1):
blocks = [[ox, oy + step_height, oz, 'minecraft:stone', {}], [ox - 1, oy + ceiling, oz, 'minecraft:stone', {}],
[ox, oy + ceiling + step_height, oz, 'minecraft:stone_slab', {'type': 'top'}]]
p = player(rng, ox - 0.5, float(oy), oz + 0.5, -90.0)
p.update(ground=True, shift=False, enabled=True, time=0, boost=None, move=[fbits(0.0), fbits(1.0)],
width=fbits(0.6), height=fbits(1.8))
yield dict(blocks=blocks, player=p, xa=fbits(0.2), za=fbits(0.0))
p.update(ground=True, shift=False, enabled=True, time=0, boost=None, move=[lab.fbits(0.0), lab.fbits(1.0)],
width=lab.fbits(0.6), height=lab.fbits(1.8))
yield dict(blocks=blocks, player=p, xa=lab.fbits(0.2), za=lab.fbits(0.0))
for _ in range(9000):
blocks = placed(rng, rng.choice([1, 2, 4, 8, 16, 30]))
x = ox + rng.uniform(-2, 3)
@@ -108,7 +98,7 @@ def cases(rng):
move = rng.choice([0.0, 0.0, 0.01, 0.1, 0.215, 0.3, rng.uniform(0, 0.4)])
angle = rng.uniform(0, 6.283)
yield dict(blocks=blocks, player=p, xa=fbits(math.cos(angle) * move), za=fbits(math.sin(angle) * move))
yield dict(blocks=blocks, player=p, xa=lab.fbits(math.cos(angle) * move), za=lab.fbits(math.sin(angle) * move))
def main():
+35 -43
View File
@@ -20,14 +20,6 @@ except ImportError:
import lab, oracle_harness as harness
def fbits(v):
return struct.pack('>f', v).hex()
def dbits(v):
return struct.pack('>d', v).hex()
def f32(v):
return struct.unpack('>f', struct.pack('>f', v))[0]
@@ -39,8 +31,8 @@ TICKS = [0, 1, 5, 10, 19, 20, 21, 72000, -5, 3]
def fov_fields(rng):
use = rng.choice([None, None, ['bow', rng.choice(TICKS)], ['spyglass', rng.choice(TICKS)], ['other', rng.choice(TICKS)]])
return dict(flying=rng.random() < 0.3, ws=fbits(rng.choice(WALK + [rng.uniform(-0.3, 0.3)])),
speed=dbits(rng.choice(SPEED + [rng.uniform(0, 2)])), use=use)
return dict(flying=rng.random() < 0.3, ws=lab.fbits(rng.choice(WALK + [rng.uniform(-0.3, 0.3)])),
speed=lab.dbits(rng.choice(SPEED + [rng.uniform(0, 2)])), use=use)
def fov_cases(rng):
@@ -50,9 +42,9 @@ def fov_cases(rng):
for use in [None, ['bow', 0], ['bow', 7], ['bow', 20], ['bow', 40], ['spyglass', 2], ['other', 5]]:
for fp in (False, True):
for scale in (0.0, 0.5, 1.0):
yield dict(t='fov', flying=flying, ws=fbits(ws), speed=dbits(speed), use=use, fp=fp, scale=fbits(scale))
yield dict(t='fov', flying=flying, ws=lab.fbits(ws), speed=lab.dbits(speed), use=use, fp=fp, scale=lab.fbits(scale))
for _ in range(3000):
yield dict(t='fov', **fov_fields(rng), fp=rng.random() < 0.6, scale=fbits(rng.choice([0.0, 1.0, rng.random()])))
yield dict(t='fov', **fov_fields(rng), fp=rng.random() < 0.6, scale=lab.fbits(rng.choice([0.0, 1.0, rng.random()])))
FULL = [[0, 0, 0, 1, 1, 1]]
@@ -65,7 +57,7 @@ def world(rng, center):
cx, cy, cz = center
for _ in range(rng.choice([0, 3, 10, 40, 120])):
p = (cx + rng.randint(-6, 6), cy + rng.randint(-3, 5), cz + rng.randint(-6, 6))
cells[f'{p[0]} {p[1]} {p[2]}'] = [[dbits(v) for v in box] for box in rng.choice(SHAPES)]
cells[f'{p[0]} {p[1]} {p[2]}'] = [[lab.dbits(v) for v in box] for box in rng.choice(SHAPES)]
return cells
@@ -81,22 +73,22 @@ def entity_state(rng, center):
prev = [p - rng.choice([0, 0, 0.1, 0.3, 1.0, rng.uniform(-1, 1)]) for p in pos]
kind = 'player' if rng.random() < 0.85 else 'marker'
xr, yr = angle(rng), angle(rng)
s = dict(kind=kind, pos=[dbits(v) for v in pos], prev=[dbits(v) for v in prev], xr=fbits(xr), yr=fbits(yr),
xro=fbits(rng.choice([xr, xr - 5, angle(rng)])), yro=fbits(rng.choice([yr, yr + 10, angle(rng)])),
eye=fbits(rng.choice([1.62, 1.27, 0.4, 0.2, 0.81, 3.24, rng.uniform(0, 4)])))
s = dict(kind=kind, pos=[lab.dbits(v) for v in pos], prev=[lab.dbits(v) for v in prev], xr=lab.fbits(xr), yr=lab.fbits(yr),
xro=lab.fbits(rng.choice([xr, xr - 5, angle(rng)])), yro=lab.fbits(rng.choice([yr, yr + 10, angle(rng)])),
eye=lab.fbits(rng.choice([1.62, 1.27, 0.4, 0.2, 0.81, 3.24, rng.uniform(0, 4)])))
s.update(fov_fields(rng))
s.update(scale_attr=dbits(rng.choice([1.0, 1.0, 0.5, 2.0, 0.0625, 16.0, rng.uniform(0.0625, 3)])),
dist=dbits(rng.choice([4.0, 4.0, 0.0, 1.0, 10.0, 32.0, rng.uniform(0, 32)])),
s.update(scale_attr=lab.dbits(rng.choice([1.0, 1.0, 0.5, 2.0, 0.0625, 16.0, rng.uniform(0.0625, 3)])),
dist=lab.dbits(rng.choice([4.0, 4.0, 0.0, 1.0, 10.0, 32.0, rng.uniform(0, 32)])),
sleeping=rng.choice([None] * 6 + [[None], [0], [1], [2], [3]]),
dead=rng.choice([None] * 6 + [0, 1, 5, 19, 20, 25]),
mount=rng.choice([None] * 4 + [[dbits(rng.choice([1.0, 2.0, 0.5])), dbits(rng.choice([4.0, 8.0, 1.0]))]]))
mount=rng.choice([None] * 4 + [[lab.dbits(rng.choice([1.0, 2.0, 0.5])), lab.dbits(rng.choice([4.0, 8.0, 1.0]))]]))
return s
def window(rng):
"""Window size and Camera.depthFar for the world and hand projections."""
w, h = rng.choice([(1600, 1000), (854, 480), (1920, 1080), (800, 1200), (1, 1), (rng.randint(1, 4000), rng.randint(1, 3000))])
return [fbits(w), fbits(h), fbits(rng.choice([768.0, 128.0, 2048.0, 4096.0, rng.uniform(32, 4096)]))]
return [lab.fbits(w), lab.fbits(h), lab.fbits(rng.choice([768.0, 128.0, 2048.0, 4096.0, rng.uniform(32, 4096)]))]
def camera_cases(rng):
@@ -113,11 +105,11 @@ def camera_cases(rng):
ops.append(['tick'])
elif kind == 'frame':
pt = rng.choice([0.0, 1.0, 0.5, 0.25, 0.999, rng.random()])
ops.append(['frame', fbits(pt), rng.choice([0, 0, 0, 1, 2, 3]), rng.random() < 0.05] + window(rng))
ops.append(['frame', lab.fbits(pt), rng.choice([0, 0, 0, 1, 2, 3]), rng.random() < 0.05] + window(rng))
else:
ops.append(['type', rng.randrange(3)])
yield dict(t='camera', fov=rng.choice([70, 30, 110, 90, rng.randint(30, 110)]),
effect=dbits(rng.choice([1.0, 0.0, 0.5, rng.random()])), type=rng.choice([0, 1, 1, 2]),
effect=lab.dbits(rng.choice([1.0, 0.0, 0.5, rng.random()])), type=rng.choice([0, 1, 1, 2]),
cells=world(rng, center), ops=ops)
# The hand's projection at FOV 30, 70 and 110 while walking, sprinting, under
# speed, drawing a bow, dying and in fluids: GameRenderer.render3dHud uses
@@ -127,13 +119,13 @@ def camera_cases(rng):
for use in (None, ['bow', 10]):
for dead in (None, 5):
for fluid in (0, 1, 2):
s = dict(kind='player', pos=[dbits(0.5), dbits(64.0), dbits(0.5)], prev=[dbits(0.5), dbits(64.0), dbits(0.5)],
xr=fbits(0.0), yr=fbits(0.0), xro=fbits(0.0), yro=fbits(0.0), eye=fbits(1.62), flying=False,
ws=fbits(0.1), speed=dbits(speed), use=use, scale_attr=dbits(1.0), dist=dbits(4.0),
s = dict(kind='player', pos=[lab.dbits(0.5), lab.dbits(64.0), lab.dbits(0.5)], prev=[lab.dbits(0.5), lab.dbits(64.0), lab.dbits(0.5)],
xr=lab.fbits(0.0), yr=lab.fbits(0.0), xro=lab.fbits(0.0), yro=lab.fbits(0.0), eye=lab.fbits(1.62), flying=False,
ws=lab.fbits(0.1), speed=lab.dbits(speed), use=use, scale_attr=lab.dbits(1.0), dist=lab.dbits(4.0),
sleeping=None, dead=dead, mount=None)
ops = [['entity', s]] + [['tick']] * 8 + [['frame', fbits(pt), fluid, False, fbits(1600.0), fbits(1000.0), fbits(768.0)]
ops = [['entity', s]] + [['tick']] * 8 + [['frame', lab.fbits(pt), fluid, False, lab.fbits(1600.0), lab.fbits(1000.0), lab.fbits(768.0)]
for pt in (0.0, 0.5, 1.0)]
yield dict(t='camera', fov=fov, effect=dbits(1.0), type=0, cells={}, ops=ops)
yield dict(t='camera', fov=fov, effect=lab.dbits(1.0), type=0, cells={}, ops=ops)
# A walled-in third-person camera stepping through every distance bound.
for dist in [0.0, 0.5, 1.0, 2.0, 4.0, 8.0, 32.0]:
for wall in range(0, 6):
@@ -141,28 +133,28 @@ def camera_cases(rng):
cells = {}
for dx in range(-6, 7):
for dy in range(62, 68):
cells[f'{dx} {dy} {-wall - 1}'] = [[dbits(v) for v in box] for box in FULL]
cells[f'{dx} {dy} {wall + 1}'] = [[dbits(v) for v in box] for box in FULL]
s = dict(kind='player', pos=[dbits(0.5), dbits(64.0), dbits(0.5)], prev=[dbits(0.5), dbits(64.0), dbits(0.5)],
xr=fbits(15.0), yr=fbits(yaw), xro=fbits(15.0), yro=fbits(yaw), eye=fbits(1.62), flying=False,
ws=fbits(0.1), speed=dbits(f32(0.1)), use=None, scale_attr=dbits(1.0), dist=dbits(dist),
cells[f'{dx} {dy} {-wall - 1}'] = [[lab.dbits(v) for v in box] for box in FULL]
cells[f'{dx} {dy} {wall + 1}'] = [[lab.dbits(v) for v in box] for box in FULL]
s = dict(kind='player', pos=[lab.dbits(0.5), lab.dbits(64.0), lab.dbits(0.5)], prev=[lab.dbits(0.5), lab.dbits(64.0), lab.dbits(0.5)],
xr=lab.fbits(15.0), yr=lab.fbits(yaw), xro=lab.fbits(15.0), yro=lab.fbits(yaw), eye=lab.fbits(1.62), flying=False,
ws=lab.fbits(0.1), speed=lab.dbits(f32(0.1)), use=None, scale_attr=lab.dbits(1.0), dist=lab.dbits(dist),
sleeping=None, dead=None, mount=None)
ops = [['entity', s]] + [['tick']] * 6 + [['frame', fbits(pt), 0, False] + window(rng) for pt in (0.0, 0.5, 1.0)]
yield dict(t='camera', fov=70, effect=dbits(1.0), type=1 + (wall % 2), cells=cells, ops=ops)
ops = [['entity', s]] + [['tick']] * 6 + [['frame', lab.fbits(pt), 0, False] + window(rng) for pt in (0.0, 0.5, 1.0)]
yield dict(t='camera', fov=70, effect=lab.dbits(1.0), type=1 + (wall % 2), cells=cells, ops=ops)
def bob_cases(rng):
for n in range(6000):
living = None
if rng.random() < 0.8:
living = dict(hurt=fbits(rng.choice([-1.0, -0.5, 0.0, 10.0, 9.5, 5.25, 1.0, rng.uniform(-2, 11)])),
duration=rng.choice([10, 10, 0, 5, 20]), dir=fbits(rng.choice([0.0, 90.0, -45.0, 180.0, rng.uniform(-360, 360)])),
death=rng.choice([None, None, None, fbits(rng.choice([0.0, 0.5, 19.5, 20.0, 25.0, rng.uniform(0, 30)]))]))
living = dict(hurt=lab.fbits(rng.choice([-1.0, -0.5, 0.0, 10.0, 9.5, 5.25, 1.0, rng.uniform(-2, 11)])),
duration=rng.choice([10, 10, 0, 5, 20]), dir=lab.fbits(rng.choice([0.0, 90.0, -45.0, 180.0, rng.uniform(-360, 360)])),
death=rng.choice([None, None, None, lab.fbits(rng.choice([0.0, 0.5, 19.5, 20.0, 25.0, rng.uniform(0, 30)]))]))
player = None
if rng.random() < 0.8:
player = [fbits(rng.choice([0.0, -0.3, -1.7, -100.25, rng.uniform(-1000, 0)])),
fbits(rng.choice([0.0, 0.1, 0.05, 0.0999, rng.uniform(0, 0.1)]))]
yield dict(t='bob', living=living, player=player, tilt=dbits(rng.choice([1.0, 0.0, 0.5, rng.random()])),
player = [lab.fbits(rng.choice([0.0, -0.3, -1.7, -100.25, rng.uniform(-1000, 0)])),
lab.fbits(rng.choice([0.0, 0.1, 0.05, 0.0999, rng.uniform(0, 0.1)]))]
yield dict(t='bob', living=living, player=player, tilt=lab.dbits(rng.choice([1.0, 0.0, 0.5, rng.random()])),
view=rng.random() < 0.8)
@@ -174,12 +166,12 @@ def avatar_cases(rng):
if kind == 'tick':
ops.append(['tick'])
elif kind == 'move':
ops.append(['move', fbits(rng.choice([0.0, 0.2158, -0.1, rng.uniform(-1, 1)])), fbits(rng.choice([0.0, 0.2806, rng.uniform(-1, 1)]))])
ops.append(['move', lab.fbits(rng.choice([0.0, 0.2158, -0.1, rng.uniform(-1, 1)])), lab.fbits(rng.choice([0.0, 0.2806, rng.uniform(-1, 1)]))])
elif kind == 'bob':
v = [rng.choice([0.0, 0.05, -0.2, rng.uniform(-0.5, 0.5)]), rng.uniform(-1, 1), rng.choice([0.0, 0.08, rng.uniform(-0.5, 0.5)])]
ops.append(['bob', rng.random() < 0.7, rng.random() < 0.1, rng.random() < 0.1, [dbits(x) for x in v]])
ops.append(['bob', rng.random() < 0.7, rng.random() < 0.1, rng.random() < 0.1, [lab.dbits(x) for x in v]])
elif kind == 'frame':
ops.append(['frame', fbits(rng.choice([0.0, 1.0, 0.5, rng.random()]))])
ops.append(['frame', lab.fbits(rng.choice([0.0, 1.0, 0.5, rng.random()]))])
else:
ops.append(['reset'])
yield dict(t='avatar', ops=ops)
+2 -6
View File
@@ -68,10 +68,12 @@ import zlib
try:
from . import lab, oracle_harness as oh, test_rust_parity as base
from .lab import free_port # the other server harnesses import it from here
except ImportError:
import lab
import oracle_harness as oh
import test_rust_parity as base
from lab import free_port
NAME = 'dedicated-server-parity'
SEED = 12345
@@ -89,12 +91,6 @@ PROPERTIES = {
}
def free_port():
with socket.socket() as s:
s.bind(('127.0.0.1', 0))
return s.getsockname()[1]
def icon_png():
"""A 64x64 RGBA PNG (server-icon.png)."""
rows = b''.join(b'\0' + b''.join(bytes((x * 4, y * 4, (x ^ y) * 4, 255)) for x in range(64)) for y in range(64))
+4 -12
View File
@@ -18,14 +18,6 @@ except ImportError:
import lab, oracle_harness as harness
def fbits(v):
return struct.pack('>f', v).hex()
def dbits(v):
return struct.pack('>d', v).hex()
def f32(v):
return struct.unpack('>f', struct.pack('>f', v))[0]
@@ -52,15 +44,15 @@ def step(hit=('miss',), ac=0, uc=0, ad=False, ud=False, busy=False, using=False,
"""rot: the player's (xRot, yRot) from this step on (LocalPlayer.applyInput's hand sway)."""
return dict(hit=list(hit), ac=ac, uc=uc, ad=ad, ud=ud, busy=busy, using=using,
uses=uses or dict(entity='pass', block='pass', item='pass'), shrink=shrink, set=set,
rot=None if rot is None else [fbits(rot[0]), fbits(rot[1])])
rot=None if rot is None else [lab.fbits(rot[0]), lab.fbits(rot[1])])
def case(steps, creative=False, main=None, off=None, blocks=None, haste=None, fatigue=None):
return dict(creative=creative, main=main, off=off, haste=haste, fatigue=fatigue,
blocks=[[*p, b] for p, b in (blocks or {}).items()], steps=steps,
rust=dict(blocks={k: [fbits(v[0]), v[1]] for k, v in BLOCKS.items()},
tools={k: [fbits(v[0]), v[1]] for k, v in TOOLS.items()},
attack_speed={k: dbits(v) for k, v in SPEEDS.items()}))
rust=dict(blocks={k: [lab.fbits(v[0]), v[1]] for k, v in BLOCKS.items()},
tools={k: [lab.fbits(v[0]), v[1]] for k, v in TOOLS.items()},
attack_speed={k: lab.dbits(v) for k, v in SPEEDS.items()}))
def block_hit(pos, face=1):
+1 -7
View File
@@ -72,12 +72,6 @@ def wait_for(endpoint, line, predicate, timeout=60, what=None):
raise AssertionError(f'timed out waiting for {what or line}: {last}')
def free_port():
with socket.socket() as s:
s.bind(('127.0.0.1', 0))
return s.getsockname()[1]
def health(relay_url):
with urllib.request.urlopen(f'http://{relay_url}/health', timeout=5) as r:
return json.loads(r.read())
@@ -204,7 +198,7 @@ def leave(host, guest, name):
def start_relay(args, work):
"""A relay on a free local port, as anyone would run their own."""
port = free_port()
port = lab.free_port()
url = f'127.0.0.1:{port}'
log = (work / 'relay.log').open('w+')
process = subprocess.Popen([str(args.relay.resolve())], env=dict(os.environ, PORT=str(port)),
+11 -21
View File
@@ -6,9 +6,7 @@ and absolute motion, sensitivity curve, cinematic smoothing, inversion,
scroll accumulation and discrete scrolling), Entity.turn and ToggleKeyMapping,
all driven with identical synthetic input and time sequences."""
import json
import math
import random
import struct
try:
from . import lab, oracle_harness as harness
@@ -16,14 +14,6 @@ except ImportError:
import lab, oracle_harness as harness
def fbits(v):
return struct.pack('>f', v).hex()
def dbits(v):
return struct.pack('>d', v).hex()
def timer_cases(rng):
for n in range(700):
tps = rng.choice([20.0, 20.0, 10.0, 60.0, 1.0, 144.0])
@@ -38,12 +28,12 @@ def timer_cases(rng):
now -= rng.randrange(0, 30)
if kind == 'adv':
mspt = rng.choice([None, None, None, 50.0, 25.0, 100.0, 500.0, 1000.0, 0.5, 5.0, 0.1])
ops.append(['adv', now, None if mspt is None else fbits(mspt)])
ops.append(['adv', now, None if mspt is None else lab.fbits(mspt)])
elif kind == 'real':
ops.append(['real', now])
else:
ops.append([kind, rng.random() < 0.5])
yield dict(t='timer', tps=fbits(tps), start=start, ops=ops)
yield dict(t='timer', tps=lab.fbits(tps), start=start, ops=ops)
def rate_cases(rng):
@@ -55,7 +45,7 @@ def rate_cases(rng):
kind = rng.choice(['rate', 'freeze', 'step', 'stopstep', 'sprint', 'check', 'check', 'check', 'end', 'stopsprint',
'tick', 'tick', 'tick'])
if kind == 'rate':
ops.append(['rate', fbits(rng.choice(rates))])
ops.append(['rate', lab.fbits(rng.choice(rates))])
elif kind == 'freeze':
ops.append(['freeze', rng.random() < 0.5])
elif kind == 'step':
@@ -78,7 +68,7 @@ def scale_cases(rng):
for uni in (False, True):
sw, sh = rng.choice([(w, h), (max(w // 2, 1), max(h // 2, 1)), (w + 1, h + 3)])
yield dict(t='scale', w=w, h=h, max=maxs, uni=uni, sw=sw, sh=sh,
x=dbits(rng.uniform(0, w)), y=dbits(rng.uniform(0, h)))
x=lab.dbits(rng.uniform(0, w)), y=lab.dbits(rng.uniform(0, h)))
def limit_cases(rng):
@@ -103,13 +93,13 @@ def mouse_cases(rng):
if kind == 'move':
x, y = rng.uniform(0, 1920), rng.uniform(0, 1080)
big = rng.choice([1, 1, 1, 50, 5000])
ops.append(['move', dbits(x), dbits(y), dbits(rng.uniform(-30, 30) * big), dbits(rng.uniform(-30, 30) * big)])
ops.append(['move', lab.dbits(x), lab.dbits(y), lab.dbits(rng.uniform(-30, 30) * big), lab.dbits(rng.uniform(-30, 30) * big)])
elif kind == 'frame':
time += rng.choice([0.0, 0.001, 0.0069, 0.016, 0.0167, 0.05, 0.5, 2.0])
ops.append(['frame', dbits(time)])
ops.append(['frame', lab.dbits(time)])
else:
ops.append(['ignore'])
yield dict(t='mouse', sens=dbits(sens), smooth=rng.random() < 0.4, scoping=rng.random() < 0.3,
yield dict(t='mouse', sens=lab.dbits(sens), smooth=rng.random() < 0.4, scoping=rng.random() < 0.3,
ix=rng.random() < 0.3, iy=rng.random() < 0.3, grabbed=rng.random() < 0.85, active=rng.random() < 0.9, ops=ops)
@@ -119,14 +109,14 @@ def scroll_cases(rng):
for _ in range(rng.randrange(1, 40)):
x = rng.choice([0.0, 0.0, 0.0, 1.0, -1.0, 0.25, -0.25, 0.4, 2.0, 5.5, -7.0, rng.uniform(-3, 3)])
y = rng.choice([0.0, 1.0, -1.0, 1.0, -1.0, 0.1, -0.3, 0.5, -0.5, 2.0, -3.0, 120.0, rng.uniform(-3, 3)])
ops.append([dbits(x), dbits(y)])
case = dict(t='scroll', discrete=rng.random() < 0.4, sens=dbits(rng.choice([1.0, 0.01, 10.0, 0.5, 2.0, 3.1622776601683795])),
ops.append([lab.dbits(x), lab.dbits(y)])
case = dict(t='scroll', discrete=rng.random() < 0.4, sens=lab.dbits(rng.choice([1.0, 0.01, 10.0, 0.5, 2.0, 3.1622776601683795])),
slot=rng.randrange(9), ops=ops)
yield case
yield dict(case, spectator=True)
# Bounds, recovery from each bound, fractional accumulation, and horizontal-only input.
yield dict(t='scroll', discrete=False, sens=dbits(1.0), slot=4, spectator=True,
ops=[[dbits(x), dbits(y)] for x, y in
yield dict(t='scroll', discrete=False, sens=lab.dbits(1.0), slot=4, spectator=True,
ops=[[lab.dbits(x), lab.dbits(y)] for x, y in
[(0, 100), (0, -1), (0, -100), (0, 1), (0, .4), (0, .7),
(0, -.4), (0, -.7), (5, 0), (-5, 0)]])
+1 -7
View File
@@ -109,15 +109,9 @@ BENIGN = re.compile(r'(Unpacking |OFFLINE/INSECURE|online-mode|authenticate user
# ---------------------------------------------------------------- vanilla server
def free_port():
with socket.socket() as s:
s.bind(('127.0.0.1', 0))
return s.getsockname()[1]
class Vanilla:
def __init__(self, directory, port=None, world='world', extra=None):
port = port or free_port()
port = port or lab.free_port()
directory.mkdir(parents=True, exist_ok=True)
(directory / 'eula.txt').write_text('eula=true\n')
props = {
+5 -15
View File
@@ -41,12 +41,12 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
try:
from . import lab, oracle_harness as oh, test_rust_parity as base
from .test_dedicated_server_parity import Server, free_port
from .test_dedicated_server_parity import Server
except ImportError:
import lab
import oracle_harness as oh
import test_rust_parity as base
from test_dedicated_server_parity import Server, free_port
from test_dedicated_server_parity import Server
NAME = 'secure-chat-parity'
SCENARIOS = {
@@ -153,20 +153,10 @@ def dashed(u):
return f'{u[:8]}-{u[8:12]}-{u[12:16]}-{u[16:20]}-{u[20:]}'
def varint(n):
out = b''
while True:
b = n & 0x7F
n >>= 7
out += bytes([b | (0x80 if n else 0)])
if not n:
return out
def status(port):
"""The status response's JSON (ServerStatus)."""
def packet(body):
return varint(len(body)) + body
return lab.varint(len(body)) + body
def read_varint(s):
value = 0
@@ -177,7 +167,7 @@ def status(port):
return value
with socket.create_connection(('127.0.0.1', port), timeout=10) as s:
host = b'127.0.0.1'
s.sendall(packet(b'\x00' + varint(777) + varint(len(host)) + host + port.to_bytes(2, 'big') + b'\x01'))
s.sendall(packet(b'\x00' + lab.varint(777) + lab.varint(len(host)) + host + port.to_bytes(2, 'big') + b'\x01'))
s.sendall(packet(b'\x00'))
length = read_varint(s)
data = b''
@@ -212,7 +202,7 @@ def chat_lines(lines):
def run_side(label, scenario, command, directory, client):
port = free_port()
port = lab.free_port()
shutil.rmtree(directory, ignore_errors=True)
directory.mkdir(parents=True)
props = dict(PROPERTIES, **SCENARIOS[scenario], **{'server-port': str(port)})
+1 -12
View File
@@ -96,17 +96,6 @@ def run_side(label, server_command, client_command, build, world):
SHOWN = ('add', 'remove', 'respawn', 'track', 'untrack', 'update')
def settled(lines):
"""One player's lines of one step, in order, with the waypoint packets reduced to the last for each waypoint:
what the locator bar shows once the step is over. Arriving in a level, vanilla sends the same waypoint more
than once on the way there; the packet by packet order is test_locator_bar_parity's."""
def waypoint(line):
parts = line.split(' ')
return parts[3] if len(parts) > 3 and parts[2] in ('track', 'untrack', 'update') else None
last = {waypoint(l): i for i, l in enumerate(lines) if waypoint(l)}
return [l for i, l in enumerate(lines) if not waypoint(l) or last[waypoint(l)] == i]
def shown(lines):
"""Each player's adds, removes, respawns and waypoints (as the step leaves them), step by step; sorted within a
step (the packets of one step may leave in another order within the tick)."""
@@ -115,7 +104,7 @@ def shown(lines):
parts = line.split(' ', 2)
if len(parts) == 3 and parts[2].split(' ')[0] in SHOWN:
steps.setdefault((parts[0], parts[1]), []).append(line)
return [l for key in steps for l in sorted(settled(steps[key]))]
return [l for key in steps for l in sorted(lab.settled(steps[key]))]
def main():
+2 -23
View File
@@ -15,7 +15,6 @@ in sorted order (the packets of one step may leave in another order within the t
waypoints as each step leaves them (one manager per level: a player in another level is untracked). What a death does besides its messages
is left to its own harness.
"""
import hashlib
import os
from pathlib import Path
import platform
@@ -23,7 +22,6 @@ import shutil
import subprocess
import sys
import time
import uuid as uuidlib
try:
from . import lab, test_rust_parity as base
@@ -39,20 +37,12 @@ NAME = 'server-player-targets-parity'
NETHER, OVERWORLD = 'minecraft:the_nether', 'minecraft:overworld'
def offline_uuid(name):
"""UUIDUtil.createOfflinePlayerUUID."""
digest = bytearray(hashlib.md5(f'OfflinePlayer:{name}'.encode()).digest())
digest[6] = (digest[6] & 0x0F) | 0x30
digest[8] = (digest[8] & 0x3F) | 0x80
return str(uuidlib.UUID(bytes=bytes(digest)))
# (label, what the console does, what the players do).
SCRIPT = [
('console-give', ['give Bob minecraft:diamond 3'], []),
('console-give-all', ['give @a minecraft:apple'], []),
('alice-give-bob', [], ['cmd Alice give Bob minecraft:stick 2']),
('console-give-uuid', [f'give {offline_uuid("Bob")} minecraft:iron_ingot'], []),
('console-give-uuid', [f'give {lab.offline_uuid("Bob")} minecraft:iron_ingot'], []),
('console-give-nearest', ['give @p minecraft:emerald'], []),
('alice-give-random', [], ['cmd Alice give @r[name=Bob] minecraft:gold_ingot']),
('console-gamemode', ['gamemode survival Bob'], []),
@@ -83,17 +73,6 @@ DEATH_STEP = 'step=console-kill'
STEP_SECONDS = 2.5
def settled(lines):
"""One player's lines of one step, in order, with the waypoint packets reduced to the last for each waypoint:
what the locator bar shows once the step is over. Arriving in a level, vanilla sends the same waypoint more
than once on the way there; the packet by packet order is test_locator_bar_parity's."""
def waypoint(line):
parts = line.split(' ')
return parts[3] if len(parts) > 3 and parts[2] in ('track', 'untrack', 'update') else None
last = {waypoint(l): i for i, l in enumerate(lines) if waypoint(l)}
return [l for i, l in enumerate(lines) if not waypoint(l) or last[waypoint(l)] == i]
def run_side(label, server_command, client_command, build, world):
directory = build / label
port = server_dir(directory, world)
@@ -151,7 +130,7 @@ def run_side(label, server_command, client_command, build, world):
steps.setdefault((who, step), []).append(line)
order = {f'step={s}': i for i, (s, _, _) in enumerate(SCRIPT)}
keys = sorted(steps, key=lambda k: (k[0], order.get(k[1], -1)))
return [l for k in keys for l in sorted(settled(steps[k]))], errors
return [l for k in keys for l in sorted(lab.settled(steps[k]))], errors
def main():
+4 -8
View File
@@ -18,14 +18,10 @@ import math
import random
try:
from . import audio_harness as h
from . import audio_harness as h, lab
except ImportError:
import audio_harness as h
def dbits(v):
import struct
return struct.pack('>d', v).hex()
import lab
class Script:
@@ -37,11 +33,11 @@ class Script:
self.lines.append(' '.join(map(str, parts)))
def play(self, name, event, source=4, volume=1.0, pitch=1.0, at=(0.0, 0.0, 0.0), looping=0, delay=0, linear=True, relative=0, seed=0, kind='simple'):
self.add('play', name, kind, event, source, h.bits(volume), h.bits(pitch), *map(dbits, at), looping, delay,
self.add('play', name, kind, event, source, h.bits(volume), h.bits(pitch), *map(lab.dbits, at), looping, delay,
'linear' if linear else 'none', relative, seed)
def cart(self, name, at, delta, removed=0):
self.add('cart', name, *map(dbits, at), *map(dbits, delta), removed)
self.add('cart', name, *map(lab.dbits, at), *map(lab.dbits, delta), removed)
def tick(self, n=1, paused=0):
for _ in range(n):
+1 -8
View File
@@ -9,7 +9,6 @@ level.dat, player and data/*.dat files) is applied exactly. The original is kept
upgrade_world.py SOURCE_WORLD DEST_WORLD
"""
import shutil
import socket
import subprocess
import sys
import tempfile
@@ -24,12 +23,6 @@ ROOT = Path(__file__).resolve().parent.parent
SERVER = lab.GAME / 'jars' / 'server.jar'
def free_port():
with socket.socket() as s:
s.bind(('127.0.0.1', 0))
return s.getsockname()[1]
def main():
if len(sys.argv) != 3:
sys.exit(__doc__)
@@ -42,7 +35,7 @@ def main():
scratch = Path(scratch)
(scratch / 'eula.txt').write_text('eula=true\n')
(scratch / 'server.properties').write_text(
f'level-name=world\nonline-mode=false\nserver-port={free_port()}\n')
f'level-name=world\nonline-mode=false\nserver-port={lab.free_port()}\n')
shutil.copytree(source, scratch / 'world')
# The server upgrades at startup, then keeps running as a normal server: stop it
# once it reports "Done".
+1 -10
View File
@@ -14,12 +14,10 @@ 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 hashlib
import json
import os
import subprocess
import sys
import uuid
import zipfile
from pathlib import Path
@@ -56,13 +54,6 @@ if sys.platform == "darwin":
OPTIONS["enableVsync"] = "false"
def offline_uuid(name):
digest = bytearray(hashlib.md5(("OfflinePlayer:" + name).encode()).digest())
digest[6] = (digest[6] & 0x0F) | 0x30
digest[8] = (digest[8] & 0x3F) | 0x80
return uuid.UUID(bytes=bytes(digest))
def host_library(path):
name = Path(path).name
if "natives-" in name:
@@ -127,7 +118,7 @@ def command(args):
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", offline_uuid(args.name).hex,
"--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"],
+12 -43
View File
@@ -11,12 +11,10 @@ its log reports the position it read, and console commands such as
`data get entity NAME` can inspect the loaded player meanwhile.
"""
import argparse
import hashlib
import socket
import struct
import sys
import time
import uuid
import zlib
try:
@@ -25,27 +23,9 @@ except ImportError:
import lab
def offline_uuid(name):
digest = bytearray(hashlib.md5(('OfflinePlayer:' + name).encode()).digest())
digest[6] = (digest[6] & 0x0F) | 0x30
digest[8] = (digest[8] & 0x3F) | 0x80
return uuid.UUID(bytes=bytes(digest))
def varint(v):
out = b''
v &= 0xFFFFFFFF
while True:
b = v & 0x7F
v >>= 7
out += bytes([b | (0x80 if v else 0)])
if not v:
return out
def string(s):
data = s.encode()
return varint(len(data)) + data
return lab.varint(len(data)) + data
class Connection:
@@ -73,44 +53,33 @@ class Connection:
return value
def send(self, packet_id, payload=b''):
body = varint(packet_id) + payload
body = lab.varint(packet_id) + payload
if self.threshold >= 0:
if len(body) >= self.threshold:
body = varint(len(body)) + zlib.compress(body)
body = lab.varint(len(body)) + zlib.compress(body)
else:
body = varint(0) + body
self.sock.sendall(varint(len(body)) + body)
body = lab.varint(0) + body
self.sock.sendall(lab.varint(len(body)) + body)
def receive(self):
length = self.read_varint()
body = self.read_exact(length)
if self.threshold >= 0:
pos, size = parse_varint(body, 0)
pos, size = lab.parse_varint(body, 0)
body = zlib.decompress(body[pos:]) if size else body[pos:]
pos, packet_id = parse_varint(body, 0)
pos, packet_id = lab.parse_varint(body, 0)
return packet_id, body[pos:]
def parse_varint(data, pos):
value = shift = 0
while True:
b = data[pos]
pos += 1
value |= (b & 0x7F) << shift
shift += 7
if not b & 0x80:
return pos, value
def join(host, port, name, seconds):
c = Connection(host, port)
c.send(0, varint(lab.version_info()['protocol_version']) + string(host) + struct.pack('>H', port) + varint(2))
c.send(0, string(name) + offline_uuid(name).bytes)
c.send(0, lab.varint(lab.version_info()['protocol_version']) + string(host) + struct.pack('>H', port) + lab.varint(2))
c.send(0, string(name) + lab.offline_uuid(name).bytes)
# Login: compression, then login finished.
while True:
pid, data = c.receive()
if pid == 3:
c.threshold = parse_varint(data, 0)[1]
c.threshold = lab.parse_varint(data, 0)[1]
elif pid == 2:
break
elif pid == 0:
@@ -119,7 +88,7 @@ def join(host, port, name, seconds):
while True:
pid, data = c.receive()
if pid == 15: # select known packs: claim none, receive full registries
c.send(7, varint(0))
c.send(7, lab.varint(0))
elif pid == 4: # keep alive
c.send(4, data)
elif pid == 5: # ping
@@ -153,7 +122,7 @@ def main():
p.add_argument('--uuid', action='store_true')
args = p.parse_args()
if args.uuid:
print(offline_uuid(args.name))
print(lab.offline_uuid(args.name))
return 0
join(args.host, args.port, args.name, args.seconds)
return 0
+6 -8
View File
@@ -17,12 +17,16 @@ load failures.
import argparse
import os
import re
import socket
import subprocess
import sys
import threading
import time
try:
from . import lab
except ImportError:
import lab
SUSPICIOUS = re.compile(
r'(ERROR|login failed|Exception|Couldn\'t load|Failed to|Skipping|Invalid|Unknown|corrupt|'
r'Recreating|upgrad|DataFix|out of bounds|missing|Mismatch)', re.IGNORECASE)
@@ -31,12 +35,6 @@ BENIGN = re.compile(r'(Unpacking |OFFLINE/INSECURE|online-mode|authenticate user
r'Failed to load properties|Failed to load eula)')
def free_port():
with socket.socket() as s:
s.bind(('127.0.0.1', 0))
return s.getsockname()[1]
def main():
p = argparse.ArgumentParser()
p.add_argument('--jar', required=True)
@@ -48,7 +46,7 @@ def main():
p.add_argument('--wait-after', type=float, default=1.0)
p.add_argument('--timeout', type=float, default=240)
args = p.parse_args()
port = args.port or free_port()
port = args.port or lab.free_port()
os.makedirs(args.dir, exist_ok=True)
with open(os.path.join(args.dir, 'eula.txt'), 'w') as f:
f.write('eula=true\n')