Compare commits

...

No commits in common. "maven" and "master" have entirely different histories.

73 changed files with 1531 additions and 279 deletions

27
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: Gradle Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: '8.0.312'
architecture: x64
- name: Build with Gradle
run: |
chmod +x ./gradlew
./gradlew build -iS
- uses: actions/upload-artifact@v2
with:
name: Package
path: build/libs

59
.github/workflows/publication.yml vendored Normal file
View file

@ -0,0 +1,59 @@
name: Publication
on:
push:
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: '8.0.312'
architecture: x64
- name: Build with Gradle
env:
IS_PUBLICATION: true
run: |
git clone -b maven https://github.com/ZekerZhayard/ForgeWrapper.git ./build/maven
rm -rf ./build/maven/.git/*
chmod +x ./gradlew
./gradlew publish -iS
- uses: actions/upload-artifact@v2
with:
name: Package
path: build/libs
- name: Get tag version
id: get_version
uses: olegtarasov/get-tag@v2.1
- uses: actions/upload-artifact@v2
with:
name: Package
path: build/libs
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.get_version.outputs.tag }}
release_name: ${{ steps.get_version.outputs.tag }}
body: ''
draft: false
prerelease: false
- name: Upload release binaries
uses: alexellis/upload-assets@0.2.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
asset_paths: '["./build/libs/*"]'
- name: Upload to Maven
uses: JamesIves/github-pages-deploy-action@4.1.4
with:
branch: maven
folder: build/maven

15
.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
.gradle
build
# eclipse
.settings
.classpath
.project
bin
# idea
.idea
*.iml
*.ipr
*.iws
out

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) ZekerZhayard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

30
README.md Normal file
View file

@ -0,0 +1,30 @@
# ForgeWrapper
Allow [MultiMC](https://github.com/MultiMC/MultiMC5) to launch Minecraft 1.13+ with Forge.
**ForgeWrapper has been adopted by MultiMC, you do not need to perform the following steps manually. (2020-03-29)**
## For other launchers
1. ForgeWrapper provides some java properties since 1.4.2:
- `forgewrapper.librariesDir` : a path to libraries folder (e.g. -Dforgewrapper.librariesDir=/home/xxx/.minecraft/libraries)
- `forgewrapper.installer` : a path to forge installer (e.g. -Dforgewrapper.installer=/home/xxx/forge-1.14.4-28.2.0-installer.jar)
- `forgewrapper.minecraft` : a path to the vanilla minecraft jar (e.g. -Dforgewrapper.minecraft=/home/xxx/.minecraft/versions/1.14.4/1.14.4.jar)
2. ForgeWrapper also provides an interface [`IFileDetector`](https://github.com/ZekerZhayard/ForgeWrapper/blob/master/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java), you can implement it and custom your own detecting rules. To load it, you should make another jar which contains `META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector` within the full implementation class name and add the jar to class path.
## How to use (Outdated)
1. Download Forge installer for Minecraft 1.13+ [here](https://files.minecraftforge.net/).
2. Download ForgeWrapper jar file at the [release](https://github.com/ZekerZhayard/ForgeWrapper/releases) page.
3. Since ForgeWrapper 1.5.1, it no longer includes the json converter, so you need to build it by yourself:
- [Download](https://github.com/ZekerZhayard/ForgeWrapper/archive/refs/heads/master.zip) ForgeWrapper sources.
- Extract the zip and open terminal in the extracted folder.
- Run `./gradlew converter:build` command in terminal and get the jar from `./converter/build/libs`
3. Run the below command in terminal:
```
java -jar <ForgeWrapper.jar> --installer=<forge-installer.jar> [--instance=<instance-path>]
```
*Notice: If you don't specify a MultiMC instance path, ForgeWrapper will create the instance folder in current working space.*
4. If the instance folder which just created is not in `MultiMC/instances` folder, you just need to move to the `MultiMC/instances` folder.
5. Run MultiMC, and you will see a new instance named `forge-<mcVersion>-<forgeVersion>`.

95
build.gradle Normal file
View file

@ -0,0 +1,95 @@
plugins {
id "java"
id "eclipse"
id "maven-publish"
}
sourceCompatibility = targetCompatibility = 1.8
compileJava {
sourceCompatibility = targetCompatibility = 1.8
}
version = "${fw_version}${-> getVersionSuffix()}"
group = "io.github.zekerzhayard"
archivesBaseName = rootProject.name
configurations {
provided {
implementation.extendsFrom provided
}
multirelase {
implementation.extendsFrom multirelase
}
}
repositories {
mavenCentral()
maven {
name = "forge"
url = "https://maven.minecraftforge.net/"
}
}
dependencies {
compileOnly "com.google.code.gson:gson:2.8.7"
compileOnly "cpw.mods:modlauncher:8.0.9"
compileOnly "net.minecraftforge:installer:2.2.7"
compileOnly "net.sf.jopt-simple:jopt-simple:5.0.4"
provided project(":common")
provided project(":legacy")
multirelase project(":jigsaw")
}
java {
withSourcesJar()
}
jar {
manifest.attributes([
"Specification-Title": "${project.name}",
"Specification-Vendor": "ZekerZhayard",
"Specification-Version": "${project.version}".split("-")[0],
"Implementation-Title": "${project.name}",
"Implementation-Version": "${project.version}",
"Implementation-Vendor" :"ZekerZhayard",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
"Automatic-Module-Name": "${project.group}.${project.archivesBaseName}".toString().toLowerCase(),
"Multi-Release": "true",
"GitCommit": String.valueOf(System.getenv("GITHUB_SHA"))
])
from configurations.provided.files.collect {
zipTree(it)
}
into "META-INF/versions/9", {
from configurations.multirelase.files.collect {
zipTree(it)
}
exclude "META-INF/**"
}
}
publishing {
publications {
maven(MavenPublication) {
groupId "${project.group}"
artifactId "${project.archivesBaseName}"
version "${project.version}"
from components.java
}
}
repositories {
maven {
url = layout.buildDirectory.dir("maven")
}
}
}
tasks.publish.dependsOn build
static String getVersionSuffix() {
return ""
}

11
common/build.gradle Normal file
View file

@ -0,0 +1,11 @@
plugins {
id "java"
id "eclipse"
id "maven-publish"
}
sourceCompatibility = targetCompatibility = 1.8
compileJava {
sourceCompatibility = targetCompatibility = 1.8
}

View file

@ -0,0 +1,40 @@
package io.github.zekerzhayard.forgewrapper.util;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class CheckedLambdaUtil {
public static <T> Consumer<T> wrapConsumer(CheckedConsumer<T> consumer) {
return consumer;
}
public static <T, U> BiConsumer<T, U> wrapBiConsumer(CheckedBiConsumer<T, U> biconsumer) {
return biconsumer;
}
public interface CheckedConsumer<T> extends Consumer<T> {
void checkedAccept(T t) throws Throwable;
@Override
default void accept(T t) {
try {
this.checkedAccept(t);
} catch (Throwable th) {
throw new RuntimeException(th);
}
}
}
public interface CheckedBiConsumer<T, U> extends BiConsumer<T, U> {
void checkedAccept(T t, U u) throws Throwable;
@Override
default void accept(T t, U u) {
try {
this.checkedAccept(t, u);
} catch (Throwable th) {
throw new RuntimeException(th);
}
}
}
}

4
gradle.properties Normal file
View file

@ -0,0 +1,4 @@
org.gradle.daemon = false
fw_version = mmc5

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored Executable file
View file

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View file

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -1 +0,0 @@
41889b31cb86400ad7053037e006407a

View file

@ -1 +0,0 @@
5ecf7d1102a4a0e82ad82b4a5499130c0bf7102b

View file

@ -1 +0,0 @@
6d8efdbda45653934d0a28797987c8783710026f7c42b3ea5e2c3f7117a7af70

View file

@ -1 +0,0 @@
fac731ea67b9d44822afbc3d21346be4efef68e6445124195566674d48c180c7e1143bffe9b985a890f8631173356d1bac2e72b94d30f0fe89242a312abfe60f

View file

@ -1 +0,0 @@
919e2d600b673fce7be6ac2d0ed1b62f

View file

@ -1 +0,0 @@
90104e9aaa8fbedf6c3d1f6d0b90cabce080b5a9

View file

@ -1 +0,0 @@
d608aad3a92d69c1c9f93c019d671b7242a4383451edeb728bb2482d2b900164

View file

@ -1 +0,0 @@
a64e471f5f7487cace9cf7d531a554edc1977b9e5f4fc28ddcfa7a8ec4e2de9af0f5fd9aa8466eab596222f5fd3b6c43e029234498132f61eabbb386877b9f34

View file

@ -1,80 +0,0 @@
{
"formatVersion": "1.1",
"component": {
"group": "io.github.zekerzhayard",
"module": "ForgeWrapper",
"version": "1.5.1",
"attributes": {
"org.gradle.status": "release"
}
},
"createdBy": {
"gradle": {
"version": "7.1.1"
}
},
"variants": [
{
"name": "apiElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 8,
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-api"
},
"files": [
{
"name": "ForgeWrapper-1.5.1.jar",
"url": "ForgeWrapper-1.5.1.jar",
"size": 29892,
"sha512": "a64e471f5f7487cace9cf7d531a554edc1977b9e5f4fc28ddcfa7a8ec4e2de9af0f5fd9aa8466eab596222f5fd3b6c43e029234498132f61eabbb386877b9f34",
"sha256": "d608aad3a92d69c1c9f93c019d671b7242a4383451edeb728bb2482d2b900164",
"sha1": "90104e9aaa8fbedf6c3d1f6d0b90cabce080b5a9",
"md5": "919e2d600b673fce7be6ac2d0ed1b62f"
}
]
},
{
"name": "runtimeElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 8,
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-runtime"
},
"files": [
{
"name": "ForgeWrapper-1.5.1.jar",
"url": "ForgeWrapper-1.5.1.jar",
"size": 29892,
"sha512": "a64e471f5f7487cace9cf7d531a554edc1977b9e5f4fc28ddcfa7a8ec4e2de9af0f5fd9aa8466eab596222f5fd3b6c43e029234498132f61eabbb386877b9f34",
"sha256": "d608aad3a92d69c1c9f93c019d671b7242a4383451edeb728bb2482d2b900164",
"sha1": "90104e9aaa8fbedf6c3d1f6d0b90cabce080b5a9",
"md5": "919e2d600b673fce7be6ac2d0ed1b62f"
}
]
},
{
"name": "sourcesElements",
"attributes": {
"org.gradle.category": "documentation",
"org.gradle.dependency.bundling": "external",
"org.gradle.docstype": "sources",
"org.gradle.usage": "java-runtime"
},
"files": [
{
"name": "ForgeWrapper-1.5.1-sources.jar",
"url": "ForgeWrapper-1.5.1-sources.jar",
"size": 11159,
"sha512": "fac731ea67b9d44822afbc3d21346be4efef68e6445124195566674d48c180c7e1143bffe9b985a890f8631173356d1bac2e72b94d30f0fe89242a312abfe60f",
"sha256": "6d8efdbda45653934d0a28797987c8783710026f7c42b3ea5e2c3f7117a7af70",
"sha1": "5ecf7d1102a4a0e82ad82b4a5499130c0bf7102b",
"md5": "41889b31cb86400ad7053037e006407a"
}
]
}
]
}

View file

@ -1 +0,0 @@
8364e8fb861af074298cfd2b0515fbf6

View file

@ -1 +0,0 @@
35a5933bba957f90603795b96311417aa5e589bd

View file

@ -1 +0,0 @@
700a7015e64e078dbd537221253336ad10714f85c93ed7695684e22e6b95fe2e

View file

@ -1 +0,0 @@
1e2108a899a30c9d05ac6485e598753839ec1fc2434d9c3d398614b77aec77e93a2dc8f5d1bbac3943f3a34f2c1e60753aa6f02890ecc5230c568587ec022e83

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- This module was also published with a richer model, Gradle metadata, -->
<!-- which should be used instead. Do not delete the following line which -->
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
<!-- that they should prefer consuming it instead. -->
<!-- do_not_remove: published-with-gradle-metadata -->
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.zekerzhayard</groupId>
<artifactId>ForgeWrapper</artifactId>
<version>1.5.1</version>
</project>

View file

@ -1 +0,0 @@
5cc4dce491edbbfb72a2e4c4bf7ea0a0

View file

@ -1 +0,0 @@
fe75a10e3a7c222e481a7f9e8a366c7c1a73f397

View file

@ -1 +0,0 @@
0f1746d3c6c7dccc87f0973d0e94723141483f13ae72cf746f42796dc8da9a00

View file

@ -1 +0,0 @@
23cc1112881f0d28aed622038a90fa755559dcde659d356188721318fd513946a020b40465d43b42fe1017be21c7d5b960c26d9a6a839fc31e1dfb6289fd8e42

View file

@ -1 +0,0 @@
b39d7be0ba9c39f4e8de4e6d1b57407b

View file

@ -1 +0,0 @@
591fbc653b4a1e6f5c138f30600aad4c72478e00

View file

@ -1 +0,0 @@
9bb6df432f8aff1b99775055e92564f408b4150ed06e3ea26e8b61e7594152fd

View file

@ -1 +0,0 @@
f08531024141fc6a17b28635851e7e6d470652979ed3986cf8104a46a6133741abadfb01d51ecb2544fb1ed5c23f8d64f4d003a5fc7215b494e3d76ef07ea9de

View file

@ -1 +0,0 @@
c0e7e25903cfb7dd6e671f12687abc4d

View file

@ -1 +0,0 @@
6930ac286c079a9df5b766974020ef7ad1fc5d3b

View file

@ -1 +0,0 @@
4343dd57b6494042ae975314d3a7b788777c18741e81bfae1352d7e46e79db43

View file

@ -1 +0,0 @@
4dbebbbc4eaeb5c8596717876c98a469066891ce619ac651d8ae15237200c8c51d8d1398c11c73fc101eef89624711331c5cadba236b521b746c8ae07ba1bc1b

View file

@ -1,103 +0,0 @@
{
"formatVersion": "1.1",
"component": {
"group": "io.github.zekerzhayard",
"module": "ForgeWrapper",
"version": "1.5.2",
"attributes": {
"org.gradle.status": "release"
}
},
"createdBy": {
"gradle": {
"version": "7.1.1"
}
},
"variants": [
{
"name": "apiElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 8,
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-api"
},
"files": [
{
"name": "ForgeWrapper-1.5.2.jar",
"url": "ForgeWrapper-1.5.2.jar",
"size": 34217,
"sha512": "4dbebbbc4eaeb5c8596717876c98a469066891ce619ac651d8ae15237200c8c51d8d1398c11c73fc101eef89624711331c5cadba236b521b746c8ae07ba1bc1b",
"sha256": "4343dd57b6494042ae975314d3a7b788777c18741e81bfae1352d7e46e79db43",
"sha1": "6930ac286c079a9df5b766974020ef7ad1fc5d3b",
"md5": "c0e7e25903cfb7dd6e671f12687abc4d"
}
]
},
{
"name": "runtimeElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 8,
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-runtime"
},
"dependencies": [
{
"group": "ForgeWrapper",
"module": "common",
"version": {
"requires": "unspecified"
}
},
{
"group": "ForgeWrapper",
"module": "legacy",
"version": {
"requires": "unspecified"
}
},
{
"group": "ForgeWrapper",
"module": "jigsaw",
"version": {
"requires": "unspecified"
}
}
],
"files": [
{
"name": "ForgeWrapper-1.5.2.jar",
"url": "ForgeWrapper-1.5.2.jar",
"size": 34217,
"sha512": "4dbebbbc4eaeb5c8596717876c98a469066891ce619ac651d8ae15237200c8c51d8d1398c11c73fc101eef89624711331c5cadba236b521b746c8ae07ba1bc1b",
"sha256": "4343dd57b6494042ae975314d3a7b788777c18741e81bfae1352d7e46e79db43",
"sha1": "6930ac286c079a9df5b766974020ef7ad1fc5d3b",
"md5": "c0e7e25903cfb7dd6e671f12687abc4d"
}
]
},
{
"name": "sourcesElements",
"attributes": {
"org.gradle.category": "documentation",
"org.gradle.dependency.bundling": "external",
"org.gradle.docstype": "sources",
"org.gradle.usage": "java-runtime"
},
"files": [
{
"name": "ForgeWrapper-1.5.2-sources.jar",
"url": "ForgeWrapper-1.5.2-sources.jar",
"size": 10991,
"sha512": "f08531024141fc6a17b28635851e7e6d470652979ed3986cf8104a46a6133741abadfb01d51ecb2544fb1ed5c23f8d64f4d003a5fc7215b494e3d76ef07ea9de",
"sha256": "9bb6df432f8aff1b99775055e92564f408b4150ed06e3ea26e8b61e7594152fd",
"sha1": "591fbc653b4a1e6f5c138f30600aad4c72478e00",
"md5": "b39d7be0ba9c39f4e8de4e6d1b57407b"
}
]
}
]
}

View file

@ -1 +0,0 @@
20d43d76411b60cf6b5e4bf2f0b29ee1

View file

@ -1 +0,0 @@
b705958a45e34838ca8960b92aeb004102703430

View file

@ -1 +0,0 @@
2b45aa3253a33e7c974b0b117ecd145ac1d576b7e0e44a301c282329809be60f

View file

@ -1 +0,0 @@
f74f2f5004b7def67bea597620cbd60d580d20a3e481f0067a39a3f31dd1e169449e55b48ad43c28bb75905db2c41e9db54aeae934873d5ffbfa739f0f9e1eda

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- This module was also published with a richer model, Gradle metadata, -->
<!-- which should be used instead. Do not delete the following line which -->
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
<!-- that they should prefer consuming it instead. -->
<!-- do_not_remove: published-with-gradle-metadata -->
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.zekerzhayard</groupId>
<artifactId>ForgeWrapper</artifactId>
<version>1.5.2</version>
<dependencies>
<dependency>
<groupId>ForgeWrapper</groupId>
<artifactId>common</artifactId>
<version>unspecified</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ForgeWrapper</groupId>
<artifactId>legacy</artifactId>
<version>unspecified</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ForgeWrapper</groupId>
<artifactId>jigsaw</artifactId>
<version>unspecified</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

View file

@ -1 +0,0 @@
fb70106ce9159b84433ba4b2d1a77620

View file

@ -1 +0,0 @@
ae262432af313ca91bf7964dad024141941fb49f

View file

@ -1 +0,0 @@
88919ff705ede755d192fac2b1eff8cea3f557f6874a8b09ebc5862d2616ab30

View file

@ -1 +0,0 @@
2904338de4bd110750b4d1c440118545ceefd10b451af76123b8ce3c18c59656dd02499138dd7b91f919ca2909c3ec00ffef419e56908ac4234836242d3e0ce3

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>io.github.zekerzhayard</groupId>
<artifactId>ForgeWrapper</artifactId>
<versioning>
<latest>1.5.2</latest>
<release>1.5.2</release>
<versions>
<version>1.5.1</version>
<version>1.5.2</version>
</versions>
<lastUpdated>20210813145125</lastUpdated>
</versioning>
</metadata>

View file

@ -1 +0,0 @@
7c5893df39f20bee0aa0ebc574569b30

View file

@ -1 +0,0 @@
4ae15411c53571d1834d004d5c2148c828cd0e98

View file

@ -1 +0,0 @@
fff3fdc18b163259a42b5426d47ffefdfabbb3b9d425bfacce6725cb339e8a06

View file

@ -1 +0,0 @@
103393c6705b2794ea6be7165a739ca80d88d337b577b7e6e5e72dce4d89c638dd3f526f0c0745a461e650b081246e3aa8783a35a78d8bd4130464b899c02ec0

32
jigsaw/build.gradle Normal file
View file

@ -0,0 +1,32 @@
plugins {
id "java"
id "eclipse"
}
compileJava {
if (JavaVersion.current() < JavaVersion.VERSION_1_9) {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(9)
}
}
sourceCompatibility = 9
targetCompatibility = 9
}
configurations {
apiElements {
attributes {
attribute TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8
}
}
runtimeElements {
attributes {
attribute TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8
}
}
}
dependencies {
compileOnly project(":common")
}

View file

@ -0,0 +1,179 @@
package io.github.zekerzhayard.forgewrapper.installer.util;
import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.github.zekerzhayard.forgewrapper.util.CheckedLambdaUtil;
import sun.misc.Unsafe;
public class ModuleUtil {
private final static MethodHandles.Lookup IMPL_LOOKUP = getImplLookup();
private static MethodHandles.Lookup getImplLookup() {
try {
// Get theUnsafe
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
// Get IMPL_LOOKUP
Field implLookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
return (MethodHandles.Lookup) unsafe.getObject(unsafe.staticFieldBase(implLookupField), unsafe.staticFieldOffset(implLookupField));
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
/**
* add module-path at runtime
*/
@SuppressWarnings("unchecked")
public static void addModules(String modulePath) throws Throwable {
// Find all extra modules, exclude all existing modules
ModuleFinder finder = ModuleFinder.of(Stream.of(modulePath.split(File.pathSeparator)).map(Paths::get).filter(p -> ModuleFinder.of(p).findAll().stream().noneMatch(mref -> ModuleLayer.boot().findModule(mref.descriptor().name()).isPresent())).toArray(Path[]::new));
MethodHandle loadModuleMH = IMPL_LOOKUP.findVirtual(Class.forName("jdk.internal.loader.BuiltinClassLoader"), "loadModule", MethodType.methodType(void.class, ModuleReference.class));
// Resolve modules to a new config and load all extra modules in system class loader (unnamed modules for now)
Configuration config = Configuration.resolveAndBind(finder, List.of(ModuleLayer.boot().configuration()), finder, finder.findAll().stream().filter(mref -> !ModuleLayer.boot().findModule(mref.descriptor().name()).isPresent()).peek(CheckedLambdaUtil.wrapConsumer(mref -> loadModuleMH.invokeWithArguments(ClassLoader.getSystemClassLoader(), mref))).map(mref -> mref.descriptor().name()).collect(Collectors.toList()));
// Copy the new config graph to boot module layer config
MethodHandle graphGetter = IMPL_LOOKUP.findGetter(Configuration.class, "graph", Map.class);
HashMap<ResolvedModule, Set<ResolvedModule>> graphMap = new HashMap<>((Map<ResolvedModule, Set<ResolvedModule>>) graphGetter.invokeWithArguments(config));
MethodHandle cfSetter = IMPL_LOOKUP.findSetter(ResolvedModule.class, "cf", Configuration.class);
// Reset all extra resolved modules config to boot module layer config
graphMap.forEach(CheckedLambdaUtil.wrapBiConsumer((k, v) -> {
cfSetter.invokeWithArguments(k, ModuleLayer.boot().configuration());
v.forEach(CheckedLambdaUtil.wrapConsumer(m -> cfSetter.invokeWithArguments(m, ModuleLayer.boot().configuration())));
}));
graphMap.putAll((Map<ResolvedModule, Set<ResolvedModule>>) graphGetter.invokeWithArguments(ModuleLayer.boot().configuration()));
IMPL_LOOKUP.findSetter(Configuration.class, "graph", Map.class).invokeWithArguments(ModuleLayer.boot().configuration(), new HashMap<>(graphMap));
// Reset boot module layer resolved modules as new config resolved modules to prepare define modules
Set<ResolvedModule> oldBootModules = ModuleLayer.boot().configuration().modules();
MethodHandle modulesSetter = IMPL_LOOKUP.findSetter(Configuration.class, "modules", Set.class);
HashSet<ResolvedModule> modulesSet = new HashSet<>(config.modules());
modulesSetter.invokeWithArguments(ModuleLayer.boot().configuration(), new HashSet<>(modulesSet));
// Prepare to add all the new config "nameToModule" to boot module layer config
MethodHandle nameToModuleGetter = IMPL_LOOKUP.findGetter(Configuration.class, "nameToModule", Map.class);
HashMap<String, ResolvedModule> nameToModuleMap = new HashMap<>((Map<String, ResolvedModule>) nameToModuleGetter.invokeWithArguments(ModuleLayer.boot().configuration()));
nameToModuleMap.putAll((Map<String, ResolvedModule>) nameToModuleGetter.invokeWithArguments(config));
IMPL_LOOKUP.findSetter(Configuration.class, "nameToModule", Map.class).invokeWithArguments(ModuleLayer.boot().configuration(), new HashMap<>(nameToModuleMap));
// Define all extra modules and add all the new config "nameToModule" to boot module layer config
((Map<String, Module>) IMPL_LOOKUP.findGetter(ModuleLayer.class, "nameToModule", Map.class).invokeWithArguments(ModuleLayer.boot())).putAll((Map<String, Module>) IMPL_LOOKUP.findStatic(Module.class, "defineModules", MethodType.methodType(Map.class, Configuration.class, Function.class, ModuleLayer.class)).invokeWithArguments(ModuleLayer.boot().configuration(), (Function<String, ClassLoader>) name -> ClassLoader.getSystemClassLoader(), ModuleLayer.boot()));
// Add all of resolved modules
modulesSet.addAll(oldBootModules);
modulesSetter.invokeWithArguments(ModuleLayer.boot().configuration(), new HashSet<>(modulesSet));
// Reset cache of boot module layer
IMPL_LOOKUP.findSetter(ModuleLayer.class, "modules", Set.class).invokeWithArguments(ModuleLayer.boot(), null);
IMPL_LOOKUP.findSetter(ModuleLayer.class, "servicesCatalog", Class.forName("jdk.internal.module.ServicesCatalog")).invokeWithArguments(ModuleLayer.boot(), null);
// Add reads from extra modules to jdk modules
MethodHandle implAddReadsMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddReads", MethodType.methodType(void.class, Module.class));
config.modules().forEach(rm -> ModuleLayer.boot().findModule(rm.name()).ifPresent(m -> oldBootModules.forEach(brm -> ModuleLayer.boot().findModule(brm.name()).ifPresent(CheckedLambdaUtil.wrapConsumer(bm -> implAddReadsMH.invokeWithArguments(m, bm))))));
}
public static void addExports(List<String> exports) {
TypeToAdd.EXPORTS.implAdd(exports);
}
public static void addOpens(List<String> opens) {
TypeToAdd.OPENS.implAdd(opens);
}
public static ClassLoader getPlatformClassLoader() {
return ClassLoader.getPlatformClassLoader();
}
private enum TypeToAdd {
EXPORTS("Exports"),
OPENS("Opens");
private final MethodHandle implAddMH;
private final MethodHandle implAddToAllUnnamedMH;
TypeToAdd(String name) {
try {
this.implAddMH = IMPL_LOOKUP.findVirtual(Module.class, "implAdd" + name, MethodType.methodType(void.class, String.class, Module.class));
this.implAddToAllUnnamedMH = IMPL_LOOKUP.findVirtual(Module.class, "implAdd" + name + "ToAllUnnamed", MethodType.methodType(void.class, String.class));
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
void implAdd(List<String> extras) {
extras.stream().map(ModuleUtil::parseModuleExtra).filter(Optional::isPresent).map(Optional::get).forEach(CheckedLambdaUtil.wrapConsumer(data -> ModuleLayer.boot().findModule(data.module).ifPresent(CheckedLambdaUtil.wrapConsumer(m -> {
if ("ALL-UNNAMED".equals(data.target)) {
this.implAddToAllUnnamedMH.invokeWithArguments(m, data.packages);
} else {
ModuleLayer.boot().findModule(data.target).ifPresent(CheckedLambdaUtil.wrapConsumer(tm -> this.implAddMH.invokeWithArguments(m, data.packages, tm)));
}
}))));
}
}
// <module>/<package>=<target>
private static Optional<ParserData> parseModuleExtra(String extra) {
String[] all = extra.split("=", 2);
if (all.length < 2) {
return Optional.empty();
}
String[] source = all[0].split("/", 2);
if (source.length < 2) {
return Optional.empty();
}
return Optional.of(new ParserData(source[0], source[1], all[1]));
}
private static class ParserData {
final String module;
final String packages;
final String target;
ParserData(String module, String packages, String target) {
this.module = module;
this.packages = packages;
this.target = target;
}
}
public static void setupClassPath(Path libraryDir, List<String> paths) throws Throwable {
Class<?> urlClassPathClass = Class.forName("jdk.internal.loader.URLClassPath");
Object ucp = IMPL_LOOKUP.findGetter(Class.forName("jdk.internal.loader.BuiltinClassLoader"), "ucp", urlClassPathClass).invokeWithArguments(ClassLoader.getSystemClassLoader());
MethodHandle addURLMH = IMPL_LOOKUP.findVirtual(urlClassPathClass, "addURL", MethodType.methodType(void.class, URL.class));
for (String path : paths) {
addURLMH.invokeWithArguments(ucp, libraryDir.resolve(path).toUri().toURL());
}
}
// ForgeWrapper need some extra settings to invoke BootstrapLauncher.
public static Class<?> setupBootstrapLauncher(Class<?> mainClass) throws Throwable {
if (!mainClass.getModule().isOpen(mainClass.getPackageName(), ModuleUtil.class.getModule())) {
TypeToAdd.OPENS.implAddMH.invokeWithArguments(mainClass.getModule(), mainClass.getPackageName(), ModuleUtil.class.getModule());
}
return mainClass;
}
}

22
legacy/build.gradle Normal file
View file

@ -0,0 +1,22 @@
plugins {
id "java"
id "eclipse"
}
sourceCompatibility = targetCompatibility = 1.8
compileJava {
sourceCompatibility = targetCompatibility = 1.8
}
repositories {
mavenCentral()
maven {
name = "forge"
url = "https://maven.minecraftforge.net/"
}
}
dependencies {
compileOnly "net.minecraftforge:installer:2.0.24"
}

View file

@ -0,0 +1,12 @@
package io.github.zekerzhayard.forgewrapper.installer.util;
import java.io.File;
import net.minecraftforge.installer.actions.ProgressCallback;
import net.minecraftforge.installer.json.Install;
public abstract class AbstractInstaller {
public abstract Install loadInstallProfile();
public abstract boolean runClientInstall(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar, File installerJar);
}

View file

@ -0,0 +1,37 @@
package io.github.zekerzhayard.forgewrapper.installer.util;
import java.io.File;
import java.util.function.Predicate;
import net.minecraftforge.installer.actions.ClientInstall;
import net.minecraftforge.installer.actions.ProgressCallback;
import net.minecraftforge.installer.json.Install;
import net.minecraftforge.installer.json.Util;
public class InstallerV0 extends AbstractInstaller {
@Override
public Install loadInstallProfile() {
return Util.loadInstallProfile();
}
@Override
public boolean runClientInstall(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar, File installerJar) {
return new ClientInstall4MultiMC(profile, monitor, libraryDir, minecraftJar).run(null, input -> true);
}
public static class ClientInstall4MultiMC extends ClientInstall {
protected File libraryDir;
protected File minecraftJar;
public ClientInstall4MultiMC(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar) {
super(profile, monitor);
this.libraryDir = libraryDir;
this.minecraftJar = minecraftJar;
}
@Override
public boolean run(File target, Predicate<String> optionals) {
return this.processors.process(this.libraryDir, this.minecraftJar);
}
}
}

5
settings.gradle Normal file
View file

@ -0,0 +1,5 @@
rootProject.name = 'ForgeWrapper'
include 'common'
include 'legacy'
include 'jigsaw'

View file

@ -0,0 +1,70 @@
package io.github.zekerzhayard.forgewrapper.installer;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import io.github.zekerzhayard.forgewrapper.installer.util.ModuleUtil;
public class Bootstrap {
public static void bootstrap(List<String> jvmArgs, String minecraftJar, String libraryDir) throws Throwable {
// Replace all placeholders
List<String> replacedJvmArgs = new ArrayList<>();
for (String arg : jvmArgs) {
replacedJvmArgs.add(arg.replace("${classpath}", System.getProperty("java.class.path").replace(File.separator, "/")).replace("${classpath_separator}", File.pathSeparator).replace("${library_directory}", libraryDir).replace("${version_name}", minecraftJar.substring(0, minecraftJar.lastIndexOf('.'))));
}
jvmArgs = replacedJvmArgs;
// Remove NewLaunch.jar from property to prevent Forge from adding it to the module path
StringBuilder newCP = new StringBuilder();
for (String path : System.getProperty("java.class.path").split(File.pathSeparator)) {
if (!path.endsWith("NewLaunch.jar")) {
newCP.append(path).append(File.pathSeparator);
}
}
System.setProperty("java.class.path", newCP.substring(0, newCP.length() - 1));
String modulePath = null;
List<String> addExports = new ArrayList<>();
List<String> addOpens = new ArrayList<>();
for (int i = 0; i < jvmArgs.size(); i++) {
String arg = jvmArgs.get(i);
if (arg.equals("-p") || arg.equals("--module-path")) {
modulePath = jvmArgs.get(i + 1);
} else if (arg.startsWith("--module-path=")) {
modulePath = arg.split("=", 2)[1];
}
if (arg.equals("--add-exports")) {
addExports.add(jvmArgs.get(i + 1));
} else if (arg.startsWith("--add-exports=")) {
addExports.add(arg.split("=", 2)[1]);
}
if (arg.equals("--add-opens")) {
addOpens.add(jvmArgs.get(i + 1));
} else if (arg.startsWith("--add-opens=")) {
addOpens.add(arg.split("=", 2)[1]);
}
// Java properties
if (arg.startsWith("-D")) {
String[] prop = arg.substring(2).split("=", 2);
if (prop[0].equals("ignoreList")) {
// The default ignoreList may cause some problems, so we define it more precisely.
System.setProperty(prop[0], prop[1] + ",NewLaunch.jar,ForgeWrapper-," + minecraftJar);
} else {
System.setProperty(prop[0], prop[1]);
}
}
}
if (modulePath != null) {
ModuleUtil.addModules(modulePath);
}
ModuleUtil.addExports(addExports);
ModuleUtil.addOpens(addOpens);
}
}

View file

@ -0,0 +1,43 @@
package io.github.zekerzhayard.forgewrapper.installer;
import java.io.File;
import io.github.zekerzhayard.forgewrapper.installer.util.AbstractInstaller;
import io.github.zekerzhayard.forgewrapper.installer.util.InstallerV0;
import io.github.zekerzhayard.forgewrapper.installer.util.InstallerV1;
import net.minecraftforge.installer.actions.ProgressCallback;
import net.minecraftforge.installer.json.Install;
import net.minecraftforge.installer.json.InstallV1;
import net.minecraftforge.installer.json.Util;
public class Installer {
public static boolean install(File libraryDir, File minecraftJar, File installerJar) {
AbstractInstaller installer = createInstaller();
ProgressCallback monitor = ProgressCallback.withOutputs(System.out);
Install profile = installer.loadInstallProfile();
if (System.getProperty("java.net.preferIPv4Stack") == null) {
System.setProperty("java.net.preferIPv4Stack", "true");
}
String vendor = System.getProperty("java.vendor", "missing vendor");
String javaVersion = System.getProperty("java.version", "missing java version");
String jvmVersion = System.getProperty("java.vm.version", "missing jvm version");
monitor.message(String.format("JVM info: %s - %s - %s", vendor, javaVersion, jvmVersion));
monitor.message("java.net.preferIPv4Stack=" + System.getProperty("java.net.preferIPv4Stack"));
return installer.runClientInstall(profile, monitor, libraryDir, minecraftJar, installerJar);
}
private static AbstractInstaller createInstaller() {
try {
Class<?> installerClass = Util.class.getMethod("loadInstallProfile").getReturnType();
if (installerClass.equals(Install.class)) {
return new InstallerV0();
} else if (installerClass.equals(InstallV1.class)) {
return new InstallerV1();
} else {
throw new IllegalArgumentException("Unable to determine the installer version. (" + installerClass + ")");
}
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}

View file

@ -0,0 +1,69 @@
package io.github.zekerzhayard.forgewrapper.installer;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import cpw.mods.modlauncher.Launcher;
import io.github.zekerzhayard.forgewrapper.installer.detector.DetectorLoader;
import io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector;
import io.github.zekerzhayard.forgewrapper.installer.util.ModuleUtil;
public class Main {
public static void main(String[] args) throws Throwable {
// --fml.neoForgeVersion 20.2.20-beta --fml.fmlVersion 1.0.2 --fml.mcVersion 1.20.2 --fml.neoFormVersion 20231019.002635 --launchTarget forgeclient
List<String> argsList = Stream.of(args).collect(Collectors.toList());
// NOTE: this is only true for NeoForge versions past 20.2.x
// early versions of NeoForge (for 1.20.1) are not supposed to be covered here
boolean isNeoForge = argsList.contains("--fml.neoForgeVersion");
String mcVersion = argsList.get(argsList.indexOf("--fml.mcVersion") + 1);
String forgeGroup = argsList.contains("--fml.forgeGroup") ? argsList.get(argsList.indexOf("--fml.forgeGroup") + 1) : "net.neoforged";
String forgeArtifact = isNeoForge ? "neoforge" : "forge";
String forgeVersionKey = isNeoForge ? "--fml.neoForgeVersion" : "--fml.forgeVersion";
String forgeVersion = argsList.get(argsList.indexOf(forgeVersionKey) + 1);
String forgeFullVersion = isNeoForge ? forgeVersion : mcVersion + "-" + forgeVersion;
IFileDetector detector = DetectorLoader.loadDetector();
try {
Bootstrap.bootstrap(detector.getJvmArgs(forgeGroup, forgeArtifact, forgeFullVersion), detector.getMinecraftJar(mcVersion).getFileName().toString(), detector.getLibraryDir().toAbsolutePath().toString());
} catch (Throwable ignored) {
// Avoid this bunch of hacks that nuke the whole wrapper.
}
if (!detector.checkExtraFiles(forgeGroup, forgeArtifact, forgeFullVersion)) {
System.out.println("Some extra libraries are missing! Running the installer to generate them now.");
// Check installer jar.
Path installerJar = detector.getInstallerJar(forgeGroup, forgeArtifact, forgeFullVersion);
if (!IFileDetector.isFile(installerJar)) {
throw new RuntimeException("Unable to detect the forge installer!");
}
// Check vanilla Minecraft jar.
Path minecraftJar = detector.getMinecraftJar(mcVersion);
if (!IFileDetector.isFile(minecraftJar)) {
throw new RuntimeException("Unable to detect the Minecraft jar!");
}
try (URLClassLoader ucl = URLClassLoader.newInstance(new URL[] {
Main.class.getProtectionDomain().getCodeSource().getLocation(),
Launcher.class.getProtectionDomain().getCodeSource().getLocation(),
installerJar.toUri().toURL()
}, ModuleUtil.getPlatformClassLoader())) {
Class<?> installer = ucl.loadClass("io.github.zekerzhayard.forgewrapper.installer.Installer");
if (!(boolean) installer.getMethod("install", File.class, File.class, File.class).invoke(null, detector.getLibraryDir().toFile(), minecraftJar.toFile(), installerJar.toFile())) {
return;
}
}
}
ModuleUtil.setupClassPath(detector.getLibraryDir(), detector.getExtraLibraries(forgeGroup, forgeArtifact, forgeFullVersion));
Class<?> mainClass = ModuleUtil.setupBootstrapLauncher(Class.forName(detector.getMainClass(forgeGroup, forgeArtifact, forgeFullVersion)));
mainClass.getMethod("main", String[].class).invoke(null, new Object[] { args });
}
}

View file

@ -0,0 +1,35 @@
package io.github.zekerzhayard.forgewrapper.installer.detector;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
public class DetectorLoader {
public static IFileDetector loadDetector() {
ServiceLoader<IFileDetector> sl = ServiceLoader.load(IFileDetector.class);
HashMap<String, IFileDetector> detectors = new HashMap<>();
for (IFileDetector detector : sl) {
detectors.put(detector.name(), detector);
}
boolean enabled = false;
IFileDetector temp = null;
for (Map.Entry<String, IFileDetector> detector : detectors.entrySet()) {
HashMap<String, IFileDetector> others = new HashMap<>(detectors);
others.remove(detector.getKey());
if (!enabled) {
enabled = detector.getValue().enabled(others);
if (enabled) {
temp = detector.getValue();
}
} else if (detector.getValue().enabled(others)) {
throw new RuntimeException("There are two or more file detectors are enabled! (" + temp.toString() + ", " + detector.toString() + ")");
}
}
if (temp == null) {
throw new RuntimeException("No file detector is enabled!");
}
return temp;
}
}

View file

@ -0,0 +1,267 @@
package io.github.zekerzhayard.forgewrapper.installer.detector;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import cpw.mods.modlauncher.Launcher;
public interface IFileDetector {
/**
* @return The name of the detector.
*/
String name();
/**
* If there are two or more detectors are enabled, an exception will be thrown. Removing anything from the map is in vain.
* @param others Other detectors.
* @return True represents enabled.
*/
boolean enabled(HashMap<String, IFileDetector> others);
/**
* @return The ".minecraft/libraries" folder for normal. It can also be defined by JVM argument "-Dforgewrapper.librariesDir=&lt;libraries-path&gt;".
*/
default Path getLibraryDir() {
String libraryDir = System.getProperty("forgewrapper.librariesDir");
if (libraryDir != null) {
return Paths.get(libraryDir).toAbsolutePath();
}
try {
Path launcher = Paths.get(Launcher.class.getProtectionDomain().getCodeSource().getLocation().toURI()).toAbsolutePath();
// /<version> /modlauncher/mods /cpw /libraries
return launcher.getParent().getParent().getParent().getParent().getParent().toAbsolutePath();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
/**
* @param forgeGroup Forge package group (e.g. net.minecraftforge).
* @param forgeArtifact Forge package artifact (e.g. forge).
* @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0).
* @return The forge installer jar path. It can also be defined by JVM argument "-Dforgewrapper.installer=&lt;installer-path&gt;".
*/
default Path getInstallerJar(String forgeGroup, String forgeArtifact, String forgeFullVersion) {
String installer = System.getProperty("forgewrapper.installer");
if (installer != null) {
return Paths.get(installer).toAbsolutePath();
}
return null;
}
/**
* @param mcVersion Minecraft version (e.g. 1.14.4).
* @return The minecraft client jar path. It can also be defined by JVM argument "-Dforgewrapper.minecraft=&lt;minecraft-path&gt;".
*/
default Path getMinecraftJar(String mcVersion) {
String minecraft = System.getProperty("forgewrapper.minecraft");
if (minecraft != null) {
return Paths.get(minecraft).toAbsolutePath();
}
return null;
}
/**
* @param forgeGroup Forge package group (e.g. net.minecraftforge).
* @param forgeArtifact Forge package artifact (e.g. forge).
* @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0).
* @return The list of jvm args.
*/
default List<String> getJvmArgs(String forgeGroup, String forgeArtifact, String forgeFullVersion) {
return this.getDataFromInstaller(forgeGroup, forgeArtifact, forgeFullVersion, "version.json", e -> {
JsonElement element = e.getAsJsonObject().get("arguments").getAsJsonObject().get("jvm");
List<String> args = new ArrayList<>();
if (!element.equals(JsonNull.INSTANCE)) {
element.getAsJsonArray().iterator().forEachRemaining(je -> args.add(je.getAsString()));
}
return args;
});
}
default List<String> getExtraLibraries(String forgeGroup, String forgeArtifact, String forgeFullVersion) {
return this.getDataFromInstaller(forgeGroup, forgeArtifact, forgeFullVersion, "version.json", e -> {
List<String> paths = new ArrayList<>();
e.getAsJsonObject().getAsJsonArray("libraries").iterator().forEachRemaining(je -> {
JsonObject artifact = je.getAsJsonObject().get("downloads").getAsJsonObject().get("artifact").getAsJsonObject();
if (artifact.get("url").getAsString().isEmpty()) {
paths.add(artifact.get("path").getAsString());
}
});
return paths;
});
}
/**
* @param forgeGroup Forge package group (e.g. net.minecraftforge).
* @param forgeArtifact Forge package artifact (e.g. forge).
* @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0).
* @return The main class.
*/
default String getMainClass(String forgeGroup, String forgeArtifact, String forgeFullVersion) {
return this.getDataFromInstaller(forgeGroup, forgeArtifact, forgeFullVersion, "version.json", e -> e.getAsJsonObject().getAsJsonPrimitive("mainClass").getAsString());
}
/**
* @param forgeGroup Forge package group (e.g. net.minecraftforge).
* @param forgeArtifact Forge package artifact (e.g. forge).
* @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0).
* @return The json object in the-installer-jar-->install_profile.json-->data-->xxx-->client.
*/
default JsonObject getInstallProfileExtraData(String forgeGroup, String forgeArtifact, String forgeFullVersion) {
return this.getDataFromInstaller(forgeGroup, forgeArtifact, forgeFullVersion, "install_profile.json", e -> e.getAsJsonObject().getAsJsonObject("data"));
}
@SuppressWarnings("deprecation")
default <R> R getDataFromInstaller(String forgeGroup, String forgeArtifact, String forgeFullVersion, String entry, Function<JsonElement, R> function) {
Path installer = this.getInstallerJar(forgeGroup, forgeArtifact, forgeFullVersion);
if (isFile(installer)) {
try (ZipFile zf = new ZipFile(installer.toFile())) {
ZipEntry ze = zf.getEntry(entry);
if (ze != null) {
try (
InputStream is = zf.getInputStream(ze);
InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)
) {
return function.apply(new JsonParser().parse(isr));
}
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
throw new RuntimeException("Unable to detect the forge installer!");
}
return null;
}
/**
* Check all cached files.
* @param forgeGroup Forge package group (e.g. net.minecraftforge).
* @param forgeArtifact Forge package artifact (e.g. forge).
* @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0).
* @return True represents all files are ready.
*/
default boolean checkExtraFiles(String forgeGroup, String forgeArtifact, String forgeFullVersion) {
JsonObject jo = this.getInstallProfileExtraData(forgeGroup, forgeArtifact, forgeFullVersion);
if (jo != null) {
Map<String, Path> libsMap = new HashMap<>();
Map<String, String> hashMap = new HashMap<>();
// Get all "data/<name>/client" elements.
Pattern artifactPattern = Pattern.compile("^\\[(?<groupId>[^:]*):(?<artifactId>[^:]*):(?<version>[^:@]*)(:(?<classifier>[^@]*))?(@(?<type>[^]]*))?]$");
for (Map.Entry<String, JsonElement> entry : jo.entrySet()) {
String clientStr = entry.getValue().getAsJsonObject().get("client").getAsString();
if (entry.getKey().endsWith("_SHA")) {
Pattern p = Pattern.compile("^'(?<sha1>[A-Za-z0-9]{40})'$");
Matcher m = p.matcher(clientStr);
if (m.find()) {
hashMap.put(entry.getKey(), m.group("sha1"));
}
} else {
Matcher m = artifactPattern.matcher(clientStr);
if (m.find()) {
String groupId = nullToDefault(m.group("groupId"), "");
String artifactId = nullToDefault(m.group("artifactId"), "");
String version = nullToDefault(m.group("version"), "");
String classifier = nullToDefault(m.group("classifier"), "");
String type = nullToDefault(m.group("type"), "jar");
libsMap.put(entry.getKey(), this.getLibraryDir()
.resolve(groupId.replace('.', File.separatorChar))
.resolve(artifactId)
.resolve(version)
.resolve(artifactId + "-" + version + (classifier.isEmpty() ? "" : "-") + classifier + "." + type).toAbsolutePath());
}
}
}
// Check all cached libraries.
boolean checked = true;
for (Map.Entry<String, Path> entry : libsMap.entrySet()) {
String sha1 = "";
String entryKey = entry.getKey();
// NOTE: only used on servers, it's busted
if(entryKey.equals("MC_UNPACKED")) {
continue;
}
/**
* NOTE: workaround for https://github.com/MultiMC/Launcher/issues/4400
* We ignore the hash of the client file and instead just rely on it being 'correct, maybe' if it's present at all
*/
System.out.println("Checking: " + entryKey);
if(!entryKey.equals("PATCHED")) {
sha1 = hashMap.get(entryKey + "_SHA");
}
checked = checkExtraFile(entry.getValue(), sha1);
if (!checked) {
System.out.println("Missing: " + entry.getValue());
break;
}
}
return checked;
}
// Skip installing process if installer profile doesn't exist.
return true;
}
/**
* Check the exact file.
* @param path The path of the file to check.
* @param sha1 The sha1 defined in installer.
* @return True represents the file is ready.
*/
static boolean checkExtraFile(Path path, String sha1) {
if (!isFile(path)) {
return false;
}
if(sha1 == null || sha1.equals("")) {
return true;
}
return sha1.toLowerCase(Locale.ENGLISH).equals(getFileSHA1(path));
}
static boolean isFile(Path path) {
return path != null && Files.isRegularFile(path);
}
static String getFileSHA1(Path path) {
try {
StringBuilder sha1 = new StringBuilder(new BigInteger(1, MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(path))).toString(16));
while (sha1.length() < 40) {
sha1.insert(0, "0");
}
return sha1.toString().toLowerCase(Locale.ENGLISH);
} catch (IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
static String nullToDefault(String string, String defaultValue) {
return string == null ? defaultValue : string;
}
}

View file

@ -0,0 +1,52 @@
package io.github.zekerzhayard.forgewrapper.installer.detector;
import java.nio.file.Path;
import java.util.HashMap;
public class MultiMCFileDetector implements IFileDetector {
protected Path libraryDir = null;
protected Path installerJar = null;
protected Path minecraftJar = null;
@Override
public String name() {
return "MultiMC";
}
@Override
public boolean enabled(HashMap<String, IFileDetector> others) {
return others.size() == 0;
}
@Override
public Path getLibraryDir() {
if (this.libraryDir == null) {
this.libraryDir = IFileDetector.super.getLibraryDir();
}
return this.libraryDir;
}
@Override
public Path getInstallerJar(String forgeGroup, String forgeArtifact, String forgeFullVersion) {
Path path = IFileDetector.super.getInstallerJar(forgeGroup, forgeArtifact, forgeFullVersion);
if (path == null) {
if (this.installerJar == null) {
Path installerBase = this.getLibraryDir();
for (String dir : forgeGroup.split("\\."))
installerBase = installerBase.resolve(dir);
this.installerJar = installerBase.resolve(forgeArtifact).resolve(forgeFullVersion).resolve(forgeArtifact + "-" + forgeFullVersion + "-installer.jar").toAbsolutePath();
}
return this.installerJar;
}
return path;
}
@Override
public Path getMinecraftJar(String mcVersion) {
Path path = IFileDetector.super.getMinecraftJar(mcVersion);
if (path == null) {
return this.minecraftJar != null ? this.minecraftJar : (this.minecraftJar = this.getLibraryDir().resolve("com").resolve("mojang").resolve("minecraft").resolve(mcVersion).resolve("minecraft-" + mcVersion + "-client.jar").toAbsolutePath());
}
return path;
}
}

View file

@ -0,0 +1,37 @@
package io.github.zekerzhayard.forgewrapper.installer.util;
import java.io.File;
import java.lang.reflect.Method;
import net.minecraftforge.installer.actions.PostProcessors;
import net.minecraftforge.installer.actions.ProgressCallback;
import net.minecraftforge.installer.json.Install;
import net.minecraftforge.installer.json.InstallV1;
import net.minecraftforge.installer.json.Util;
public class InstallerV1 extends AbstractInstaller {
@Override
public Install loadInstallProfile() {
return Util.loadInstallProfile();
}
@Override
public boolean runClientInstall(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar,
File installerJar) {
PostProcessors processors = new PostProcessors(
profile instanceof InstallV1 ? (InstallV1) profile : new InstallV1(profile), true, monitor);
try {
Method method = processors.getClass().getMethod("process", File.class, File.class, File.class, File.class);
Object result = method.invoke(processors, libraryDir, minecraftJar, libraryDir.getParentFile(),
installerJar);
if (method.getReturnType() == boolean.class)
return (boolean) result;
return result != null;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,40 @@
package io.github.zekerzhayard.forgewrapper.installer.util;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.List;
public class ModuleUtil {
public static void addModules(String modulePath) {
// nothing to do with Java 8
}
public static void addExports(List<String> exports) {
// nothing to do with Java 8
}
public static void addOpens(List<String> opens) {
// nothing to do with Java 8
}
public static void setupClassPath(Path libraryDir, List<String> paths) throws Throwable {
Method addURLMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addURLMethod.setAccessible(true);
for (String path : paths) {
addURLMethod.invoke(ClassLoader.getSystemClassLoader(), libraryDir.resolve(path).toUri().toURL());
}
}
public static Class<?> setupBootstrapLauncher(Class<?> mainClass) {
// nothing to do with Java 8
return mainClass;
}
public static ClassLoader getPlatformClassLoader() {
// PlatformClassLoader does not exist in Java 8
return null;
}
}

View file

@ -0,0 +1 @@
io.github.zekerzhayard.forgewrapper.installer.detector.MultiMCFileDetector