levistone 0.10.15__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- endstone/__init__.py +30 -0
- endstone/__main__.py +4 -0
- endstone/_internal/__init__.py +0 -0
- endstone/_internal/bootstrap/__init__.py +91 -0
- endstone/_internal/bootstrap/base.py +301 -0
- endstone/_internal/bootstrap/linux.py +82 -0
- endstone/_internal/bootstrap/windows.py +199 -0
- endstone/_internal/endstone_python.cp312-win_amd64.pyd +0 -0
- endstone/_internal/endstone_python.pyi +4880 -0
- endstone/_internal/metrics.py +101 -0
- endstone/_internal/plugin_loader.py +252 -0
- endstone/_internal/version.py +14 -0
- endstone/actor.py +3 -0
- endstone/ban.py +3 -0
- endstone/block.py +3 -0
- endstone/boss.py +3 -0
- endstone/command.py +17 -0
- endstone/config/endstone.toml +9 -0
- endstone/damage.py +3 -0
- endstone/enchantments.py +3 -0
- endstone/event.py +143 -0
- endstone/form.py +29 -0
- endstone/inventory.py +21 -0
- endstone/lang.py +3 -0
- endstone/level.py +9 -0
- endstone/map.py +3 -0
- endstone/permissions.py +17 -0
- endstone/plugin.py +142 -0
- endstone/scheduler.py +3 -0
- endstone/scoreboard.py +3 -0
- endstone/util.py +3 -0
- endstone_runtime.dll +0 -0
- endstone_runtime.pdb +0 -0
- levistone-0.10.15.dist-info/METADATA +71 -0
- levistone-0.10.15.dist-info/RECORD +40 -0
- levistone-0.10.15.dist-info/WHEEL +5 -0
- levistone-0.10.15.dist-info/entry_points.txt +2 -0
- levistone-0.10.15.dist-info/licenses/LICENSE +201 -0
- levistone-0.10.15.dist-info/top_level.txt +2 -0
- manifest.json +6 -0
endstone/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from endstone._internal.endstone_python import (
|
|
2
|
+
ColorFormat,
|
|
3
|
+
EnchantmentRegistry,
|
|
4
|
+
GameMode,
|
|
5
|
+
ItemRegistry,
|
|
6
|
+
Logger,
|
|
7
|
+
NamespacedKey,
|
|
8
|
+
OfflinePlayer,
|
|
9
|
+
Player,
|
|
10
|
+
Server,
|
|
11
|
+
Skin,
|
|
12
|
+
)
|
|
13
|
+
from endstone._internal.version import __version__
|
|
14
|
+
|
|
15
|
+
__minecraft_version__ = "1.21.124"
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"__version__",
|
|
19
|
+
"__minecraft_version__",
|
|
20
|
+
"ColorFormat",
|
|
21
|
+
"EnchantmentRegistry",
|
|
22
|
+
"GameMode",
|
|
23
|
+
"ItemRegistry",
|
|
24
|
+
"Logger",
|
|
25
|
+
"NamespacedKey",
|
|
26
|
+
"OfflinePlayer",
|
|
27
|
+
"Player",
|
|
28
|
+
"Server",
|
|
29
|
+
"Skin",
|
|
30
|
+
]
|
endstone/__main__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import logging
|
|
3
|
+
import platform
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import colorlog
|
|
9
|
+
|
|
10
|
+
from endstone._internal.version import __version__
|
|
11
|
+
|
|
12
|
+
handler = colorlog.StreamHandler()
|
|
13
|
+
handler.setFormatter(
|
|
14
|
+
colorlog.ColoredFormatter(
|
|
15
|
+
fmt="%(log_color)s[%(asctime)s.%(msecs)03d %(levelname)s] [%(name)s] %(message)s",
|
|
16
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
17
|
+
reset=True,
|
|
18
|
+
log_colors={
|
|
19
|
+
"DEBUG": "cyan",
|
|
20
|
+
"INFO": "reset",
|
|
21
|
+
"WARNING": "bold_yellow",
|
|
22
|
+
"ERROR": "bold_red",
|
|
23
|
+
"CRITICAL": "bold,bg_red",
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
logging.basicConfig(level=logging.INFO, handlers=[handler])
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
__all__ = ["cli"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def catch_exceptions(func):
|
|
34
|
+
"""Decorator to catch and log exceptions."""
|
|
35
|
+
|
|
36
|
+
@functools.wraps(func)
|
|
37
|
+
def wrapper(*args, **kwargs):
|
|
38
|
+
try:
|
|
39
|
+
return func(*args, **kwargs)
|
|
40
|
+
except Exception as e:
|
|
41
|
+
logger.exception(e)
|
|
42
|
+
sys.exit(-1)
|
|
43
|
+
|
|
44
|
+
return wrapper
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@click.command(help="Starts an endstone server.")
|
|
48
|
+
@click.option(
|
|
49
|
+
"-s",
|
|
50
|
+
"--server-folder",
|
|
51
|
+
default="bedrock_server",
|
|
52
|
+
help="Specify the folder for the bedrock server. Defaults to 'bedrock_server'.",
|
|
53
|
+
)
|
|
54
|
+
@click.option(
|
|
55
|
+
"-y",
|
|
56
|
+
"--no-confirm",
|
|
57
|
+
"--yes",
|
|
58
|
+
default=False,
|
|
59
|
+
is_flag=True,
|
|
60
|
+
show_default=True,
|
|
61
|
+
help="Assume yes as answer to all prompts",
|
|
62
|
+
)
|
|
63
|
+
@click.option(
|
|
64
|
+
"-r",
|
|
65
|
+
"--remote",
|
|
66
|
+
default="https://raw.githubusercontent.com/EndstoneMC/bedrock-server-data/v2",
|
|
67
|
+
help="The remote URL to retrieve bedrock server data from.",
|
|
68
|
+
)
|
|
69
|
+
@click.version_option(__version__)
|
|
70
|
+
@catch_exceptions
|
|
71
|
+
def cli(server_folder: str, no_confirm: bool, remote: str) -> None:
|
|
72
|
+
system = platform.system()
|
|
73
|
+
if system == "Windows":
|
|
74
|
+
from endstone._internal.bootstrap.windows import WindowsBootstrap
|
|
75
|
+
|
|
76
|
+
cls = WindowsBootstrap
|
|
77
|
+
|
|
78
|
+
elif system == "Linux":
|
|
79
|
+
from endstone._internal.bootstrap.linux import LinuxBootstrap
|
|
80
|
+
|
|
81
|
+
cls = LinuxBootstrap
|
|
82
|
+
else:
|
|
83
|
+
raise NotImplementedError(f"{system} is not supported.")
|
|
84
|
+
|
|
85
|
+
bootstrap = cls(server_folder=server_folder, no_confirm=no_confirm, remote=remote)
|
|
86
|
+
exit_code = bootstrap.run()
|
|
87
|
+
if exit_code != 0:
|
|
88
|
+
logger.error(f"Server exited with non-zero code {exit_code}.")
|
|
89
|
+
time.sleep(2)
|
|
90
|
+
|
|
91
|
+
sys.exit(exit_code)
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import errno
|
|
2
|
+
import fnmatch
|
|
3
|
+
import hashlib
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
import zipfile
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Union
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
import importlib_resources
|
|
17
|
+
import requests
|
|
18
|
+
import sentry_crashpad
|
|
19
|
+
import tomlkit
|
|
20
|
+
from packaging.version import Version
|
|
21
|
+
from rich.progress import BarColumn, DownloadColumn, Progress, TextColumn, TimeRemainingColumn
|
|
22
|
+
|
|
23
|
+
from endstone import __minecraft_version__ as minecraft_version
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Bootstrap:
|
|
27
|
+
def __init__(self, server_folder: str, no_confirm: bool, remote: str) -> None:
|
|
28
|
+
self._server_path = Path(server_folder).absolute()
|
|
29
|
+
self._no_confirm = no_confirm
|
|
30
|
+
self._remote = remote
|
|
31
|
+
self._logger = logging.getLogger(self.name)
|
|
32
|
+
self._process: subprocess.Popen
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def name(self) -> str:
|
|
36
|
+
return __name__
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def target_system(self) -> str:
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def executable_filename(self) -> str:
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def server_path(self) -> Path:
|
|
48
|
+
return self._server_path
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def executable_path(self) -> Path:
|
|
52
|
+
return self.server_path / self.executable_filename
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def config_path(self) -> Path:
|
|
56
|
+
return self.server_path / "endstone.toml"
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def plugin_path(self) -> Path:
|
|
60
|
+
return self.server_path / "plugins"
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def user_agent(self) -> str:
|
|
64
|
+
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
|
|
65
|
+
|
|
66
|
+
def _validate(self) -> None:
|
|
67
|
+
if platform.system().lower() != self.target_system:
|
|
68
|
+
raise NotImplementedError(f"{platform.system()} is not supported by this bootstrap.")
|
|
69
|
+
if not self.executable_path.exists():
|
|
70
|
+
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(self.executable_path))
|
|
71
|
+
if not self._endstone_runtime_path.exists():
|
|
72
|
+
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(self._endstone_runtime_path))
|
|
73
|
+
|
|
74
|
+
def _download(self, dst: Union[str, os.PathLike]) -> None:
|
|
75
|
+
dst = Path(dst)
|
|
76
|
+
|
|
77
|
+
self._logger.info("Loading index from the remote server...")
|
|
78
|
+
channel = "preview" if Version(minecraft_version).is_prerelease else "release"
|
|
79
|
+
metadata_url = "/".join([self._remote, channel, minecraft_version, "metadata.json"])
|
|
80
|
+
response = requests.get(metadata_url, timeout=10)
|
|
81
|
+
response.raise_for_status()
|
|
82
|
+
metadata = response.json()
|
|
83
|
+
|
|
84
|
+
if minecraft_version != metadata["version"]:
|
|
85
|
+
raise ValueError(f"Version mismatch, expect: {minecraft_version}, actual: {metadata['version']}")
|
|
86
|
+
|
|
87
|
+
should_modify_server_properties = True
|
|
88
|
+
|
|
89
|
+
with tempfile.TemporaryFile(dir=dst) as f:
|
|
90
|
+
url = metadata["binary"][self.target_system.lower()]["url"]
|
|
91
|
+
self._logger.info(f"Downloading server from {url}...")
|
|
92
|
+
response = requests.get(url, stream=True, headers={"User-Agent": self.user_agent})
|
|
93
|
+
response.raise_for_status()
|
|
94
|
+
total_size = int(response.headers.get("Content-Length", 0))
|
|
95
|
+
m = hashlib.sha256()
|
|
96
|
+
|
|
97
|
+
with Progress(
|
|
98
|
+
TextColumn("[progress.description]{task.description}"),
|
|
99
|
+
BarColumn(),
|
|
100
|
+
DownloadColumn(),
|
|
101
|
+
TimeRemainingColumn(),
|
|
102
|
+
) as progress:
|
|
103
|
+
task = progress.add_task("[bold blue]Downloading...", total=total_size)
|
|
104
|
+
for data in response.iter_content(chunk_size=1024):
|
|
105
|
+
progress.update(task, advance=len(data))
|
|
106
|
+
f.write(data)
|
|
107
|
+
m.update(data)
|
|
108
|
+
|
|
109
|
+
self._logger.info("Download complete. Verifying integrity...")
|
|
110
|
+
if m.hexdigest() != metadata["binary"][self.target_system.lower()]["sha256"]:
|
|
111
|
+
raise ValueError("SHA256 mismatch: the downloaded file may be corrupted or tampered with.")
|
|
112
|
+
|
|
113
|
+
self._logger.info(f"Integrity check passed. Extracting to {dst}...")
|
|
114
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
override_patterns = [
|
|
116
|
+
self.executable_filename,
|
|
117
|
+
"behavior_packs/*",
|
|
118
|
+
"definitions/*",
|
|
119
|
+
"resource_packs/*",
|
|
120
|
+
"bedrock_server_how_to.html",
|
|
121
|
+
"profanity_filter.wlist",
|
|
122
|
+
"release-notes.txt",
|
|
123
|
+
]
|
|
124
|
+
with zipfile.ZipFile(f) as zip_ref:
|
|
125
|
+
for file in zip_ref.namelist():
|
|
126
|
+
dest_path = dst / file
|
|
127
|
+
if dest_path.exists():
|
|
128
|
+
if not any(fnmatch.fnmatch(file, pattern) for pattern in override_patterns):
|
|
129
|
+
should_modify_server_properties = False
|
|
130
|
+
self._logger.info(f"{dest_path} already exists, skipping.")
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
zip_ref.extract(file, dst)
|
|
134
|
+
|
|
135
|
+
if should_modify_server_properties:
|
|
136
|
+
properties = dst / "server.properties"
|
|
137
|
+
with properties.open("r", encoding="utf-8") as file:
|
|
138
|
+
in_lines = file.readlines()
|
|
139
|
+
|
|
140
|
+
out_lines = []
|
|
141
|
+
for line in in_lines:
|
|
142
|
+
if line.strip() == "server-name=Dedicated Server":
|
|
143
|
+
out_lines.append("server-name=Endstone Server\n")
|
|
144
|
+
elif line.strip() == "client-side-chunk-generation-enabled=true":
|
|
145
|
+
out_lines.append("client-side-chunk-generation-enabled=false\n")
|
|
146
|
+
else:
|
|
147
|
+
out_lines.append(line)
|
|
148
|
+
|
|
149
|
+
with properties.open("w", encoding="utf-8") as file:
|
|
150
|
+
file.writelines(out_lines)
|
|
151
|
+
|
|
152
|
+
version_file = dst / "version.txt"
|
|
153
|
+
with version_file.open("w", encoding="utf-8") as file:
|
|
154
|
+
file.writelines(minecraft_version)
|
|
155
|
+
|
|
156
|
+
def _prepare(self) -> None:
|
|
157
|
+
# ensure the plugin folder exists
|
|
158
|
+
self.plugin_path.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
|
|
160
|
+
# prepare the crashpad handler
|
|
161
|
+
src_dir, dst_dir = Path(sentry_crashpad._get_executable("crashpad_handler")).parent, self.server_path
|
|
162
|
+
for s in src_dir.rglob("*"):
|
|
163
|
+
if s.is_dir():
|
|
164
|
+
continue
|
|
165
|
+
rel = s.relative_to(src_dir)
|
|
166
|
+
d = dst_dir / rel
|
|
167
|
+
if d.exists() and d.is_dir():
|
|
168
|
+
raise RuntimeError(f"Destination path is a directory: {d}")
|
|
169
|
+
d.parent.mkdir(parents=True, exist_ok=True)
|
|
170
|
+
if not d.exists():
|
|
171
|
+
try:
|
|
172
|
+
shutil.copy2(s, d)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
raise RuntimeError(f"Failed to copy {s} -> {d}: {e}")
|
|
175
|
+
|
|
176
|
+
# create or update the config file
|
|
177
|
+
if not self.config_path.exists():
|
|
178
|
+
ref = importlib_resources.files("endstone") / "config" / "endstone.toml"
|
|
179
|
+
with importlib_resources.as_file(ref) as path:
|
|
180
|
+
shutil.copy(path, self.config_path)
|
|
181
|
+
else:
|
|
182
|
+
ref = importlib_resources.files("endstone") / "config" / "endstone.toml"
|
|
183
|
+
with importlib_resources.as_file(ref) as path:
|
|
184
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
185
|
+
default_config = tomlkit.load(f)
|
|
186
|
+
|
|
187
|
+
with open(self.config_path, "r", encoding="utf-8") as f:
|
|
188
|
+
config = tomlkit.load(f)
|
|
189
|
+
|
|
190
|
+
def migrate_config(from_doc: tomlkit.TOMLDocument, to_doc: tomlkit.TOMLDocument) -> None:
|
|
191
|
+
for key, val in from_doc.items():
|
|
192
|
+
if key not in to_doc:
|
|
193
|
+
# if the user hasn’t set it, copy it (with comments!)
|
|
194
|
+
to_doc[key] = val
|
|
195
|
+
else:
|
|
196
|
+
# if both are tables, dive deeper
|
|
197
|
+
if isinstance(val, tomlkit.TOMLDocument) and isinstance(to_doc[key], tomlkit.TOMLDocument):
|
|
198
|
+
migrate_config(val, to_doc[key])
|
|
199
|
+
|
|
200
|
+
migrate_config(default_config, config)
|
|
201
|
+
with open(self.config_path, "w", encoding="utf-8") as f:
|
|
202
|
+
tomlkit.dump(config, f)
|
|
203
|
+
|
|
204
|
+
def _install(self) -> None:
|
|
205
|
+
"""
|
|
206
|
+
Installs the server if not already installed.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
if self.executable_path.exists():
|
|
210
|
+
self._update()
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
if not self._no_confirm:
|
|
214
|
+
download = click.confirm(
|
|
215
|
+
f"Bedrock Dedicated Server (v{minecraft_version}) "
|
|
216
|
+
f"is not found in {str(self.executable_path.parent)}. "
|
|
217
|
+
f"Would you like to download it now?",
|
|
218
|
+
default=True,
|
|
219
|
+
)
|
|
220
|
+
else:
|
|
221
|
+
download = True
|
|
222
|
+
|
|
223
|
+
if not download:
|
|
224
|
+
sys.exit(1)
|
|
225
|
+
|
|
226
|
+
self.server_path.mkdir(parents=True, exist_ok=True)
|
|
227
|
+
self._download(self.server_path)
|
|
228
|
+
|
|
229
|
+
def _update(self) -> None:
|
|
230
|
+
current_version = Version("0.0.0")
|
|
231
|
+
supported_version = Version(minecraft_version)
|
|
232
|
+
|
|
233
|
+
version_file = self.server_path / "version.txt"
|
|
234
|
+
if version_file.exists():
|
|
235
|
+
with version_file.open("r", encoding="utf-8") as file:
|
|
236
|
+
current_version = Version(file.readline())
|
|
237
|
+
|
|
238
|
+
if current_version == supported_version:
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
if current_version > supported_version:
|
|
242
|
+
raise RuntimeError(
|
|
243
|
+
f"A newer version of Bedrock Dedicated Server (v{current_version}) "
|
|
244
|
+
f"is found in {str(self.executable_path.parent)}. Please update your Endstone server."
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if not self._no_confirm:
|
|
248
|
+
update = click.confirm(
|
|
249
|
+
f"An older version of Bedrock Dedicated Server (v{current_version}) "
|
|
250
|
+
f"is found in {str(self.executable_path.parent)}. "
|
|
251
|
+
f"Would you like to update to v{minecraft_version} now?",
|
|
252
|
+
default=True,
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
update = True
|
|
256
|
+
|
|
257
|
+
if not update:
|
|
258
|
+
sys.exit(1)
|
|
259
|
+
|
|
260
|
+
self._logger.info(f"Updating server from v{current_version} to v{minecraft_version}...")
|
|
261
|
+
self._download(self.server_path)
|
|
262
|
+
|
|
263
|
+
def run(self) -> int:
|
|
264
|
+
self._install()
|
|
265
|
+
self._validate()
|
|
266
|
+
self._prepare()
|
|
267
|
+
return self._run()
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def _endstone_runtime_filename(self) -> str:
|
|
271
|
+
raise NotImplementedError
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def _endstone_runtime_path(self) -> Path:
|
|
275
|
+
p = Path(__file__).parent.parent / self._endstone_runtime_filename
|
|
276
|
+
return p.resolve().absolute()
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def _endstone_runtime_env(self) -> dict[str, str]:
|
|
280
|
+
env = os.environ.copy()
|
|
281
|
+
env["PATH"] = os.pathsep.join(sys.path)
|
|
282
|
+
env["PYTHONPATH"] = os.pathsep.join(sys.path)
|
|
283
|
+
env["PYTHONIOENCODING"] = "UTF-8"
|
|
284
|
+
env["ENDSTONE_PYTHON_EXECUTABLE"] = sys.executable
|
|
285
|
+
return env
|
|
286
|
+
|
|
287
|
+
def _run(self, *args, **kwargs) -> int:
|
|
288
|
+
"""
|
|
289
|
+
Runs the server and returns its exit code.
|
|
290
|
+
|
|
291
|
+
This method blocks until the server process terminates. It returns the exit code of the process, which can be
|
|
292
|
+
used to determine if the server shut down successfully or if there were errors.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
*args: Variable length argument list.
|
|
296
|
+
**kwargs: Arbitrary keyword arguments.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
int: The exit code of the server process.
|
|
300
|
+
"""
|
|
301
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import ctypes.util
|
|
2
|
+
import os
|
|
3
|
+
import stat
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from endstone._internal.bootstrap.base import Bootstrap
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LinuxBootstrap(Bootstrap):
|
|
11
|
+
@property
|
|
12
|
+
def name(self) -> str:
|
|
13
|
+
return "LinuxBootstrap"
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def target_system(self) -> str:
|
|
17
|
+
return "linux"
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def executable_filename(self) -> str:
|
|
21
|
+
return "bedrock_server"
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def _endstone_runtime_filename(self) -> str:
|
|
25
|
+
return "libendstone_runtime.so"
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def _endstone_runtime_env(self) -> dict[str, str]:
|
|
29
|
+
env = super()._endstone_runtime_env
|
|
30
|
+
env["LD_PRELOAD"] = str(self._endstone_runtime_path.absolute())
|
|
31
|
+
env["LD_LIBRARY_PATH"] = str(self._linked_libpython_path.parent.absolute())
|
|
32
|
+
return env
|
|
33
|
+
|
|
34
|
+
def _prepare(self) -> None:
|
|
35
|
+
super()._prepare()
|
|
36
|
+
st = os.stat(self.executable_path)
|
|
37
|
+
os.chmod(self.executable_path, st.st_mode | stat.S_IEXEC)
|
|
38
|
+
os.chmod(self.server_path / "crashpad_handler", st.st_mode | stat.S_IEXEC)
|
|
39
|
+
|
|
40
|
+
def _run(self, *args, **kwargs) -> int:
|
|
41
|
+
process = subprocess.Popen(
|
|
42
|
+
[str(self.executable_path.absolute())],
|
|
43
|
+
text=True,
|
|
44
|
+
encoding="utf-8",
|
|
45
|
+
cwd=str(self.server_path.absolute()),
|
|
46
|
+
env=self._endstone_runtime_env,
|
|
47
|
+
*args,
|
|
48
|
+
**kwargs,
|
|
49
|
+
)
|
|
50
|
+
return process.wait()
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def _linked_libpython_path(self) -> Path:
|
|
54
|
+
"""
|
|
55
|
+
Find the path of the linked libpython on Unix systems.
|
|
56
|
+
|
|
57
|
+
From https://gist.github.com/tkf/d980eee120611604c0b9b5fef5b8dae6
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
(Path): Path object representing the path of the linked libpython.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
class DlInfo(ctypes.Structure):
|
|
64
|
+
# https://www.man7.org/linux/man-pages/man3/dladdr.3.html
|
|
65
|
+
_fields_ = [
|
|
66
|
+
("dli_fname", ctypes.c_char_p),
|
|
67
|
+
("dli_fbase", ctypes.c_void_p),
|
|
68
|
+
("dli_sname", ctypes.c_char_p),
|
|
69
|
+
("dli_saddr", ctypes.c_void_p),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
libdl = ctypes.CDLL(ctypes.util.find_library("dl"))
|
|
73
|
+
libdl.dladdr.argtypes = [ctypes.c_void_p, ctypes.POINTER(DlInfo)]
|
|
74
|
+
libdl.dladdr.restype = ctypes.c_int
|
|
75
|
+
|
|
76
|
+
dlinfo = DlInfo()
|
|
77
|
+
retcode = libdl.dladdr(ctypes.cast(ctypes.pythonapi.Py_GetVersion, ctypes.c_void_p), ctypes.pointer(dlinfo))
|
|
78
|
+
if retcode == 0:
|
|
79
|
+
raise ValueError("dladdr cannot match the address of ctypes.pythonapi.Py_GetVersion to a shared object")
|
|
80
|
+
|
|
81
|
+
path = Path(dlinfo.dli_fname.decode()).resolve()
|
|
82
|
+
return path
|