Skip to content

Commit

Permalink
Fixed issues with relative path for work/main directories. Relative p…
Browse files Browse the repository at this point in the history
…aths are still usable in the launcher code, but when running subprocesses, it is important to make absolute paths. #171
  • Loading branch information
mindstorm38 committed Aug 27, 2023
1 parent 0a29390 commit 30fc1c6
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 18 deletions.
26 changes: 19 additions & 7 deletions portablemc/forge.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def _fetch_version(self, version: VersionHandle, watcher: Watcher) -> None:
if data_val.startswith("/"):
dst_path = post_info.tmp_dir / data_val[1:]
zip_extract_file(install_jar, data_val[1:], dst_path)
data_val = str(dst_path) # Replace by the path of extracted file.
data_val = str(dst_path.absolute()) # Replace by the path of extracted file.

post_info.variables[data_key] = data_val

Expand Down Expand Up @@ -253,6 +253,18 @@ def _resolve_jar(self, watcher: Watcher) -> None:
self._finalize_forge(watcher)

def _finalize_forge(self, watcher: Watcher) -> None:
try:
self._finalize_forge_internal(watcher)
except:
# We just intercept errors and remove the version metadata in case of errors,
# this allows us to re-run the whole install on next attempt.
try:
self._hierarchy[0].metadata_file().unlink()
except FileNotFoundError:
pass # Not a problem if the file isn't present.
raise

def _finalize_forge_internal(self, watcher: Watcher) -> None:
"""This step finalize the forge installation, after both JVM and version's JAR
files has been resolved. This is not always used, it depends on installer's
version.
Expand All @@ -271,14 +283,14 @@ def _finalize_forge(self, watcher: Watcher) -> None:
# Additional missing variables, the version's jar file is the same as the vanilla
# one, so we use its path.
info.variables["SIDE"] = "client"
info.variables["MINECRAFT_JAR"] = str(self._jar_path)
info.variables["MINECRAFT_JAR"] = str(self._jar_path.absolute())

def replace_install_args(txt: str) -> str:
txt = txt.format_map(info.variables)
# Replace the pattern [lib name] with lib path.
if txt[0] == "[" and txt[-1] == "]":
spec = LibrarySpecifier.from_str(txt[1:-1])
txt = str(self.context.libraries_dir / spec.file_path())
txt = str((self.context.libraries_dir / spec.file_path()).absolute())
elif txt[0] == "'" and txt[-1] == "'":
txt = txt[1:-1]
return txt
Expand All @@ -287,7 +299,7 @@ def replace_install_args(txt: str) -> str:

# Extract the main-class from manifest. Required because we cannot use
# both -cp and -jar.
jar_path = info.libraries[processor.jar_name]
jar_path = info.libraries[processor.jar_name].absolute()
main_class = None
with ZipFile(jar_path) as jar_fp:
with jar_fp.open("META-INF/MANIFEST.MF") as manifest_fp:
Expand Down Expand Up @@ -315,8 +327,8 @@ def replace_install_args(txt: str) -> str:

# Compute the full arguments list.
args = [
str(self._jvm_path),
"-cp", os.pathsep.join([str(jar_path), *(str(info.libraries[lib_name]) for lib_name in processor.class_path)]),
str(self._jvm_path.absolute()),
"-cp", os.pathsep.join([str(jar_path), *(str(info.libraries[lib_name].absolute()) for lib_name in processor.class_path)]),
main_class,
*(replace_install_args(arg) for arg in processor.args)
]
Expand All @@ -325,7 +337,7 @@ def replace_install_args(txt: str) -> str:

completed = subprocess.run(args, cwd=self.context.work_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if completed.returncode != 0:
raise ValueError("ERROR")
raise ValueError("ERROR", completed.stdout)

# If there are sha1, check them.
for lib_name, expected_sha1 in processor.sha1.items():
Expand Down
29 changes: 18 additions & 11 deletions portablemc/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ def __init__(self,
"""Construct a Minecraft installation context. This context is used by most of the
installer's tasks to know where to install files and from where to launch the game.
Note that these paths can perfectly be relative paths, they are computed to
absolute paths when needed, so you don't have to care. By default they will be
resolved relatively to the current working directory (of the executing Python
program).
:param main_dir: The main directory where versions, assets, libraries and
optionally JVM are installed. If not specified this path will be set the usual
`.minecraft` (see https://minecraft.fandom.com/fr/wiki/.minecraft).
Expand Down Expand Up @@ -987,6 +992,8 @@ def _resolve_env(self, watcher: Watcher) -> Environment:
"""

assert self._assets_index_version is not None, "_resolve_assets() missing"
assert self._jvm_path is not None, "_resolve_jvm() missing"
assert self._jar_path is not None, "_resolve_jar() missing"

# Main class
main_class = self._metadata.get("mainClass")
Expand All @@ -997,11 +1004,11 @@ def _resolve_env(self, watcher: Watcher) -> Environment:
auth_session = self.auth_session or OfflineAuthSession(None, None)

# Class path, without main class (added later depending on arguments present).
class_path = list(map(str, self._class_libs))
class_path = list(map(lambda path: str(path.absolute()), self._class_libs))

# Environment definition.
env = Environment(self.context, main_class)
env.jvm_args.append(str(self._jvm_path))
env.jvm_args.append(str(self._jvm_path.absolute()))

env.native_libs = self._native_libs.copy()
env.fixes = self._applied_fixes
Expand Down Expand Up @@ -1035,19 +1042,19 @@ def _resolve_env(self, watcher: Watcher) -> Environment:

# JVM argument for logging config
if self._logger_path is not None and self._logger_arg is not None:
env.jvm_args.append(self._logger_arg.replace("${path}", str(self._logger_path)))
env.jvm_args.append(self._logger_arg.replace("${path}", str(self._logger_path.absolute())))

# JVM argument for launch wrapper JAR path
if main_class == "net.minecraft.launchwrapper.Launch":
env.jvm_args.append(f"-Dminecraft.client.jar={self._jar_path}")
env.jvm_args.append(f"-Dminecraft.client.jar={self._jar_path.absolute()}")

# If no modern arguments, fix some arguments.
if modern_args is None:
# Old versions seems to prefer having the main class first in class path.
class_path.insert(0, str(self._jar_path))
class_path.insert(0, str(self._jar_path.absolute()))
else:
# Modern versions seems to prefer having the main class last in class path.
class_path.append(str(self._jar_path))
class_path.append(str(self._jar_path.absolute()))

# Get the last version in the parent's tree, we use it to apply legacy fixes.
ancestor_id = list(self._hierarchy[0].recurse())[-1].id
Expand Down Expand Up @@ -1104,9 +1111,9 @@ def _resolve_env(self, watcher: Watcher) -> Environment:
# Game
"auth_player_name": auth_session.username,
"version_name": self._hierarchy[0].id,
"library_directory": str(self.context.libraries_dir),
"game_directory": str(self.context.work_dir),
"assets_root": str(self.context.assets_dir),
"library_directory": str(self.context.libraries_dir.absolute()),
"game_directory": str(self.context.work_dir.absolute()),
"assets_root": str(self.context.assets_dir.absolute()),
"assets_index_name": self._assets_index_version,
"auth_uuid": auth_session.uuid,
"auth_access_token": auth_session.format_token_argument(False),
Expand All @@ -1116,7 +1123,7 @@ def _resolve_env(self, watcher: Watcher) -> Environment:
"version_type": self._metadata.get("type", ""),
# Game (legacy)
"auth_session": auth_session.format_token_argument(True),
"game_assets": str(self._assets_virtual_dir or ""),
"game_assets": "" if self._assets_virtual_dir is None else str(self._assets_virtual_dir.absolute()),
"user_properties": "{}",
# JVM
"natives_directory": "",
Expand Down Expand Up @@ -1488,7 +1495,7 @@ def run(self, env: Environment) -> None:

from zipfile import ZipFile

bin_dir = env.context.gen_bin_dir()
bin_dir = env.context.gen_bin_dir().absolute()
replacements = env.args_replacements.copy()
replacements["natives_directory"] = str(bin_dir)

Expand Down

0 comments on commit 30fc1c6

Please sign in to comment.