Now the *launcher* can actually launch mInECraFT.

This commit is contained in:
2026-07-13 03:13:17 +08:00
parent ccbe2052b8
commit 648cde6e33
3 changed files with 447 additions and 6 deletions
+77 -3
View File
@@ -5,7 +5,7 @@ from core_lib.game.library import get_natives_platform_info
logger = logging.getLogger("Launcher.CoreLib")
def is_argument_allowed(argument: dict | str, allow_features: dict=dict) -> bool:
def is_argument_allowed(argument: dict | str, allow_features: dict | None=None) -> bool:
"""
Check if argument is allowed or not.
:param argument:
@@ -16,6 +16,9 @@ def is_argument_allowed(argument: dict | str, allow_features: dict=dict) -> bool
if isinstance(argument, str):
return True
if allow_features is None:
allow_features = {}
rules = argument.get('rules', {})
_, platform_rule_name, _ = get_natives_platform_info()
@@ -125,7 +128,48 @@ def convert_argument_to_multiple_string(argument: str | dict) -> list[str]:
raise TypeError("Argument must be of type str or dict")
def parse_and_generate_arguments(arguments: list | str, mappings: ArgumentMappings, features: dict=dict) -> list[str]:
def find_game_and_jvm_args(arguments: dict | str) -> tuple[list, list]:
game_args = []
jvm_args = []
if isinstance(arguments, dict):
game = arguments.get("game", [])
jvm = arguments.get("jvm", [])
if isinstance(game, list):
game_args.extend(game)
elif isinstance(game, str):
game_args.append(game)
if isinstance(jvm, list):
jvm_args.extend(jvm)
elif isinstance(jvm, str):
jvm_args.append(jvm)
if isinstance(arguments, str):
# Convert legacy argument into a list type
game_args = shlex.split(arguments)
return game_args, jvm_args
REMAPPING_ARGUMENTS = {
# For some (I don't know) reason, in 26.2, Mojang include some
"-Djava.library.path=${natives_directory}/java": "-Djava.library.path=${natives_directory}"
}
def hard_remapping_special_arguments(argument: str) -> str:
for arg in REMAPPING_ARGUMENTS:
new_value = REMAPPING_ARGUMENTS[arg]
if arg == argument:
return new_value
return argument
def parse_and_generate_arguments(arguments: list | str, mappings: ArgumentMappings, features: dict | None=None) -> list[str]:
if features is None:
features = {}
parsed_arguments = []
if isinstance(arguments, str):
@@ -139,8 +183,11 @@ def parse_and_generate_arguments(arguments: list | str, mappings: ArgumentMappin
if not is_argument_allowed(argument, allow_features=features):
continue
# Replace some special arguments
new_argument = hard_remapping_special_arguments(argument)
# Each argument (section) may contain multiple items, convert it to list before replace placeholder
converted = convert_argument_to_multiple_string(argument)
converted = convert_argument_to_multiple_string(new_argument)
for item in converted:
parsed = replace_placeholders(item, placeholders)
@@ -149,3 +196,30 @@ def parse_and_generate_arguments(arguments: list | str, mappings: ArgumentMappin
return parsed_arguments
def generate_launch_command(arg_maps: ArgumentMappings, main_class: str, java_path: str, full_args: dict | str
) -> list[str]:
cmd = [java_path]
game_args, jvm_args = find_game_and_jvm_args(full_args)
parsed_jvm_args = parse_and_generate_arguments(
jvm_args,
arg_maps
)
parsed_game_args = parse_and_generate_arguments(
game_args,
arg_maps
)
# Legacy Minecraft don't have jvm arguments exist in the version library. We need to generate by ourselves
if len(parsed_jvm_args) == 0:
cmd.extend([
f"-Djava.library.path={arg_maps.natives_directory}",
"-cp",
arg_maps.classpath,
])
else:
cmd.extend(parsed_jvm_args)
cmd.append(main_class)
cmd.extend(parsed_game_args)
return cmd
+130 -1
View File
@@ -13,7 +13,8 @@ from .version_info import (
NATIVE_NEW_TO_OLD_ARCH,
NATIVE_OLD_TO_NEW_ARCH, find_useful_part_in_specific_version_manifest,
)
from ..common.exception import UnsupportedPlatformException, NativeResolveException, DependencyException
from ..common.exception import UnsupportedPlatformException, NativeResolveException, DependencyException, \
VersionDataKeyNotFoundException
logger = logging.getLogger("Launcher.CoreLib")
@@ -513,3 +514,131 @@ def unzip_natives(filtered, libraries_dir, destination: Path):
unzip(full_path, destination, exclude=exclude, flatten=flatten)
logger.debug(f"Unzipped {full_path}.")
def check_libraries_exists(manifest: dict, libraries_dir: Path):
"""
Check if libraries are present in the libraries directory
:param manifest:
:param libraries_dir:
:return:
not_found: list
"""
libraries = manifest.get("libraries", [])
if not libraries:
raise VersionDataKeyNotFoundException(
"Libraries not found in manifest.",
)
not_found = []
# Same as natives
_, _, os_version = get_platform_info()
platform_key_name, platform_rule_name, platform_arch_type = get_natives_platform_info()
for library in libraries:
name = library.get("name", "")
artifact_raw = library.get("downloads", {}).get("artifact", {})
rules = library.get("rules", [])
classifiers = library.get("downloads", {}).get("classifiers", {})
natives = library.get("natives", {})
# Ignore natives
if (len(natives) > 0 or bool(re.search(r":natives-[^:]+$", name)) or
any(native.startswith("natives-") for native in classifiers)):
logger.info(f"Skipping {name} because its a native library.")
continue
if not is_library_allowed(
rules,
platform_rule_name,
platform_arch_type,
os_version,
):
continue
artifact = generate_formatted_artifact_with_name(
name,
artifact_raw
)
if not artifact:
logger.warning(f"Artifact {name} are not valid. Skipping...")
continue
relative_path = artifact.get("path")
full_path = libraries_dir / Path(relative_path)
if not full_path.is_file() or not full_path.exists():
logger.debug(f"Artifact {name} missing.")
not_found.append(full_path)
return not_found
def generate_classpath(manifest: dict, libraries_dir: Path, core_file_path: Path):
"""
Generate classpath by using specific version's data
:param manifest:
:param libraries_dir:
:param core_file_path:
:return:
not_found: list
"""
libraries = manifest.get("libraries", [])
if not libraries:
raise VersionDataKeyNotFoundException(
"Libraries not found in manifest.",
)
# Same as natives
_, _, os_version = get_platform_info()
platform_key_name, platform_rule_name, platform_arch_type = get_natives_platform_info()
separator = ";" if platform_key_name == "windows" else ":"
classes = []
for library in libraries:
name = library.get("name", "")
artifact_raw = library.get("downloads", {}).get("artifact", {})
rules = library.get("rules", [])
classifiers = library.get("downloads", {}).get("classifiers", {})
natives = library.get("natives", {})
# Ignore natives
if (len(natives) > 0 or bool(re.search(r":natives-[^:]+$", name)) or
any(native.startswith("natives-") for native in classifiers)):
logger.info(f"Skipping {name} because its a native library.")
continue
if not is_library_allowed(
rules,
platform_rule_name,
platform_arch_type,
os_version,
):
continue
artifact = generate_formatted_artifact_with_name(
name,
artifact_raw
)
if not artifact:
logger.warning(f"Artifact {name} are not valid. Skipping...")
continue
relative_path = artifact.get("path")
full_path = libraries_dir / Path(relative_path)
if not full_path.is_file() or not full_path.exists():
raise FileNotFoundError(f"Artifact {name} missing.")
classes.append(full_path.as_posix())
# Append core file
classes.append(core_file_path.as_posix())
return f"{separator}".join(classes)