Code some ui and fix parse library will throw exception sometimes.
This commit is contained in:
@@ -11,7 +11,10 @@ from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QW
|
||||
from core_lib.qt.hack import move_window_to_center, is_light_theme, \
|
||||
recolor_icon
|
||||
from core_lib.qt import messagebox
|
||||
from core_lib.launcher.object import Launcher as LauncherObject
|
||||
|
||||
from pages.account import AccountPage
|
||||
from pages.create_profile import CreateProfile
|
||||
from pages.test import TestPage
|
||||
|
||||
LAUNCHER_VERSION = "alpha_0.0.2"
|
||||
@@ -63,7 +66,7 @@ class ToolBar(QtWidgets.QToolBar):
|
||||
button.setToolButtonStyle(
|
||||
Qt.ToolButtonStyle.ToolButtonTextBesideIcon
|
||||
)
|
||||
button.setFixedWidth(150)
|
||||
button.setMinimumWidth(60)
|
||||
|
||||
def add_button(self, text=None, icon: QIcon=None) -> QToolButton:
|
||||
button = QtWidgets.QToolButton(self)
|
||||
@@ -71,6 +74,8 @@ class ToolBar(QtWidgets.QToolBar):
|
||||
if text:
|
||||
text = " "+text
|
||||
button.setText(text)
|
||||
# else:
|
||||
# button.setText("\u00a0")
|
||||
|
||||
if icon:
|
||||
button.setIcon(icon)
|
||||
@@ -86,6 +91,7 @@ class ToolBar(QtWidgets.QToolBar):
|
||||
)
|
||||
|
||||
button.setAutoRaise(True)
|
||||
button.setFixedHeight(60)
|
||||
|
||||
return button
|
||||
|
||||
@@ -93,7 +99,7 @@ class ToolBar(QtWidgets.QToolBar):
|
||||
self._update_widget_size(self.orientation())
|
||||
self.update()
|
||||
|
||||
class Launcher(QApplication):
|
||||
class Launcher(QApplication, LauncherObject):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# Window config
|
||||
@@ -157,10 +163,14 @@ class Launcher(QApplication):
|
||||
# Account page
|
||||
self.account_page = AccountPage(self, self.main_window)
|
||||
|
||||
# Profile page
|
||||
self.create_profile_page = CreateProfile(self, self.main_window)
|
||||
|
||||
# Add page
|
||||
self.central.addWidget(self.home_page)
|
||||
self.central.addWidget(self.test_page)
|
||||
self.central.addWidget(self.account_page)
|
||||
self.central.addWidget(self.create_profile_page)
|
||||
|
||||
# Bind toolbar and center widget
|
||||
self.main_window.addToolBar(Qt.ToolBarArea.LeftToolBarArea, self.toolbar)
|
||||
@@ -182,11 +192,13 @@ class Launcher(QApplication):
|
||||
# Account btn (will be moved to the bottom (or last one item) of the toolbar
|
||||
self.open_account_page = self.toolbar.add_button(icon=self.get_icon(Path(self.icon_dir, "head.png")))
|
||||
|
||||
# Profile page
|
||||
# Profile btn
|
||||
self.open_create_profile_page = self.toolbar.add_button(icon=self.get_icon(Path(self.icon_dir, "add_temp.png")))
|
||||
|
||||
# Bind tool buttons
|
||||
self.toolbar.addWidget(self.home_button)
|
||||
self.toolbar.addWidget(self.open_test_page)
|
||||
self.toolbar.addWidget(self.open_create_profile_page)
|
||||
self.toolbar.setMovable(True)
|
||||
self.toolbar.setFloatable(False)
|
||||
self.toolbar.setAllowedAreas(Qt.ToolBarArea.AllToolBarAreas)
|
||||
@@ -208,6 +220,10 @@ class Launcher(QApplication):
|
||||
lambda: self.set_current_page(self.account_page, self.open_account_page)
|
||||
)
|
||||
|
||||
self.open_create_profile_page.clicked.connect(
|
||||
lambda: self.set_current_page(self.create_profile_page, self.open_create_profile_page)
|
||||
)
|
||||
|
||||
self.set_current_page(self.home_page, self.home_button)
|
||||
|
||||
def exec(self):
|
||||
|
||||
@@ -19,16 +19,18 @@ THREADED_DOWNLOAD_MAX_WORKERS = 10
|
||||
logger = logging.getLogger("Launcher.CoreLib")
|
||||
|
||||
class FileObject:
|
||||
def __init__(self, path: Path, url=None, sha1=None, sha256=None, md5=None, size=None):
|
||||
def __init__(self, path: Path, url=None, sha1=None, sha256=None, sha512=None, md5=None, size=None):
|
||||
self.__path = path
|
||||
|
||||
self.expected_sha1 = sha1
|
||||
self.expected_sha256 = sha256
|
||||
self.expected_sha512 = sha512
|
||||
self.expected_md5 = md5
|
||||
self.expected_size = size
|
||||
|
||||
self.__sha1 = None
|
||||
self.__sha256 = None
|
||||
self.__sha512 = None
|
||||
self.__md5 = None
|
||||
|
||||
self.url = url
|
||||
@@ -73,6 +75,13 @@ class FileObject:
|
||||
|
||||
return self.__sha256
|
||||
|
||||
@property
|
||||
def sha512(self):
|
||||
if self.__sha512 is None:
|
||||
self.__sha512 = self._calculate_hash("sha512")
|
||||
|
||||
return self.__sha512
|
||||
|
||||
@property
|
||||
def md5(self):
|
||||
if self.__md5 is None:
|
||||
@@ -86,6 +95,9 @@ class FileObject:
|
||||
def verify_sha256(self, expected_hash=None):
|
||||
return self.sha256 == expected_hash if expected_hash else self.expected_sha256 == self.sha256
|
||||
|
||||
def verify_sha512(self, expected_hash=None):
|
||||
return self.sha512 == expected_hash if expected_hash else self.expected_sha512 == self.sha512
|
||||
|
||||
def verify_md5(self, expected_hash=None):
|
||||
return self.sha1 == expected_hash if expected_hash else self.expected_sha1 == self.sha1
|
||||
|
||||
@@ -96,6 +108,9 @@ class FileObject:
|
||||
if algorithm == "sha256":
|
||||
return self.verify_sha256(expected_hash if expected_hash else self.expected_sha256)
|
||||
|
||||
if algorithm == "sha512":
|
||||
return self.verify_sha512(expected_hash if expected_hash else self.expected_sha512)
|
||||
|
||||
if algorithm == "md5":
|
||||
return self.verify_md5(expected_hash if expected_hash else self.expected_md5)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import re
|
||||
import shlex
|
||||
|
||||
from core_lib.game.library import get_natives_platform_info
|
||||
@@ -230,3 +231,53 @@ def generate_launch_command(arg_maps: ArgumentMappings, main_class: str, java_pa
|
||||
cmd.extend(parsed_game_args)
|
||||
|
||||
return cmd
|
||||
|
||||
def is_a_argument(arg: str, startswith="--"):
|
||||
pattern = re.compile(rf"{startswith}(.+)")
|
||||
return pattern.match(arg) is not None
|
||||
|
||||
def generate_argument_details(arguments: list[str | dict], startswith="--") -> list[dict]:
|
||||
items = []
|
||||
|
||||
for index, arg in enumerate(arguments):
|
||||
if isinstance(arg, str) and is_a_argument(arg, startswith):
|
||||
items.append({
|
||||
"value": arg,
|
||||
"type": "argument",
|
||||
"dataType": "string",
|
||||
})
|
||||
|
||||
if index > 0:
|
||||
previous = items[index - 1]
|
||||
if previous["type"] == "argument":
|
||||
previous["isFlag"] = True
|
||||
elif isinstance(arg, str):
|
||||
items.append({
|
||||
"value": arg,
|
||||
"type": "value",
|
||||
})
|
||||
elif isinstance(arg, dict):
|
||||
value = arg.get("value", None)
|
||||
|
||||
if not value:
|
||||
raise ValueError("Dictionary argument must contain a value")
|
||||
|
||||
items.append({
|
||||
"value": value,
|
||||
"type": "argument",
|
||||
"dataType": "dictionary",
|
||||
"dictValue": arg,
|
||||
})
|
||||
|
||||
return items
|
||||
|
||||
def is_same_argument(item: dict, another: dict) -> bool:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
from urllib.parse import urlsplit, unquote
|
||||
|
||||
from ..common.common import get_platform_info, FileObject, download_multiple_files, unzip
|
||||
from .version_info import (
|
||||
@@ -19,6 +20,87 @@ from ..common.exception import UnsupportedPlatformException, NativeResolveExcept
|
||||
|
||||
logger = logging.getLogger("Launcher.CoreLib")
|
||||
|
||||
def parse_maven_coordinate(value: str, default_extension="jar"):
|
||||
value = value.strip()
|
||||
|
||||
coordinate, separator, extension = value.partition("@")
|
||||
parts = coordinate.split(":")
|
||||
|
||||
if len(parts) not in (3, 4):
|
||||
raise ValueError(f"Invalid maven coordinate {value}")
|
||||
|
||||
group, artifact, version = parts[:3]
|
||||
classifier = parts[3] if len(parts) == 4 else None
|
||||
|
||||
if version.count("-") > 0:
|
||||
version, extra = version.split("-", 1)
|
||||
|
||||
if not group or not artifact or not version:
|
||||
raise ValueError(f"Coordinate key can not be empty: {value!r}")
|
||||
|
||||
if not extension:
|
||||
extension = default_extension
|
||||
|
||||
return group, artifact, version, classifier, extension
|
||||
|
||||
def convert_maven_version(version: str, failback=None) -> tuple[int, int, int]:
|
||||
try:
|
||||
print(version)
|
||||
items = version.split(".")
|
||||
if len(items) >= 3:
|
||||
major, minor, patch = items[0], items[1], items[2]
|
||||
if len(items) > 3:
|
||||
logger.debug(f"Found version tag: {".".join(items)}")
|
||||
elif len(items) == 2:
|
||||
major, minor, patch = 0, items[0], items[1]
|
||||
else:
|
||||
raise ValueError(f"Unable to parse version: {version!r}")
|
||||
|
||||
return int(major), int(minor), int(patch)
|
||||
except ValueError:
|
||||
return failback
|
||||
|
||||
def generate_filename_from_maven_coordinate(artifact: str, version, classifier="", extension="jar") -> str:
|
||||
classifier = f"-{classifier}" if classifier else ""
|
||||
|
||||
return (
|
||||
f"{artifact}-"
|
||||
f"{version}"
|
||||
f"{classifier}."
|
||||
f"{extension}"
|
||||
)
|
||||
|
||||
def generate_repo_path(group: str, artifact: str, version: str, filename) -> str:
|
||||
group_path = group.replace(".", "/")
|
||||
|
||||
return (
|
||||
f"{group_path}/"
|
||||
f"{artifact}/"
|
||||
f"{version}/"
|
||||
f"{filename}"
|
||||
)
|
||||
|
||||
def is_complete_maven_url(url: str, group: str, artifact: str, version: str, classifier: str="", extension: str=".jar") -> bool:
|
||||
filename = generate_filename_from_maven_coordinate(artifact, version, classifier=classifier, extension=extension)
|
||||
repository_path = generate_repo_path(group, artifact, version, filename)
|
||||
parsed = urlsplit(url)
|
||||
|
||||
if parsed.scheme not in ("http", "https"):
|
||||
return False
|
||||
|
||||
path = unquote(parsed.path).replace("\\", "/")
|
||||
expected_path = "/" + repository_path
|
||||
|
||||
return path.endswith(expected_path)
|
||||
|
||||
def generate_repo_url(maven_url: str, group: str, artifact: str, version: str, classifier="", extension=".jar") -> str:
|
||||
filename = generate_filename_from_maven_coordinate(artifact, version, classifier=classifier, extension=extension)
|
||||
path = generate_repo_path(group, artifact, version, filename)
|
||||
|
||||
if not maven_url.endswith("/"):
|
||||
maven_url += "/"
|
||||
|
||||
return f"{maven_url}{path}"
|
||||
|
||||
def is_library_allowed(rules, platform_rule_name, platform_arch_type, platform_version):
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from core_lib.common.exception import VersionManifestFetchException, VersionDataKeyNotFoundException, \
|
||||
VersionManifestException
|
||||
|
||||
logger = logging.getLogger("Launcher.CoreLib")
|
||||
|
||||
FABRIC_META_VERSION_ENDPOINT_V2 = "https://meta.fabricmc.net/v2/versions"
|
||||
FABRIC_META_LOADER_VERSION_ENDPOINT_V2 = "https://meta.fabricmc.net/v1/versions/loader/{}/"
|
||||
FABRIC_META_LOADER_MANIFEST_ENDPOINT_V2 = "https://meta.fabricmc.net//v2/versions/loader/{}/{}/profile/json"
|
||||
FABRIC_MAVEN_URL = "https://maven.fabricmc.net/"
|
||||
|
||||
def fetch_fabric_version_list(version_manifest_url: str=FABRIC_META_VERSION_ENDPOINT_V2, timeout: int=5) -> list[dict]:
|
||||
try:
|
||||
r = requests.get(version_manifest_url, timeout=timeout)
|
||||
except requests.exceptions.Timeout:
|
||||
raise VersionManifestFetchException(
|
||||
"Timeout while fetch from {}".format(version_manifest_url)
|
||||
)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
raise VersionManifestFetchException(
|
||||
"Connection error while fetch from {}: {}".format(version_manifest_url, e)
|
||||
)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
raise VersionManifestFetchException(
|
||||
"HTTP error while fetch from {}: HTTP Status: {}".format(version_manifest_url, e.response.status_code)
|
||||
)
|
||||
except Exception as e:
|
||||
raise VersionManifestFetchException(
|
||||
"Unknown error while fetch from {}: {}".format(version_manifest_url, e)
|
||||
)
|
||||
|
||||
try:
|
||||
data = r.json()
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
raise VersionManifestFetchException(
|
||||
"JSON decode error while fetch from {}: {}".format(version_manifest_url, e)
|
||||
)
|
||||
|
||||
versions = data.get("versions")
|
||||
|
||||
if not versions:
|
||||
raise VersionManifestFetchException(
|
||||
"Version this is empty or not set yet in mod version manifest"
|
||||
)
|
||||
|
||||
return versions
|
||||
|
||||
def is_version_supported_and_stable(fabric_version_list: list[dict], target_version: str) -> tuple[bool, bool | None]:
|
||||
if not fabric_version_list:
|
||||
raise VersionManifestException(
|
||||
"Provided fabric version list is empty."
|
||||
)
|
||||
|
||||
for version in fabric_version_list:
|
||||
if version["version"] == target_version:
|
||||
is_stable = version.get("stable", None)
|
||||
return True, is_stable
|
||||
|
||||
return False, None
|
||||
|
||||
def get_version_support_loader_list(target_version: str, loader_version_url: str=FABRIC_META_LOADER_VERSION_ENDPOINT_V2, timeout: int=5)\
|
||||
-> list[dict]:
|
||||
try:
|
||||
r = requests.get(loader_version_url.format(target_version), timeout=timeout)
|
||||
except requests.exceptions.Timeout:
|
||||
raise VersionManifestFetchException(
|
||||
"Timeout while fetch from {}".format(loader_version_url)
|
||||
)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
raise VersionManifestFetchException(
|
||||
"Connection error while fetch from {}: {}".format(loader_version_url, e)
|
||||
)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
raise VersionManifestFetchException(
|
||||
"HTTP error while fetch from {}: HTTP Status: {}".format(loader_version_url, e.response.status_code)
|
||||
)
|
||||
except Exception as e:
|
||||
raise VersionManifestFetchException(
|
||||
"Unknown error while fetch from {}: {}".format(loader_version_url, e)
|
||||
)
|
||||
|
||||
try:
|
||||
versions = r.json()
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
raise VersionManifestFetchException(
|
||||
"JSON decode error while fetch from {}: {}".format(loader_version_url, e)
|
||||
)
|
||||
|
||||
loaders = []
|
||||
|
||||
for version in versions:
|
||||
loader = version.get("loader", None)
|
||||
if not loader:
|
||||
logger.warning("Found corrupted or unsupported loader manifest")
|
||||
continue
|
||||
|
||||
loaders.append(loader)
|
||||
|
||||
if not loaders:
|
||||
raise VersionManifestException(
|
||||
"No available loader found in {}".format(loader_version_url)
|
||||
)
|
||||
|
||||
return loaders
|
||||
|
||||
def get_loader_manifest_from_loader_data(target_version: str, loader_data: dict,
|
||||
loader_manifest_url: str=FABRIC_META_LOADER_MANIFEST_ENDPOINT_V2) -> dict:
|
||||
loader_ver = loader_data.get("version", None)
|
||||
if not loader_ver:
|
||||
raise VersionDataKeyNotFoundException(
|
||||
"Version key not found in loader manifest {}".format(loader_manifest_url)
|
||||
)
|
||||
|
||||
# Create url
|
||||
url = loader_manifest_url.format(target_version, loader_ver)
|
||||
logger.debug("Full manifest url: {}".format(url))
|
||||
|
||||
try:
|
||||
r = requests.get(url, timeout=5)
|
||||
except requests.exceptions.Timeout:
|
||||
raise VersionManifestFetchException(
|
||||
"Timeout while fetch from {}".format(url)
|
||||
)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
raise VersionManifestFetchException(
|
||||
"Connection error while fetch from {}: {}".format(url, e)
|
||||
)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
raise VersionManifestFetchException(
|
||||
"HTTP error while fetch from {}: {}".format(url, e.response.status_code)
|
||||
)
|
||||
except Exception as e:
|
||||
raise VersionManifestFetchException(
|
||||
"Unknown error while fetch from {}: {}".format(url, e)
|
||||
)
|
||||
|
||||
# We don't need verify data, fabric didn't provide manifest hash.
|
||||
try:
|
||||
return r.json()
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
raise VersionManifestFetchException(
|
||||
"JSON decode error while fetch from {}: {}".format(url, e)
|
||||
)
|
||||
except Exception as e:
|
||||
raise VersionManifestFetchException(
|
||||
"Unknown error while fetch from {}: {}".format(url, e)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
import shlex
|
||||
|
||||
from core_lib.common.exception import VersionDataKeyNotFoundException, DependencyException
|
||||
from core_lib.game.library import parse_maven_coordinate, convert_maven_version, generate_repo_url, \
|
||||
is_complete_maven_url
|
||||
from core_lib.game.version_info import MOJANG_LIBRARIES_ENDPOINT
|
||||
|
||||
logger = logging.getLogger("Launcher.CoreLib")
|
||||
|
||||
def find_useful_part_in_mod_version_manifest(mod_version_manifest: dict):
|
||||
ver_id = mod_version_manifest.get("id", None)
|
||||
inherits_from = mod_version_manifest.get("inheritsFrom", None)
|
||||
ver_type = mod_version_manifest.get("type", None)
|
||||
main_class = mod_version_manifest.get("mainClass", None)
|
||||
arguments = mod_version_manifest.get("arguments", {})
|
||||
old_arguments = mod_version_manifest.get("minecraftArguments", None)
|
||||
libraries = mod_version_manifest.get("libraries", [])
|
||||
|
||||
if not ver_id:
|
||||
raise VersionDataKeyNotFoundException(
|
||||
"Provided mod loader version manifest does not contain key \"id\"."
|
||||
)
|
||||
|
||||
if not ver_type:
|
||||
raise VersionDataKeyNotFoundException(
|
||||
"Provided mod loader version manifest does not contain key \"type\"."
|
||||
)
|
||||
|
||||
if not inherits_from:
|
||||
raise VersionDataKeyNotFoundException(
|
||||
"Provided mod loader version manifest does not contain key \"inheritsFrom\"."
|
||||
)
|
||||
|
||||
if not main_class:
|
||||
raise VersionDataKeyNotFoundException(
|
||||
"Provided mod loader version manifest does not contain key \"mainClass\"."
|
||||
)
|
||||
|
||||
if not arguments and not old_arguments:
|
||||
raise VersionDataKeyNotFoundException(
|
||||
"Provided mod loader version manifest does not contain key \"arguments\" or \"minecraftArguments\"."
|
||||
)
|
||||
|
||||
if not libraries:
|
||||
raise VersionDataKeyNotFoundException(
|
||||
"Provided mod loader version manifest does not contain key \"libraries\"."
|
||||
)
|
||||
|
||||
return {
|
||||
"id": ver_id,
|
||||
"type": ver_type,
|
||||
"inheritsFrom": inherits_from,
|
||||
"minecraftArguments": old_arguments,
|
||||
"libraries": libraries,
|
||||
"arguments": arguments,
|
||||
"mainClass": main_class,
|
||||
}
|
||||
|
||||
def merge_necessary_dependencies(inherits_libraries: list, mod_libraries: list, mod_maven_url=None,
|
||||
inherits_maven_url=MOJANG_LIBRARIES_ENDPOINT, default_extension="jar") -> list[dict]:
|
||||
|
||||
inherits = {}
|
||||
result = []
|
||||
|
||||
# ensure below step won't modify original data
|
||||
inherits_libraries = deepcopy(inherits_libraries)
|
||||
mod_libraries = deepcopy(mod_libraries)
|
||||
|
||||
def check_lib(lib: dict):
|
||||
url: str = lib.get("url", "")
|
||||
source_from = lib["sourceFrom"]
|
||||
lib_group=lib["parsed"]["group"]
|
||||
lib_artifact=lib["parsed"]["artifact"]
|
||||
lib_version=lib["parsed"]["version"]
|
||||
lib_classifier=lib["parsed"]["classifier"]
|
||||
lib_extension=lib["parsed"]["extension"]
|
||||
|
||||
recreated = False
|
||||
|
||||
if not url:
|
||||
if source_from == "official":
|
||||
new_url = generate_repo_url(inherits_maven_url, lib_group, lib_artifact, lib_version, lib_classifier,
|
||||
lib_extension)
|
||||
else:
|
||||
new_url = generate_repo_url(mod_maven_url, lib_group, lib_artifact, lib_version, lib_classifier,
|
||||
lib_extension)
|
||||
|
||||
lib["url"] = new_url
|
||||
|
||||
recreated = True
|
||||
|
||||
if not recreated:
|
||||
url_result = is_complete_maven_url(url, lib_group, lib_artifact, lib_version, lib_classifier, lib_extension)
|
||||
|
||||
if not url_result:
|
||||
# deal with some weird maven url that don't contain repo path
|
||||
# Some mod loader (such as fabric), did not contain a full path inside the library. We will need to
|
||||
# generate it from maven coordinate (aka lib_name)
|
||||
target_maven = mod_maven_url or url
|
||||
repo_url = generate_repo_url(target_maven, lib_group, lib_artifact, lib_version, lib_classifier,
|
||||
lib_extension)
|
||||
lib["url"] = repo_url
|
||||
|
||||
for library in inherits_libraries:
|
||||
name = library.get("name", None)
|
||||
|
||||
if not name:
|
||||
raise DependencyException(
|
||||
"Provided inherited library does not contain key \"name\"."
|
||||
)
|
||||
|
||||
group, artifact, version, classifier, extension = parse_maven_coordinate(name,
|
||||
default_extension=default_extension)
|
||||
|
||||
# Save into dict for next step to check if library is existing
|
||||
if not f"{group}:{artifact}" in inherits:
|
||||
inherits[f"{group}:{artifact}"] = library
|
||||
inherits[f"{group}:{artifact}"]["parsed"] = {
|
||||
"group": group,
|
||||
"artifact": artifact,
|
||||
"version": version,
|
||||
"classifier": classifier,
|
||||
"extension": extension,
|
||||
}
|
||||
inherits[f"{group}:{artifact}"]["sourceFrom"] = "official"
|
||||
else:
|
||||
logger.warning("Duplicate library: {}".format(name))
|
||||
|
||||
for mod_library in mod_libraries:
|
||||
name = mod_library.get("name", None)
|
||||
group, artifact, version, classifier, extension = parse_maven_coordinate(name, default_extension=default_extension)
|
||||
|
||||
ver_converted = convert_maven_version(version)
|
||||
|
||||
if not name:
|
||||
raise DependencyException(
|
||||
"Provided mod library does not contain key \"name\"."
|
||||
)
|
||||
|
||||
if ver_converted is None:
|
||||
raise DependencyException(
|
||||
"Unable to convert inherits library version {} to tuple".format(version),
|
||||
)
|
||||
|
||||
group, artifact, version, classifier, extension = parse_maven_coordinate(name, default_extension=default_extension)
|
||||
|
||||
mod_library["sourceFrom"] = "mod"
|
||||
mod_library["parsed"] = {
|
||||
"group": group,
|
||||
"artifact": artifact,
|
||||
"version": version,
|
||||
"classifier": classifier,
|
||||
"extension": extension,
|
||||
}
|
||||
|
||||
if f"{group}:{artifact}" in inherits:
|
||||
found_lib = inherits.pop(f"{group}:{artifact}")
|
||||
found_lib_ver = convert_maven_version(found_lib["parsed"]["version"], None)
|
||||
|
||||
if found_lib_ver is None:
|
||||
raise DependencyException(
|
||||
"Unable to convert mod library version {} to tuple".format(found_lib["version"])
|
||||
)
|
||||
|
||||
if found_lib_ver > ver_converted or found_lib_ver == ver_converted:
|
||||
result.append(found_lib)
|
||||
elif found_lib_ver < ver_converted:
|
||||
result.append(mod_library)
|
||||
else:
|
||||
result.append(mod_library)
|
||||
|
||||
# Add the remaining dependencies back to result
|
||||
for key in inherits:
|
||||
value = inherits[key]
|
||||
result.append(value)
|
||||
|
||||
for library in result:
|
||||
check_lib(library)
|
||||
|
||||
return result
|
||||
|
||||
def merge_arguments(inherits_arguments: str | list, mod_arguments: str | list) -> list[str | dict]:
|
||||
def str_to_list(args):
|
||||
p = shlex.split(args)
|
||||
return p
|
||||
|
||||
if isinstance(inherits_arguments, str):
|
||||
inherits_arguments = str_to_list(inherits_arguments)
|
||||
|
||||
if isinstance(mod_arguments, str):
|
||||
mod_arguments = str_to_list(mod_arguments)
|
||||
|
||||
result = []
|
||||
result.extend(inherits_arguments)
|
||||
result.extend(mod_arguments)
|
||||
|
||||
return result
|
||||
|
||||
def merge_game_and_jvm_args(inherits_args: dict[str, list[str]] | str, mod_args: dict[str, list[str]] | str) -> dict:
|
||||
inherits_game = inherits_args.get("game", []) if isinstance(inherits_args, dict) else inherits_args
|
||||
inherits_jvm = inherits_args.get("jvm", []) if isinstance(inherits_args, dict) else []
|
||||
|
||||
mod_game = mod_args.get("game", []) if isinstance(mod_args, dict) else mod_args
|
||||
mod_jvm = mod_args.get("jvm", []) if isinstance(mod_args, dict) else []
|
||||
|
||||
merged = {
|
||||
"game": merge_arguments(inherits_game, mod_game),
|
||||
"jvm": merge_arguments(inherits_jvm, mod_jvm),
|
||||
}
|
||||
|
||||
return merged
|
||||
|
||||
def merge_manifest(inherits_version_manifest: dict, mod_version_manifest: dict, mod_maven_url=None) -> dict:
|
||||
merged_manifest = deepcopy(inherits_version_manifest)
|
||||
|
||||
# main class
|
||||
main_class = mod_version_manifest.get("mainClass", None)
|
||||
|
||||
# Apply main class change
|
||||
if main_class:
|
||||
merged_manifest["mainClass"] = main_class
|
||||
|
||||
# Libraries
|
||||
inherits_libraries = inherits_version_manifest.get("libraries", [])
|
||||
mod_libraries = mod_version_manifest.get("libraries", [])
|
||||
|
||||
if not inherits_libraries or not mod_libraries:
|
||||
raise VersionDataKeyNotFoundException(
|
||||
"Inherited (or mod) version manifest does not contain key \"libraries\"."
|
||||
)
|
||||
|
||||
merged_manifest["libraries"] = merge_necessary_dependencies(inherits_libraries, mod_libraries,
|
||||
mod_maven_url=mod_maven_url)
|
||||
|
||||
# arguments
|
||||
inherits_arguments = inherits_version_manifest.get("arguments", []) or inherits_version_manifest.get("minecraftArguments", "")
|
||||
mod_arguments = mod_version_manifest.get("arguments", {}) or mod_version_manifest.get("minecraftArguments", {})
|
||||
|
||||
merged_manifest["arguments"] = merge_game_and_jvm_args(inherits_arguments, mod_arguments)
|
||||
|
||||
return merged_manifest
|
||||
@@ -1,6 +1,5 @@
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
class Launcher:
|
||||
def __init__(self):
|
||||
pass
|
||||
+108
-22
@@ -1,38 +1,124 @@
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtCore import Qt, QSize
|
||||
from PySide6.QtGui import QPixmap
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QListWidget, QPushButton, QHBoxLayout, QListWidgetItem, \
|
||||
QMenu
|
||||
|
||||
|
||||
class AccountWidget(QWidget):
|
||||
def __init__(self, account_name, account_head: QPixmap, login_status, parent=None,
|
||||
/, delete_account_callback=None, check_account_callback=None, switch_account_callback=None):
|
||||
QWidget.__init__(self, parent)
|
||||
|
||||
self.layout = QHBoxLayout(self)
|
||||
|
||||
self.head_icon_label = QLabel()
|
||||
self.head_icon_label.setObjectName("HeadIcon")
|
||||
self.head_icon_label.setPixmap(account_head)
|
||||
|
||||
self.account_name = QLabel(account_name)
|
||||
|
||||
self.status_label = QLabel(login_status)
|
||||
|
||||
self.layout.addWidget(self.head_icon_label)
|
||||
self.layout.addWidget(self.account_name)
|
||||
self.layout.addStretch()
|
||||
self.layout.addWidget(self.status_label)
|
||||
|
||||
self.delete_account_callback = delete_account_callback
|
||||
self.check_account_callback = check_account_callback
|
||||
self.switch_account_callback = switch_account_callback
|
||||
|
||||
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self.show_right_click_menu)
|
||||
|
||||
def show_right_click_menu(self, position):
|
||||
context_menu = QMenu(self)
|
||||
|
||||
switch_acc_action = context_menu.addAction("Switch to this account")
|
||||
check_log_stat_action = context_menu.addAction("Check login status")
|
||||
|
||||
context_menu.addSeparator()
|
||||
|
||||
delete_account_action = context_menu.addAction("Delete account")
|
||||
|
||||
global_position = self.mapToGlobal(position)
|
||||
|
||||
selected_action = context_menu.exec(global_position)
|
||||
|
||||
if selected_action == delete_account_action:
|
||||
# handle callback here
|
||||
if callable(self.delete_account_callback): self.delete_account_callback()
|
||||
elif selected_action == check_log_stat_action and callable(self.check_account_callback): self.check_account_callback()
|
||||
elif selected_action == switch_acc_action and callable(self.switch_account_callback): self.switch_account_callback()
|
||||
|
||||
class AccountPage(QWidget):
|
||||
def __init__(self, app, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
self.app = app
|
||||
|
||||
self.layout = QVBoxLayout()
|
||||
self.layout = QHBoxLayout()
|
||||
|
||||
# center message (will be deleted if launcher finished development)
|
||||
content = """Still digging!"""
|
||||
message = "If you found that something might be a issue. You can report it to the main repo! Any suggestion are welcome."
|
||||
self.icon_label = QLabel()
|
||||
self.icon_label.setObjectName("icon_label")
|
||||
self.dev_info_box = QLabel(content)
|
||||
self.dev_info_box.setObjectName("dev_info_box")
|
||||
self.dev_info_2 = QLabel(message)
|
||||
self.dev_info_2.setObjectName("dev_info_message")
|
||||
# content = """Still digging!"""
|
||||
# message = "If you found that something might be a issue. You can report it to the main repo! Any suggestion are welcome."
|
||||
# self.icon_label = QLabel()
|
||||
# self.icon_label.setObjectName("icon_label")
|
||||
# self.dev_info_box = QLabel(content)
|
||||
# self.dev_info_box.setObjectName("dev_info_box")
|
||||
# self.dev_info_2 = QLabel(message)
|
||||
# self.dev_info_2.setObjectName("dev_info_message")
|
||||
#
|
||||
# self.layout.addStretch()
|
||||
# self.layout.addWidget(self.icon_label, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||
# self.layout.addWidget(self.dev_info_box, alignment=Qt.AlignmentFlag.AlignHCenter)
|
||||
# self.layout.addWidget(self.dev_info_2, alignment=Qt.AlignmentFlag.AlignHCenter)
|
||||
# self.layout.addStretch()
|
||||
#
|
||||
# if Path(self.app.resources_dir, "pictures", "in_progress.png").exists():
|
||||
# image = QPixmap(Path(self.app.resources_dir, "pictures", "in_progress.png"))
|
||||
# self.icon_label.setPixmap(image.scaled(300, 300))
|
||||
# else:
|
||||
# self.icon_label.setText("Ouch. The icon is missing.")
|
||||
#
|
||||
# self.setLayout(self.layout)
|
||||
# self.app.apply_qss(Path("main.qss"), self.setStyleSheet)
|
||||
|
||||
self.layout.addStretch()
|
||||
self.layout.addWidget(self.icon_label, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||
self.layout.addWidget(self.dev_info_box, alignment=Qt.AlignmentFlag.AlignHCenter)
|
||||
self.layout.addWidget(self.dev_info_2, alignment=Qt.AlignmentFlag.AlignHCenter)
|
||||
self.layout.addStretch()
|
||||
self.main_layout = QVBoxLayout()
|
||||
self.right_layout = QVBoxLayout()
|
||||
|
||||
if Path(self.app.resources_dir, "pictures", "in_progress.png").exists():
|
||||
image = QPixmap(Path(self.app.resources_dir, "pictures", "in_progress.png"))
|
||||
self.icon_label.setPixmap(image.scaled(300, 300))
|
||||
else:
|
||||
self.icon_label.setText("Ouch. The icon is missing.")
|
||||
self.account_list = QListWidget(self)
|
||||
|
||||
self.add_account_button = QPushButton("Add Account")
|
||||
self.refresh_button = QPushButton("Refresh all")
|
||||
self.delete_all_button = QPushButton("Delete All")
|
||||
|
||||
self.main_layout.addWidget(self.account_list)
|
||||
self.right_layout.addWidget(self.add_account_button)
|
||||
self.right_layout.addWidget(self.refresh_button)
|
||||
self.right_layout.addWidget(self.delete_all_button)
|
||||
self.right_layout.addStretch()
|
||||
|
||||
self.layout.addLayout(self.main_layout)
|
||||
self.layout.addLayout(self.right_layout)
|
||||
|
||||
self.setLayout(self.layout)
|
||||
self.app.apply_qss(Path("main.qss"), self.setStyleSheet)
|
||||
|
||||
self.test()
|
||||
|
||||
def test(self):
|
||||
head_icon = self.app.get_icon(Path(self.app.icon_dir, "head.png"))
|
||||
for i in range(1, 11):
|
||||
name = f"Player_{i}"
|
||||
head_pixmap = head_icon.pixmap(QSize(50, 50))
|
||||
list_item = QListWidgetItem()
|
||||
|
||||
def delete():
|
||||
self.account_list.removeItemWidget(list_item)
|
||||
item = AccountWidget(name, head_pixmap, self, delete_account_callback=delete)
|
||||
list_item.setSizeHint(item.sizeHint())
|
||||
self.account_list.addItem(list_item)
|
||||
self.account_list.setItemWidget(list_item, item)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6 import QtGui
|
||||
from PySide6.QtCore import QSize
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QListWidget, QPushButton, QLabel, QHBoxLayout
|
||||
|
||||
from core_lib.launcher.object import Launcher as LauncherObject
|
||||
|
||||
class CreateProfile(QWidget):
|
||||
def __init__(self, launcher: "LauncherObject",parent=None):
|
||||
super(CreateProfile, self).__init__(parent)
|
||||
self.app = launcher
|
||||
|
||||
self.layout = QHBoxLayout()
|
||||
|
||||
self.main_layout = QVBoxLayout()
|
||||
self.right_layout = QVBoxLayout()
|
||||
self.operate_layout = QHBoxLayout()
|
||||
|
||||
# Some items
|
||||
self.version_type_label = QLabel("Version Type")
|
||||
self.version_type_dropdown = QComboBox()
|
||||
self.version_type_dropdown.setObjectName("Dropdown")
|
||||
|
||||
self.mod_loader_icon_label = QLabel()
|
||||
icon = self.app.get_icon(Path(self.app.icon_dir, "mod_loader_temp.png"))
|
||||
pixmap = icon.pixmap(QSize(100, 100))
|
||||
self.mod_loader_icon_label.setPixmap(pixmap)
|
||||
self.mod_loader_label = QLabel("Mod Loader")
|
||||
self.mod_loader_dropdown = QComboBox()
|
||||
self.mod_loader_dropdown.setObjectName("Dropdown")
|
||||
|
||||
self.version_list_type_label = QLabel("Version: Vanilla")
|
||||
self.version_list_label = QLabel("Select a version to continue:")
|
||||
self.version_list = QListWidget()
|
||||
self.version_list.setObjectName("ListWidget")
|
||||
|
||||
self.create_profile = QPushButton("Create Profile")
|
||||
self.create_profile.setObjectName("Button")
|
||||
|
||||
self.refresh_button = QPushButton("Refresh")
|
||||
self.refresh_button.setObjectName("Button")
|
||||
|
||||
self.main_layout.addWidget(self.version_list_label)
|
||||
self.main_layout.addWidget(self.version_list)
|
||||
self.operate_layout.addWidget(self.version_list_type_label)
|
||||
self.operate_layout.addStretch()
|
||||
self.operate_layout.addWidget(self.refresh_button)
|
||||
self.operate_layout.addWidget(self.create_profile)
|
||||
|
||||
self.main_layout.addLayout(self.operate_layout)
|
||||
|
||||
self.right_layout.addWidget(self.mod_loader_icon_label)
|
||||
self.right_layout.addWidget(self.mod_loader_label)
|
||||
self.right_layout.addWidget(self.mod_loader_dropdown)
|
||||
self.right_layout.addWidget(self.version_type_label)
|
||||
self.right_layout.addWidget(self.version_type_dropdown)
|
||||
self.right_layout.addStretch()
|
||||
self.right_layout.setContentsMargins(0, 20, 0, 0)
|
||||
|
||||
self.setLayout(self.layout)
|
||||
self.layout.addLayout(self.main_layout)
|
||||
self.layout.addLayout(self.right_layout)
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ QToolBar:left QToolButton,
|
||||
QToolBar:right QToolButton {
|
||||
padding: 14px 6px;
|
||||
min-width: 0;
|
||||
border-bottom: 2px solid palette(midlight);
|
||||
}
|
||||
|
||||
QToolBar QPushButton:hover,
|
||||
|
||||
Reference in New Issue
Block a user