-
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #55 from mindstorm38/addon/forge
Addon/forge
- Loading branch information
Showing
37 changed files
with
926 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/.idea | ||
.idea | ||
__pycache__ | ||
/packages | ||
/dist | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Forge add-on | ||
The forge add-on allows you to install and run Minecraft with forge mod loader in a single command | ||
line! | ||
|
||
### Usage | ||
This add-on extends the syntax accepted by the [start](/README.md#start-the-game) sub-command, by | ||
prepending the version with `forge:`. Almost all releases are supported by forge, the latest | ||
releases are often supported, if not please refer to forge website. You can also append either | ||
`-recommended` or `-latest` to the version to take the corresponding version according to the | ||
forge public information, this is reflecting the "Download Latest" and "Download Recommended" on | ||
the forge website. You can also use version aliases like `release` or equivalent empty version | ||
(just `forge:`). You can also give the exact forge version like `1.18.1-39.0.7`, in such cases, | ||
no HTTP request is made if the version is already installed. | ||
|
||
### Examples | ||
```sh | ||
portablemc start forge: # Install recommended forge version for latest release | ||
portablemc start forge:release # Same as above | ||
portablemc start forge:1.18.1 # Install recommended forge for 1.18.1 | ||
portablemc start forge:1.18.1-39.0.7 # Install the exact forge version 1.18.1-39.0.7 | ||
``` | ||
|
||
### Credits | ||
- [Forge Website](https://files.minecraftforge.net/net/minecraftforge/forge/) | ||
- Consider supporting [LexManos](https://www.patreon.com/LexManos/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
from argparse import ArgumentParser, Namespace | ||
from typing import Dict, List | ||
from os import path | ||
import subprocess | ||
import sys | ||
import os | ||
|
||
from portablemc import Version, \ | ||
DownloadList, DownloadEntry, DownloadError, \ | ||
BaseError, \ | ||
http_request, json_simple_request, cli as pmc | ||
|
||
from portablemc.cli import CliContext | ||
|
||
|
||
def load(_pmc): | ||
|
||
# Private mixins | ||
|
||
@pmc.mixin() | ||
def register_start_arguments(old, parser: ArgumentParser): | ||
_ = pmc.get_message | ||
parser.add_argument("--forge-prefix", help=_("args.start.forge_prefix"), default="forge", metavar="PREFIX") | ||
old(parser) | ||
|
||
@pmc.mixin() | ||
def cmd_start(old, ns: Namespace, ctx: CliContext): | ||
try: | ||
return old(ns, ctx) | ||
except ForgeInvalidMainDirectory: | ||
pmc.print_task("FAILED", "start.forge.error.invalid_main_dir", done=True) | ||
sys.exit(pmc.EXIT_FAILURE) | ||
except ForgeInstallerFailed as err: | ||
pmc.print_task("FAILED", f"start.forge.error.installer_{err.return_code}", done=True) | ||
sys.exit(pmc.EXIT_FAILURE) | ||
except ForgeVersionNotFound as err: | ||
pmc.print_task("FAILED", f"start.forge.error.{err.code}", {"version": err.version}, done=True) | ||
sys.exit(pmc.EXIT_VERSION_NOT_FOUND) | ||
|
||
@pmc.mixin() | ||
def new_version(old, ctx: CliContext, version_id: str) -> Version: | ||
|
||
if version_id.startswith("forge:"): | ||
|
||
main_dir = path.dirname(ctx.versions_dir) | ||
if main_dir != path.dirname(ctx.libraries_dir): | ||
raise ForgeInvalidMainDirectory() | ||
|
||
game_version = version_id[6:] | ||
if not len(game_version): | ||
game_version = "release" | ||
|
||
manifest = pmc.load_version_manifest(ctx) | ||
game_version, game_version_alias = manifest.filter_latest(game_version) | ||
|
||
forge_version = None | ||
|
||
# If the version is an alias, we know that the version needs to be resolved from the forge | ||
# promotion metadata. It's also the case if the version ends with '-recommended' or '-latest', | ||
# or if the version doesn't contains a "-". | ||
if game_version_alias or game_version.endswith(("-recommended", "-latest")) or "-" not in game_version: | ||
promo_versions = request_promo_versions() | ||
for suffix in ("", "-recommended", "-latest"): | ||
tmp_forge_version = promo_versions.get(f"{game_version}{suffix}") | ||
if tmp_forge_version is not None: | ||
if game_version.endswith("-recommended"): | ||
game_version = game_version[:-12] | ||
elif game_version.endswith("-latest"): | ||
game_version = game_version[:-7] | ||
forge_version = f"{game_version}-{tmp_forge_version}" | ||
break | ||
|
||
if forge_version is None: | ||
# Test if the user has given the full forge version | ||
forge_version = game_version | ||
|
||
version_id = f"{ctx.ns.forge_prefix}-{forge_version}" | ||
version_dir = ctx.get_version_dir(version_id) | ||
version = Version(ctx, version_id) | ||
|
||
# Extract minecraft version from the full forge version | ||
mc_version_id = forge_version[:max(0, forge_version.find("-"))] | ||
|
||
# List of possible artifacts names-version, some versions (e.g. 1.7) have the minecraft | ||
# version in suffix of the version in addition to the suffix. | ||
possible_artifact_versions = [forge_version, f"{forge_version}-{mc_version_id}"] | ||
|
||
# Check if Forge should be installed, based on version meta file and potentially missing forge lib. | ||
version_meta_file = path.join(version_dir, f"{version_id}.json") | ||
should_install = not path.isfile(version_meta_file) | ||
if not should_install: | ||
should_install = True | ||
local_artifact_path = path.join(ctx.libraries_dir, "net", "minecraftforge", "forge") | ||
for possible_version in possible_artifact_versions: | ||
for possible_classifier in (possible_version, f"{possible_version}-client"): | ||
artifact_jar = path.join(local_artifact_path, possible_version, f"forge-{possible_classifier}.jar") | ||
if path.isfile(artifact_jar): | ||
should_install = False | ||
break | ||
|
||
if should_install: | ||
|
||
# 1.7 used to have an additional suffix with minecraft version. | ||
installer_file = path.join(version_dir, "installer.jar") | ||
|
||
pmc.print_task("", "start.forge.installer.resolving", {"version": forge_version}) | ||
|
||
found_installer = False | ||
dl_list = DownloadList() | ||
for possible_version in possible_artifact_versions: | ||
try: | ||
installer_url = f"https://maven.minecraftforge.net/net/minecraftforge/forge/{possible_version}/forge-{possible_version}-installer.jar" | ||
dl_list.reset() | ||
dl_list.append(DownloadEntry(installer_url, installer_file)) | ||
dl_list.download_files() | ||
pmc.print_task("OK", "start.forge.installer.found", {"version": forge_version}, done=True) | ||
found_installer = True | ||
break | ||
except DownloadError: | ||
pass | ||
|
||
if not found_installer: | ||
raise ForgeVersionNotFound(ForgeVersionNotFound.INSTALLER_NOT_FOUND, forge_version) | ||
|
||
# We ensure that the parent Minecraft version JAR and metadata are | ||
# downloaded because it's needed by installers. | ||
if len(mc_version_id): | ||
try: | ||
pmc.print_task("", "start.forge.vanilla.resolving", {"version": mc_version_id}) | ||
mc_version = Version(ctx, mc_version_id) | ||
mc_version.prepare_meta() | ||
mc_version.prepare_jar() | ||
# mc_version.download() # Use pretty download?? | ||
pmc.pretty_download(mc_version.dl) | ||
pmc.print_task("OK", "start.forge.vanilla.found", {"version": mc_version_id}, done=True) | ||
except DownloadError: | ||
raise ForgeVersionNotFound(ForgeVersionNotFound.MINECRAFT_VERSION_NOT_FOUND, mc_version_id) | ||
|
||
pmc.print_task("", "start.forge.wrapper.running") | ||
wrapper_jar_file = path.join(path.dirname(__file__), "wrapper", "target", "wrapper.jar") | ||
wrapper_completed = subprocess.run([ | ||
"java", | ||
"-cp", path.pathsep.join([wrapper_jar_file, installer_file]), | ||
"portablemc.wrapper.Main", | ||
main_dir, | ||
version_id | ||
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | ||
os.remove(installer_file) | ||
pmc.print_task("OK", "start.forge.wrapper.done", done=True) | ||
|
||
if wrapper_completed.returncode != 0: | ||
raise ForgeInstallerFailed(wrapper_completed.returncode) | ||
|
||
pmc.print_task("INFO", "start.forge.consider_support", done=True) | ||
|
||
return version | ||
|
||
return old(ctx, version_id) | ||
|
||
# Messages | ||
|
||
pmc.messages.update({ | ||
"args.start.forge_prefix": "Change the prefix of the version ID when starting with Forge.", | ||
"start.forge.installer.resolving": "Resolving forge {version}...", | ||
"start.forge.installer.found": "Found installer for forge {version}.", | ||
"start.forge.vanilla.resolving": "Preparing parent Minecraft version {version}...", | ||
"start.forge.vanilla.found": "Found parent Minecraft version {version}.", | ||
"start.forge.wrapper.running": "Running installer (can take few minutes)...", | ||
"start.forge.wrapper.done": "Forge installation done.", | ||
"start.forge.consider_support": "Consider supporting the forge project through https://www.patreon.com/LexManos/.", | ||
"start.forge.error.invalid_main_dir": "The main directory cannot be determined, because version directory " | ||
"and libraries directory must have the same parent directory.", | ||
"start.forge.error.installer_3": "This forge installer is currently not supported.", | ||
"start.forge.error.installer_4": "This forge installer is missing something to run (internal).", | ||
"start.forge.error.installer_5": "This forge installer failed to install forge (internal).", | ||
f"start.forge.error.{ForgeVersionNotFound.INSTALLER_NOT_FOUND}": "No installer found for forge {version}.", | ||
f"start.forge.error.{ForgeVersionNotFound.MINECRAFT_VERSION_NOT_FOUND}": "Parent Minecraft version not found " | ||
"{version}.", | ||
}) | ||
|
||
|
||
# Forge API | ||
|
||
def request_promo_versions() -> Dict[str, str]: | ||
raw = json_simple_request("https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json") | ||
return raw["promos"] | ||
|
||
|
||
def request_maven_versions() -> List[str]: | ||
|
||
status, raw = http_request("https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml", "GET", headers={ | ||
"Accept": "application/xml" | ||
}) | ||
|
||
text = raw.decode() | ||
|
||
versions = [] | ||
last_idx = 0 | ||
|
||
while True: | ||
start_idx = text.find("<version>", last_idx) | ||
if start_idx == -1: | ||
break | ||
end_idx = text.find("</version>", start_idx + 9) | ||
if end_idx == -1: | ||
break | ||
versions.append(text[(start_idx + 9):end_idx]) | ||
last_idx = end_idx + 10 | ||
|
||
return versions | ||
|
||
|
||
# Errors | ||
|
||
class ForgeInvalidMainDirectory(Exception): | ||
pass | ||
|
||
|
||
class ForgeInstallerFailed(Exception): | ||
def __init__(self, return_code: int): | ||
self.return_code = return_code | ||
|
||
|
||
class ForgeVersionNotFound(BaseError): | ||
|
||
INSTALLER_NOT_FOUND = "installer_not_found" | ||
MINECRAFT_VERSION_NOT_FOUND = "minecraft_version_not_found" | ||
|
||
def __init__(self, code: str, version: str): | ||
super().__init__(code) | ||
self.version = version |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"name": "Forge", | ||
"version": "1.0.0", | ||
"authors": ["Théo Rozier"], | ||
"description": "Start Minecraft using the Forge mod loader using '<exec> start forge:[<mc-version>]'." | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/target/classes | ||
/target/generated-sources | ||
/target/maven-archiver | ||
/target/maven-status |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>portablemc</groupId> | ||
<artifactId>wrapper</artifactId> | ||
<version>1.0.0</version> | ||
|
||
<properties> | ||
<maven.compiler.source>8</maven.compiler.source> | ||
<maven.compiler.target>8</maven.compiler.target> | ||
</properties> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-jar-plugin</artifactId> | ||
<version>3.2.0</version> | ||
<configuration> | ||
<finalName>wrapper</finalName> | ||
<archive> | ||
<manifest> | ||
<addClasspath>true</addClasspath> | ||
<mainClass>portablemc.wrapper.Main</mainClass> | ||
</manifest> | ||
</archive> | ||
<includes> | ||
<include>portablemc/**</include> | ||
</includes> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
</project> |
5 changes: 5 additions & 0 deletions
5
addons/forge/wrapper/src/main/java/argo/jdom/AbstractJsonObject.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package argo.jdom; | ||
|
||
abstract class AbstractJsonObject extends JsonRootNode { | ||
|
||
} |
21 changes: 21 additions & 0 deletions
21
addons/forge/wrapper/src/main/java/argo/jdom/JsonField.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package argo.jdom; | ||
|
||
public class JsonField { | ||
|
||
private final JsonStringNode name; | ||
private final JsonNode value; | ||
|
||
public JsonField(final JsonStringNode name, final JsonNode value) { | ||
this.name = name; | ||
this.value = value; | ||
} | ||
|
||
public JsonStringNode getName() { | ||
return this.name; | ||
} | ||
|
||
public JsonNode getValue() { | ||
return this.value; | ||
} | ||
|
||
} |
11 changes: 11 additions & 0 deletions
11
addons/forge/wrapper/src/main/java/argo/jdom/JsonNode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package argo.jdom; | ||
|
||
import java.util.List; | ||
|
||
public abstract class JsonNode { | ||
|
||
public abstract String getText(); | ||
|
||
public abstract List<JsonField> getFieldList(); | ||
|
||
} |
15 changes: 15 additions & 0 deletions
15
addons/forge/wrapper/src/main/java/argo/jdom/JsonNodeFactories.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package argo.jdom; | ||
|
||
import java.util.Map; | ||
|
||
public class JsonNodeFactories { | ||
|
||
public static JsonStringNode string(final String value) { | ||
return null; | ||
} | ||
|
||
public static JsonRootNode object(final Map<JsonStringNode, ? extends JsonNode> fields) { | ||
return null; | ||
} | ||
|
||
} |
17 changes: 17 additions & 0 deletions
17
addons/forge/wrapper/src/main/java/argo/jdom/JsonObject.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package argo.jdom; | ||
|
||
import java.util.List; | ||
|
||
final class JsonObject extends AbstractJsonObject { | ||
|
||
@Override | ||
public String getText() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public List<JsonField> getFieldList() { | ||
return null; | ||
} | ||
|
||
} |
Oops, something went wrong.