├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── DateFetcher ├── .gitignore ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradle.properties ├── build.gradle ├── update-commit-dates.sh ├── gradlew.bat ├── src │ └── main │ │ └── java │ │ └── de │ │ └── metroite │ │ └── datefetcher │ │ └── DateFetcher.java └── gradlew ├── src ├── main │ └── groovy │ │ └── dex │ │ └── mcgitmaker │ │ ├── data │ │ ├── outlet │ │ │ ├── ReleaseType.groovy │ │ │ ├── McOutletMeta.groovy │ │ │ └── McFabric.groovy │ │ ├── McArtifacts.groovy │ │ ├── Artifact.groovy │ │ ├── McVersion.groovy │ │ └── McMetadata.groovy │ │ ├── loom │ │ ├── Remapper.groovy │ │ ├── Decompiler.groovy │ │ ├── BundleMetadata.groovy │ │ └── FileSystemUtil.groovy │ │ ├── GitCraft.groovy │ │ ├── RepoManager.groovy │ │ └── Util.groovy └── test │ └── groovy │ └── dex │ └── mcgitmaker │ └── McMetadataTest.groovy ├── .editorconfig ├── .gitignore ├── gradle.properties ├── readme.md ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'GitCraft' 2 | include('DateFetcher') 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexman545/GitCraft/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /DateFetcher/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | -------------------------------------------------------------------------------- /DateFetcher/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexman545/GitCraft/HEAD/DateFetcher/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /DateFetcher/gradle.properties: -------------------------------------------------------------------------------- 1 | # DateFetcher Properties 2 | group = de.metroite.datefetcher 3 | version = 0.1.0-SNAPSHOT 4 | 5 | # DateFetcher Dependencies 6 | jackson_version = 2.17.+ 7 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/data/outlet/ReleaseType.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker.data.outlet 2 | 3 | enum ReleaseType { 4 | OLD_ALPHA, 5 | OLD_BETA, 6 | EXPERIMENT, 7 | RELEASE, 8 | SNAPSHOT, 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/data/outlet/McOutletMeta.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker.data.outlet 2 | 3 | import groovy.transform.ToString 4 | 5 | @ToString 6 | class McOutletMeta { 7 | Date lastChanged 8 | ArrayList versions 9 | } 10 | -------------------------------------------------------------------------------- /DateFetcher/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/data/McArtifacts.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker.data 2 | 3 | class McArtifacts { 4 | Artifact clientJar 5 | Artifact clientMappings 6 | Artifact serverJar 7 | Artifact serverMappings 8 | boolean hasMappings 9 | } 10 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/data/outlet/McFabric.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker.data.outlet 2 | 3 | import groovy.transform.ToString 4 | 5 | @ToString 6 | class McFabric { 7 | String id 8 | String normalized 9 | Integer javaVersion = 8 10 | ReleaseType type 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{yml,yaml,json,toml,md}] 12 | indent_size = 2 13 | 14 | [*.bat] 15 | end_of_line = crlf 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # GitCraft 2 | artifact-store/ 3 | minecraft-repo/ 4 | 5 | # Gradle 6 | .gradle/ 7 | build/ 8 | bin/ 9 | out/ 10 | classes/ 11 | 12 | # IntelliJ Idea 13 | .idea/ 14 | *.iml 15 | *.ipr 16 | *.iws 17 | 18 | # Eclipse 19 | .eclipse/ 20 | *.launch 21 | 22 | # VS Code 23 | .vscode/ 24 | 25 | # Fleet 26 | .fleet/ 27 | 28 | # MacOS 29 | *.DS_Store 30 | 31 | # Java 32 | hs_err_*.log 33 | replay_*.log 34 | *.hprof 35 | *.jfr 36 | 37 | # DataFetcher 38 | version_manifest.json 39 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle Properties 2 | org.gradle.jvmargs = -Xmx3G 3 | 4 | # GitCraft Properties 5 | group = dex.mcgitmaker 6 | version = 0.1.4-SNAPSHOT 7 | 8 | # GitCraft Dependencies 9 | groovy_version = 3.0.+ 10 | fabric_loader_version = 0.15.+ 11 | asm_version = 9.7 12 | stitch_version = 0.6.+ 13 | tiny_remapper_version = 0.10.+ 14 | access_widener_version = 2.1.+ 15 | mappingio_version = 0.6.+ 16 | lorenz_tiny_version = 4.0.+ 17 | jgit_version = 6.8.+ 18 | vineflower_version = 1.10.+ 19 | -------------------------------------------------------------------------------- /DateFetcher/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.github.johnrengelman.shadow' version '8.1.1' 3 | id 'java' 4 | id 'application' 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | implementation "com.fasterxml.jackson.core:jackson-databind:${jackson_version}" 13 | shadow "com.fasterxml.jackson.core:jackson-databind:${jackson_version}" 14 | implementation "com.fasterxml.jackson.core:jackson-core:${jackson_version}}" 15 | shadow "com.fasterxml.jackson.core:jackson-core:${jackson_version}" 16 | } 17 | 18 | java { 19 | toolchain { 20 | languageVersion = JavaLanguageVersion.of(21) 21 | } 22 | } 23 | 24 | application { 25 | mainClass = "de.metroite.datefetcher.DateFetcher" 26 | } 27 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # GitCraft 2 | Generates a Git repository of decompiled Minecraft, starting from 1.14.4. For personal use only. Do not share or upload the resulting repository. 3 | 4 | To get started, execute `./gradlew run` from the command line. 5 | 6 | Artifacts are stored in the current working directory: 7 | - Metadata, mappings and other temporary files go into `artifact-store`. 8 | - The generated Git repository with MC's source code goes into `minecraft-repo`. 9 | 10 | Powered by: 11 | - [Quiltflower](https://github.com/QuiltMC/quiltflower) 12 | - [Fabric Loader](https://github.com/FabricMC/fabric-loader) 13 | - [Mapping-IO](https://github.com/FabricMC/mapping-io) 14 | - [Tiny Remapper](https://github.com/FabricMC/tiny-remapper) 15 | - Mojang's generous mapping files (Mojmap) 16 | -------------------------------------------------------------------------------- /src/test/groovy/dex/mcgitmaker/McMetadataTest.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker 2 | 3 | 4 | import net.fabricmc.loader.api.SemanticVersion 5 | 6 | class McMetadataTest { 7 | static void main(String[] args) { 8 | //def x = new GitCraft() 9 | //def v = x.versions.get(SemanticVersion.parse('1.19.1-beta.1')) 10 | 11 | println Util.Outlet.INSTANCE.outletDatabase 12 | 13 | //x.merged() // has to be called or it endlessly tries to merge them 14 | 15 | //v.remappedJar() 16 | //v.decompiledMc() 17 | 18 | //Decompiler.decompile(v) 19 | //x.decompileAllVersions(false) 20 | //x.updateRepo() 21 | //v.decompiledMc() 22 | //Util.updateMcVersionPath(v) 23 | 24 | //keyset is in ascending order, good 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/data/Artifact.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker.data 2 | 3 | import java.nio.file.Path 4 | import java.nio.file.Paths 5 | 6 | class Artifact { 7 | String url // Download URL 8 | String name // File name 9 | String containingPath 10 | 11 | File fetchArtifact() { 12 | def path = getContainingPath().resolve(name) 13 | ensureArtifactPresence(path) 14 | 15 | return path.toFile() 16 | } 17 | 18 | void ensureArtifactPresence(Path p) { 19 | def f = p.toFile() 20 | if (!f.exists()) { 21 | p.parent.toFile().mkdirs() 22 | println 'Missing artifact: ' + name + ' At: ' + containingPath 23 | println 'Attempting to download...' 24 | 25 | p.withOutputStream { it << new URL(url).newInputStream() } 26 | } 27 | } 28 | 29 | Path getContainingPath() { 30 | return Paths.get(containingPath) 31 | } 32 | 33 | static String nameFromUrl(String url) { 34 | if (url == null) return "" 35 | return url.split("/").last() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/loom/Remapper.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker.loom 2 | 3 | import dex.mcgitmaker.GitCraft 4 | import dex.mcgitmaker.data.McVersion 5 | import net.fabricmc.tinyremapper.OutputConsumerPath 6 | import net.fabricmc.tinyremapper.TinyRemapper 7 | 8 | import java.nio.file.Path 9 | import java.util.regex.Pattern 10 | 11 | class Remapper { 12 | // From Fabric-loom 13 | private static final Pattern MC_LV_PATTERN = Pattern.compile('\\$\\$\\d+') 14 | 15 | static Path doRemap(McVersion mcVersion) { 16 | def output = GitCraft.REMAPPED.resolve(mcVersion.version + '.jar') 17 | 18 | // Based on what Fabric-loom does 19 | if (!output.toFile().exists()) { 20 | def remapper = TinyRemapper.newRemapper() 21 | .renameInvalidLocals(true) 22 | .rebuildSourceFilenames(true) 23 | .invalidLvNamePattern(MC_LV_PATTERN) 24 | .inferNameFromSameLvIndex(true) 25 | .withMappings(mcVersion.mappingsProvider()) 26 | .fixPackageAccess(true) 27 | .threads(Runtime.getRuntime().availableProcessors() - 3) 28 | .build() 29 | 30 | remapper.readInputs(mcVersion.merged().toPath()) 31 | 32 | try (OutputConsumerPath consumer = new OutputConsumerPath.Builder(output).build()) { 33 | remapper.apply(consumer, remapper.createInputTag()) 34 | } 35 | 36 | remapper.finish() 37 | } 38 | 39 | return output 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /DateFetcher/update-commit-dates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage 4 | if [ ! "$#" -eq 1 ]; then 5 | echo "This script will fetch the release dates of all committed Minecraft versions and reflect those dates in the author date of the corresponding commit. This is a very dangerous script! Please pass the absolute path to the minecraft-repo as an argument! Example: ./update-commit-dates.sh ../minecraft-repo" 6 | exit 1 7 | fi 8 | 9 | DateFetcher="./build/libs/DateFetcher-0.1.0-SNAPSHOT-all.jar" 10 | 11 | TARGET_DIR="$(cd "$(dirname "$1")"; pwd)/$(basename "$1")" 12 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 13 | 14 | cd $SCRIPT_DIR 15 | 16 | # Build DateFetcher 17 | if [ ! -f $DateFetcher ]; then 18 | echo "DateFetcher must be built first.." 19 | if ( ! sh ./gradlew build ); then 20 | echo "Could not build DateFetcher!" 21 | exit 2 22 | fi 23 | fi 24 | 25 | # Validate repository 26 | if [ ! -d "$TARGET_DIR/.git" ]; then 27 | echo "No git repository found in $TARGET_DIR!" 28 | ls $TARGET_DIR | echo 29 | exit 3 30 | fi 31 | 32 | read -p "You are about to perform the modifications in $TARGET_DIR. Are you sure? (y/n)" -n 1 -r 33 | echo 34 | if [[ $REPLY =~ ^[Yy]$ ]]; then 35 | cd $TARGET_DIR 36 | 37 | git filter-repo --force --commit-callback " 38 | import subprocess 39 | import time 40 | from datetime import datetime, timedelta, timezone 41 | 42 | def getDatetime(timestamp_string): 43 | timestamp_string, timezone_string = timestamp_string.split() 44 | timestamp = int(timestamp_string) 45 | tz = timezone(timedelta(hours = int(timezone_string[:3]), minutes = int(timezone_string[0] + timezone_string[3:]))) 46 | return datetime.fromtimestamp(timestamp, tz) 47 | 48 | mcVersion = commit.message.decode().split('\n')[0] 49 | print('\nVersion: ' + mcVersion) 50 | 51 | old_dt = getDatetime(commit.author_date.decode()) 52 | print('From: ' + old_dt.isoformat()) 53 | 54 | releaseTime = subprocess.run(['java', '-jar', '$SCRIPT_DIR/$DateFetcher', mcVersion, 'epoch'], capture_output=True, text=True).stdout.strip() 55 | new_dt = getDatetime(releaseTime) 56 | commit.author_date = (releaseTime).encode() 57 | commit.committer_date = (releaseTime).encode() 58 | print('To: ' + new_dt.isoformat()) 59 | " 60 | fi 61 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /DateFetcher/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/loom/Decompiler.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker.loom 2 | 3 | import dex.mcgitmaker.GitCraft 4 | import dex.mcgitmaker.data.Artifact 5 | import dex.mcgitmaker.data.McVersion 6 | import groovy.json.JsonGenerator 7 | import groovy.json.JsonOutput 8 | import org.jetbrains.java.decompiler.main.Fernflower 9 | import org.jetbrains.java.decompiler.main.decompiler.DirectoryResultSaver 10 | import org.jetbrains.java.decompiler.main.decompiler.PrintStreamLogger 11 | import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences 12 | 13 | import java.nio.file.Files 14 | import java.nio.file.Path 15 | 16 | class Decompiler { 17 | private static final def NULL_IS = new PrintStream(OutputStream.nullOutputStream()) 18 | 19 | static Path decompiledPath(McVersion mcVersion) { 20 | return GitCraft.DECOMPILED_WORKINGS.resolve(mcVersion.version) 21 | } 22 | 23 | // Adapted from loom-quiltflower by Juuxel 24 | static def decompile(McVersion mcVersion) { 25 | println 'Decompiling: ' + mcVersion.version + '...' 26 | println 'Decompiler log output is suppressed!' 27 | Map options = new HashMap<>() 28 | 29 | options.put(IFernflowerPreferences.INDENT_STRING, "\t"); 30 | options.put(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1"); 31 | options.put(IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1"); 32 | options.put(IFernflowerPreferences.REMOVE_SYNTHETIC, "1"); 33 | options.put(IFernflowerPreferences.LOG_LEVEL, "trace"); 34 | options.put(IFernflowerPreferences.THREADS, Integer.toString(Runtime.getRuntime().availableProcessors() - 3)); 35 | //options.put(IFabricJavadocProvider.PROPERTY_NAME, new QfTinyJavadocProvider(metaData.javaDocs().toFile())); 36 | 37 | // Experimental VF preferences 38 | options.put(IFernflowerPreferences.PATTERN_MATCHING, "1"); 39 | options.put(IFernflowerPreferences.TRY_LOOP_FIX, "1"); 40 | //options.putAll(ReflectionUtil.>maybeGetFieldOrRecordComponent(metaData, "options").orElse(Map.of())); 41 | 42 | Fernflower ff = new Fernflower(new DirectoryResultSaver(decompiledPath(mcVersion).toFile()), options, new PrintStreamLogger(/*System.out*/NULL_IS)) 43 | 44 | println 'Adding libraries...' 45 | for (Artifact library : mcVersion.libraries) { 46 | ff.addLibrary(library.fetchArtifact()) 47 | } 48 | 49 | ff.addSource(mcVersion.remappedJar()) 50 | 51 | println 'Decompiling...' 52 | ff.decompileContext() 53 | 54 | println 'Writing dependencies file...' 55 | writeLibraries(mcVersion) 56 | } 57 | 58 | private static void writeLibraries(McVersion mcVersion) { 59 | def p = GitCraft.DECOMPILED_WORKINGS.resolve(mcVersion.version).resolve('dependencies.json') 60 | def generator = new JsonGenerator.Options() 61 | .excludeFieldsByName('containingPath') 62 | .build() 63 | 64 | def c = mcVersion.libraries.collect().sort { 65 | def names = it.name.split('-').dropRight(1) 66 | names.join("") 67 | } 68 | c.push(new Artifact(url: '', name: 'Java ' + mcVersion.javaVersion, containingPath: '')) 69 | 70 | def x = p.toFile() 71 | x.createNewFile() 72 | x.write(JsonOutput.prettyPrint(generator.toJson(c))) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/loom/BundleMetadata.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of fabric-loom, licensed under the MIT License (MIT). 3 | * 4 | * Copyright (c) 2021 FabricMC 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package dex.mcgitmaker.loom 25 | 26 | import java.nio.file.Files 27 | import java.nio.file.Path 28 | import java.nio.file.StandardCopyOption 29 | 30 | class BundleMetadata { 31 | List libraries 32 | List versions 33 | String mainClass 34 | private static final String LIBRARIES_LIST_PATH = "META-INF/libraries.list"; 35 | private static final String VERSIONS_LIST_PATH = "META-INF/versions.list"; 36 | private static final String MAINCLASS_PATH = "META-INF/main-class"; 37 | 38 | static BundleMetadata fromJar(Path jar) throws IOException { 39 | final List libraries; 40 | final List versions; 41 | final String mainClass; 42 | 43 | try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar)) { 44 | if (!Files.exists(fs.get().getPath(VERSIONS_LIST_PATH))) { 45 | // Legacy jar 46 | return null; 47 | } 48 | 49 | libraries = readEntries(fs.readString(LIBRARIES_LIST_PATH), "META-INF/libraries/"); 50 | versions = readEntries(fs.readString(VERSIONS_LIST_PATH), "META-INF/versions/"); 51 | mainClass = fs.readString(MAINCLASS_PATH).trim(); 52 | } 53 | 54 | return new BundleMetadata(libraries: libraries, versions: versions, mainClass: mainClass); 55 | } 56 | 57 | private static List readEntries(String content, String pathPrefix) { 58 | List entries = new ArrayList<>(); 59 | 60 | for (String entry : content.split("\n")) { 61 | if (entry.isBlank()) { 62 | continue; 63 | } 64 | 65 | String[] split = entry.split("\t"); 66 | 67 | if (split.length != 3) { 68 | continue; 69 | } 70 | 71 | entries.add(new Entry(sha1: split[0], name: split[1], path: pathPrefix + split[2])); 72 | } 73 | 74 | return Collections.unmodifiableList(entries); 75 | } 76 | 77 | static class Entry { 78 | public String sha1 79 | public String name 80 | public String path 81 | void unpackEntry(Path jar, Path dest) throws IOException { 82 | try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar); 83 | InputStream is = Files.newInputStream(fs.get().getPath(path))) { 84 | Files.copy(is, dest, StandardCopyOption.REPLACE_EXISTING); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/GitCraft.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker 2 | 3 | import dex.mcgitmaker.data.McMetadata 4 | import dex.mcgitmaker.data.McVersion 5 | import dex.mcgitmaker.loom.Decompiler 6 | import net.fabricmc.loader.api.SemanticVersion 7 | 8 | import java.nio.file.Paths 9 | 10 | class GitCraft { 11 | public static final def MAIN_ARTIFACT_STORE = Paths.get(new File('.').canonicalPath).resolve('artifact-store') 12 | public static final def DECOMPILED_WORKINGS = MAIN_ARTIFACT_STORE.resolve('decompiled') 13 | public static final def MAPPINGS = MAIN_ARTIFACT_STORE.resolve('mappings') 14 | public static final def REPO_OLD = MAIN_ARTIFACT_STORE.parent.resolve('minecaft-repo') 15 | public static final def REPO = MAIN_ARTIFACT_STORE.parent.resolve('minecraft-repo') 16 | public static final def MC_VERSION_STORE = MAIN_ARTIFACT_STORE.resolve('mc-versions') 17 | public static final def LIBRARY_STORE = MAIN_ARTIFACT_STORE.resolve('libraries') 18 | public static final def METADATA_STORE = MAIN_ARTIFACT_STORE.resolve('metadata.json') 19 | public static final def REMAPPED = MAIN_ARTIFACT_STORE.resolve('remapped-mc') 20 | McMetadata mcMetadata 21 | TreeMap versions 22 | 23 | static { 24 | MAIN_ARTIFACT_STORE.toFile().mkdirs() 25 | DECOMPILED_WORKINGS.toFile().mkdirs() 26 | MAPPINGS.toFile().mkdirs() 27 | REPO.toFile().mkdirs() 28 | MC_VERSION_STORE.toFile().mkdirs() 29 | LIBRARY_STORE.toFile().mkdirs() 30 | REMAPPED.toFile().mkdirs() 31 | } 32 | 33 | GitCraft() { 34 | this.mcMetadata = new McMetadata() 35 | versions = Util.orderVersionMap(mcMetadata.metadata) 36 | println 'Saving updated metadata...' 37 | Util.saveMetadata(mcMetadata.metadata) 38 | } 39 | 40 | static void main(String[] args) { 41 | if (REPO_OLD.toFile().isDirectory()) { 42 | println 'It looks like you have a misspelled repo file at: ' + REPO_OLD.toString() 43 | println 'Please rename that folder so that it matches: ' + REPO.toString() 44 | System.exit(1) 45 | } 46 | def gitCraft = new GitCraft() 47 | gitCraft.updateRepo() 48 | 49 | println 'Repo can be found at: ' + REPO.toString() 50 | } 51 | 52 | // Removes files that are unlikely to be needed again that are large 53 | private def cleanupFiles() { 54 | MC_VERSION_STORE.deleteDir() 55 | DECOMPILED_WORKINGS.deleteDir() 56 | REMAPPED.deleteDir() 57 | } 58 | 59 | def test() { 60 | def r = new RepoManager() 61 | 62 | r.commitDecompiled(versions.values().first()) 63 | r.commitDecompiled(versions.values().last()) 64 | 65 | r.finish() 66 | } 67 | 68 | def updateRepo() { 69 | def r = new RepoManager() 70 | 71 | versions.each {sv, mcv -> 72 | r.commitDecompiled(mcv) 73 | } 74 | 75 | r.finish() 76 | } 77 | 78 | /** 79 | * @param destroyOldVersions Deletes old decompiled directories and decompiles them again 80 | */ 81 | def decompileAllVersions(boolean destroyOldVersions) { 82 | println 'Decompiling all versions! This may take some time!' 83 | if (destroyOldVersions) println 'This will delete old decompiled versions!' 84 | 85 | versions.each {sv, mcv -> 86 | def d = Decompiler.decompiledPath(mcv).toFile() 87 | if (d.exists()) { 88 | if (destroyOldVersions) { 89 | d.deleteDir() 90 | } else { 91 | return 92 | } 93 | } 94 | 95 | Decompiler.decompile(mcv) 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/loom/FileSystemUtil.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of fabric-loom, licensed under the MIT License (MIT). 3 | * 4 | * Copyright (c) 2016-2017 FabricMC 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package dex.mcgitmaker.loom 25 | 26 | import java.nio.charset.StandardCharsets 27 | import java.nio.file.FileSystem 28 | import java.nio.file.FileSystemAlreadyExistsException 29 | import java.nio.file.FileSystems 30 | import java.nio.file.Files 31 | import java.nio.file.NoSuchFileException 32 | import java.nio.file.Path 33 | import java.util.function.Supplier 34 | 35 | final class FileSystemUtil { 36 | static class Delegate implements AutoCloseable, Supplier { 37 | public FileSystem fs 38 | public boolean owner 39 | 40 | byte[] readAllBytes(String path) throws IOException { 41 | Path fsPath = get().getPath(path); 42 | if (Files.exists(fsPath)) { 43 | return Files.readAllBytes(fsPath); 44 | } else { 45 | throw new NoSuchFileException(fsPath.toString()); 46 | } 47 | } 48 | 49 | String readString(String path) throws IOException { 50 | return new String(readAllBytes(path), StandardCharsets.UTF_8); 51 | } 52 | 53 | @Override 54 | void close() throws IOException { 55 | if (owner) { 56 | fs.close(); 57 | } 58 | } 59 | 60 | @Override 61 | FileSystem get() { 62 | return fs; 63 | } 64 | } 65 | 66 | private FileSystemUtil() { 67 | } 68 | 69 | private static final Map jfsArgsCreate = Map.of("create", "true"); 70 | private static final Map jfsArgsEmpty = Collections.emptyMap(); 71 | 72 | static Delegate getJarFileSystem(File file, boolean create) throws IOException { 73 | return getJarFileSystem(file.toURI(), create); 74 | } 75 | 76 | static Delegate getJarFileSystem(Path path, boolean create) throws IOException { 77 | return getJarFileSystem(path.toUri(), create); 78 | } 79 | 80 | static Delegate getJarFileSystem(Path path) throws IOException { 81 | return getJarFileSystem(path, false); 82 | } 83 | 84 | static Delegate getJarFileSystem(URI uri, boolean create) throws IOException { 85 | URI jarUri; 86 | 87 | try { 88 | jarUri = new URI("jar:" + uri.getScheme(), uri.getHost(), uri.getPath(), uri.getFragment()); 89 | } catch (URISyntaxException e) { 90 | throw new IOException(e); 91 | } 92 | 93 | try { 94 | return new Delegate(fs: FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), owner: true); 95 | } catch (FileSystemAlreadyExistsException e) { 96 | return new Delegate(fs: FileSystems.getFileSystem(jarUri), owner: false); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/RepoManager.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker 2 | 3 | import de.metroite.datefetcher.DateFetcher 4 | import dex.mcgitmaker.data.McVersion 5 | import org.eclipse.jgit.api.CommitCommand 6 | import org.eclipse.jgit.api.Git 7 | import org.eclipse.jgit.api.ResetCommand 8 | import org.eclipse.jgit.lib.Constants 9 | import org.eclipse.jgit.lib.PersonIdent 10 | import org.eclipse.jgit.revwalk.RevCommit 11 | import org.eclipse.jgit.revwalk.RevWalk 12 | import org.eclipse.jgit.revwalk.filter.RevFilter 13 | import org.eclipse.jgit.util.SystemReader 14 | import org.eclipse.jgit.util.time.MonotonicSystemClock 15 | 16 | import java.nio.file.Files 17 | import java.nio.file.Path 18 | import java.nio.file.StandardCopyOption 19 | import java.time.ZonedDateTime 20 | import java.util.stream.Stream 21 | 22 | class RepoManager { 23 | Git git 24 | 25 | RepoManager() { 26 | this.git = setupRepo() 27 | } 28 | 29 | def finish() { 30 | git.close() 31 | } 32 | 33 | void commitDecompiled(McVersion mcVersion) { 34 | //todo detect april fools versions and make each their own branch based on semver 35 | // match to avoid polluting main history 36 | // add april fools clasification to outlet-database? 37 | def msg = mcVersion.version + '\n\nSemVer: ' + mcVersion.loaderVersion 38 | 39 | if (git.getRepository().resolve(Constants.HEAD) != null) { // Don't run on empty repo 40 | if (git.log().setRevFilter(new CommitMsgFilter(msg)).call().size() > 0) return 41 | } 42 | 43 | // Run from latest version 44 | git.reset().setMode(ResetCommand.ResetType.HARD) 45 | 46 | // Clear repo 47 | GitCraft.REPO.toFile().listFiles().each { 48 | if (!it.toPath().toString().endsWith('.git')) { 49 | if (it.isDirectory()) { 50 | it.deleteDir() 51 | } else { 52 | it.delete() 53 | } 54 | } 55 | } 56 | 57 | // Copy decompiled MC to repo directory 58 | println 'Moving files to repo...' 59 | copyLargeDir(mcVersion.decompiledMc().toPath(), GitCraft.REPO.resolve('minecraft')) 60 | 61 | // Make commit 62 | git.add().addFilepattern(".").call() 63 | 64 | // Use DateFetcher to find release date 65 | ZonedDateTime releaseDate = DateFetcher.getReleaseDate(mcVersion.version) 66 | PersonIdent author = new PersonIdent("Mojang", "gitcraft@decompiled.mc", Date.from(releaseDate.toInstant()), TimeZone.getTimeZone(releaseDate.getZone())) 67 | 68 | git.commit().setAll(true).setMessage(msg).setAuthor(author).call() 69 | 70 | println 'Commited ' + mcVersion.version + ' to the repository!' 71 | } 72 | 73 | def setupRepo() { 74 | return Git.init().setDirectory(GitCraft.REPO.toFile()).call() 75 | } 76 | 77 | private static void copyLargeDir(Path source, Path target) { 78 | if (Files.isDirectory(source)) { 79 | if (Files.notExists(target)) { 80 | Files.createDirectories(target) 81 | } 82 | 83 | try (Stream paths = Files.list(source)) { 84 | paths.forEach(p -> copyLargeDir(p, target.resolve(source.relativize(p))) 85 | ) 86 | } 87 | 88 | } else { 89 | Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING) 90 | } 91 | } 92 | 93 | private static final class CommitMsgFilter extends RevFilter { 94 | String msg 95 | 96 | CommitMsgFilter(String msg) { 97 | this.msg = msg 98 | } 99 | 100 | @Override 101 | boolean include(RevWalk walker, RevCommit c) { 102 | c.fullMessage == this.msg 103 | } 104 | 105 | @Override 106 | RevFilter clone() { 107 | return this 108 | } 109 | 110 | @Override 111 | boolean requiresCommitBody() { 112 | return true 113 | } 114 | 115 | @Override 116 | String toString() { 117 | return "MSG FILTER" 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/data/McVersion.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker.data 2 | 3 | import dex.mcgitmaker.GitCraft 4 | import dex.mcgitmaker.Util 5 | import dex.mcgitmaker.loom.BundleMetadata 6 | import dex.mcgitmaker.loom.Decompiler 7 | import dex.mcgitmaker.loom.Remapper 8 | import net.fabricmc.mappingio.MappingWriter 9 | import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch 10 | import net.fabricmc.mappingio.format.MappingFormat 11 | import net.fabricmc.mappingio.format.proguard.ProGuardFileReader 12 | import net.fabricmc.mappingio.format.tiny.Tiny2FileWriter 13 | import net.fabricmc.mappingio.tree.MemoryMappingTree 14 | import net.fabricmc.stitch.merge.JarMerger 15 | import net.fabricmc.tinyremapper.IMappingProvider 16 | import net.fabricmc.tinyremapper.TinyUtils 17 | 18 | import java.nio.charset.StandardCharsets 19 | import java.nio.file.Files 20 | import java.nio.file.Path 21 | import java.nio.file.Paths 22 | 23 | class McVersion { 24 | String version // MC version string from launcher 25 | String loaderVersion // Version from Fabric loader 26 | boolean snapshot // If the version is a release 27 | boolean hasMappings // If the version has mappings provided 28 | int javaVersion = 8 29 | McArtifacts artifacts 30 | Collection libraries // The libraries for this version 31 | String mainClass 32 | String mergedJar // merged client and server 33 | 34 | File decompiledMc() { 35 | def p = Decompiler.decompiledPath(this) 36 | def f = p.toFile() 37 | if (!f.exists()) { 38 | Decompiler.decompile(this) 39 | } 40 | 41 | return f 42 | } 43 | 44 | File remappedJar() { 45 | return Remapper.doRemap(this).toFile() 46 | } 47 | 48 | Path mergedJarPath() { 49 | return mergedJar == null ? GitCraft.MC_VERSION_STORE.resolve(version).resolve('merged-jar.jar') : Paths.get(mergedJar) 50 | } 51 | 52 | File merged() { 53 | def p = mergedJarPath() 54 | def f = p.toFile() 55 | if (!f.exists()) makeMergedJar() 56 | return f 57 | } 58 | 59 | IMappingProvider mappingsProvider() { 60 | return TinyUtils.createTinyMappingProvider(mappingsPath(), 61 | Util.MappingsNamespace.OFFICIAL.toString(), Util.MappingsNamespace.MOJMAP.toString()) 62 | } 63 | 64 | Path mappingsPath() { 65 | def mappingsFile = GitCraft.MAPPINGS.resolve(version + '.tiny') 66 | 67 | if (!mappingsFile.toFile().exists()) { 68 | MemoryMappingTree mappingTree = new MemoryMappingTree() 69 | 70 | // Make official the source namespace 71 | MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, Util.MappingsNamespace.OFFICIAL.toString()) 72 | 73 | try (BufferedReader clientBufferedReader = Files.newBufferedReader(artifacts.clientMappings.fetchArtifact().toPath(), StandardCharsets.UTF_8) 74 | BufferedReader serverBufferedReader = Files.newBufferedReader(artifacts.serverMappings.fetchArtifact().toPath(), StandardCharsets.UTF_8)) { 75 | ProGuardFileReader.read(clientBufferedReader as Reader, Util.MappingsNamespace.MOJMAP.toString(), Util.MappingsNamespace.OFFICIAL.toString(), nsSwitch) 76 | ProGuardFileReader.read(serverBufferedReader as Reader, Util.MappingsNamespace.MOJMAP.toString(), Util.MappingsNamespace.OFFICIAL.toString(), nsSwitch) 77 | } 78 | 79 | try (MappingWriter w = MappingWriter.create(mappingsFile, MappingFormat.TINY_2_FILE)) { 80 | mappingTree.accept(w) 81 | w.close() 82 | } 83 | } 84 | 85 | return mappingsFile 86 | } 87 | 88 | void makeMergedJar() { 89 | println 'Merging jars... ' + version 90 | def client = artifacts.clientJar.fetchArtifact() 91 | def server2merge = artifacts.serverJar.fetchArtifact() 92 | 93 | def sbm = BundleMetadata.fromJar(server2merge.toPath()) 94 | if (sbm != null) { 95 | def minecraftExtractedServerJar = GitCraft.MC_VERSION_STORE 96 | .resolve(version).resolve('extracted-server.jar') 97 | 98 | if (sbm.versions.size() != 1) { 99 | throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(serverBundleMetadata.versions().size())); 100 | } 101 | 102 | sbm.versions.first().unpackEntry(server2merge.toPath(), minecraftExtractedServerJar) 103 | server2merge = minecraftExtractedServerJar.toFile() 104 | } 105 | 106 | try (def jarMerger = new JarMerger(client, server2merge, mergedJarPath().toFile())) { 107 | jarMerger.enableSyntheticParamsOffset() 108 | jarMerger.merge() 109 | } 110 | 111 | mergedJar = mergedJarPath().toString() 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/data/McMetadata.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker.data 2 | 3 | import dex.mcgitmaker.Util 4 | import groovy.json.JsonOutput 5 | import groovy.json.JsonSlurper 6 | 7 | import java.nio.file.Path 8 | 9 | import static dex.mcgitmaker.GitCraft.* 10 | 11 | class McMetadata { 12 | static final def MC_META_URL = 'https://piston-meta.mojang.com/mc/game/version_manifest_v2.json' 13 | public final LinkedHashMap metadata 14 | 15 | McMetadata() { 16 | metadata = getInitialMetadata() 17 | } 18 | 19 | McVersion getVersion(String name) { 20 | return metadata.get(name) as McVersion 21 | } 22 | 23 | McVersion getVersionFromSemver(String version) { 24 | return metadata.find {c, m -> 25 | def x = m as McVersion 26 | x.loaderVersion == version 27 | }.value as McVersion 28 | } 29 | 30 | static Path getMcArtifactRootPath(String version) { 31 | return MC_VERSION_STORE.resolve(version) 32 | } 33 | 34 | private static LinkedHashMap getInitialMetadata() { 35 | println 'Creating metadata...' 36 | println 'Reading metadata from Mojang...' 37 | def mcVersions = new JsonSlurper().parse(new URL(MC_META_URL)) 38 | def data = new LinkedHashMap() 39 | def read = new LinkedHashMap() 40 | 41 | println 'Attempting to read metadata from file...' 42 | if (METADATA_STORE.toFile().exists()) read = new JsonSlurper().parseText(METADATA_STORE.toFile().text) as LinkedHashMap 43 | 44 | read.each {s, v -> 45 | data.put(s, v as McVersion) 46 | } 47 | 48 | mcVersions.versions.each { v -> 49 | if (!data.containsKey(v.id)) { 50 | println 'Creating data for: ' + v.id 51 | data.put(v.id, createVersionData(v.url)) 52 | } 53 | } 54 | 55 | return data 56 | } 57 | 58 | private static McVersion createVersionData(String metaURL) { 59 | def meta = new JsonSlurper().parse(new URL(metaURL)) 60 | 61 | def libs = new HashSet() 62 | 63 | // Ignores natives, not needed as we don't have a runtime 64 | meta.libraries.each { d -> 65 | if (d.downloads.artifact != null) 66 | libs.add(new Artifact(name: Artifact.nameFromUrl(d.downloads.artifact.url), url: d.downloads.artifact.url, containingPath: LIBRARY_STORE)) 67 | } 68 | 69 | def javaVersion = meta.javaVersion != null ? meta.javaVersion.majorVersion ?: 8 : 8 70 | def artifacts = getMcArtifacts(meta) 71 | 72 | if (artifacts.hasMappings) { 73 | getMcArtifactRootPath(meta.id).toFile().mkdirs() 74 | def x = getMcArtifactRootPath(meta.id).resolve('version.json').toFile() 75 | x.createNewFile() 76 | x.write(JsonOutput.toJson(meta)) 77 | } 78 | 79 | return new McVersion(version: meta.id, javaVersion: javaVersion, 80 | mainClass: meta.mainClass, snapshot: meta.type == 'snapshot', artifacts: artifacts, 81 | hasMappings: artifacts.hasMappings, libraries: libs) 82 | } 83 | 84 | private static McArtifacts getMcArtifacts(def meta) { 85 | Tuple2 client = getMcArtifactData(meta.id, meta.downloads.client.url) 86 | def cmUrl = meta.downloads.client_mappings != null ? meta.downloads.client_mappings.url : '' 87 | def smUrl = meta.downloads.server_mappings != null ? meta.downloads.server_mappings.url : '' 88 | Tuple2 client_mapping = getMcArtifactData(meta.id, cmUrl) 89 | Tuple2 server = getMcArtifactData(meta.id, meta.downloads.server != null ? meta.downloads.server.url : '') 90 | Tuple2 server_mapping = getMcArtifactData(meta.id, smUrl) 91 | 92 | def clientArtifact = new Artifact(containingPath: client.getV1(), url: meta.downloads.client.url, name: client.getV2()) 93 | def clientMappingArtifact = new Artifact(containingPath: client_mapping.getV1(), url: cmUrl, name: client_mapping.getV2()) 94 | def serverArtifact = new Artifact(containingPath: server.getV1(), url: meta.downloads.server != null ? meta.downloads.server.url : '', name: server.getV2()) 95 | def serverMappingArtifact = new Artifact(containingPath: server_mapping.getV1(), url: smUrl, name: server_mapping.getV2()) 96 | 97 | boolean hasMappings = smUrl != '' && smUrl != '' 98 | return new McArtifacts(hasMappings: hasMappings, clientJar: clientArtifact, clientMappings: clientMappingArtifact, serverJar: serverArtifact, serverMappings: serverMappingArtifact) 99 | } 100 | 101 | // containing path, file name 102 | private static Tuple2 getMcArtifactData(String version, String url) { 103 | return new Tuple2<>(getMcArtifactRootPath(version).toString(), Artifact.nameFromUrl(url)) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/groovy/dex/mcgitmaker/Util.groovy: -------------------------------------------------------------------------------- 1 | package dex.mcgitmaker 2 | 3 | import dex.mcgitmaker.data.Artifact 4 | import dex.mcgitmaker.data.McVersion 5 | import dex.mcgitmaker.data.outlet.McFabric 6 | import dex.mcgitmaker.data.outlet.McOutletMeta 7 | import groovy.json.JsonGenerator 8 | import groovy.json.JsonParserType 9 | import groovy.json.JsonSlurper 10 | import net.fabricmc.loader.api.SemanticVersion 11 | import net.fabricmc.loader.impl.game.minecraft.McVersionLookup 12 | 13 | import java.nio.file.Path 14 | import java.nio.file.Paths 15 | import java.time.ZoneId 16 | import java.time.ZonedDateTime 17 | 18 | class Util { 19 | static enum MappingsNamespace { 20 | OFFICIAL, 21 | MOJMAP 22 | 23 | @Override 24 | String toString() { 25 | return name().toLowerCase(Locale.ENGLISH) 26 | } 27 | } 28 | 29 | static def saveMetadata(Map data) { 30 | def generator = new JsonGenerator.Options() 31 | .addConverter(Path) { it.toFile().canonicalPath } 32 | .build() 33 | 34 | def x = GitCraft.METADATA_STORE.toFile() 35 | x.createNewFile() 36 | x.write(generator.toJson(data)) 37 | } 38 | 39 | static def addLoaderVersion(McVersion mcVersion) { 40 | if (mcVersion.loaderVersion == null) { 41 | // Attempt lookup in Outlet database as newer MC versions require a loader update 42 | def v = Outlet.INSTANCE.outletDatabase.versions.find { 43 | it.id == mcVersion.version 44 | } 45 | if (v != null) { 46 | println 'Successfully looked up new semver version...' 47 | mcVersion.loaderVersion = v.normalized 48 | return 49 | } 50 | 51 | println 'Creating new semver version...' 52 | def x = McVersionLookup.getVersion( 53 | List.of(mcVersion.artifacts.clientJar.fetchArtifact().toPath()), mcVersion.mainClass, null 54 | ) 55 | mcVersion.loaderVersion = x.normalized 56 | println 'Semver made for: ' + x.raw + ' as ' + x.normalized 57 | println 'If generated semver is incorrect, it will break the order of the generated repo. ' + 58 | 'Consider updating Fabric Loader.' 59 | } 60 | } 61 | 62 | static TreeMap orderVersionMap(LinkedHashMap metadata) { 63 | def ORDERED_MAP = new TreeMap() 64 | println 'Sorting on semver MC versions...' 65 | metadata.values().each {it -> 66 | if (it.hasMappings) { 67 | addLoaderVersion(it) 68 | ORDERED_MAP.put(SemanticVersion.parse(it.loaderVersion), it) 69 | } 70 | } 71 | 72 | return ORDERED_MAP 73 | } 74 | 75 | //todo make work 76 | static def updateMcVersionPath(McVersion mcVersion) { 77 | def root = GitCraft.MAIN_ARTIFACT_STORE.parent 78 | 79 | if (mcVersion.mergedJar != null) { 80 | def p = Paths.get(mcVersion.mergedJar) 81 | def po = p.toString() 82 | for (int i in 1..p.getNameCount()-1) { 83 | if (p.getName(i).toString() == 'artifact-store') { 84 | p = p.subpath(i, p.getNameCount()) 85 | } 86 | } 87 | 88 | mcVersion.mergedJar = root.resolve(p) 89 | 90 | println 'Remapped ' + po + ' to ' + mcVersion.mergedJar 91 | } 92 | 93 | mcVersion.libraries.each {updateArtifactPath(it as Artifact)} 94 | updateArtifactPath(mcVersion.artifacts.clientMappings) 95 | updateArtifactPath(mcVersion.artifacts.clientJar) 96 | updateArtifactPath(mcVersion.artifacts.serverJar) 97 | updateArtifactPath(mcVersion.artifacts.serverMappings) 98 | } 99 | 100 | static def updateArtifactPath(Artifact artifact) { 101 | def root = GitCraft.MAIN_ARTIFACT_STORE.parent 102 | if (artifact.containingPath != null) { 103 | def p = artifact.containingPath 104 | def po = p.toString() 105 | for (int i in 1..p.getNameCount()-1) { 106 | if (p.getName(i).toString() == 'artifact-store') { 107 | p = p.subpath(i, p.getNameCount()) 108 | } 109 | } 110 | 111 | artifact.containingPath = root.resolve(p) 112 | 113 | println 'Remapped ' + po + ' to ' + artifact.containingPath 114 | } 115 | } 116 | 117 | enum Outlet { 118 | INSTANCE(); 119 | 120 | public McOutletMeta outletDatabase = new McOutletMeta(lastChanged: Date.from(ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()).toInstant()), versions: []) 121 | private final def OUTLET_DATABASE = 'https://raw.githubusercontent.com/dexman545/outlet-database/master/mc2fabric.json' 122 | Outlet() { 123 | outletDatabase = new JsonSlurper(type: JsonParserType.INDEX_OVERLAY).parse(new URL(OUTLET_DATABASE)) as McOutletMeta 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /DateFetcher/src/main/java/de/metroite/datefetcher/DateFetcher.java: -------------------------------------------------------------------------------- 1 | package de.metroite.datefetcher; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.net.URI; 9 | import java.net.URL; 10 | import java.nio.channels.Channels; 11 | import java.nio.channels.FileChannel; 12 | import java.nio.channels.ReadableByteChannel; 13 | import java.nio.file.Files; 14 | import java.nio.file.attribute.BasicFileAttributes; 15 | import java.nio.file.attribute.FileTime; 16 | import java.text.ParseException; 17 | import java.time.ZonedDateTime; 18 | import java.time.format.DateTimeFormatter; 19 | 20 | public class DateFetcher { 21 | private static final String manifestFileName = "version_manifest.json"; 22 | private static final String urlString = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; 23 | 24 | private static void fetchManifest() throws FetchException { 25 | try { 26 | URL url = URI.create(urlString).toURL(); 27 | ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream()); 28 | FileOutputStream fileOutputStream = new FileOutputStream(manifestFileName); 29 | FileChannel fileChannel = fileOutputStream.getChannel(); 30 | fileChannel.transferFrom(readableByteChannel, 0L, Long.MAX_VALUE); 31 | fileOutputStream.close(); 32 | } catch (Exception e) { 33 | throw new FetchException(e.getMessage()); 34 | } 35 | } 36 | 37 | public static ZonedDateTime getReleaseDate(String version) throws IOException, ParseException { 38 | File manifestFile = new File(manifestFileName); 39 | if (manifestFile.isDirectory()) { 40 | throw new AssertionError(String.format("%s should not be a directory!%n", manifestFile.toPath())); 41 | } else { 42 | if (!manifestFile.exists()) { 43 | fetchManifest(); 44 | } else { 45 | BasicFileAttributes attrs = Files.readAttributes(manifestFile.toPath(), BasicFileAttributes.class); 46 | FileTime creationTime = attrs.lastModifiedTime(); 47 | long then = creationTime.toMillis(); 48 | long now = System.currentTimeMillis(); 49 | if (now - then > 86400000) { 50 | fetchManifest(); 51 | } 52 | } 53 | 54 | ObjectMapper mapper = new ObjectMapper(); 55 | JsonNode rootNode = mapper.readTree(manifestFile); 56 | if (rootNode.get("versions").isArray()) { 57 | for (JsonNode versionNode : rootNode.get("versions")) { 58 | if (version.equals(versionNode.get("id").asText())) { 59 | String releaseTime = versionNode.get("releaseTime").asText(); 60 | return ZonedDateTime.parse(releaseTime, DateTimeFormatter.ISO_OFFSET_DATE_TIME); 61 | } 62 | } 63 | } 64 | 65 | return null; 66 | } 67 | } 68 | 69 | public static void main(String[] args) { 70 | if (args.length < 1) { 71 | System.err.println("Usage: >"); 72 | System.exit(1); 73 | } 74 | 75 | String version = args[0]; 76 | String format = null; 77 | if (args.length > 1) { 78 | format = args[1]; 79 | } 80 | 81 | File manifestFile = new File(manifestFileName); 82 | 83 | try { 84 | ZonedDateTime releaseDate = getReleaseDate(version); 85 | if (releaseDate == null) { 86 | System.err.println("Version not found: " + version); 87 | System.exit(1); 88 | } 89 | 90 | String formattedDate = switch (format) { 91 | case "epoch" -> { 92 | if (releaseDate.getOffset().getId().equals("Z")) { 93 | yield String.format("%d %s", releaseDate.toInstant().getEpochSecond(), "+0000"); 94 | } 95 | yield String.format("%d %s", releaseDate.toInstant().getEpochSecond(), releaseDate.getOffset().toString().replace(":", "")); 96 | } 97 | case null -> { 98 | if (releaseDate.getOffset().getId().equals("Z")) { 99 | yield releaseDate.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "+00:00"; 100 | } 101 | yield releaseDate.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); 102 | } 103 | default -> releaseDate.toString(); 104 | }; 105 | 106 | System.out.println(formattedDate); 107 | } catch (FetchException | ParseException | IOException e) { 108 | System.err.printf("Error with %s: %s%n", manifestFile.toPath(), e.getMessage()); 109 | System.exit(1); 110 | } 111 | 112 | } 113 | 114 | private static class FetchException extends RuntimeException { 115 | public FetchException(String msg) { 116 | super(String.format("Failed to fetch %s: %s", manifestFileName, msg)); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /DateFetcher/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | --------------------------------------------------------------------------------