mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2025-10-16 12:02:51 +00:00
193 lines
6.5 KiB
Python
193 lines
6.5 KiB
Python
|
|
# Released under the MIT License. See LICENSE for details.
|
||
|
|
#
|
||
|
|
"""Bootstrapping."""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
|
||
|
|
from efro.log import setup_logging, LogLevel
|
||
|
|
import _ba
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from typing import Any
|
||
|
|
from efro.log import LogEntry
|
||
|
|
|
||
|
|
_g_did_bootstrap = False # pylint: disable=invalid-name
|
||
|
|
|
||
|
|
|
||
|
|
def bootstrap() -> None:
|
||
|
|
"""Run bootstrapping logic.
|
||
|
|
|
||
|
|
This is the very first ballistica code that runs (aside from imports).
|
||
|
|
It sets up low level environment bits and creates the app instance.
|
||
|
|
"""
|
||
|
|
|
||
|
|
global _g_did_bootstrap # pylint: disable=global-statement, invalid-name
|
||
|
|
if _g_did_bootstrap:
|
||
|
|
raise RuntimeError('Bootstrap has already been called.')
|
||
|
|
_g_did_bootstrap = True
|
||
|
|
|
||
|
|
# The first thing we do is set up our logging system and feed
|
||
|
|
# Python's stdout/stderr into it. Then we can at least debug problems
|
||
|
|
# on systems where native stdout/stderr is not easily accessible
|
||
|
|
# such as Android.
|
||
|
|
log_handler = setup_logging(
|
||
|
|
log_path=None,
|
||
|
|
level=LogLevel.DEBUG,
|
||
|
|
suppress_non_root_debug=True,
|
||
|
|
log_stdout_stderr=True,
|
||
|
|
cache_size_limit=1024 * 1024,
|
||
|
|
)
|
||
|
|
|
||
|
|
log_handler.add_callback(_on_log)
|
||
|
|
|
||
|
|
env = _ba.env()
|
||
|
|
|
||
|
|
# Give a soft warning if we're being used with a different binary
|
||
|
|
# version than we expect.
|
||
|
|
expected_build = 21005
|
||
|
|
running_build: int = env['build_number']
|
||
|
|
if running_build != expected_build:
|
||
|
|
print(
|
||
|
|
f'WARNING: These script files are meant to be used with'
|
||
|
|
f' Ballistica build {expected_build}.\n'
|
||
|
|
f' You are running build {running_build}.'
|
||
|
|
f' This might cause the app to error or misbehave.',
|
||
|
|
file=sys.stderr,
|
||
|
|
)
|
||
|
|
|
||
|
|
# In bootstrap_monolithic.py we told Python not to handle SIGINT itself
|
||
|
|
# (because that must be done in the main thread). Now we finish the
|
||
|
|
# job by adding our own handler to replace it.
|
||
|
|
|
||
|
|
# Note: I've found we need to set up our C signal handling AFTER
|
||
|
|
# we've told Python to disable its own; otherwise (on Mac at least) it
|
||
|
|
# wipes out our existing C handler.
|
||
|
|
_ba.setup_sigint()
|
||
|
|
|
||
|
|
# Sanity check: we should always be run in UTF-8 mode.
|
||
|
|
if sys.flags.utf8_mode != 1:
|
||
|
|
print(
|
||
|
|
'ERROR: Python\'s UTF-8 mode is not set.'
|
||
|
|
' This will likely result in errors.',
|
||
|
|
file=sys.stderr,
|
||
|
|
)
|
||
|
|
|
||
|
|
debug_build = env['debug_build']
|
||
|
|
|
||
|
|
# We expect dev_mode on in debug builds and off otherwise.
|
||
|
|
if debug_build != sys.flags.dev_mode:
|
||
|
|
print(
|
||
|
|
f'WARNING: Mismatch in debug_build {debug_build}'
|
||
|
|
f' and sys.flags.dev_mode {sys.flags.dev_mode}',
|
||
|
|
file=sys.stderr,
|
||
|
|
)
|
||
|
|
|
||
|
|
# In embedded situations (when we're providing our own Python) let's
|
||
|
|
# also provide our own root certs so ssl works. We can consider overriding
|
||
|
|
# this in particular embedded cases if we can verify that system certs
|
||
|
|
# are working.
|
||
|
|
# (We also allow forcing this via an env var if the user desires)
|
||
|
|
if (
|
||
|
|
_ba.contains_python_dist()
|
||
|
|
or os.environ.get('BA_USE_BUNDLED_ROOT_CERTS') == '1'
|
||
|
|
):
|
||
|
|
import certifi
|
||
|
|
|
||
|
|
# Let both OpenSSL and requests (if present) know to use this.
|
||
|
|
os.environ['SSL_CERT_FILE'] = os.environ[
|
||
|
|
'REQUESTS_CA_BUNDLE'
|
||
|
|
] = certifi.where()
|
||
|
|
|
||
|
|
# On Windows I'm seeing the following error creating asyncio loops in
|
||
|
|
# background threads with the default proactor setup:
|
||
|
|
# ValueError: set_wakeup_fd only works in main thread of the main
|
||
|
|
# interpreter
|
||
|
|
# So let's explicitly request selector loops.
|
||
|
|
# Interestingly this error only started showing up once I moved
|
||
|
|
# Python init to the main thread; previously the various asyncio
|
||
|
|
# bg thread loops were working fine (maybe something caused them
|
||
|
|
# to default to selector in that case?..
|
||
|
|
if sys.platform == 'win32':
|
||
|
|
import asyncio
|
||
|
|
|
||
|
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||
|
|
|
||
|
|
# pylint: disable=c-extension-no-member
|
||
|
|
if not TYPE_CHECKING:
|
||
|
|
import __main__
|
||
|
|
|
||
|
|
# Clear out the standard quit/exit messages since they don't
|
||
|
|
# work in our embedded situation (should revisit this once we're
|
||
|
|
# usable from a standard interpreter).
|
||
|
|
del __main__.__builtins__.quit
|
||
|
|
del __main__.__builtins__.exit
|
||
|
|
|
||
|
|
# Also replace standard interactive help with our simplified
|
||
|
|
# one which is more friendly to cloud/in-game console situations.
|
||
|
|
__main__.__builtins__.help = _CustomHelper()
|
||
|
|
|
||
|
|
# Now spin up our App instance and store it on both _ba and ba.
|
||
|
|
from ba._app import App
|
||
|
|
import ba
|
||
|
|
|
||
|
|
_ba.app = ba.app = App()
|
||
|
|
_ba.app.log_handler = log_handler
|
||
|
|
|
||
|
|
|
||
|
|
class _CustomHelper:
|
||
|
|
"""Replacement 'help' that behaves better for our setup."""
|
||
|
|
|
||
|
|
def __repr__(self) -> str:
|
||
|
|
return 'Type help(object) for help about object.'
|
||
|
|
|
||
|
|
def __call__(self, *args: Any, **kwds: Any) -> Any:
|
||
|
|
# We get an ugly error importing pydoc on our embedded
|
||
|
|
# platforms due to _sysconfigdata_xxx.py not being present
|
||
|
|
# (but then things mostly work). Let's get the ugly error out
|
||
|
|
# of the way explicitly.
|
||
|
|
import sysconfig
|
||
|
|
|
||
|
|
try:
|
||
|
|
# This errors once but seems to run cleanly after, so let's
|
||
|
|
# get the error out of the way.
|
||
|
|
sysconfig.get_path('stdlib')
|
||
|
|
except ModuleNotFoundError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
import pydoc
|
||
|
|
|
||
|
|
# Disable pager and interactive help since neither works well
|
||
|
|
# with our funky multi-threaded setup or in-game/cloud consoles.
|
||
|
|
# Let's just do simple text dumps.
|
||
|
|
pydoc.pager = pydoc.plainpager
|
||
|
|
if not args and not kwds:
|
||
|
|
print(
|
||
|
|
'Interactive help is not available in this environment.\n'
|
||
|
|
'Type help(object) for help about object.'
|
||
|
|
)
|
||
|
|
return None
|
||
|
|
return pydoc.help(*args, **kwds)
|
||
|
|
|
||
|
|
|
||
|
|
def _on_log(entry: LogEntry) -> None:
|
||
|
|
|
||
|
|
# Just forward this along to the engine to display in the in-game console,
|
||
|
|
# in the Android log, etc.
|
||
|
|
_ba.display_log(
|
||
|
|
name=entry.name,
|
||
|
|
level=entry.level.name,
|
||
|
|
message=entry.message,
|
||
|
|
)
|
||
|
|
|
||
|
|
# We also want to feed some logs to the old V1-cloud-log system.
|
||
|
|
# Let's go with anything warning or higher as well as the stdout/stderr
|
||
|
|
# log messages that ba.app.log_handler creates for us.
|
||
|
|
if entry.level.value >= LogLevel.WARNING.value or entry.name in (
|
||
|
|
'stdout',
|
||
|
|
'stderr',
|
||
|
|
):
|
||
|
|
_ba.v1_cloud_log(entry.message)
|