├── .editorconfig
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ ├── gradle.yml
│ └── publish.yml
├── .gitignore
├── LICENSE
├── MCP-CREDITS.TXT
├── README.md
├── build.gradle
├── cli
├── build.gradle
└── src
│ └── main
│ └── java
│ └── org
│ └── mcphackers
│ └── mcp
│ └── main
│ └── MainCLI.java
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── gui
├── build.gradle
└── src
│ └── main
│ └── java
│ └── org
│ └── mcphackers
│ └── mcp
│ ├── Theme.java
│ ├── gui
│ ├── GridBagConstraintsBuilder.java
│ ├── MCPFrame.java
│ ├── MenuBar.java
│ ├── SideProgressBar.java
│ ├── TaskButton.java
│ ├── TextAreaContextMenu.java
│ ├── TextAreaOutputStream.java
│ └── WrapLayout.java
│ └── main
│ └── MainGUI.java
├── settings.gradle
└── src
├── main
├── java
│ └── org
│ │ └── mcphackers
│ │ └── mcp
│ │ ├── DownloadListener.java
│ │ ├── Language.java
│ │ ├── MCP.java
│ │ ├── MCPPaths.java
│ │ ├── Options.java
│ │ ├── TranslatorUtil.java
│ │ ├── Update.java
│ │ ├── plugin
│ │ ├── MCPPlugin.java
│ │ ├── PluginClassLoader.java
│ │ └── PluginManager.java
│ │ ├── tasks
│ │ ├── ProgressListener.java
│ │ ├── Task.java
│ │ ├── TaskApplyPatch.java
│ │ ├── TaskBuild.java
│ │ ├── TaskCleanup.java
│ │ ├── TaskCreatePatch.java
│ │ ├── TaskDecompile.java
│ │ ├── TaskDownloadUpdate.java
│ │ ├── TaskRecompile.java
│ │ ├── TaskReobfuscate.java
│ │ ├── TaskRun.java
│ │ ├── TaskRunnable.java
│ │ ├── TaskSetup.java
│ │ ├── TaskSourceBackup.java
│ │ ├── TaskStaged.java
│ │ ├── TaskUpdateMD5.java
│ │ └── mode
│ │ │ ├── TaskMode.java
│ │ │ ├── TaskModeBuilder.java
│ │ │ ├── TaskParameter.java
│ │ │ └── TaskParameterMap.java
│ │ └── tools
│ │ ├── ClassUtils.java
│ │ ├── FileUtil.java
│ │ ├── JSONUtil.java
│ │ ├── OS.java
│ │ ├── Util.java
│ │ ├── fernflower
│ │ ├── DecompileLogger.java
│ │ ├── Decompiler.java
│ │ ├── SimpleJavadocProvider.java
│ │ ├── TinyJavadocProvider.java
│ │ └── ZipFileCache.java
│ │ ├── injector
│ │ └── GLConstants.java
│ │ ├── mappings
│ │ └── MappingUtil.java
│ │ ├── project
│ │ ├── EclipseProjectWriter.java
│ │ ├── IdeaProjectWriter.java
│ │ ├── PairWriter.java
│ │ ├── ProjectWriter.java
│ │ ├── VSCProjectWriter.java
│ │ ├── XMLWriter.java
│ │ └── eclipse
│ │ │ ├── EclipseClasspath.java
│ │ │ ├── EclipsePreferences.java
│ │ │ ├── EclipseProject.java
│ │ │ └── EclipseRunConfig.java
│ │ ├── source
│ │ └── Source.java
│ │ └── versions
│ │ ├── DownloadData.java
│ │ ├── IDownload.java
│ │ ├── VersionParser.java
│ │ └── json
│ │ ├── Artifact.java
│ │ ├── AssetIndex.java
│ │ ├── AssetIndexMeta.java
│ │ ├── Classifiers.java
│ │ ├── DependDownload.java
│ │ ├── Downloads.java
│ │ ├── Manifest.java
│ │ ├── Rule.java
│ │ ├── Version.java
│ │ └── VersionMetadata.java
└── resources
│ ├── gl_constants.json
│ ├── icon
│ ├── rmcp.ico
│ └── rmcp.png
│ └── lang
│ ├── cs_CZ.lang
│ ├── de_DE.lang
│ ├── en_US.lang
│ ├── es_ES.lang
│ ├── fr_FR.lang
│ ├── nb_NO.lang
│ ├── ru_RU.lang
│ └── zh_CN.lang
└── test
├── java
└── org
│ └── mcphackers
│ └── mcp
│ ├── TestPlugin.java
│ └── tasks
│ └── TaskMergeMappings.java
└── resources
├── META-INF
└── services
│ └── org.mcphackers.mcp.plugin.MCPPlugin
└── lang
└── en_US.lang
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | tab_width = 4
8 |
9 | [*.gradle]
10 | indent_style = tab
11 |
12 | [*.java]
13 | indent_style = tab
14 | ij_continuation_indent_size = 8
15 | ij_java_imports_layout = $*,|,java.**,|,javax.**,|,*,|org.mcphackers.**
16 | ij_java_class_count_to_use_import_on_demand = 999
17 |
18 | [*.json]
19 | indent_style = space
20 | indent_size = 2
21 |
22 | [*.properties]
23 | indent_style = space
24 | indent_size = 2
25 |
26 | [.editorconfig]
27 | indent_style = space
28 | indent_size = 4
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | #
2 | # https://help.github.com/articles/dealing-with-line-endings/
3 | #
4 | # These are explicitly windows files and should use crlf
5 | *.bat text eol=crlf
6 |
7 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Expected behavior**
14 | A clear and concise description of what you expected to happen.
15 |
16 | **Screenshots**
17 | If applicable, add screenshots to help explain your problem.
18 |
19 | **RetroMCP Version**
20 | v1.0
21 |
22 | **Minecraft version**
23 |
24 | **Additional context**
25 | Add any other context about the problem here.
26 |
--------------------------------------------------------------------------------
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | name: Java CI with Gradle
2 |
3 | on: [ push, pull_request ]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - uses: actions/checkout@v4
11 | - name: Set up JDK 8
12 | uses: actions/setup-java@v4
13 | with:
14 | distribution: 'temurin'
15 | java-version: 8
16 | - name: Grant execute permission for gradlew
17 | run: chmod +x gradlew
18 | - name: Build with Gradle
19 | run: ./gradlew build
20 | - name: Upload build artifacts
21 | uses: actions/upload-artifact@v4
22 | with:
23 | name: build-artifacts
24 | path: build/libs
25 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish Release
2 |
3 | on:
4 | release:
5 | types:
6 | - published
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout sources
13 | uses: actions/checkout@v4
14 | - name: Set up JDK 8
15 | uses: actions/setup-java@v4
16 | with:
17 | distribution: 'temurin'
18 | java-version: 8
19 | - name: Grant execute permission for gradlew
20 | run: chmod +x gradlew
21 | - name: Run build
22 | run: ./gradlew build
23 | env:
24 | BUILD_RELEASE: ${{ github.event.prerelease == false }}
25 | - name: Upload assets to GitHub
26 | uses: softprops/action-gh-release@v2
27 | with:
28 | files: 'build/libs/*;LICENSE'
29 | repo-token: ${{ secrets.GITHUB_TOKEN }}
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore Gradle project-specific cache directory
2 | .gradle
3 |
4 | out/
5 |
6 | # Ignore Gradle build output directory
7 | build/
8 |
9 | # Ignore everything in testing folder
10 | test/*
11 |
12 | # Ignore IDE-specific files
13 | .classpath
14 | .project
15 | .idea
16 | .vscode
17 | .settings
18 | bin
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 MCPHackers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MCP-CREDITS.TXT:
--------------------------------------------------------------------------------
1 | Credits:
2 | ========
3 | Searge
4 | * Creator of MCP
5 | * Fixes all compile errors in the decompiled sourcecode
6 | * Created the MCP Mod Framework system and API
7 | * Created the new RetroGuard deobfuscation module for MCP 3.x
8 | * Created the Exceptor tool for MCP 4.x
9 | * Created the Eclipse workspace for MCP 4.x
10 | ProfMobius
11 | * Creator of the renaming codes and re-obfuscation procedures
12 | * Helped to port scripts to Linux
13 | * Developer and maintainer of the MCP chan bot
14 | * Is now bald after working too much with java constant pool and re-obfuscation
15 | * Created the new workflow scripts and renamer for MCP 3.0
16 | IngisKahn
17 | * Creator of the bytecode compare tool that helps us to update the name mappings quickly for new minecraft versions
18 | * Contributed to the de-obfuscation spreadsheet
19 | * Working hard on creating better internal tools for mapping updates and decompiling
20 | Fesh0r
21 | * php/sql code monkey
22 | * Uses his magic to create the mappings, patches, and general release work
23 | * Has Searge's approval to make official MCP releases ;)
24 | * Makes sure we get proper patches for the sourcecode that JAD generates
25 | ZeuX
26 | * Helps out in the IRC channels - Head of HR
27 | * Did server patches for the most recent versions - if you run into any (patch-related) problems, it's his fault :P
28 | 303
29 | * Wiki contributor
30 | * Tries to help out newbies in the IRC channels
31 | * Created some scripts for mod loader support
32 | Mr_okushama
33 | * Wiki contributor
34 | * Public Support Manager
35 | * IRC Operator
36 | * Savior of the 2011 April fools prank
37 | Generic
38 | * Works on improving IngisKahn's bytecode compare tool
39 | * Added some important features to retroguard
40 | Risugami
41 | * The guy who created the first mods I (Searge) ever used in Minecraft
42 | * The creator of modloader who gave us permission to include files from his system in MCP
43 | fotoply
44 | * Helped to improve the batch files
45 | ScottyDoesKnow
46 | * obfuscathonCharmer, the obfuscathon GUI
47 | Cadde
48 | * Community manager and Wiki manager
49 | * Works on the de-obfuscation spreadsheet
50 | * Mod support (making old mods work with MCP)
51 | * All round handyman
52 | Vaprtek
53 | * Works on the de-obfuscation spreadsheet
54 | * Knows how to make pet creepers
55 | gronk
56 | * Script support
57 | n00bish
58 | * Linux script maintenance
59 | Sage Pourpre
60 | * His thread in the forums inspired me (Searge) to create this toolpack in the first place
61 | Tei
62 | * Supported the MCP project since the first version was released
63 | spec10
64 | * The new linux scripts guy
65 | Head
66 | * Wiki contributor / Administrator
67 | * Explains classes and their members on the Wiki
68 | MissLil
69 | * Various scripting stuff
70 | * Lots of reverse engineering
71 | * OpenGL constants annoting
72 | Chase
73 | * MCP Launcher Work
74 | * External jar loading
75 | * Scrollable mod list.
76 |
77 | and of course:
78 | - Everybody who contributed to the great google spreadsheet or who created some mods (I've got them all :).
79 | - NOTCH for creating a game that is just awesome, I hope he does not feel offended by our decompiling efforts.
80 | Please, Notch, support our ambitions to mod your game. I know people who bought it just because of
81 | some great mods.
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RetroMCP-Java
2 |
3 | RetroMCP is a modification of the Minecraft Coder Pack to create a "Long Term Service" patch for Minecraft.
4 | RetroMCP-Java is a complete re-design of RetroMCP in Java.
5 |
6 | # Using
7 |
8 | Using RetroMCP-Java is simple!
9 | 1. Download and install JDK 8. MCPHackers recommend [Azul Zulu](https://www.azul.com/downloads/?version=java-8-lts&package=jdk).
10 | 2. Run the latest [release](https://github.com/MCPHackers/RetroMCP-Java/releases) from the command line or via double click. If you run it via double click and RMCP errors, make sure your PATH
11 | and your JAR file associations are properly configured.
12 | > Be careful! Using "Open with" context menu on Windows will not use a proper directory, be sure to change the default .jar file associations
13 | 3. Run `setup` and choose the version you wish to decompile.
14 | 4. Run the `decompile` task
15 | 5. Mod away! Now it's Yourcraft!
16 |
17 | For more info you can check [RetroMCP Wiki](https://github.com/MCPHackers/RetroMCP-Java/wiki).
18 |
19 | # Features
20 |
21 | * Automatically download Minecraft .jar and libraries from version JSONs
22 | * An improved launch method using [LaunchWrapper](https://github.com/MCPHackers/LaunchWrapper)
23 | * Reobfuscation for all available versions
24 | * Automatic project creation
25 | * Merged codebase generation
26 |
27 | # Building
28 |
29 | 1. Use a Git client or download the sources as a zip.
30 | > `git clone git@github.com:MCPHackers/RetroMCP-Java.git`
31 | 2. Switch to the repository folder.
32 | > `cd RetroMCP-Java`
33 | 3. Invoke the build task using Gradle or the Gradle wrapper.
34 | > `gradlew build`
35 |
36 | # Contributing
37 |
38 | If you encounter any issues or bugs with RetroMCP, please create an issue and explain it in detail!
39 | If you want to contribute, please keep pull requests about one topic instead of one huge pull request.
40 | We thank everyone who contributes to this project!
41 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "java"
3 | }
4 |
5 | allprojects {
6 | apply plugin: "java"
7 |
8 | repositories {
9 | mavenCentral()
10 | maven { url = "https://jitpack.io/" }
11 | maven { url = "https://maven.fabricmc.net/" }
12 | maven { url = "https://mcphackers.org/libraries/" }
13 | maven { url = "https://maven.glass-launcher.net/releases" }
14 | }
15 |
16 | dependencies {
17 | runtimeOnly sourceSets.test.output
18 |
19 | // Required libraries
20 | implementation libs.asm
21 | implementation libs.asm.analysis
22 | implementation libs.asm.commons
23 | implementation libs.asm.tree
24 | implementation libs.asm.util
25 |
26 | implementation libs.rdi.nio
27 | implementation libs.rdi
28 |
29 | implementation libs.fernflower
30 |
31 | implementation libs.json
32 | implementation libs.diffpatch
33 | implementation libs.commons.lang3
34 | implementation libs.mapping.io
35 | }
36 |
37 | tasks.withType(JavaCompile).configureEach {
38 | it.options.encoding = "UTF-8"
39 |
40 | // Use release flag on Java 9+ instead of source & target flags
41 | // This makes the JDK read the boot classpath and use the proper
42 | // libraries to be compliant with Java 8 without issues
43 | if (JavaVersion.current().isJava9Compatible()) {
44 | it.options.release.set(8)
45 | }
46 | }
47 |
48 | java {
49 | sourceCompatibility = JavaVersion.VERSION_1_8
50 | targetCompatibility = JavaVersion.VERSION_1_8
51 | }
52 | }
53 |
54 | subprojects {
55 | apply plugin: "application"
56 | }
57 |
--------------------------------------------------------------------------------
/cli/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias libs.plugins.shadow
3 | }
4 |
5 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
6 |
7 | dependencies {
8 | implementation rootProject
9 |
10 | implementation libs.jansi
11 | }
12 |
13 | application {
14 | mainClass = "org.mcphackers.mcp.main.MainCLI"
15 | executableDir = "test"
16 | mkdir(executableDir)
17 | }
18 |
19 | run {
20 | workingDir 'test'
21 | }
22 |
23 | runShadow {
24 | workingDir 'test'
25 | }
26 |
27 | tasks.named("shadowJar", ShadowJar) {
28 | configurations = [project.configurations.compileClasspath]
29 | archiveBaseName.set("RetroMCP-CLI")
30 | archiveClassifier.set("all")
31 | setDestinationDirectory(rootProject.getLayout().getBuildDirectory().dir("libs"))
32 | minimize()
33 | }
34 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | asm = '9.8'
3 | commons-lang3 = '3.17.0'
4 | diffpatch = 'cde1224'
5 | fernflower = '1.0.0'
6 | flatlaf = '3.6'
7 | jansi = '2.4.1'
8 | json = '20250107'
9 | mapping-io = '0.7.1'
10 | rdi = '1.1'
11 | shadow-plugin = '8.3.1'
12 |
13 | [libraries]
14 | asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
15 | asm-analysis = { module = "org.ow2.asm:asm-analysis", version.ref = "asm" }
16 | asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
17 | asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" }
18 | asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" }
19 | commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commons-lang3" }
20 | diffpatch = { module = "com.github.MCPHackers:DiffPatch", version.ref = "diffpatch" }
21 | fernflower = { module = "io.github.lassebq:fernflower", version.ref = "fernflower" }
22 | flatlaf = { module = "com.formdev:flatlaf", version.ref = "flatlaf" }
23 | jansi = { module = "org.fusesource.jansi:jansi", version.ref = "jansi" }
24 | json = { module = "org.json:json", version.ref = "json" }
25 | mapping-io = { module = "net.fabricmc:mapping-io", version.ref = "mapping-io" }
26 | rdi = { module = "org.mcphackers.rdi:rdi", version.ref = "rdi" }
27 | rdi-nio = { module = "org.mcphackers.rdi:rdi-nio", version.ref = "rdi" }
28 |
29 | [plugins]
30 | shadow = { id = "com.gradleup.shadow", version.ref = "shadow-plugin" }
31 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MCPHackers/RetroMCP-Java/fd1c4e0cb98176631ce17141fe91610dfc82240e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/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 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/gui/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias libs.plugins.shadow
3 | }
4 |
5 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
6 |
7 | dependencies {
8 | implementation rootProject
9 |
10 | implementation libs.flatlaf
11 | }
12 |
13 | application {
14 | mainClass = "org.mcphackers.mcp.main.MainGUI"
15 | executableDir = "test"
16 | mkdir(executableDir)
17 | }
18 |
19 | run {
20 | workingDir "test"
21 | }
22 |
23 | runShadow {
24 | workingDir "test"
25 | }
26 |
27 | tasks.named("shadowJar", ShadowJar) {
28 | configurations = [project.configurations.compileClasspath]
29 | archiveBaseName.set("RetroMCP-GUI")
30 | archiveClassifier.set("all")
31 | setDestinationDirectory(rootProject.getLayout().getBuildDirectory().dir("libs"))
32 | minimize() // This breaks FlatLaf
33 | }
34 |
--------------------------------------------------------------------------------
/gui/src/main/java/org/mcphackers/mcp/Theme.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | import javax.swing.*;
9 | import javax.swing.UIManager.LookAndFeelInfo;
10 |
11 | import com.formdev.flatlaf.FlatDarculaLaf;
12 | import com.formdev.flatlaf.FlatDarkLaf;
13 | import com.formdev.flatlaf.FlatIntelliJLaf;
14 | import com.formdev.flatlaf.FlatLightLaf;
15 |
16 | public class Theme {
17 | public static final Map THEMES_MAP = new HashMap<>();
18 | public static final List THEMES = new ArrayList<>();
19 |
20 | static {
21 | for (LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
22 | addTheme(laf.getName(), laf.getClassName());
23 | }
24 | addTheme(FlatLightLaf.NAME, FlatLightLaf.class.getName());
25 | addTheme(FlatDarkLaf.NAME, FlatDarkLaf.class.getName());
26 | addTheme(FlatIntelliJLaf.NAME, FlatIntelliJLaf.class.getName());
27 | addTheme(FlatDarculaLaf.NAME, FlatDarculaLaf.class.getName());
28 | }
29 |
30 | public final String themeName;
31 | public final String themeClass;
32 |
33 | Theme(String themeName, String themeClassName) {
34 | this.themeName = themeName;
35 | this.themeClass = themeClassName;
36 | }
37 |
38 | public static void addTheme(String name, String className) {
39 | Theme theme = new Theme(name, className);
40 | THEMES_MAP.put(className, theme);
41 | THEMES.add(theme);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/gui/src/main/java/org/mcphackers/mcp/gui/GridBagConstraintsBuilder.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.gui;
2 |
3 | import java.awt.*;
4 |
5 | public final class GridBagConstraintsBuilder {
6 |
7 | private final GridBagConstraints inner;
8 |
9 | public GridBagConstraintsBuilder(GridBagConstraints inner) {
10 | this.inner = inner;
11 | }
12 |
13 | public GridBagConstraintsBuilder pos(int x, int y) {
14 | GridBagConstraintsBuilder copy = this.copy();
15 | copy.inner.gridx = x;
16 | copy.inner.gridy = y;
17 | return copy;
18 | }
19 |
20 | public GridBagConstraintsBuilder size(int width, int height) {
21 | GridBagConstraintsBuilder copy = this.copy();
22 | copy.inner.gridwidth = width;
23 | copy.inner.gridheight = height;
24 | return copy;
25 | }
26 |
27 | public GridBagConstraintsBuilder width(int width) {
28 | GridBagConstraintsBuilder copy = this.copy();
29 | copy.inner.gridwidth = width;
30 | return copy;
31 | }
32 |
33 | public GridBagConstraintsBuilder height(int height) {
34 | GridBagConstraintsBuilder copy = this.copy();
35 | copy.inner.gridheight = height;
36 | return copy;
37 | }
38 |
39 | public GridBagConstraintsBuilder dimensions(int x, int y, int width, int height) {
40 | return this.pos(x, y).size(width, height);
41 | }
42 |
43 | public GridBagConstraintsBuilder weight(double x, double y) {
44 | GridBagConstraintsBuilder copy = this.copy();
45 | copy.inner.weightx = x;
46 | copy.inner.weighty = y;
47 | return copy;
48 | }
49 |
50 | public GridBagConstraintsBuilder weightX(double x) {
51 | GridBagConstraintsBuilder copy = this.copy();
52 | copy.inner.weightx = x;
53 | return copy;
54 | }
55 |
56 | public GridBagConstraintsBuilder weightY(double y) {
57 | GridBagConstraintsBuilder copy = this.copy();
58 | copy.inner.weighty = y;
59 | return copy;
60 | }
61 |
62 | public GridBagConstraintsBuilder anchor(int anchor) {
63 | GridBagConstraintsBuilder copy = this.copy();
64 | copy.inner.anchor = anchor;
65 | return copy;
66 | }
67 |
68 | public GridBagConstraintsBuilder fill(int fill) {
69 | GridBagConstraintsBuilder copy = this.copy();
70 | copy.inner.fill = fill;
71 | return copy;
72 | }
73 |
74 | public GridBagConstraintsBuilder insetsUnscaled(int all) {
75 | return this.insetsUnscaled(all, all, all, all);
76 | }
77 |
78 | public GridBagConstraintsBuilder insetsUnscaled(int vertical, int horizontal) {
79 | return this.insetsUnscaled(vertical, horizontal, vertical, horizontal);
80 | }
81 |
82 | public GridBagConstraintsBuilder insetsUnscaled(int top, int horizontal, int bottom) {
83 | return this.insetsUnscaled(top, horizontal, bottom, horizontal);
84 | }
85 |
86 | public GridBagConstraintsBuilder insetsUnscaled(int top, int right, int bottom, int left) {
87 | GridBagConstraintsBuilder copy = this.copy();
88 | copy.inner.insets.set(top, left, bottom, right);
89 | return copy;
90 | }
91 |
92 | public GridBagConstraintsBuilder paddingUnscaled(int pad) {
93 | return this.paddingUnscaled(pad, pad);
94 | }
95 |
96 | public GridBagConstraintsBuilder paddingUnscaled(int padX, int padY) {
97 | GridBagConstraintsBuilder copy = this.copy();
98 | copy.inner.ipadx = padX;
99 | copy.inner.ipady = padY;
100 | return copy;
101 | }
102 |
103 | public GridBagConstraintsBuilder copy() {
104 | GridBagConstraints c = (GridBagConstraints) this.inner.clone();
105 | return new GridBagConstraintsBuilder(c);
106 | }
107 |
108 | public GridBagConstraints build() {
109 | return (GridBagConstraints) this.inner.clone();
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/gui/src/main/java/org/mcphackers/mcp/gui/SideProgressBar.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.gui;
2 |
3 | import javax.swing.*;
4 |
5 | public class SideProgressBar extends JProgressBar {
6 |
7 | private static final long serialVersionUID = -8002821179520037516L;
8 |
9 | public String progressMsg;
10 | public int progress;
11 |
12 | public SideProgressBar() {
13 | setStringPainted(true);
14 | }
15 |
16 | public void updateProgress() {
17 | setValue(progress);
18 | setString(progress + "% " + progressMsg);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/gui/src/main/java/org/mcphackers/mcp/gui/TaskButton.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.gui;
2 |
3 | import static org.mcphackers.mcp.tools.Util.enqueueRunnable;
4 |
5 | import java.awt.event.ActionListener;
6 |
7 | import javax.swing.*;
8 |
9 | import org.mcphackers.mcp.MCP;
10 | import org.mcphackers.mcp.main.MainGUI;
11 | import org.mcphackers.mcp.tasks.mode.TaskMode;
12 |
13 | public class TaskButton extends JButton {
14 |
15 | private static final long serialVersionUID = -2625827711322112358L;
16 |
17 | private final TaskMode linkedTask;
18 | private final MainGUI mcp;
19 |
20 | public TaskButton(MainGUI owner, TaskMode task) {
21 | super(task.getFullName());
22 | linkedTask = task;
23 | mcp = owner;
24 | addActionListener(performTask(mcp, linkedTask));
25 | }
26 |
27 | public TaskButton(MainGUI owner, TaskMode task, ActionListener defaultActionListener) {
28 | super(task.getFullName());
29 | linkedTask = task;
30 | mcp = owner;
31 | addActionListener(defaultActionListener);
32 | }
33 |
34 | public static ActionListener performTask(MCP mcp, TaskMode mode) {
35 | return event -> enqueueRunnable(() -> mcp.performTask(mode, mcp.getOptions().side));
36 | }
37 |
38 | public boolean getEnabled() {
39 | return linkedTask.isAvailable(mcp, mcp.getSide());
40 | }
41 |
42 | public void updateName() {
43 | setText(linkedTask.getFullName());
44 | setToolTipText(linkedTask.getDesc());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/gui/src/main/java/org/mcphackers/mcp/gui/TextAreaContextMenu.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.gui;
2 |
3 | import org.mcphackers.mcp.MCP;
4 | import org.mcphackers.mcp.main.MainGUI;
5 |
6 | import javax.swing.*;
7 | import javax.swing.text.Style;
8 | import javax.swing.text.StyleContext;
9 | import javax.swing.text.StyledDocument;
10 |
11 | public class TextAreaContextMenu extends JPopupMenu {
12 | private final MCP mcp;
13 |
14 | public TextAreaContextMenu(MCP mcp) {
15 | this.mcp = mcp;
16 | this.addItems();
17 | }
18 |
19 | private void addItems() {
20 | JMenuItem clearText = new JMenuItem(MCP.TRANSLATOR.translateKey("mcp.clearConsole"));
21 | clearText.addActionListener(actionEvent -> {
22 | if (this.mcp instanceof MainGUI) {
23 | MainGUI mainGUI = (MainGUI) this.mcp;
24 | mainGUI.textPane.setText("");
25 |
26 | // Clear the styles
27 | StyledDocument doc = mainGUI.textPane.getStyledDocument();
28 | Style defaultStyle = mainGUI.textPane.getStyle(StyleContext.DEFAULT_STYLE);
29 | doc.setCharacterAttributes(0, doc.getLength(), defaultStyle, true);
30 | }
31 | });
32 | this.add(clearText);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/gui/src/main/java/org/mcphackers/mcp/gui/TextAreaOutputStream.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.gui;
2 |
3 | import java.io.OutputStream;
4 | import java.io.PrintStream;
5 |
6 | import javax.swing.*;
7 | import javax.swing.text.BadLocationException;
8 |
9 | public class TextAreaOutputStream extends PrintStream {
10 | private final JTextPane textPane;
11 |
12 | public TextAreaOutputStream(JTextPane textArea, OutputStream out) {
13 | super(out, true);
14 | this.textPane = textArea;
15 | }
16 |
17 | @Override
18 | public void print(Object o) {
19 | printString(String.valueOf(o));
20 | super.print(o);
21 | }
22 |
23 | @Override
24 | public void println(Object o) {
25 | super.println(o);
26 | printString("\n");
27 | }
28 |
29 | @Override
30 | public void print(String s) {
31 | printString(s);
32 | super.print(s);
33 | }
34 |
35 | @Override
36 | public void println(String s) {
37 | super.println(s);
38 | printString("\n");
39 | }
40 |
41 | private void printString(String msg) {
42 | try {
43 | textPane.getStyledDocument().insertString(textPane.getStyledDocument().getLength(), msg, null);
44 | } catch (BadLocationException ex) {
45 | ex.printStackTrace();
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/gui/src/main/java/org/mcphackers/mcp/gui/WrapLayout.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.gui;
2 |
3 | import java.awt.*;
4 |
5 | import javax.swing.*;
6 |
7 | /**
8 | * FlowLayout subclass that fully supports wrapping of components.
9 | */
10 | public class WrapLayout extends FlowLayout {
11 |
12 | private static final long serialVersionUID = 1080066265713791251L;
13 |
14 | public WrapLayout() {
15 | }
16 |
17 | public WrapLayout(int align) {
18 | super(align);
19 | }
20 |
21 | public WrapLayout(int align, int hgap, int vgap) {
22 | super(align, hgap, vgap);
23 | }
24 |
25 | /**
26 | * Returns the preferred dimensions for this layout given the
27 | * visible components in the specified target container.
28 | *
29 | * @param target the component which needs to be laid out
30 | * @return the preferred dimensions to lay out the
31 | * subcomponents of the specified container
32 | */
33 | @Override
34 | public Dimension preferredLayoutSize(Container target) {
35 | return layoutSize(target, true);
36 | }
37 |
38 | /**
39 | * Returns the minimum dimensions needed to layout the visible
40 | * components contained in the specified target container.
41 | *
42 | * @param target the component which needs to be laid out
43 | * @return the minimum dimensions to lay out the
44 | * subcomponents of the specified container
45 | */
46 | @Override
47 | public Dimension minimumLayoutSize(Container target) {
48 | Dimension minimum = layoutSize(target, false);
49 | minimum.width -= (getHgap() + 1);
50 | return minimum;
51 | }
52 |
53 | /**
54 | * Returns the minimum or preferred dimension needed to layout the target
55 | * container.
56 | *
57 | * @param target target to get layout size for
58 | * @param preferred should preferred size be calculated
59 | * @return the dimension to layout the target container
60 | */
61 | private Dimension layoutSize(Container target, boolean preferred) {
62 | synchronized (target.getTreeLock()) {
63 | // Each row must fit with the width allocated to the containter.
64 | // When the container width = 0, the preferred width of the container
65 | // has not yet been calculated so lets ask for the maximum.
66 |
67 | Container container = target;
68 |
69 | while (container.getSize().width == 0 && container.getParent() != null) {
70 | container = container.getParent();
71 | }
72 |
73 | int targetWidth = container.getSize().width;
74 |
75 | if (targetWidth == 0)
76 | targetWidth = Integer.MAX_VALUE;
77 |
78 | int hgap = getHgap();
79 | int vgap = getVgap();
80 | Insets insets = target.getInsets();
81 | int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
82 | int maxWidth = targetWidth - horizontalInsetsAndGap;
83 |
84 | // Fit components into the allowed width
85 |
86 | Dimension dim = new Dimension(0, 0);
87 | int rowWidth = 0;
88 | int rowHeight = 0;
89 |
90 | int nmembers = target.getComponentCount();
91 |
92 | for (int i = 0; i < nmembers; i++) {
93 | Component m = target.getComponent(i);
94 |
95 | if (m.isVisible()) {
96 | Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
97 |
98 | // Can't add the component to current row. Start a new row.
99 |
100 | if (rowWidth + d.width > maxWidth) {
101 | addRow(dim, rowWidth, rowHeight);
102 | rowWidth = 0;
103 | rowHeight = 0;
104 | }
105 |
106 | // Add a horizontal gap for all components after the first
107 |
108 | if (rowWidth != 0) {
109 | rowWidth += hgap;
110 | }
111 |
112 | rowWidth += d.width;
113 | rowHeight = Math.max(rowHeight, d.height);
114 | }
115 | }
116 |
117 | addRow(dim, rowWidth, rowHeight);
118 |
119 | dim.width += horizontalInsetsAndGap;
120 | dim.height += insets.top + insets.bottom + vgap * 2;
121 |
122 | // When using a scroll pane or the DecoratedLookAndFeel we need to
123 | // make sure the preferred size is less than the size of the
124 | // target container so shrinking the container size works
125 | // correctly. Removing the horizontal gap is an easy way to do this.
126 |
127 | Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
128 |
129 | if (scrollPane != null && target.isValid()) {
130 | dim.width -= (hgap + 1);
131 | }
132 |
133 | return dim;
134 | }
135 | }
136 |
137 | /*
138 | * A new row has been completed. Use the dimensions of this row
139 | * to update the preferred size for the container.
140 | *
141 | * @param dim update the width and height when appropriate
142 | * @param rowWidth the width of the row to add
143 | * @param rowHeight the height of the row to add
144 | */
145 | private void addRow(Dimension dim, int rowWidth, int rowHeight) {
146 | dim.width = Math.max(dim.width, rowWidth);
147 |
148 | if (dim.height > 0) {
149 | dim.height += getVgap();
150 | }
151 |
152 | dim.height += rowHeight;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'RetroMCP-Java'
2 |
3 | include "gui"
4 | include "cli"
5 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/DownloadListener.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp;
2 |
3 | import org.mcphackers.mcp.tools.versions.IDownload;
4 |
5 | public interface DownloadListener {
6 | void notify(IDownload object, long totalSize);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/Language.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp;
2 |
3 | import java.util.Locale;
4 |
5 | /**
6 | * All available languages
7 | */
8 | public enum Language {
9 |
10 | ENGLISH(Locale.US),
11 | SPANISH(new Locale("es", "ES")),
12 | RUSSIAN(new Locale("ru", "RU")),
13 | GERMAN(new Locale("de", "DE")),
14 | FRENCH(new Locale("fr", "FR")),
15 | CHINESE(new Locale("zh", "CN")),
16 | CZECH(new Locale("cs", "CZ")),
17 | NORSK_BOKMAL(new Locale("nb", "NO"));
18 |
19 | /**
20 | * Internal locale
21 | */
22 | public final Locale locale;
23 |
24 | Language(Locale locale) {
25 | this.locale = locale;
26 | }
27 |
28 | /**
29 | * @param locale
30 | * @return A language enum from a locale
31 | */
32 | public static Language get(Locale locale) {
33 | for (Language lang : Language.values()) {
34 | // Perfect match
35 | if (lang.locale.equals(locale)) {
36 | return lang;
37 | }
38 |
39 | // Language match
40 | if (lang.locale.getLanguage().equals(locale.getLanguage())) {
41 | return lang;
42 | }
43 | }
44 |
45 | // No matches found for locale
46 | return ENGLISH;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/MCPPaths.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp;
2 |
3 | import java.nio.file.Path;
4 |
5 | import org.mcphackers.mcp.tasks.Task.Side;
6 |
7 | public class MCPPaths {
8 |
9 | //Directories
10 | public static final String JARS = "jars/";
11 | public static final String LIB = "libraries/";
12 | public static final String CONF = "conf/";
13 | public static final String BUILD = "build/";
14 | public static final String PROJECT = "minecraft_%s/";
15 |
16 | //Files and subdirectories
17 | public static final String JAR_ORIGINAL = JARS + "minecraft_%s.jar";
18 |
19 | public static final String NATIVES = LIB + "natives";
20 |
21 | public static final String BUILD_ZIP = BUILD + "minecraft_%s.zip";
22 | public static final String BUILD_JAR = BUILD + "minecraft_%s.jar";
23 |
24 | public static final String SOURCE_UNPATCHED = PROJECT + "src_original";
25 | public static final String SOURCE = PROJECT + "src";
26 | public static final String BIN = PROJECT + "bin";
27 | public static final String MD5_DIR = PROJECT + "md5";
28 | public static final String MD5 = PROJECT + "md5/original.md5";
29 | public static final String MD5_RO = PROJECT + "md5/modified.md5";
30 | public static final String JARS_DIR = PROJECT + "jars";
31 | public static final String REMAPPED = PROJECT + "jars/deobfuscated.jar";
32 | public static final String REOBF_JAR = PROJECT + "jars/reobfuscated.jar";
33 | public static final String SOURCE_JAR = PROJECT + "jars/deobfuscated-source.jar";
34 | public static final String REOBF_SIDE = PROJECT + "reobf";
35 | public static final String GAMEDIR = PROJECT + "game/";
36 |
37 | public static final String MAPPINGS = CONF + "mappings.tiny";
38 | public static final String EXC = CONF + "exceptions.exc";
39 | public static final String ACCESS = CONF + "%s.access";
40 | public static final String PATCHES = CONF + "%s.patch";
41 | public static final String VERSION = CONF + "version.json";
42 |
43 | public static final String PATCH = "patches/%s.patch";
44 |
45 | public static final String UPDATE_JAR = "update.jar";
46 |
47 | public static Path get(MCP mcp, String path, Side side) {
48 | String newPath = path;
49 | if (side == Side.CLIENT) {
50 | newPath = path.replace("minecraft_%s", "minecraft");
51 | }
52 | return get(mcp, String.format(newPath, side.name));
53 | }
54 |
55 | public static Path get(MCP mcp, String path) {
56 | return mcp.getWorkingDir().resolve(path);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/TranslatorUtil.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 | import java.net.URL;
8 | import java.nio.charset.StandardCharsets;
9 | import java.util.Enumeration;
10 | import java.util.HashMap;
11 | import java.util.Map;
12 |
13 | public class TranslatorUtil {
14 | public static final Language DEFAULT_LANG = Language.ENGLISH;
15 | private final Map translations = new HashMap<>();
16 | public Language currentLang;
17 |
18 | public void changeLang(Language lang) {
19 | translations.clear();
20 | currentLang = lang;
21 | readTranslation(MCP.class);
22 | MCP.reloadPluginTranslations();
23 | }
24 |
25 | public void readTranslation(Class> cls) {
26 | readTranslation(cls, DEFAULT_LANG);
27 | if (currentLang != DEFAULT_LANG) {
28 | readTranslation(cls, currentLang);
29 | }
30 | }
31 |
32 | private void readTranslation(Class> cls, Language lang) {
33 | readTranslation(translations, cls, lang);
34 | }
35 |
36 | private void readTranslation(Map map, Class> cls, Language lang) {
37 | String resourceName = "lang/" + lang.locale.toString() + ".lang";
38 | try {
39 | Enumeration translations = cls.getClassLoader().getResources(resourceName);
40 | while (translations.hasMoreElements()) {
41 | URL url = translations.nextElement();
42 | this.readTranslation(map, url.openStream());
43 | }
44 | } catch (IOException ex) {
45 | ex.printStackTrace();
46 | }
47 | }
48 |
49 | private void readTranslation(Map map, InputStream resource) {
50 | if (resource == null) {
51 | return;
52 | }
53 | try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource, StandardCharsets.UTF_8))) {
54 | bufferedReader.lines().forEach(line -> {
55 | if (line.startsWith("#") || line.trim().isEmpty()) return;
56 | String key = line.split("=")[0].trim();
57 | String translated = line.split("=")[1].trim();
58 | map.put(key, translated);
59 | });
60 | } catch (IOException ex) {
61 | ex.printStackTrace();
62 | }
63 | }
64 |
65 | public String getLangName(Language lang) {
66 | Map entries = new HashMap<>();
67 | readTranslation(entries, MCP.class, lang);
68 | String languageName = entries.get("language");
69 | if (languageName != null) {
70 | return languageName;
71 | }
72 | return "Unknown language";
73 | }
74 |
75 | public boolean hasKey(String key) {
76 | return translations.containsKey(key);
77 | }
78 |
79 | public String translateKey(String key) {
80 | String translatedString = this.translations.get(key);
81 | if (translatedString == null) {
82 | return key;
83 | }
84 | return translatedString;
85 | }
86 |
87 | public String translateKeyWithFormatting(String key, Object... formatting) {
88 | String translatedString = this.translations.get(key);
89 | if (translatedString == null) {
90 | return key;
91 | }
92 | return String.format(translatedString, formatting);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/Update.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.Files;
5 | import java.nio.file.Paths;
6 |
7 | import org.mcphackers.mcp.tools.Util;
8 |
9 | public class Update {
10 |
11 | public static void main(String[] args) {
12 | if (args.length >= 1) {
13 | boolean keepTrying = true;
14 | long startTime = System.currentTimeMillis();
15 | while (keepTrying) {
16 | try {
17 | Files.deleteIfExists(Paths.get(args[0]));
18 | Files.copy(Paths.get(MCPPaths.UPDATE_JAR), Paths.get(args[0]));
19 | Util.runCommand(new String[]{
20 | Util.getJava(),
21 | "-jar",
22 | args[0]
23 | });
24 | keepTrying = false;
25 | } catch (IOException e) {
26 | keepTrying = System.currentTimeMillis() - startTime < 10000;
27 | }
28 | }
29 | }
30 | }
31 |
32 | public static void attemptToDeleteUpdateJar() {
33 | long startTime = System.currentTimeMillis();
34 | boolean keepTrying = true;
35 | while (keepTrying) {
36 | try {
37 | Files.deleteIfExists(Paths.get(MCPPaths.UPDATE_JAR));
38 | keepTrying = false;
39 | } catch (IOException e) {
40 | keepTrying = System.currentTimeMillis() - startTime < 10000;
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/plugin/MCPPlugin.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.plugin;
2 |
3 | import org.mcphackers.mcp.MCP;
4 | import org.mcphackers.mcp.tasks.Task;
5 | import org.mcphackers.mcp.tasks.TaskStaged;
6 |
7 | public interface MCPPlugin {
8 |
9 | /**
10 | * @return A unique string id
11 | */
12 | String pluginId();
13 |
14 | /**
15 | * Called when the plugin is loaded
16 | */
17 | void init(MCP mcp);
18 |
19 | /**
20 | * Called whenever a certain task event happens inside of task instance
21 | */
22 | void onTaskEvent(TaskEvent event, Task task);
23 |
24 | /**
25 | * Called whenever a certain task event happens inside of task instance
26 | */
27 | void onMCPEvent(MCPEvent event, MCP mcp);
28 |
29 | /**
30 | * Called whenever an instance of TaskStaged starts execution.
31 | * Use {@link TaskStaged#overrideStage(int, org.mcphackers.mcp.tasks.TaskRunnable)}
32 | * to replace one of the stages.
33 | *
34 | * @param task the task with stages to override
35 | */
36 | void setTaskOverrides(TaskStaged task);
37 |
38 | enum TaskEvent {
39 | /**
40 | * Called before a task is executed
41 | */
42 | PRE_TASK,
43 | /**
44 | * Called after the task has executed
45 | */
46 | POST_TASK,
47 | /**
48 | * Called each time a task moves to the next execution stage
49 | */
50 | TASK_STEP
51 | }
52 |
53 | enum MCPEvent {
54 | /**
55 | * Called when a task begins execution
56 | */
57 | STARTED_TASKS,
58 | /**
59 | * Called when all tasks have finished execution
60 | */
61 | FINISHED_TASKS,
62 | /**
63 | * Called when RMCP starts up
64 | */
65 | ENV_STARTUP
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/plugin/PluginClassLoader.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.plugin;
2 |
3 | import java.net.URL;
4 | import java.net.URLClassLoader;
5 |
6 | public class PluginClassLoader extends URLClassLoader {
7 | public PluginClassLoader(ClassLoader parent) {
8 | super(new URL[] {}, parent);
9 | }
10 |
11 | public void addURL(URL url) {
12 | super.addURL(url);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/plugin/PluginManager.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.plugin;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.FileSystem;
5 | import java.nio.file.FileSystems;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.nio.file.Paths;
9 | import java.util.HashMap;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.ServiceLoader;
13 |
14 | import org.mcphackers.mcp.MCP;
15 | import org.mcphackers.mcp.tools.FileUtil;
16 |
17 | public class PluginManager {
18 | private final Map loadedPlugins = new HashMap<>();
19 |
20 | public Map getLoadedPlugins() {
21 | return this.loadedPlugins;
22 | }
23 |
24 | public void discoverPlugins(MCP mcp) {
25 | ClassLoader classLoader = mcp.getClass().getClassLoader();
26 | try (PluginClassLoader pluginClassLoader = new PluginClassLoader(classLoader)) {
27 | Path workingDir = mcp.getWorkingDir();
28 | if (workingDir == null) {
29 | workingDir = Paths.get(".");
30 | }
31 | Path pluginsDir = workingDir.resolve("plugins");
32 | if (Files.exists(pluginsDir) && Files.isDirectory(pluginsDir)) {
33 | List pluginCandidates = FileUtil.getPathsOfType(pluginsDir, ".jar", ".zip");
34 | for (Path pluginCandidate : pluginCandidates) {
35 | try (FileSystem fs = FileSystems.newFileSystem(pluginCandidate, pluginClassLoader)) {
36 | Path serviceConfigPath = fs.getPath("META-INF", "services", MCPPlugin.class.getName());
37 | if (!Files.exists(serviceConfigPath)) {
38 | System.out.println("Plugin candidate (" + pluginCandidate.getFileName() + ") does not contain a valid service configuration. Skipping!");
39 | } else {
40 | System.out.println("Adding plugin candidate (" + pluginCandidate.getFileName() + ") to classpath!");
41 | pluginClassLoader.addURL(pluginCandidate.toUri().toURL());
42 | }
43 | } catch (IOException ex) {
44 | ex.printStackTrace();
45 | }
46 | }
47 | }
48 |
49 | ServiceLoader serviceLoader = ServiceLoader.load(MCPPlugin.class, pluginClassLoader);
50 | for (MCPPlugin plugin : serviceLoader) {
51 | plugin.init(mcp);
52 | this.loadedPlugins.put(plugin.pluginId(), plugin);
53 |
54 | // Load translations from plugins
55 | MCP.TRANSLATOR.readTranslation(plugin.getClass());
56 | }
57 | } catch (IOException ex) {
58 | ex.printStackTrace();
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/ProgressListener.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | public interface ProgressListener {
4 |
5 | void setProgress(String progressMessage);
6 |
7 | void setProgress(int progress);
8 |
9 | default void setProgress(String progressMessage, int progress) {
10 | setProgress(progressMessage);
11 | setProgress(progress);
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/Task.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | import org.mcphackers.mcp.MCP;
9 | import org.mcphackers.mcp.plugin.MCPPlugin.TaskEvent;
10 |
11 | public abstract class Task implements ProgressListener, TaskRunnable {
12 |
13 | public static final Map sides = new HashMap<>();
14 | public static final byte INFO = 0;
15 | public static final byte WARNING = 1;
16 | public static final byte ERROR = 2;
17 | public final Side side;
18 | protected final MCP mcp;
19 | private final List logMessages = new ArrayList<>();
20 | private byte result = INFO;
21 | private ProgressListener progressListener;
22 | private int progressBarIndex = -1;
23 |
24 | public Task(Side side, MCP instance, ProgressListener listener) {
25 | this(side, instance);
26 | this.progressListener = listener;
27 | }
28 |
29 | public Task(Side side, MCP instance) {
30 | this.side = side;
31 | this.mcp = instance;
32 | }
33 |
34 | public Task(MCP instance) {
35 | this(Side.ANY, instance);
36 | }
37 |
38 | public final void performTask() throws Exception {
39 | triggerEvent(TaskEvent.PRE_TASK);
40 | doTask();
41 | triggerEvent(TaskEvent.POST_TASK);
42 | }
43 |
44 | protected final void triggerEvent(TaskEvent event) {
45 | mcp.triggerTaskEvent(event, this);
46 | }
47 |
48 | protected final void addMessage(String msg, byte logLevel) {
49 | if (progressListener != null) {
50 | if (progressListener instanceof Task) {
51 | Task task = (Task) progressListener;
52 | task.addMessage(msg, logLevel);
53 | }
54 | }
55 | logMessages.add(msg);
56 | result = logLevel < result ? result : logLevel;
57 | }
58 |
59 | public final byte getResult() {
60 | return result;
61 | }
62 |
63 | public final List getMessageList() {
64 | return logMessages;
65 | }
66 |
67 | @Override
68 | public void setProgress(String progressString) {
69 | if (progressListener != null) {
70 | progressListener.setProgress(progressString);
71 | } else if (progressBarIndex >= 0) {
72 | mcp.setProgress(progressBarIndex, progressString);
73 | }
74 | }
75 |
76 | @Override
77 | public void setProgress(int progress) {
78 | if (progressListener != null) {
79 | progressListener.setProgress(progress);
80 | } else if (progressBarIndex >= 0) {
81 | mcp.setProgress(progressBarIndex, progress);
82 | }
83 | }
84 |
85 | public void log(String msg) {
86 | mcp.log(msg);
87 | }
88 |
89 | public void setProgressBarIndex(int i) {
90 | progressBarIndex = i;
91 | }
92 |
93 | public final String getLocalizedStage(String stage, Object... formatting) {
94 | return MCP.TRANSLATOR.translateKeyWithFormatting("task.stage." + stage, formatting);
95 | }
96 |
97 | public enum Side {
98 | ANY(-1, "any"),
99 | CLIENT(0, "client"),
100 | SERVER(1, "server"),
101 | MERGED(2, "merged");
102 |
103 | public static final Side[] ALL = {CLIENT, SERVER, MERGED};
104 | public static final Side[] VALUES = Side.values();
105 |
106 | public final int index;
107 | public final String name;
108 |
109 | Side(int index, String name) {
110 | this.index = index;
111 | this.name = name;
112 | sides.put(index, this);
113 | }
114 |
115 | public String getName() {
116 | return MCP.TRANSLATOR.translateKey("side." + name);
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskApplyPatch.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import static org.mcphackers.mcp.MCPPaths.PATCH;
4 | import static org.mcphackers.mcp.MCPPaths.SOURCE;
5 |
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.IOException;
8 | import java.io.PrintStream;
9 | import java.nio.file.Path;
10 |
11 | import codechicken.diffpatch.PatchOperation;
12 | import codechicken.diffpatch.util.PatchMode;
13 | import org.mcphackers.mcp.MCP;
14 | import org.mcphackers.mcp.MCPPaths;
15 |
16 | public class TaskApplyPatch extends TaskStaged {
17 |
18 | public TaskApplyPatch(Side side, MCP instance) {
19 | super(side, instance);
20 | }
21 |
22 | @Override
23 | protected Stage[] setStages() {
24 | return new Stage[] {
25 | stage(getLocalizedStage("patching"), () -> {
26 | final Path patchesPath = MCPPaths.get(mcp, PATCH, side);
27 | final Path srcPath = MCPPaths.get(mcp, SOURCE, side);
28 | patch(this, srcPath, srcPath, patchesPath);
29 | })
30 | };
31 | }
32 |
33 | public static void patch(Task task, Path base, Path out, Path patches) throws IOException {
34 | ByteArrayOutputStream logger = new ByteArrayOutputStream();
35 | PatchOperation patchOperation = PatchOperation.builder()
36 | .basePath(base)
37 | .patchesPath(patches)
38 | .outputPath(out)
39 | .mode(PatchMode.OFFSET)
40 | .build();
41 | boolean success = patchOperation.doPatch();
42 | patchOperation.getSummary().print(new PrintStream(logger), false);
43 | if (!success) {
44 | task.addMessage(logger.toString(), Task.INFO);
45 | task.addMessage("Patching failed!", Task.ERROR);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskBuild.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import static org.mcphackers.mcp.MCPPaths.*;
4 |
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 | import java.util.List;
8 |
9 | import org.mcphackers.mcp.MCP;
10 | import org.mcphackers.mcp.MCPPaths;
11 | import org.mcphackers.mcp.tasks.mode.TaskParameter;
12 | import org.mcphackers.mcp.tools.FileUtil;
13 |
14 | public class TaskBuild extends TaskStaged {
15 | /*
16 | * Indexes of stages for plugin overrides
17 | */
18 | public static final int STAGE_RECOMPILE = 0;
19 | public static final int STAGE_REOBF = 1;
20 | public static final int STAGE_BUILD = 2;
21 |
22 | public TaskBuild(Side side, MCP instance) {
23 | super(side, instance);
24 | }
25 |
26 | @Override
27 | protected Stage[] setStages() {
28 | Path bin = MCPPaths.get(mcp, BIN, side);
29 | return new Stage[]{
30 | stage(getLocalizedStage("recompile"),
31 | () -> new TaskRecompile(side, mcp, this).doTask()),
32 | stage(getLocalizedStage("reobf"), 50,
33 | () -> new TaskReobfuscate(side, mcp, this).doTask()),
34 | stage(getLocalizedStage("build"), 70,
35 | () -> {
36 | Side[] sides = side == Side.MERGED ? new Side[]{Side.CLIENT, Side.SERVER} : new Side[]{side};
37 | for (Side localSide : sides) {
38 | Path originalJar = MCPPaths.get(mcp, JAR_ORIGINAL, localSide);
39 | Path reobfDir = MCPPaths.get(mcp, REOBF_SIDE, localSide);
40 | Path buildJar = MCPPaths.get(mcp, BUILD_JAR, localSide);
41 | Path buildZip = MCPPaths.get(mcp, BUILD_ZIP, localSide);
42 | FileUtil.createDirectories(MCPPaths.get(mcp, BUILD));
43 | if (mcp.getOptions().getBooleanParameter(TaskParameter.FULL_BUILD)) {
44 | Files.deleteIfExists(buildJar);
45 | Files.copy(originalJar, buildJar);
46 | List reobfClasses = FileUtil.walkDirectory(reobfDir, path -> !Files.isDirectory(path));
47 | FileUtil.packFilesToZip(buildJar, reobfClasses, reobfDir);
48 | List assets = FileUtil.walkDirectory(bin, path -> !Files.isDirectory(path) && !path.getFileName().toString().endsWith(".class"));
49 | FileUtil.packFilesToZip(buildJar, assets, bin);
50 | FileUtil.deleteFileInAZip(buildJar, "/META-INF/MOJANG_C.DSA");
51 | FileUtil.deleteFileInAZip(buildJar, "/META-INF/MOJANG_C.SF");
52 | FileUtil.deleteFileInAZip(buildJar, "/META-INF/CODESIGN.DSA");
53 | FileUtil.deleteFileInAZip(buildJar, "/META-INF/CODESIGN.SF");
54 | } else {
55 | Files.deleteIfExists(buildZip);
56 | FileUtil.compress(reobfDir, buildZip);
57 | List assets = FileUtil.walkDirectory(bin, path -> !Files.isDirectory(path) && !path.getFileName().toString().endsWith(".class"));
58 | FileUtil.packFilesToZip(buildZip, assets, bin);
59 | }
60 | }
61 | })
62 | };
63 | }
64 |
65 | @Override
66 | public void setProgress(int progress) {
67 | switch (step) {
68 | case STAGE_RECOMPILE: {
69 | int percent = (int) (progress * 0.49D);
70 | super.setProgress(1 + percent);
71 | break;
72 | }
73 | case STAGE_REOBF: {
74 | int percent = (int) (progress * 0.20D);
75 | super.setProgress(50 + percent);
76 | break;
77 | }
78 | default:
79 | super.setProgress(progress);
80 | break;
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskCleanup.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import static org.mcphackers.mcp.MCPPaths.*;
4 |
5 | import java.io.IOException;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.text.DecimalFormat;
9 | import java.text.DecimalFormatSymbols;
10 | import java.time.Duration;
11 | import java.time.Instant;
12 | import java.time.temporal.ChronoUnit;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.Locale;
16 |
17 | import org.mcphackers.mcp.MCP;
18 | import org.mcphackers.mcp.MCPPaths;
19 | import org.mcphackers.mcp.tools.FileUtil;
20 |
21 | public class TaskCleanup extends TaskStaged {
22 |
23 | private static final DecimalFormat DECIMAL = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
24 |
25 | public TaskCleanup(MCP instance) {
26 | super(Side.ANY, instance);
27 | }
28 |
29 | @Override
30 | protected Stage[] setStages() {
31 | return new Stage[] {
32 | stage(getLocalizedStage("cleaning"), () -> {
33 | Instant startTime = Instant.now();
34 |
35 | boolean deleted = cleanup();
36 |
37 | mcp.setCurrentVersion(null);
38 |
39 | if (deleted) {
40 | log("Cleanup finished in " + DECIMAL.format(Duration.between(startTime, Instant.now()).get(ChronoUnit.NANOS) / 1e+9F) + "s");
41 | } else {
42 | log("Nothing to clear!");
43 | }
44 | })
45 | };
46 | }
47 |
48 | public boolean cleanup() throws IOException {
49 | boolean deleted = false;
50 | List filesToDelete = new ArrayList<>();
51 | for (Side side : Side.ALL) {
52 | filesToDelete.add(MCPPaths.get(mcp, JAR_ORIGINAL, side));
53 | filesToDelete.add(MCPPaths.get(mcp, PROJECT, side));
54 | filesToDelete.add(MCPPaths.get(mcp, PATCHES, side));
55 | filesToDelete.add(MCPPaths.get(mcp, BUILD_ZIP, side));
56 | filesToDelete.add(MCPPaths.get(mcp, BUILD_JAR, side));
57 | }
58 | filesToDelete.add(MCPPaths.get(mcp, CONF));
59 | filesToDelete.add(MCPPaths.get(mcp, NATIVES));
60 |
61 | Path[] foldersToDelete = new Path[]{
62 | MCPPaths.get(mcp, JARS),
63 | };
64 | for (Path path : filesToDelete) {
65 | if (Files.exists(path)) {
66 | deleted = true;
67 | FileUtil.delete(path);
68 | }
69 | }
70 | for (Path path : foldersToDelete) {
71 | if (Files.exists(path) && path.toFile().list().length == 0) {
72 | deleted = true;
73 | Files.delete(path);
74 | }
75 | }
76 | return deleted;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskCreatePatch.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import static org.mcphackers.mcp.MCPPaths.*;
4 |
5 | import java.io.IOException;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 |
9 | import codechicken.diffpatch.DiffOperation;
10 | import org.mcphackers.mcp.MCP;
11 | import org.mcphackers.mcp.MCPPaths;
12 |
13 | public class TaskCreatePatch extends TaskStaged {
14 | public TaskCreatePatch(Side side, MCP instance) {
15 | super(side, instance);
16 | }
17 |
18 | @Override
19 | protected Stage[] setStages() {
20 | return new Stage[] {
21 | stage(getLocalizedStage("createpatch"), () -> {
22 | Path srcPathUnpatched = MCPPaths.get(mcp, SOURCE_UNPATCHED, side);
23 | Path srcPathPatched = MCPPaths.get(mcp, SOURCE, side);
24 | Path patchesOut = MCPPaths.get(mcp, PATCH, side);
25 | setProgress(getLocalizedStage("createpatch"));
26 | if (!Files.exists(srcPathPatched)) {
27 | throw new IOException("Patched " + side.name + " sources cannot be found!");
28 | }
29 | if (!Files.exists(srcPathUnpatched)) {
30 | throw new IOException("Unpatched " + side.name + " sources cannot be found!");
31 | }
32 | createDiffOperation(srcPathUnpatched, srcPathPatched, patchesOut);
33 | })
34 | };
35 | }
36 |
37 | public boolean createDiffOperation(Path aPath, Path bPath, Path outputPath) throws Exception {
38 | DiffOperation diffOperation = DiffOperation.builder()
39 | .aPath(aPath)
40 | .bPath(bPath)
41 | .aPrefix(null)
42 | .bPrefix(null)
43 | .singleDiff(true)
44 | .outputPath(outputPath)
45 | .filter(p -> p.endsWith(".java"))
46 | .build();
47 | return diffOperation.doDiff();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskDownloadUpdate.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.net.URL;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.nio.file.Paths;
9 |
10 | import org.json.JSONObject;
11 | import org.mcphackers.mcp.MCP;
12 | import org.mcphackers.mcp.MCPPaths;
13 | import org.mcphackers.mcp.tasks.mode.TaskMode;
14 | import org.mcphackers.mcp.tools.FileUtil;
15 | import org.mcphackers.mcp.tools.JSONUtil;
16 | import org.mcphackers.mcp.tools.Util;
17 |
18 | public class TaskDownloadUpdate extends TaskStaged {
19 |
20 | private static final String API = "https://api.github.com/repos/MCPHackers/RetroMCP-Java/releases/latest";
21 |
22 | public TaskDownloadUpdate(MCP instance) {
23 | super(Side.ANY, instance);
24 | }
25 |
26 | @Override
27 | protected Stage[] setStages() {
28 | return new Stage[] {
29 | stage(getLocalizedStage("downloadupdate"), () -> {
30 | URL updateURL = new URL(API);
31 | InputStream in = updateURL.openStream();
32 | JSONObject releaseJson = JSONUtil.parseJSON(in);
33 | String latestVersion = releaseJson.getString("tag_name");
34 | String notes = releaseJson.getString("body");
35 | if (!latestVersion.equals(MCP.VERSION)) {
36 | boolean confirmed = mcp.updateDialogue(notes, latestVersion);
37 | if (confirmed) {
38 | log("Downloading update...");
39 | for (Object obj : releaseJson.getJSONArray("assets")) {
40 | if (obj instanceof JSONObject) {
41 | JSONObject assetObj = (JSONObject) obj;
42 | if (!assetObj.getString("name").endsWith(mcp.isGUI() ? "-GUI.jar" : "-CLI.jar")) {
43 | continue;
44 | }
45 | FileUtil.downloadFile(assetObj.getString("browser_download_url"), Paths.get(MCPPaths.UPDATE_JAR));
46 | break;
47 | }
48 | }
49 | Path jarPath = Paths.get(MCP.class
50 | .getProtectionDomain()
51 | .getCodeSource()
52 | .getLocation()
53 | .toURI());
54 | if (!Files.isDirectory(jarPath)) {
55 | String[] cmd = new String[]{
56 | Util.getJava(),
57 | "-cp",
58 | MCPPaths.UPDATE_JAR,
59 | "org.mcphackers.mcp.Update",
60 | jarPath.toString()
61 | };
62 | Util.runCommand(cmd);
63 | System.exit(0);
64 | } else {
65 | throw new IOException("Running from a folder! Aborting");
66 | }
67 | } else {
68 | log("Cancelling update!");
69 | }
70 | } else {
71 | mcp.showMessage(TaskMode.UPDATE_MCP.getFullName(), MCP.TRANSLATOR.translateKey("mcp.upToDate"), Task.INFO);
72 | }
73 | })
74 | };
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskRecompile.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import org.mcphackers.mcp.MCP;
4 | import org.mcphackers.mcp.MCPPaths;
5 | import org.mcphackers.mcp.tasks.mode.TaskParameter;
6 | import org.mcphackers.mcp.tools.FileUtil;
7 |
8 | import javax.tools.*;
9 |
10 | import java.io.File;
11 | import java.io.IOException;
12 | import java.nio.charset.StandardCharsets;
13 | import java.nio.file.Files;
14 | import java.nio.file.Path;
15 | import java.nio.file.Paths;
16 | import java.util.ArrayList;
17 | import java.util.Arrays;
18 | import java.util.List;
19 | import java.util.stream.Collectors;
20 | import java.util.stream.Stream;
21 |
22 | import static org.mcphackers.mcp.MCPPaths.*;
23 |
24 | public class TaskRecompile extends TaskStaged {
25 |
26 | public TaskRecompile(Side side, MCP instance) {
27 | super(side, instance);
28 | }
29 |
30 | public TaskRecompile(Side side, MCP instance, ProgressListener listener) {
31 | super(side, instance, listener);
32 | }
33 |
34 | public static List collectClassPath(MCP mcp, Side side) {
35 | List classpath = new ArrayList<>();
36 | classpath.add(MCPPaths.get(mcp, REMAPPED, side));
37 | if (mcp.getCurrentVersion() != null) {
38 | classpath.addAll(mcp.getLibraries());
39 | }
40 | return classpath;
41 | }
42 |
43 | @Override
44 | protected Stage[] setStages() {
45 | JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
46 | if (compiler == null) {
47 | throw new RuntimeException("Could not find compiling API. Please install or use a Java Development Kit to run this program.");
48 | }
49 | Path binPath = MCPPaths.get(mcp, BIN, side);
50 | Path srcPath = MCPPaths.get(mcp, SOURCE, side);
51 | return new Stage[]{
52 | stage(getLocalizedStage("recompile"), 1,
53 | () -> {
54 | Files.createDirectories(binPath);
55 | FileUtil.cleanDirectory(binPath);
56 | setProgress(2);
57 | if (!Files.exists(srcPath)) {
58 | throw new IOException(side.getName() + " sources not found!");
59 | }
60 | try (Stream paths = Files.list(srcPath)) {
61 | if (!paths.findAny().isPresent()) {
62 | return;
63 | }
64 | }
65 |
66 | final List src = collectSource();
67 | final List classpath = collectClassPath(mcp, side);
68 | final List bootclasspath = collectBootClassPath();
69 |
70 | List cp = new ArrayList<>();
71 | classpath.forEach(p -> cp.add(p.toAbsolutePath().toString()));
72 |
73 | List options = new ArrayList<>(Arrays.asList("-d", binPath.toString()));
74 |
75 | int sourceVersion = mcp.getOptions().getIntParameter(TaskParameter.SOURCE_VERSION);
76 | if (sourceVersion >= 0) {
77 | options.addAll(Arrays.asList("-source", Integer.toString(sourceVersion)));
78 | }
79 |
80 | int targetVersion = mcp.getOptions().getIntParameter(TaskParameter.TARGET_VERSION);
81 | if (targetVersion >= 0) {
82 | options.addAll(Arrays.asList("-target", Integer.toString(targetVersion)));
83 | }
84 |
85 | List bootcp = new ArrayList<>();
86 | bootclasspath.forEach(p -> bootcp.add(p.toAbsolutePath().toString()));
87 | if (!bootclasspath.isEmpty()) {
88 | options.addAll(Arrays.asList("-bootclasspath", String.join(System.getProperty("path.separator"), bootcp)));
89 | }
90 |
91 | options.addAll(Arrays.asList("-cp", String.join(System.getProperty("path.separator"), cp)));
92 |
93 | setProgress(3);
94 |
95 | DiagnosticCollector ds = new DiagnosticCollector<>();
96 | recompile(compiler, ds, src, options);
97 | }),
98 | stage(getLocalizedStage("copyres"), 50,
99 | () -> {
100 | // Copy assets from source folder
101 | List assets = collectResources();
102 | int i = 0;
103 | for (Path path : assets) {
104 | if (srcPath.relativize(path).getParent() != null) {
105 | Files.createDirectories(binPath.resolve(srcPath.relativize(path).getParent()));
106 | }
107 | Files.copy(path, binPath.resolve(srcPath.relativize(path)));
108 | i++;
109 | setProgress(50 + (int) ((double) i / assets.size() * 49));
110 | }
111 | })
112 | };
113 | }
114 |
115 | public List collectResources() throws IOException {
116 | Path srcPath = MCPPaths.get(mcp, SOURCE, side);
117 | return FileUtil.walkDirectory(srcPath, path -> !Files.isDirectory(path) && !path.getFileName().toString().endsWith(".java") && !path.getFileName().toString().endsWith(".class"));
118 | }
119 |
120 | public List collectBootClassPath() throws IOException {
121 | List bootclasspath = new ArrayList<>();
122 | String javaHome = mcp.getOptions().getStringParameter(TaskParameter.JAVA_HOME);
123 | if (!javaHome.isEmpty()) {
124 | Path libs = Paths.get(javaHome).resolve("lib");
125 | Path libsJre = Paths.get(javaHome).resolve("jre/lib");
126 | if (Files.exists(libs)) {
127 | FileUtil.collectJars(libs, bootclasspath);
128 | }
129 | if (Files.exists(libsJre)) {
130 | FileUtil.collectJars(libsJre, bootclasspath);
131 | }
132 | }
133 | return bootclasspath;
134 | }
135 |
136 | public List collectSource() throws IOException {
137 | Path srcPath = MCPPaths.get(mcp, SOURCE, side);
138 | List src;
139 | try (Stream pathStream = Files.walk(srcPath)) {
140 | src = pathStream.filter(path -> !Files.isDirectory(path) && path.getFileName().toString().endsWith(".java")).map(Path::toFile).collect(Collectors.toList());
141 | }
142 | return src;
143 | }
144 |
145 | public void recompile(JavaCompiler compiler, DiagnosticCollector ds, Iterable src, Iterable recompileOptions) throws IOException, RuntimeException {
146 | StandardJavaFileManager mgr = compiler.getStandardFileManager(ds, null, StandardCharsets.UTF_8);
147 | Iterable extends JavaFileObject> sources = mgr.getJavaFileObjectsFromFiles(src);
148 | JavaCompiler.CompilationTask task = compiler.getTask(null, mgr, ds, recompileOptions, null, sources);
149 | mgr.close();
150 | task.call();
151 | for (Diagnostic extends JavaFileObject> diagnostic : ds.getDiagnostics())
152 | if (diagnostic.getKind() == Diagnostic.Kind.ERROR || diagnostic.getKind() == Diagnostic.Kind.WARNING) {
153 | String[] kindString = {"Info", "Warning", "Error"};
154 | byte kind = diagnostic.getKind() == Diagnostic.Kind.ERROR ? Task.ERROR : Task.WARNING;
155 | JavaFileObject source = diagnostic.getSource();
156 | if (source == null) {
157 | addMessage(kindString[kind] + String.format("%n%s%n",
158 | diagnostic.getMessage(null)),
159 | kind);
160 | } else {
161 | addMessage(kindString[kind] + String.format(" on line %d in %s%n%s%n",
162 | diagnostic.getLineNumber(),
163 | source.getName(),
164 | diagnostic.getMessage(null)),
165 | kind);
166 | }
167 | }
168 | mgr.close();
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskRun.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import static org.mcphackers.mcp.MCPPaths.*;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.nio.file.Files;
8 | import java.nio.file.Path;
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.Collections;
12 | import java.util.List;
13 | import java.util.zip.ZipEntry;
14 | import java.util.zip.ZipInputStream;
15 |
16 | import org.mcphackers.mcp.MCP;
17 | import org.mcphackers.mcp.MCPPaths;
18 | import org.mcphackers.mcp.tasks.mode.TaskParameter;
19 | import org.mcphackers.mcp.tools.Util;
20 | import org.mcphackers.mcp.tools.versions.json.Version;
21 | import org.mcphackers.mcp.tools.versions.json.Version.Arguments;
22 |
23 | public class TaskRun extends TaskStaged {
24 |
25 | public static final List SERVER_MAIN = Arrays.asList("net.minecraft.server.Main", "net.minecraft.server.MinecraftServer", "com.mojang.minecraft.server.MinecraftServer");
26 |
27 | public TaskRun(Side side, MCP instance) {
28 | super(side, instance);
29 | }
30 |
31 | @Override
32 | protected Stage[] setStages() {
33 | return new Stage[] {
34 | stage(getLocalizedStage("run"), () -> {
35 | Version currentVersion = mcp.getCurrentVersion();
36 | Side mcpSide = mcp.getOptions().side;
37 | if (mcpSide == Side.ANY) {
38 | mcpSide = side;
39 | }
40 |
41 | String main = getMain(mcp, currentVersion, side);
42 | if (main == null) {
43 | mcp.log("Start class not found");
44 | return;
45 | }
46 |
47 | boolean runBuild = mcp.getOptions().getBooleanParameter(TaskParameter.RUN_BUILD);
48 | boolean fullBuild = mcp.getOptions().getBooleanParameter(TaskParameter.FULL_BUILD);
49 | String[] runArgs = mcp.getOptions().getStringArrayParameter(TaskParameter.RUN_ARGS);
50 | List cpList = getClasspath(mcp, mcpSide, side, runBuild, fullBuild);
51 |
52 | List classPath = new ArrayList<>();
53 | cpList.forEach(p -> classPath.add(p.toAbsolutePath().toString()));
54 |
55 | Path natives = MCPPaths.get(mcp, NATIVES).toAbsolutePath();
56 |
57 | List args = new ArrayList<>();
58 | args.add(Util.getJava());
59 | Collections.addAll(args, runArgs);
60 | args.add("-Djava.library.path=" + natives);
61 | args.add("-cp");
62 | args.add(String.join(File.pathSeparator, classPath));
63 | args.add(main);
64 | if (side == Side.CLIENT) {
65 | args.addAll(getLaunchArgs(mcp, mcpSide));
66 | Collections.addAll(args, mcp.getOptions().getStringParameter(TaskParameter.GAME_ARGS).split(" "));
67 | }
68 |
69 | Util.runCommand(args.toArray(new String[0]), getMCDir(mcp, mcpSide), true);
70 | })
71 | };
72 | }
73 |
74 | public static String getMain(MCP mcp, Version version, Side side) throws IOException {
75 | if (side == Side.CLIENT) {
76 | return version.mainClass;
77 | }
78 | if (side == Side.SERVER) {
79 | Path jarPath = MCPPaths.get(mcp, JAR_ORIGINAL, Side.SERVER);
80 | try (ZipInputStream zipIn = new ZipInputStream(Files.newInputStream(jarPath))) {
81 | ZipEntry zipEntry;
82 | while ((zipEntry = zipIn.getNextEntry()) != null) {
83 | if (zipEntry.getName().endsWith(".class")) {
84 | String className = zipEntry.getName().substring(0, zipEntry.getName().length() - 6).replace('\\', '.').replace('/', '.');
85 | if (SERVER_MAIN.contains(className)) {
86 | return className;
87 | }
88 | }
89 | }
90 | }
91 | }
92 | return null;
93 | }
94 |
95 | /**
96 | * @param mcp
97 | * @return arguments for launching client
98 | */
99 | public static List getLaunchArgs(MCP mcp, Side side) {
100 | Version ver = mcp.getCurrentVersion();
101 | Arguments args = ver.arguments;
102 | String mcArgs = ver.minecraftArguments;
103 | List argsList = new ArrayList<>();
104 | if (args != null) {
105 | for (Object o : args.game) {
106 | if (o instanceof String) {
107 | argsList.add((String) o);
108 | }
109 | }
110 | } else {
111 | argsList.addAll(Arrays.asList(mcArgs.split(" ")));
112 | }
113 |
114 | Path gameDir = getMCDir(mcp, side).toAbsolutePath();
115 | Path assets = gameDir.resolve("assets");
116 |
117 | for (int i = 0; i < argsList.size(); i++) {
118 | String arg = argsList.get(i);
119 | switch (arg) {
120 | case "${auth_player_name}":
121 | arg = "Player";
122 | break;
123 | case "${auth_session}":
124 | case "${auth_uuid}":
125 | case "${auth_access_token}":
126 | arg = "-";
127 | break;
128 | case "${user_properties}":
129 | arg = "{}";
130 | break;
131 | case "${version_name}":
132 | arg = ver.id;
133 | break;
134 | case "${version_type}":
135 | arg = ver.type;
136 | break;
137 | case "${user_type}":
138 | arg = "legacy";
139 | break;
140 | case "${assets_index_name}":
141 | arg = ver.assets;
142 | break;
143 | case "${assets_root}":
144 | case "${game_assets}":
145 | arg = assets.toString();
146 | break;
147 | case "${game_directory}":
148 | arg = gameDir.toString();
149 | break;
150 | }
151 | argsList.set(i, arg);
152 | }
153 | return argsList;
154 | }
155 |
156 | public static Path getMCDir(MCP mcp, Side side) {
157 | return MCPPaths.get(mcp, GAMEDIR, side);
158 | }
159 |
160 | private static List getClasspath(MCP mcp, Side side, Side runSide, boolean runBuild, boolean fullBuild) {
161 | List cpList = new ArrayList<>(mcp.getLibraries());
162 | if (runBuild) {
163 | if (fullBuild) {
164 | cpList.add(MCPPaths.get(mcp, BUILD_JAR, runSide));
165 | } else {
166 | cpList.add(MCPPaths.get(mcp, BUILD_ZIP, runSide));
167 | cpList.add(MCPPaths.get(mcp, JAR_ORIGINAL, runSide));
168 | }
169 | } else {
170 | cpList.add(MCPPaths.get(mcp, BIN, side));
171 | if (Files.exists(MCPPaths.get(mcp, REMAPPED, side))) {
172 | cpList.add(MCPPaths.get(mcp, REMAPPED, side));
173 | }
174 | }
175 | return cpList;
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskRunnable.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | @FunctionalInterface
4 | public interface TaskRunnable {
5 |
6 | void doTask() throws Exception;
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskSetup.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import static org.mcphackers.mcp.MCPPaths.CONF;
4 | import static org.mcphackers.mcp.MCPPaths.JARS;
5 | import static org.mcphackers.mcp.MCPPaths.LIB;
6 | import static org.mcphackers.mcp.MCPPaths.NATIVES;
7 | import static org.mcphackers.mcp.MCPPaths.VERSION;
8 |
9 | import java.io.BufferedWriter;
10 | import java.io.InputStream;
11 | import java.net.MalformedURLException;
12 | import java.net.URL;
13 | import java.nio.charset.StandardCharsets;
14 | import java.nio.file.Files;
15 | import java.nio.file.Path;
16 | import java.nio.file.Paths;
17 | import java.util.List;
18 |
19 | import org.json.JSONObject;
20 | import org.mcphackers.mcp.MCP;
21 | import org.mcphackers.mcp.MCPPaths;
22 | import org.mcphackers.mcp.tasks.mode.TaskMode;
23 | import org.mcphackers.mcp.tasks.mode.TaskParameter;
24 | import org.mcphackers.mcp.tools.FileUtil;
25 | import org.mcphackers.mcp.tools.Util;
26 | import org.mcphackers.mcp.tools.versions.DownloadData;
27 | import org.mcphackers.mcp.tools.versions.VersionParser;
28 | import org.mcphackers.mcp.tools.versions.VersionParser.VersionData;
29 | import org.mcphackers.mcp.tools.versions.json.Version;
30 |
31 | public class TaskSetup extends TaskStaged {
32 |
33 | private long libsSize = 0;
34 |
35 | public TaskSetup(MCP instance) {
36 | super(Side.ANY, instance);
37 | }
38 |
39 | @Override
40 | protected Stage[] setStages() {
41 | return new Stage[] {
42 | stage(getLocalizedStage("setup"), 0, () -> {
43 | new TaskCleanup(mcp).cleanup();
44 | FileUtil.createDirectories(MCPPaths.get(mcp, JARS));
45 | FileUtil.createDirectories(MCPPaths.get(mcp, LIB));
46 | FileUtil.createDirectories(MCPPaths.get(mcp, NATIVES));
47 |
48 | setProgress(getLocalizedStage("setup"), 1);
49 | List versions = VersionParser.getInstance().getVersions();
50 | String chosenVersion = mcp.getOptions().getStringParameter(TaskParameter.SETUP_VERSION);
51 | VersionData chosenVersionData;
52 |
53 | // Keep asking until chosenVersion equals one of the versionData
54 | input:
55 | while (true) {
56 | for (VersionData data : versions) {
57 | if (data.id.equals(chosenVersion)) {
58 | chosenVersionData = data;
59 | break input;
60 | }
61 | }
62 | chosenVersion = mcp.inputString(TaskMode.SETUP.getFullName(), MCP.TRANSLATOR.translateKey("task.setup.selectVersion"));
63 | }
64 |
65 | InputStream versionStream;
66 | try {
67 | versionStream = new URL(chosenVersionData.url).openStream();
68 | } catch (MalformedURLException ex) {
69 | versionStream = Files.newInputStream(MCPPaths.get(mcp, chosenVersionData.url));
70 | }
71 | JSONObject versionJsonObj = new JSONObject(new String(Util.readAllBytes(versionStream), StandardCharsets.UTF_8));
72 | Version versionJson = Version.from(versionJsonObj);
73 | FileUtil.createDirectories(MCPPaths.get(mcp, CONF));
74 |
75 | if (chosenVersionData.resources != null) {
76 | setProgress(getLocalizedStage("download", chosenVersionData.resources), 2);
77 | try {
78 | URL url = new URL(chosenVersionData.resources);
79 | FileUtil.extract(url.openStream(), MCPPaths.get(mcp, CONF));
80 | } catch (MalformedURLException e) {
81 | Path p = Paths.get(chosenVersionData.resources);
82 | if (Files.exists(p)) {
83 | FileUtil.extract(p, MCPPaths.get(mcp, CONF));
84 | }
85 | }
86 | }
87 |
88 | DownloadData dlData = new DownloadData(mcp, versionJson);
89 | dlData.performDownload((dl, totalSize) -> {
90 | libsSize += dl.downloadSize();
91 | int percent = (int) ((double) libsSize / totalSize * 97D);
92 | setProgress(getLocalizedStage("download", dl.downloadURL()), 3 + percent);
93 | });
94 | Path natives = MCPPaths.get(mcp, NATIVES);
95 | for (Path nativeArchive : DownloadData.getNatives(MCPPaths.get(mcp, LIB), versionJson)) {
96 | FileUtil.extract(nativeArchive, natives);
97 | }
98 | try (BufferedWriter writer = Files.newBufferedWriter(MCPPaths.get(mcp, VERSION))) {
99 | versionJsonObj.write(writer, 1, 0);
100 | }
101 | mcp.setCurrentVersion(versionJson);
102 | })
103 | };
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskSourceBackup.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import static org.mcphackers.mcp.MCPPaths.SOURCE;
4 |
5 | import java.io.IOException;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.nio.file.Paths;
9 | import java.time.LocalDate;
10 | import java.time.format.DateTimeFormatter;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 | import java.util.stream.Stream;
14 | import java.util.zip.ZipEntry;
15 | import java.util.zip.ZipOutputStream;
16 |
17 | import org.mcphackers.mcp.MCP;
18 | import org.mcphackers.mcp.MCPPaths;
19 |
20 | public class TaskSourceBackup extends TaskStaged {
21 |
22 | private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.BASIC_ISO_DATE;
23 |
24 | public TaskSourceBackup(Side side, MCP instance) {
25 | super(side, instance);
26 | }
27 |
28 | @Override
29 | protected Stage[] setStages() {
30 | return new Stage[] {
31 | stage(getLocalizedStage("backupsource"), this::packSourceIntoZip)
32 | };
33 | }
34 |
35 | public TaskSourceBackup(Side side, MCP instance, ProgressListener listener) {
36 | super(side, instance, listener);
37 | }
38 |
39 | private void packSourceIntoZip() throws IOException {
40 | String filename = "src-backup-" + side.name + "-" + DATE_FORMATTER.format(LocalDate.now());
41 | Path backupPath = MCPPaths.get(mcp, filename + ".zip");
42 | for (int i = 1; Files.exists(backupPath); i++) {
43 | backupPath = MCPPaths.get(mcp, filename + "-" + i + ".zip");
44 | }
45 |
46 | Path srcPath = MCPPaths.get(mcp, SOURCE, side);
47 | try (ZipOutputStream zip = new ZipOutputStream(Files.newOutputStream(backupPath))) {
48 | List srcFiles = new ArrayList<>();
49 | try (Stream paths = Files.walk(srcPath)) {
50 | paths.forEach(file -> {
51 | if (file.getFileName().toString().endsWith(".java")) {
52 | srcFiles.add(file.toString());
53 | }
54 | });
55 | }
56 | long nFiles = srcFiles.size();
57 |
58 | int i = 0;
59 | for (String path : srcFiles) {
60 | if (path.endsWith(".java")) {
61 | try {
62 | zip.putNextEntry(new ZipEntry(path));
63 | zip.write(Files.readAllBytes(Paths.get(path)));
64 | zip.closeEntry();
65 | } catch (Exception e) {
66 | e.printStackTrace();
67 | }
68 | }
69 | setProgress(MCP.TRANSLATOR.translateKey("task.stage.backupsrc") + " " + path, (int) ((i / (float) nFiles) * 100));
70 | i++;
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskStaged.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import org.mcphackers.mcp.MCP;
4 | import org.mcphackers.mcp.plugin.MCPPlugin.TaskEvent;
5 |
6 | public abstract class TaskStaged extends Task {
7 |
8 | public int step;
9 | public Stage[] stages;
10 |
11 | public TaskStaged(Side side, MCP instance, ProgressListener listener) {
12 | super(side, instance, listener);
13 | }
14 |
15 | public TaskStaged(Side side, MCP instance) {
16 | super(side, instance);
17 | }
18 |
19 | public TaskStaged(MCP instance) {
20 | super(instance);
21 | }
22 |
23 | protected final void step() {
24 | step++;
25 | triggerEvent(TaskEvent.TASK_STEP);
26 | }
27 |
28 | protected abstract Stage[] setStages();
29 |
30 | @Override
31 | public void doTask() throws Exception {
32 | stages = setStages();
33 | mcp.setPluginOverrides(this);
34 | while (step < stages.length) {
35 | setProgress(stages[step].stageName, stages[step].completion);
36 | stages[step].doTask();
37 | step();
38 | }
39 | }
40 |
41 | /**
42 | * Replaces stage operation at stageIndex
with task
43 | *
44 | * @param stageIndex
45 | * @param task
46 | */
47 | public void overrideStage(int stageIndex, TaskRunnable task) {
48 | if (stageIndex < stages.length) {
49 | stages[stageIndex].setOperation(task);
50 | }
51 | }
52 |
53 | public Stage stage(String name, int percentage, TaskRunnable task) {
54 | return new Stage(name, percentage, task);
55 | }
56 |
57 | public Stage stage(String name, TaskRunnable task) {
58 | return new Stage(name, -1, task);
59 | }
60 |
61 | public static class Stage {
62 | private final String stageName;
63 | private final int completion;
64 | private TaskRunnable runnable;
65 |
66 | public Stage(String name, int i, TaskRunnable task) {
67 | setOperation(task);
68 | stageName = name;
69 | completion = i;
70 | }
71 |
72 | private void setOperation(TaskRunnable task) {
73 | runnable = task;
74 | }
75 |
76 | private void doTask() throws Exception {
77 | runnable.doTask();
78 | }
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/TaskUpdateMD5.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks;
2 |
3 | import static org.mcphackers.mcp.MCPPaths.*;
4 |
5 | import java.io.BufferedWriter;
6 | import java.io.IOException;
7 | import java.nio.file.FileVisitResult;
8 | import java.nio.file.Files;
9 | import java.nio.file.Path;
10 | import java.nio.file.SimpleFileVisitor;
11 | import java.nio.file.attribute.BasicFileAttributes;
12 | import java.util.stream.Stream;
13 |
14 | import org.mcphackers.mcp.MCP;
15 | import org.mcphackers.mcp.MCPPaths;
16 | import org.mcphackers.mcp.tools.Util;
17 |
18 | public class TaskUpdateMD5 extends TaskStaged {
19 | /*
20 | * Indexes of stages for plugin overrides
21 | */
22 | public static final int STAGE_RECOMPILE = 0;
23 | public static final int STAGE_MD5 = 1;
24 | private int progress = 0;
25 |
26 | public TaskUpdateMD5(Side side, MCP instance) {
27 | super(side, instance);
28 | }
29 |
30 | public TaskUpdateMD5(Side side, MCP instance, ProgressListener listener) {
31 | super(side, instance, listener);
32 | }
33 |
34 | @Override
35 | protected Stage[] setStages() {
36 | return new Stage[]{
37 | stage(getLocalizedStage("recompile"),
38 | () -> new TaskRecompile(side, mcp, this).doTask()),
39 | stage(getLocalizedStage("updatemd5"), 50,
40 | () -> updateMD5(false))
41 | };
42 | }
43 |
44 | @Override
45 | public void setProgress(int progress) {
46 | if (step == 0) {
47 | int percent = (int) (progress * 0.50D);
48 | super.setProgress(percent);
49 | } else {
50 | super.setProgress(progress);
51 | }
52 | }
53 |
54 | public void updateMD5(boolean reobf) throws IOException {
55 | final Path binPath = MCPPaths.get(mcp, BIN, side);
56 | final Path md5 = MCPPaths.get(mcp, reobf ? MD5_RO : MD5, side);
57 |
58 | if (!Files.exists(binPath)) {
59 | throw new IOException(side.name + " classes not found!");
60 | }
61 | try (BufferedWriter writer = Files.newBufferedWriter(md5)) {
62 | progress = 0;
63 | int total;
64 | try (Stream pathStream = Files.walk(binPath)) {
65 | total = (int) pathStream.parallel()
66 | .filter(p -> !p.toFile().isDirectory())
67 | .count();
68 | }
69 | Files.walkFileTree(binPath, new SimpleFileVisitor() {
70 | @Override
71 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
72 | String md5_hash = Util.getMD5(file);
73 | String fileName = binPath.relativize(file).toString().replace("\\", "/").replace(".class", "");
74 | writer.append(fileName).append(" ").append(md5_hash).append("\n");
75 | progress++;
76 | setProgress(50 + (int) ((double) progress / (double) total * 50));
77 | return FileVisitResult.CONTINUE;
78 | }
79 | });
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/mode/TaskModeBuilder.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks.mode;
2 |
3 | import org.mcphackers.mcp.tasks.Task;
4 | import org.mcphackers.mcp.tasks.mode.TaskMode.Requirement;
5 |
6 | public class TaskModeBuilder {
7 |
8 | private String name;
9 | private boolean usesProgressBars = true;
10 | private Class extends Task> taskClass;
11 | private TaskParameter[] params = new TaskParameter[]{};
12 | private Requirement requirement;
13 |
14 | public TaskModeBuilder setName(String name) {
15 | this.name = name;
16 | return this;
17 | }
18 |
19 | public TaskModeBuilder setProgressBars(boolean enabled) {
20 | this.usesProgressBars = enabled;
21 | return this;
22 | }
23 |
24 | public TaskModeBuilder setTaskClass(Class extends Task> taskClass) {
25 | this.taskClass = taskClass;
26 | return this;
27 | }
28 |
29 | public TaskModeBuilder setParameters(TaskParameter[] params) {
30 | this.params = params;
31 | return this;
32 | }
33 |
34 | public TaskModeBuilder addRequirement(Requirement condition) {
35 | this.requirement = condition;
36 | return this;
37 | }
38 |
39 | public TaskMode build() {
40 | return new TaskMode(this.name, this.taskClass, this.params, this.usesProgressBars, this.requirement);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/mode/TaskParameter.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks.mode;
2 |
3 | import de.fernflower.main.extern.IFernflowerPreferences;
4 | import org.mcphackers.mcp.MCP;
5 | import org.mcphackers.mcp.Options;
6 | import org.mcphackers.mcp.tasks.Task.Side;
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 |
12 | /**
13 | * Any optional parameters which can be accessed from tasks
14 | *
15 | * @see Options#getParameter(TaskParameter)
16 | */
17 | public enum TaskParameter {
18 |
19 | SIDE("side", Integer.class, Side.ANY.index),
20 | PATCHES("patch", Boolean.class, true),
21 | IGNORED_PACKAGES("ignore", String[].class, new String[]{"paulscode", "com/jcraft", "de/jarnbjo", "isom"}),
22 | FERNFLOWER_OPTIONS("ff_options", String.class, getDefaultFFOptions()),
23 | OBFUSCATION("obf", Boolean.class, false),
24 | SRG_OBFUSCATION("srgobf", Boolean.class, false),
25 | FULL_BUILD("fullbuild", Boolean.class, false),
26 | RUN_BUILD("runbuild", Boolean.class, false),
27 | RUN_ARGS("runargs", String[].class, new String[]{"-Xms1024M", "-Xmx1024M"}),
28 | GAME_ARGS("gameargs", String.class, ""),
29 | SETUP_VERSION("setup", String.class, null),
30 | SOURCE_VERSION("source", Integer.class, -1),
31 | TARGET_VERSION("target", Integer.class, -1),
32 | JAVA_HOME("javahome", String.class, ""),
33 | EXCLUDED_CLASSES("excludedclasses", String.class, ""),
34 | DECOMPILE_RESOURCES("resources", Boolean.class, false),
35 | GUESS_GENERICS("generics", Boolean.class, false),
36 | STRIP_GENERICS("stripgenerics", Boolean.class, false),
37 | OUTPUT_SRC("outputsrc", Boolean.class, true);
38 |
39 | public static final TaskParameter[] VALUES = TaskParameter.values();
40 |
41 | public final String name;
42 | public final Class> type;
43 | public final Object defaultValue;
44 |
45 | TaskParameter(String name, Class c, T value) {
46 | TaskParameterMap.nameToParamMap.put(name, this);
47 | this.name = name;
48 | this.type = c;
49 | this.defaultValue = value;
50 | }
51 |
52 | public String getDesc() {
53 | String s = "task.param." + name;
54 | if (MCP.TRANSLATOR.hasKey(s)) {
55 | return MCP.TRANSLATOR.translateKey(s);
56 | }
57 | return MCP.TRANSLATOR.translateKey("task.noDesc");
58 | }
59 |
60 | private static String getDefaultFFOptions() {
61 | Map ffOptions = new HashMap<>(IFernflowerPreferences.DEFAULTS);
62 | ffOptions.put(IFernflowerPreferences.NO_COMMENT_OUTPUT, "1");
63 | ffOptions.put(IFernflowerPreferences.REMOVE_BRIDGE, "0");
64 | ffOptions.put(IFernflowerPreferences.ASCII_STRING_CHARACTERS, "1");
65 | ffOptions.put(IFernflowerPreferences.OVERRIDE_ANNOTATION, "0");
66 | ffOptions.put(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1");
67 | ffOptions.put(IFernflowerPreferences.INDENT_STRING, "\t");
68 | ffOptions.remove(IFernflowerPreferences.BANNER);
69 | return ffOptions.toString();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tasks/mode/TaskParameterMap.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tasks.mode;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | public class TaskParameterMap {
7 | /**
8 | * Cached task parameter types
9 | */
10 | static final Map nameToParamMap = new HashMap<>();
11 |
12 | public static TaskParameter get(String param) {
13 | return nameToParamMap.get(param);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/ClassUtils.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools;
2 |
3 | import java.lang.reflect.Method;
4 | import java.lang.reflect.Modifier;
5 | import java.net.URL;
6 | import java.net.URLClassLoader;
7 | import java.nio.file.Path;
8 | import java.util.ArrayList;
9 | import java.util.Enumeration;
10 | import java.util.List;
11 | import java.util.jar.JarEntry;
12 | import java.util.jar.JarFile;
13 |
14 | public abstract class ClassUtils {
15 | @SuppressWarnings("unchecked")
16 | public static List> getClasses(Path p, Class type) throws Exception {
17 | String pathToJar = p.toAbsolutePath().toString();
18 | JarFile jarFile = new JarFile(pathToJar);
19 | Enumeration e = jarFile.entries();
20 |
21 | List> classes = new ArrayList<>();
22 | URL[] urls = {new URL("jar:file:" + pathToJar + "!/")};
23 | URLClassLoader cl = URLClassLoader.newInstance(urls);
24 |
25 | while (e.hasMoreElements()) {
26 | JarEntry je = e.nextElement();
27 | if (je.isDirectory() || !je.getName().endsWith(".class")) {
28 | continue;
29 | }
30 | // -6 because of .class
31 | String className = je.getName().substring(0, je.getName().length() - 6);
32 | className = className.replace('/', '.');
33 | Class> cls = cl.loadClass(className);
34 | if (type.isAssignableFrom(cls)) {
35 | classes.add((Class) cls);
36 | }
37 | }
38 | jarFile.close();
39 | return classes;
40 | }
41 |
42 | /**
43 | * {@link Modifier#isAbstract(int)} does no guarantee that all methods were implemented in the compiled class
44 | * And there is a chance it was compiled from a different source where one of the methods didn't exist
45 | */
46 | public static boolean isClassAbstract(Class> type) {
47 | for (Method meth : type.getMethods()) {
48 | if (Modifier.isAbstract(meth.getModifiers())) {
49 | return true;
50 | }
51 | }
52 | return false;
53 | }
54 |
55 | public static int getSourceFromClassVersion(int classVersion) {
56 | if (classVersion >= 45) {
57 | return classVersion - 44;
58 | }
59 | return -1;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/JSONUtil.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 | import java.util.ArrayList;
8 | import java.util.Collections;
9 | import java.util.HashMap;
10 | import java.util.List;
11 | import java.util.Map;
12 |
13 | import org.json.JSONArray;
14 | import org.json.JSONException;
15 | import org.json.JSONObject;
16 |
17 | public final class JSONUtil {
18 |
19 | public static Object[] getArray(JSONArray array) {
20 | Object[] objs = new Object[array.length()];
21 | for (int i = 0; i < array.length(); i++) {
22 | objs[i] = array.get(i);
23 | }
24 | return objs;
25 | }
26 |
27 | public static JSONObject getJSON(InputStream stream) {
28 | try {
29 | return parseJSON(stream);
30 | } catch (JSONException | IOException ex) {
31 | return null;
32 | }
33 | }
34 |
35 | public static List toList(JSONArray array) {
36 | if (array == null) {
37 | return Collections.emptyList();
38 | }
39 | List list = new ArrayList<>();
40 | for (int i = 0; i < array.length(); i++) {
41 | String s = array.optString(i, null);
42 | if (s == null) continue;
43 | list.add(s);
44 | }
45 | return list;
46 | }
47 |
48 | public static Map toMap(JSONObject object) {
49 | if (object == null) {
50 | return Collections.emptyMap();
51 | }
52 | Map map = new HashMap<>();
53 | for (String key : object.keySet()) {
54 | String value = object.optString(key, null);
55 | if (value == null) continue;
56 | try {
57 | int i = Integer.parseInt(key);
58 | map.put(i, value);
59 | } catch (NumberFormatException ignored) {
60 | }
61 | }
62 | return map;
63 | }
64 |
65 | public static JSONObject parseJSONFile(Path path) throws JSONException, IOException {
66 | String content = new String(Files.readAllBytes(path));
67 | return new JSONObject(content);
68 | }
69 |
70 | public static JSONObject parseJSON(InputStream stream) throws JSONException, IOException {
71 | byte[] bytes = Util.readAllBytes(stream);
72 | String content = new String(bytes);
73 | return new JSONObject(content);
74 | }
75 |
76 | public static JSONArray parseJSONArray(InputStream stream) throws JSONException, IOException {
77 | byte[] bytes = Util.readAllBytes(stream);
78 | String content = new String(bytes);
79 | return new JSONArray(content);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/OS.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools;
2 |
3 | import java.util.Locale;
4 |
5 | public enum OS {
6 |
7 | linux,
8 | windows,
9 | osx,
10 | unknown;
11 |
12 | public static OS os;
13 |
14 |
15 | public static OS getOs() {
16 | if (os == null) {
17 | String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT);
18 | if (osName.contains("mac") || osName.contains("darwin")) {
19 | os = osx;
20 | } else if (osName.contains("win")) {
21 | os = windows;
22 | } else if (osName.contains("nix") || osName.contains("nux")
23 | || osName.contains("aix") || osName.contains("sunos")) {
24 | os = linux;
25 | } else {
26 | os = unknown;
27 | }
28 | }
29 | return os;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/Util.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools;
2 |
3 | import java.awt.*;
4 | import java.awt.datatransfer.StringSelection;
5 | import java.io.BufferedInputStream;
6 | import java.io.BufferedReader;
7 | import java.io.ByteArrayOutputStream;
8 | import java.io.File;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.io.InputStreamReader;
12 | import java.net.URI;
13 | import java.net.URISyntaxException;
14 | import java.nio.file.Files;
15 | import java.nio.file.Path;
16 | import java.security.MessageDigest;
17 | import java.security.NoSuchAlgorithmException;
18 | import java.util.Objects;
19 | import java.util.concurrent.ExecutorService;
20 | import java.util.concurrent.Executors;
21 | import java.util.concurrent.Future;
22 |
23 | public abstract class Util {
24 | public static final ExecutorService SINGLE_THREAD_EXECUTOR = Executors.newSingleThreadExecutor();
25 |
26 | public static int runCommand(String[] cmd, Path dir, boolean killOnShutdown) throws IOException {
27 | ProcessBuilder procBuilder = new ProcessBuilder(cmd);
28 | if (dir != null) {
29 | procBuilder.directory(dir.toAbsolutePath().toFile());
30 | }
31 | Process proc = procBuilder.start();
32 | Thread hook = new Thread(proc::destroy);
33 | if (killOnShutdown) {
34 | Runtime.getRuntime().addShutdownHook(hook);
35 | }
36 | BufferedReader err = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
37 | BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
38 | Thread stderr = new Thread(()-> {
39 | String line;
40 | while (true) {
41 | try {
42 | line = err.readLine();
43 | if(line != null) {
44 | System.out.println("Minecraft STDERR: " + line);
45 | }
46 | } catch (IOException ignored) {
47 | // we don't really care what happens here
48 | }
49 | }
50 |
51 | });
52 | Thread stdout = new Thread(()-> {
53 | String line;
54 | while (true) {
55 | try {
56 | line = in.readLine();
57 | if(line != null) {
58 | System.out.println( line);
59 | }
60 | } catch (IOException ignored) {
61 | // we don't really care what happens here
62 | }
63 | }
64 |
65 | });
66 | try {
67 | proc.waitFor();
68 | } catch (InterruptedException e) {
69 | throw new RuntimeException("thread interrupted while runCommand was waiting for a process to finish", e );
70 | }
71 | in.close();
72 | err.close();
73 | stderr.interrupt();
74 | stdout.interrupt();
75 |
76 | if (killOnShutdown) {
77 | Runtime.getRuntime().removeShutdownHook(hook);
78 | }
79 | return proc.exitValue();
80 | }
81 |
82 | public static void runCommand(String[] cmd) throws IOException {
83 | ProcessBuilder procBuilder = new ProcessBuilder(cmd);
84 | procBuilder.start();
85 | }
86 |
87 | public static void copyToClipboard(String text) {
88 | Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(text), null);
89 | }
90 |
91 | public static Thread operateOnThread(Runnable function) {
92 | Thread thread = new Thread(function);
93 | thread.start();
94 | return thread;
95 | }
96 |
97 | public static Future> enqueueRunnable(Runnable function) {
98 | return SINGLE_THREAD_EXECUTOR.submit(function);
99 | }
100 |
101 | public static void openUrl(String url) {
102 | try {
103 | if (Objects.requireNonNull(OS.getOs()) == OS.linux) {
104 | new ProcessBuilder("/usr/bin/env", "xdg-open", url).start();
105 | } else {
106 | if (Desktop.isDesktopSupported()) {
107 | Desktop desktop = Desktop.getDesktop();
108 | desktop.browse(new URI(url));
109 | }
110 | }
111 | } catch (IOException ex) {
112 | throw new RuntimeException(ex);
113 | } catch (URISyntaxException ex) {
114 | throw new IllegalArgumentException(ex);
115 | }
116 | }
117 |
118 | public static byte[] readAllBytes(InputStream inputStream) throws IOException {
119 | final int bufLen = 4 * 0x400; // 4KB
120 | byte[] buf = new byte[bufLen];
121 | int readLen;
122 | IOException exception = null;
123 |
124 | try {
125 | try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
126 | while ((readLen = inputStream.read(buf, 0, bufLen)) != -1)
127 | outputStream.write(buf, 0, readLen);
128 |
129 | return outputStream.toByteArray();
130 | }
131 | } catch (IOException e) {
132 | exception = e;
133 | throw e;
134 | } finally {
135 | if (exception == null) inputStream.close();
136 | else try {
137 | inputStream.close();
138 | } catch (IOException e) {
139 | exception.addSuppressed(e);
140 | }
141 | }
142 | }
143 |
144 | public static String getMD5(Path file) throws IOException {
145 | try {
146 | MessageDigest md = MessageDigest.getInstance("MD5");
147 | byte[] digest = getDigest(md, file);
148 |
149 | StringBuilder sb = new StringBuilder();
150 | for (byte bite : digest) {
151 | sb.append(String.format("%02x", bite & 0xff));
152 | }
153 | return sb.toString();
154 | } catch (NoSuchAlgorithmException e) {
155 | throw new IOException(e);
156 | }
157 | }
158 |
159 | public static String getSHA1(Path file) throws IOException {
160 | try {
161 | MessageDigest md = MessageDigest.getInstance("SHA-1");
162 | byte[] digest = getDigest(md, file);
163 |
164 | StringBuilder sb = new StringBuilder();
165 | for (byte bite : digest) {
166 | sb.append(Integer.toString((bite & 255) + 256, 16).substring(1).toLowerCase());
167 | }
168 | return sb.toString();
169 | } catch (NoSuchAlgorithmException e) {
170 | throw new IOException(e);
171 | }
172 | }
173 |
174 | public static byte[] getDigest(MessageDigest md, Path file) {
175 | try (InputStream fs = Files.newInputStream(file)) {
176 | BufferedInputStream bs = new BufferedInputStream(fs);
177 | byte[] buffer = new byte[1024];
178 | int bytesRead;
179 |
180 | while ((bytesRead = bs.read(buffer, 0, buffer.length)) != -1) {
181 | md.update(buffer, 0, bytesRead);
182 | }
183 | return md.digest();
184 | } catch (IOException ex) {
185 | ex.printStackTrace();
186 | }
187 | return new byte[] {};
188 | }
189 |
190 | public static String firstUpperCase(String s) {
191 | if (s == null) {
192 | return null;
193 | }
194 | if (s.length() <= 1) {
195 | return s.toUpperCase();
196 | } else {
197 | return Character.toUpperCase(s.charAt(0)) + s.substring(1);
198 | }
199 | }
200 |
201 | public static String convertFromEscapedString(String s) {
202 | return s.replace("\\n", "\n").replace("\\t", "\t");
203 | }
204 |
205 | public static String convertToEscapedString(String s) {
206 | return s.replace("\n", "\\n").replace("\t", "\\t");
207 | }
208 |
209 | public static String getJava() {
210 | return System.getProperties().getProperty("java.home") + File.separator + "bin" + File.separator + "java";
211 | }
212 |
213 | public static int getJavaVersion() {
214 | String javaVersion = System.getProperty("java.version");
215 | String[] versionParts = javaVersion.split("\\.");
216 | int versionNumber = Integer.parseInt(versionParts[0]);
217 |
218 | if (versionNumber < 9) {
219 | versionNumber = Integer.parseInt(versionParts[1]);
220 | } else {
221 | versionNumber = Integer.parseInt(versionParts[0]);
222 | }
223 | return versionNumber;
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/fernflower/DecompileLogger.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.fernflower;
2 |
3 | import de.fernflower.main.extern.IFernflowerLogger;
4 | import org.mcphackers.mcp.MCP;
5 | import org.mcphackers.mcp.tasks.ProgressListener;
6 |
7 | public class DecompileLogger extends IFernflowerLogger {
8 |
9 | private final ProgressListener listener;
10 | private int total;
11 |
12 | public DecompileLogger(ProgressListener listener) {
13 | this.listener = listener;
14 | }
15 |
16 | @Override
17 | public void writeMessage(String message, Severity severity) {
18 | if(severity.ordinal() >= Severity.WARN.ordinal()) {
19 | // System.out.println(message);
20 | }
21 | }
22 |
23 | @Override
24 | public void writeMessage(String message, Throwable t) {
25 | }
26 |
27 | @Override
28 | public void startReadingClass(String className) {
29 | listener.setProgress(MCP.TRANSLATOR.translateKey("task.stage.decompile") + " " + className);
30 | }
31 |
32 | @Override
33 | public void startSave(int total) {
34 | this.total = total;
35 | }
36 |
37 | @Override
38 | public void updateSave(int current) {
39 | listener.setProgress((int) ((double) current / (double) total * 100));
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/fernflower/Decompiler.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.fernflower;
2 |
3 | import de.fernflower.main.decompiler.BaseDecompiler;
4 | import de.fernflower.main.decompiler.DirectoryResultSaver;
5 | import de.fernflower.main.extern.IBytecodeProvider;
6 | import de.fernflower.main.extern.IFernflowerPreferences;
7 | import de.fernflower.util.InterpreterUtil;
8 | import org.mcphackers.mcp.MCP;
9 | import org.mcphackers.mcp.tasks.ProgressListener;
10 | import org.mcphackers.mcp.tasks.mode.TaskParameter;
11 |
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.nio.file.Files;
15 | import java.nio.file.Path;
16 | import java.util.List;
17 | import java.util.Map;
18 | import java.util.zip.ZipEntry;
19 | import java.util.zip.ZipFile;
20 |
21 | public class Decompiler implements IBytecodeProvider {
22 | public final DecompileLogger log;
23 | private final Path source;
24 | private final List libraries;
25 | private final Path destination;
26 | private final Map mapOptions;
27 | private final ZipFileCache openZips = new ZipFileCache();
28 |
29 | public Decompiler(ProgressListener listener, Path source, Path out, List libs, MCP mcp) {
30 | this.source = source;
31 | this.libraries = libs;
32 | this.destination = out;
33 | this.log = new DecompileLogger(listener);
34 | this.mapOptions = mcp.getOptions().getFernflowerOptions();
35 | this.mapOptions.put(IFernflowerPreferences.REMOVE_BRIDGE, mcp.getOptions().getBooleanParameter(TaskParameter.GUESS_GENERICS) ? "1" : "0");
36 | }
37 |
38 | public void decompile() throws IOException {
39 | BaseDecompiler decompiler = new BaseDecompiler(this, new DirectoryResultSaver(destination.toFile()), mapOptions, log/*, javadocs.exists() ? new TinyJavadocProvider(javadocs) : null*/);
40 | for (Path lib : libraries) {
41 | if (Files.exists(lib))
42 | decompiler.addSpace(lib.toAbsolutePath().toFile(), false);
43 | }
44 | decompiler.addSpace(source.toAbsolutePath().toFile(), true);
45 | decompiler.decompileContext();
46 | this.openZips.close();
47 | }
48 |
49 | @Override
50 | public byte[] getBytecode(String externalPath, String internalPath) throws IOException {
51 | if (internalPath == null) {
52 | File file = new File(externalPath);
53 | return InterpreterUtil.getBytes(file);
54 | } else {
55 | final ZipFile archive = this.openZips.get(externalPath);
56 | final ZipEntry entry = archive.getEntry(internalPath);
57 | if (entry == null) {
58 | throw new IOException("Entry not found: " + internalPath);
59 | }
60 | return InterpreterUtil.getBytes(archive, entry);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/fernflower/SimpleJavadocProvider.java:
--------------------------------------------------------------------------------
1 | //package org.mcphackers.mcp.tools.fernflower;
2 | //
3 | //import de.fernflower.main.providers.IJavadocProvider;
4 | //import de.fernflower.struct.StructClass;
5 | //import de.fernflower.struct.StructField;
6 | //import de.fernflower.struct.StructMethod;
7 | //
8 | //import java.io.BufferedReader;
9 | //import java.io.File;
10 | //import java.io.FileReader;
11 | //import java.io.IOException;
12 | //import java.util.HashMap;
13 | //import java.util.Map;
14 | //
15 | //public class SimpleJavadocProvider implements IJavadocProvider {
16 | //
17 | // private static final Map classes = new HashMap<>();
18 | // private static final Map methods = new HashMap<>();
19 | // private static final Map fields = new HashMap<>();
20 | //
21 | // public SimpleJavadocProvider(File javadocFile) throws IOException {
22 | // readMappings(javadocFile);
23 | // }
24 | //
25 | // @Override
26 | // public String getClassDoc(StructClass structClass) {
27 | // return classes.get(structClass.qualifiedName.replace(".", "/"));
28 | // }
29 | //
30 | // @Override
31 | // public String getMethodDoc(StructClass structClass, StructMethod structMethod) {
32 | // return methods.get(structClass.qualifiedName.replace(".", "/") + "/" + structMethod.getName() + structMethod.getDescriptor());
33 | // }
34 | //
35 | //
36 | // @Override
37 | // public String getFieldDoc(StructClass structClass, StructField structField) {
38 | // return fields.get(structClass.qualifiedName.replace(".", "/") + "/" + structField.getName() + "(" + structField.getDescriptor() + ")");
39 | // }
40 | //
41 | // private static void readMappings(File inputFile) throws IOException {
42 | // try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {
43 | // for (String line = reader.readLine(); line != null; line = reader.readLine()) {
44 | // String[] splitLine = line.split("=");
45 | //
46 | // if (splitLine[0].startsWith("c ")) {
47 | // // Class Javadoc: c net/minecraft/client/Minecraft="Hello\nWorld"
48 | // classes.put(splitLine[0].replaceFirst("c ", ""), splitLine[1].replaceAll("\"", ""));
49 | // } else if (splitLine[0].startsWith("m ")) {
50 | // // Method Javadoc: m net/minecraft/client/Minecraft/runTick()V="Hello\nWorld"
51 | // methods.put(splitLine[0].replace("m ", ""), splitLine[1].replaceAll("\"", ""));
52 | // } else if (splitLine[0].startsWith("f ")) {
53 | // // Field Javadoc: f net/minecraft/client/Minecraft/fullscreen(Z)="Hello\nWorld"
54 | // fields.put(splitLine[0].replace("f ", ""), splitLine[1].replaceAll("\"", ""));
55 | // } else {
56 | // System.err.println("Invalid keyword with: " + line);
57 | // }
58 | // }
59 | // }
60 | // }
61 | //}
62 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/fernflower/TinyJavadocProvider.java:
--------------------------------------------------------------------------------
1 | //package org.mcphackers.mcp.tools.fernflower;
2 | //
3 | //import de.fernflower.main.providers.IJavadocProvider;
4 | //import de.fernflower.struct.StructClass;
5 | //import de.fernflower.struct.StructField;
6 | //import de.fernflower.struct.StructMethod;
7 | //import net.fabricmc.mappingio.MappingReader;
8 | //import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
9 | //import net.fabricmc.mappingio.tree.MappingTree;
10 | //import net.fabricmc.mappingio.tree.MemoryMappingTree;
11 | //import org.objectweb.asm.Opcodes;
12 | //
13 | //import java.io.BufferedReader;
14 | //import java.io.File;
15 | //import java.io.IOException;
16 | //import java.nio.file.Files;
17 | //import java.util.ArrayList;
18 | //import java.util.List;
19 | //
20 | //public class TinyJavadocProvider implements IJavadocProvider {
21 | // public final MappingTree mappingTree;
22 | //
23 | // public TinyJavadocProvider(File tinyFile) {
24 | // mappingTree = readMappings(tinyFile);
25 | // }
26 | //
27 | // @Override
28 | // public String getClassDoc(StructClass structClass) {
29 | // MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
30 | //
31 | // if (classMapping == null) {
32 | // return null;
33 | // }
34 | //
35 | // if (!isRecord(structClass)) {
36 | // return classMapping.getComment();
37 | // } else {
38 | // // A RECORD??? IN MY JAVA 8???
39 | // return null;
40 | // }
41 | // }
42 | //
43 | // @Override
44 | // public String getFieldDoc(StructClass structClass, StructField structField) {
45 | // // None static fields in records are handled in the class javadoc.
46 | // if (isRecord(structClass) && !isStatic(structField)) {
47 | // return null;
48 | // }
49 | //
50 | // MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
51 | //
52 | // if (classMapping == null) {
53 | // return null;
54 | // }
55 | //
56 | // MappingTree.FieldMapping fieldMapping = classMapping.getField(structField.getName(), structField.getDescriptor());
57 | //
58 | // return fieldMapping != null ? fieldMapping.getComment() : null;
59 | // }
60 | //
61 | // @Override
62 | // public String getMethodDoc(StructClass structClass, StructMethod structMethod) {
63 | // MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName);
64 | //
65 | // if (classMapping == null) {
66 | // return null;
67 | // }
68 | //
69 | // MappingTree.MethodMapping methodMapping = classMapping.getMethod(structMethod.getName(), structMethod.getDescriptor());
70 | //
71 | // if (methodMapping != null) {
72 | // List parts = new ArrayList<>();
73 | //
74 | // if (methodMapping.getComment() != null) {
75 | // parts.add(methodMapping.getComment());
76 | // }
77 | //
78 | // boolean addedParam = false;
79 | //
80 | // for (MappingTree.MethodArgMapping argMapping : methodMapping.getArgs()) {
81 | // String comment = argMapping.getComment();
82 | //
83 | // if (comment != null) {
84 | // if (!addedParam && methodMapping.getComment() != null) {
85 | // //Add a blank line before params when the method has a comment
86 | // parts.add("");
87 | // addedParam = true;
88 | // }
89 | //
90 | // parts.add(String.format("@param %s %s", argMapping.getName("named"), comment));
91 | // }
92 | // }
93 | //
94 | // if (parts.isEmpty()) {
95 | // return null;
96 | // }
97 | //
98 | // return String.join("\n", parts);
99 | // }
100 | //
101 | // return null;
102 | // }
103 | //
104 | // private static MappingTree readMappings(File input) throws RuntimeException {
105 | // try (BufferedReader reader = Files.newBufferedReader(input.toPath())) {
106 | // MemoryMappingTree mappingTree = new MemoryMappingTree();
107 | // MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, "named");
108 | // MappingReader.read(reader, nsSwitch);
109 | //
110 | // return mappingTree;
111 | // } catch (IOException e) {
112 | // throw new RuntimeException("Failed to read mappings", e);
113 | // }
114 | // }
115 | //
116 | // public static boolean isRecord(StructClass structClass) {
117 | // return (structClass.getAccessFlags() & Opcodes.ACC_RECORD) != 0;
118 | // }
119 | //
120 | // public static boolean isStatic(StructField structField) {
121 | // return (structField.getAccessFlags() & Opcodes.ACC_STATIC) != 0;
122 | // }
123 | //}
124 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/fernflower/ZipFileCache.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.fernflower;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.io.UncheckedIOException;
6 | import java.util.Map;
7 | import java.util.concurrent.ConcurrentHashMap;
8 | import java.util.zip.ZipFile;
9 |
10 | // Taken from VineFlower graciously
11 | public final class ZipFileCache implements AutoCloseable {
12 | private final Map files = new ConcurrentHashMap<>();
13 |
14 | public ZipFile get(final String path) throws IOException {
15 | try {
16 | return this.files.computeIfAbsent(path, pth -> {
17 | try {
18 | return new ZipFile(new File(pth));
19 | } catch (IOException ex) {
20 | throw new UncheckedIOException(ex);
21 | }
22 | });
23 | } catch (UncheckedIOException ex) {
24 | throw ex.getCause();
25 | }
26 | }
27 |
28 | @Override
29 | public void close() {
30 | IOException failure = null;
31 |
32 | for (Map.Entry entry : this.files.entrySet()) {
33 | try {
34 | entry.getValue().close();
35 | } catch (IOException ex) {
36 | if (failure == null) {
37 | failure = ex;
38 | } else {
39 | failure.addSuppressed(ex);
40 | }
41 | }
42 | }
43 |
44 | this.files.clear();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/mappings/MappingUtil.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.mappings;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.Collections;
10 | import java.util.HashMap;
11 | import java.util.List;
12 | import java.util.Map;
13 |
14 | import net.fabricmc.mappingio.MappingReader;
15 | import net.fabricmc.mappingio.MappingWriter;
16 | import net.fabricmc.mappingio.adapter.MappingNsRenamer;
17 | import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
18 | import net.fabricmc.mappingio.format.MappingFormat;
19 | import net.fabricmc.mappingio.tree.MemoryMappingTree;
20 |
21 | public final class MappingUtil {
22 |
23 | /**
24 | * @param number
25 | * @return Obfuscated string based on number index
26 | */
27 | public static String getObfuscatedName(int number) {
28 | // Default obfuscation scheme
29 | return getObfuscatedName('a', 'z', number);
30 | }
31 |
32 | /**
33 | * @param from
34 | * @param to
35 | * @param number
36 | * @return Obfuscated string based on number index and character range
37 | */
38 | public static String getObfuscatedName(char from, char to, int number) {
39 | if (number == 0) {
40 | return String.valueOf(from);
41 | }
42 | int num = number;
43 | int allChars = to - from + 1;
44 | StringBuilder retName = new StringBuilder();
45 | while (num >= 0) {
46 | char c = (char) (from + (num % allChars));
47 | retName.insert(0, c);
48 | num = num / allChars - 1;
49 | }
50 | return retName.toString();
51 | }
52 |
53 | /**
54 | * @param chars
55 | * @param number
56 | * @return Obfuscated string based on number index and character array
57 | */
58 | public static String getObfuscatedName(char[] chars, int number) {
59 | if (number == 0) {
60 | return String.valueOf(chars[0]);
61 | }
62 | int num = number;
63 | int allChars = chars.length;
64 | StringBuilder retName = new StringBuilder();
65 | while (num >= 0) {
66 | char c = chars[num % allChars];
67 | retName.insert(0, c);
68 | num = num / allChars - 1;
69 | }
70 | return retName.toString();
71 | }
72 |
73 | public static List readNamespaces(Path mappings) throws IOException {
74 | List namespaces = new ArrayList<>();
75 | boolean invalid = false;
76 | try (BufferedReader reader = Files.newBufferedReader(mappings)) {
77 | String header = reader.readLine();
78 | if (header != null) {
79 | if (header.startsWith("tiny\t2\t0\t")) {
80 | namespaces.addAll(Arrays.asList(header.substring(9).trim().split("\t")));
81 | } else if (header.startsWith("v1\t")) {
82 | namespaces.addAll(Arrays.asList(header.substring(3).trim().split("\t")));
83 | } else {
84 | invalid = true;
85 | }
86 | } else {
87 | invalid = true;
88 | }
89 | }
90 | if (invalid) {
91 | throw new IllegalStateException("No valid tiny header in " + mappings);
92 | }
93 | return namespaces;
94 | }
95 |
96 | //official named -> named client server
97 | public static void mergeMappings(Path client, Path server, Path out) throws IOException {
98 | MemoryMappingTree mergedMappingTree = new MemoryMappingTree();
99 |
100 | // Create visitors to flip the mapping tree
101 | Map clientNsRenames = Collections.singletonMap("official", "client");
102 | Map serverNsRenames = Collections.singletonMap("official", "server");
103 |
104 | MappingNsRenamer clientNsRenamer = new MappingNsRenamer(mergedMappingTree, clientNsRenames);
105 | MappingSourceNsSwitch clientNsSwitch = new MappingSourceNsSwitch(clientNsRenamer, "named");
106 | MappingNsRenamer serverNsRenamer = new MappingNsRenamer(mergedMappingTree, serverNsRenames);
107 | MappingSourceNsSwitch serverNsSwitch = new MappingSourceNsSwitch(serverNsRenamer, "named");
108 |
109 | // Read client/server mappings with the above visitors
110 | MappingReader.read(client, MappingFormat.TINY_2_FILE, clientNsSwitch);
111 | MappingReader.read(server, MappingFormat.TINY_2_FILE, serverNsSwitch);
112 |
113 | // Export the merged mapping trees
114 | mergedMappingTree.accept(MappingWriter.create(out, MappingFormat.TINY_2_FILE));
115 | }
116 |
117 | //official named -> named client
118 | public static void mergeMappings(Path client, Path out) throws IOException {
119 | String currentSrc = "official";
120 | String currentDst = "named";
121 | String newDst = "client";
122 | Map nsRenames = new HashMap<>();
123 | nsRenames.put(currentSrc, newDst);
124 |
125 | // Switch the namespaces from official -> named to named -> official
126 | // Rename the namespaces from named -> official to named -> client
127 | MappingNsRenamer nsRenamer = new MappingNsRenamer(MappingWriter.create(out, MappingFormat.TINY_2_FILE), nsRenames);
128 | MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(nsRenamer, currentDst);
129 | MappingReader.read(client, MappingFormat.TINY_2_FILE, nsSwitch);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/project/EclipseProjectWriter.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.project;
2 |
3 | import static org.mcphackers.mcp.MCPPaths.PROJECT;
4 |
5 | import java.io.IOException;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 |
9 | import org.mcphackers.mcp.MCP;
10 | import org.mcphackers.mcp.MCPPaths;
11 | import org.mcphackers.mcp.tasks.Task;
12 | import org.mcphackers.mcp.tasks.Task.Side;
13 | import org.mcphackers.mcp.tools.Util;
14 | import org.mcphackers.mcp.tools.project.eclipse.EclipseClasspath;
15 | import org.mcphackers.mcp.tools.project.eclipse.EclipsePreferences;
16 | import org.mcphackers.mcp.tools.project.eclipse.EclipseProject;
17 | import org.mcphackers.mcp.tools.project.eclipse.EclipseRunConfig;
18 | import org.mcphackers.mcp.tools.versions.json.DependDownload;
19 | import org.mcphackers.mcp.tools.versions.json.Version;
20 |
21 | public class EclipseProjectWriter implements ProjectWriter {
22 |
23 | @Override
24 | public void createProject(MCP mcp, Side side, int sourceVersion) throws IOException {
25 | Path proj = MCPPaths.get(mcp, PROJECT, side);
26 | Version version = mcp.getCurrentVersion();
27 | String clientArgs = ProjectWriter.getLaunchArgs(mcp, side);
28 | Task.Side[] launchSides = side == Task.Side.MERGED ? new Task.Side[]{Task.Side.CLIENT, Task.Side.SERVER} : new Task.Side[]{side};
29 |
30 | String projectName = "Minecraft " + (side == Task.Side.CLIENT ? "Client" : side == Task.Side.SERVER ? "Server" : side == Task.Side.MERGED ? "Merged" : "Project");
31 |
32 | try (XMLWriter writer = new XMLWriter(Files.newBufferedWriter(proj.resolve(".classpath")))) {
33 | EclipseClasspath classpath = new EclipseClasspath(mcp, projectName, sourceVersion);
34 | for (DependDownload dependency : version.libraries) {
35 | classpath.addDependency(dependency);
36 | }
37 | classpath.toXML(writer, side);
38 | }
39 |
40 | try (XMLWriter writer = new XMLWriter(Files.newBufferedWriter(proj.resolve(".project")))) {
41 | EclipseProject project = new EclipseProject(projectName);
42 | project.toXML(writer);
43 | }
44 |
45 | for (Task.Side launchSide : launchSides) {
46 | try (XMLWriter writer = new XMLWriter(Files.newBufferedWriter(proj.resolve(Util.firstUpperCase(launchSide.name) + ".launch")))) {
47 | EclipseRunConfig runConfig = new EclipseRunConfig(mcp);
48 | runConfig.setProjectName(projectName);
49 | runConfig.setLaunchSide(launchSide);
50 | runConfig.setClientArgs(clientArgs);
51 | runConfig.toXML(writer);
52 | }
53 | }
54 |
55 | Path settings = proj.resolve(".settings");
56 | Files.createDirectories(settings);
57 |
58 | String sourceVer = sourceVersion >= 9 ? String.valueOf(sourceVersion) : "1." + sourceVersion;
59 |
60 | try (PairWriter writer = new PairWriter(Files.newBufferedWriter(settings.resolve("org.eclipse.jdt.core.prefs")))) {
61 | EclipsePreferences preferences = new EclipsePreferences();
62 | preferences.setSourceVersion(sourceVer);
63 | preferences.toString(writer);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/project/PairWriter.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.project;
2 |
3 | import java.io.BufferedWriter;
4 | import java.io.IOException;
5 | import java.io.Writer;
6 |
7 | public class PairWriter extends BufferedWriter {
8 | public PairWriter(Writer out) {
9 | super(out);
10 | }
11 |
12 | public void writePair(String key, String value) throws IOException {
13 | this.write(key + "=" + value + System.lineSeparator());
14 | }
15 |
16 | public void writePair(String key, int value) throws IOException {
17 | this.write(key + "=" + value + System.lineSeparator());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/project/ProjectWriter.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.project;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 |
6 | import org.mcphackers.mcp.MCP;
7 | import org.mcphackers.mcp.tasks.Task.Side;
8 | import org.mcphackers.mcp.tasks.TaskRun;
9 |
10 | public interface ProjectWriter {
11 | static String getLaunchArgs(MCP mcp, Side side) {
12 | List args = TaskRun.getLaunchArgs(mcp, side);
13 | for (int i = 0; i < args.size(); i++) {
14 | String arg = args.get(i);
15 | if (arg.contains(" ")) {
16 | arg = "\"" + arg + "\"";
17 | }
18 | args.set(i, arg);
19 | }
20 | return String.join(" ", args).replace("\"", """);
21 | }
22 |
23 | void createProject(MCP mcp, Side side, int sourceVersion) throws IOException;
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/project/VSCProjectWriter.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.project;
2 |
3 | import static org.mcphackers.mcp.MCPPaths.*;
4 |
5 | import java.io.BufferedWriter;
6 | import java.io.IOException;
7 | import java.nio.file.Files;
8 | import java.nio.file.Path;
9 | import java.util.List;
10 |
11 | import org.json.JSONArray;
12 | import org.json.JSONObject;
13 | import org.mcphackers.mcp.MCP;
14 | import org.mcphackers.mcp.MCPPaths;
15 | import org.mcphackers.mcp.tasks.Task;
16 | import org.mcphackers.mcp.tasks.Task.Side;
17 | import org.mcphackers.mcp.tasks.TaskRun;
18 |
19 | public class VSCProjectWriter implements ProjectWriter {
20 |
21 | @Override
22 | public void createProject(MCP mcp, Side side, int sourceVersion) throws IOException {
23 | Path proj = MCPPaths.get(mcp, PROJECT, side);
24 | Task.Side[] launchSides = side == Task.Side.MERGED ? new Task.Side[]{Task.Side.CLIENT, Task.Side.SERVER} : new Task.Side[]{side};
25 |
26 | String projectName = "Minecraft " + (side == Task.Side.CLIENT ? "Client" : side == Task.Side.SERVER ? "Server" : side == Task.Side.MERGED ? "Merged" : "Project");
27 |
28 | Files.createDirectories(proj.resolve(".vscode"));
29 | try (BufferedWriter writer = Files.newBufferedWriter(proj.resolve(".vscode/settings.json"))) {
30 | JSONObject settingsJson = new JSONObject();
31 | JSONObject searchExclude = new JSONObject();
32 | searchExclude.put("src_original/**", true);
33 | searchExclude.put("bin/**", true);
34 | searchExclude.put("output/**", true);
35 | settingsJson.put("search.exclude", searchExclude);
36 | settingsJson.write(writer);
37 | }
38 | try (BufferedWriter writer = Files.newBufferedWriter(proj.resolve(".vscode/launch.json"))) {
39 | JSONObject launchJson = new JSONObject();
40 | launchJson.put("version", "0.2.0");
41 | JSONArray configurations = new JSONArray();
42 | for (Task.Side launchSide : launchSides) {
43 | JSONObject config = new JSONObject();
44 | config.put("type", "java");
45 | config.put("name", projectName);
46 | config.put("request", "launch");
47 | config.put("mainClass", TaskRun.getMain(mcp, mcp.getCurrentVersion(), launchSide));
48 | config.put("vmArgs", "-Djava.library.path=${workspaceFolder}/../libraries/natives");
49 | config.put("projectName", projectName);
50 | List args = TaskRun.getLaunchArgs(mcp, launchSide);
51 | JSONArray arguments = new JSONArray();
52 | for(String arg : args) {
53 | arguments.put(arg);
54 | }
55 | config.put("args", arguments);
56 | configurations.put(config);
57 | }
58 | launchJson.put("configurations", configurations);
59 | launchJson.write(writer);
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/project/XMLWriter.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.project;
2 |
3 | import java.io.BufferedWriter;
4 | import java.io.IOException;
5 |
6 | public class XMLWriter extends BufferedWriter {
7 |
8 | private int indent;
9 |
10 | public XMLWriter(BufferedWriter writer) {
11 | super(writer);
12 | }
13 |
14 | private void appendInd(StringBuilder s) {
15 | for (int i = 0; i < indent; i++) {
16 | s.append("\t");
17 | }
18 | }
19 |
20 | public void writeln(String s) throws IOException {
21 | StringBuilder b = new StringBuilder();
22 | appendInd(b);
23 | b.append(s);
24 | super.write(b.toString());
25 | newLine();
26 | }
27 |
28 | public void writeAttribute(String attribute) throws IOException {
29 | writeln("<" + attribute + "/>");
30 | }
31 |
32 | public void startAttribute(String attribute) throws IOException {
33 | writeln("<" + attribute + ">");
34 | indent++;
35 | }
36 |
37 | public void writeSelfEndingAttribute(String attribute) throws IOException {
38 | writeln("<" + attribute + " />");
39 | }
40 |
41 | public void closeAttribute(String attribute) throws IOException {
42 | indent--;
43 | writeln("" + attribute + ">");
44 | }
45 |
46 | public void stringAttribute(String attribute, String value) throws IOException {
47 | writeln("<" + attribute + ">" + value + "" + attribute + ">");
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/project/eclipse/EclipseClasspath.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.project.eclipse;
2 |
3 | import org.mcphackers.mcp.MCP;
4 | import org.mcphackers.mcp.MCPPaths;
5 | import org.mcphackers.mcp.tasks.Task;
6 | import org.mcphackers.mcp.tasks.Task.Side;
7 | import org.mcphackers.mcp.tools.project.XMLWriter;
8 | import org.mcphackers.mcp.tools.versions.json.DependDownload;
9 | import org.mcphackers.mcp.tools.versions.json.Rule;
10 |
11 | import java.io.IOException;
12 | import java.nio.file.Files;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | public class EclipseClasspath {
17 | private final MCP mcp;
18 | private final String projectName;
19 | private final int javaVersion;
20 | private final List dependencies = new ArrayList<>();
21 |
22 | private final static String[] VM_TYPES = {
23 | "J2SE-1.5",
24 | "JavaSE-1.6",
25 | "JavaSE-1.7",
26 | "JavaSE-1.8"
27 | };
28 |
29 | private String getStandardVMType() {
30 | if(javaVersion >= 5 && javaVersion <= 8) {
31 | return VM_TYPES[javaVersion - 5];
32 | }
33 | return "JavaSE-" + javaVersion;
34 | }
35 |
36 | public EclipseClasspath(MCP mcp, String projectName, int javaVersion) {
37 | this.mcp = mcp;
38 | this.projectName = projectName;
39 | this.javaVersion = javaVersion;
40 | }
41 |
42 | public void addDependency(DependDownload dependency) {
43 | this.dependencies.add(dependency);
44 | }
45 |
46 | public void toXML(XMLWriter writer, Task.Side side) throws IOException {
47 | writer.writeln("");
48 | writer.startAttribute("classpath");
49 | writer.startAttribute("classpathentry kind=\"src\" path=\"src\"");
50 | writer.startAttribute("attributes");
51 | writer.writeAttribute("attribute name=\"org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY\" value=\"" + projectName + "/libraries/natives\"");
52 | writer.closeAttribute("attributes");
53 | writer.closeAttribute("classpathentry");
54 | // TODO: Implement proper client/server side libraries
55 | if (!side.equals(Side.SERVER)) {
56 | for (DependDownload dependencyDownload : this.dependencies) {
57 | if (Rule.apply(dependencyDownload.rules)) {
58 | String lib = dependencyDownload.getArtifactPath(null);
59 | if(lib == null) {
60 | continue;
61 | }
62 | String src = dependencyDownload.getArtifactPath("sources");
63 | if (Files.exists(MCPPaths.get(mcp, "libraries/" + lib))) {
64 | if (src != null) {
65 | writer.writeAttribute("classpathentry kind=\"lib\" path=\"libraries/" + lib + "\" sourcepath=\"libraries/" + src + "\"");
66 | } else {
67 | writer.writeAttribute("classpathentry kind=\"lib\" path=\"libraries/" + lib + "\"");
68 | }
69 | }
70 | }
71 | }
72 | }
73 | writer.writeAttribute("classpathentry kind=\"lib\" path=\"jars/deobfuscated.jar\" sourcepath=\"jars/deobfuscated-source.jar\"");
74 | writer.writeAttribute("classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/" + getStandardVMType() + "\"");
75 | writer.writeAttribute("classpathentry kind=\"output\" path=\"output\"");
76 | writer.closeAttribute("classpath");
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/project/eclipse/EclipsePreferences.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.project.eclipse;
2 |
3 | import org.mcphackers.mcp.tools.project.PairWriter;
4 |
5 | import java.io.IOException;
6 |
7 | public class EclipsePreferences {
8 | private String sourceVer;
9 |
10 | public void setSourceVersion(String sourceVer) {
11 | this.sourceVer = sourceVer;
12 | }
13 |
14 | public String getSourceVer() {
15 | return this.sourceVer;
16 | }
17 |
18 | public void toString(PairWriter writer) throws IOException {
19 | writer.writePair("eclipse.preferences.version", 1);
20 | writer.writePair("org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode", "enabled");
21 | writer.writePair("org.eclipse.jdt.core.compiler.codegen.methodParameters", "do not generate");
22 | writer.writePair("org.eclipse.jdt.core.compiler.codegen.targetPlatform", this.getSourceVer());
23 | writer.writePair("org.eclipse.jdt.core.compiler.codegen.unusedLocal", "preserve");
24 | writer.writePair("org.eclipse.jdt.core.compiler.compliance", this.getSourceVer());
25 | writer.writePair("org.eclipse.jdt.core.compiler.debug.lineNumber", "generate");
26 | writer.writePair("org.eclipse.jdt.core.compiler.debug.localVariable", "generate");
27 | writer.writePair("org.eclipse.jdt.core.compiler.debug.sourceFile", "generate");
28 | writer.writePair("org.eclipse.jdt.core.compiler.problem.assertIdentifier", "error");
29 | writer.writePair("org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures", "disabled");
30 | writer.writePair("org.eclipse.jdt.core.compiler.problem.enumIdentifier", "error");
31 | writer.writePair("org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures", "warning");
32 | writer.writePair("org.eclipse.jdt.core.compiler.release", "disabled");
33 | writer.writePair("org.eclipse.jdt.core.compiler.source", this.getSourceVer());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/project/eclipse/EclipseProject.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.project.eclipse;
2 |
3 | import org.mcphackers.mcp.tools.project.XMLWriter;
4 |
5 | import java.io.IOException;
6 | import java.util.Random;
7 |
8 | public class EclipseProject {
9 | private final String projectName;
10 |
11 | public EclipseProject(String projectName) {
12 | this.projectName = projectName;
13 | }
14 |
15 | public void toXML(XMLWriter writer) throws IOException {
16 | writer.writeln("");
17 | writer.startAttribute("projectDescription");
18 | writer.stringAttribute("name", this.projectName);
19 | writer.stringAttribute("comment", "");
20 | writer.startAttribute("projects");
21 | writer.closeAttribute("projects");
22 | writer.startAttribute("buildSpec");
23 | writer.startAttribute("buildCommand");
24 | writer.stringAttribute("name", "org.eclipse.jdt.core.javabuilder");
25 | writer.startAttribute("arguments");
26 | writer.closeAttribute("arguments");
27 | writer.closeAttribute("buildCommand");
28 | writer.closeAttribute("buildSpec");
29 | writer.startAttribute("natures");
30 | writer.stringAttribute("nature", "org.eclipse.jdt.core.javanature");
31 | writer.closeAttribute("natures");
32 | writer.startAttribute("linkedResources");
33 | writer.startAttribute("link");
34 | writer.stringAttribute("name", "libraries");
35 | writer.stringAttribute("type", "2");
36 | writer.stringAttribute("locationURI", "$%7BPARENT-1-PROJECT_LOC%7D/libraries");
37 | writer.closeAttribute("link");
38 | writer.closeAttribute("linkedResources");
39 | // Filter out src and jars
40 | long id = new Random().nextLong();
41 | writer.startAttribute("filteredResources");
42 | // Broken for VSCode even though it's supposedly eclipse-project compatible
43 | // String[] matches = {"src", "jars", "source"};
44 | // for (String match : matches) {
45 | // writer.startAttribute("filter");
46 | // writer.stringAttribute("id", Long.toString(id++));
47 | // writer.stringAttribute("name", "");
48 | // writer.stringAttribute("type", "9");
49 | // writer.startAttribute("matcher");
50 | // writer.stringAttribute("id", "org.eclipse.ui.ide.multiFilter");
51 | // writer.stringAttribute("arguments", "1.0-name-matches-false-false-" + match);
52 | // writer.closeAttribute("matcher");
53 | // writer.closeAttribute("filter");
54 | // }
55 | writer.closeAttribute("filteredResources");
56 | writer.closeAttribute("projectDescription");
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/project/eclipse/EclipseRunConfig.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.project.eclipse;
2 |
3 | import org.mcphackers.mcp.MCP;
4 | import org.mcphackers.mcp.tasks.Task;
5 | import org.mcphackers.mcp.tasks.TaskRun;
6 | import org.mcphackers.mcp.tools.project.XMLWriter;
7 |
8 | import java.io.IOException;
9 |
10 | public class EclipseRunConfig {
11 | private final MCP mcp;
12 | private String projectName;
13 | private Task.Side launchSide;
14 | private String clientArgs;
15 |
16 | public EclipseRunConfig(MCP mcp) {
17 | this.mcp = mcp;
18 | }
19 |
20 | public void setProjectName(String projectName) {
21 | this.projectName = projectName;
22 | }
23 |
24 | public String getProjectName() {
25 | return this.projectName;
26 | }
27 |
28 | public void setLaunchSide(Task.Side launchSide) {
29 | this.launchSide = launchSide;
30 | }
31 |
32 | public Task.Side getLaunchSide() {
33 | return this.launchSide;
34 | }
35 |
36 | public void setClientArgs(String clientArgs) {
37 | this.clientArgs = clientArgs;
38 | }
39 |
40 | public String getClientArgs() {
41 | return this.clientArgs;
42 | }
43 |
44 | public void toXML(XMLWriter writer) throws IOException {
45 | writer.writeln("");
46 | writer.startAttribute("launchConfiguration type=\"org.eclipse.jdt.launching.localJavaApplication\"");
47 | writer.startAttribute("listAttribute key=\"org.eclipse.debug.core.MAPPED_RESOURCE_PATHS\"");
48 | writer.writeAttribute("listEntry value=\"/" + this.getProjectName() + "\"");
49 | writer.closeAttribute("listAttribute");
50 | writer.startAttribute("listAttribute key=\"org.eclipse.debug.core.MAPPED_RESOURCE_TYPES\"");
51 | writer.writeAttribute("listEntry value=\"4\"");
52 | writer.closeAttribute("listAttribute");
53 | writer.startAttribute("listAttribute key=\"org.eclipse.debug.ui.favoriteGroups\"");
54 | writer.writeAttribute("listEntry value=\"org.eclipse.debug.ui.launchGroup.run\"");
55 | writer.writeAttribute("listEntry value=\"org.eclipse.debug.ui.launchGroup.debug\"");
56 | writer.closeAttribute("listAttribute");
57 | writer.writeAttribute("booleanAttribute key=\"org.eclipse.jdt.launching.ATTR_ATTR_USE_ARGFILE\" value=\"false\"");
58 | writer.writeAttribute("booleanAttribute key=\"org.eclipse.jdt.launching.ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES\" value=\"true\"");
59 | writer.writeAttribute("booleanAttribute key=\"org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD\" value=\"true\"");
60 | writer.writeAttribute("stringAttribute key=\"org.eclipse.jdt.launching.MAIN_TYPE\" value=\"" + TaskRun.getMain(mcp, mcp.getCurrentVersion(), this.getLaunchSide()) + "\"");
61 | writer.writeAttribute("stringAttribute key=\"org.eclipse.jdt.launching.MODULE_NAME\" value=\"" + this.getProjectName() + "\"");
62 | if (launchSide == Task.Side.CLIENT) {
63 | writer.writeAttribute("stringAttribute key=\"org.eclipse.jdt.launching.PROGRAM_ARGUMENTS\" value=\"" + this.getClientArgs() + "\"");
64 | }
65 | writer.writeAttribute("stringAttribute key=\"org.eclipse.jdt.launching.PROJECT_ATTR\" value=\"" + this.getProjectName() + "\"");
66 | writer.closeAttribute("launchConfiguration");
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/source/Source.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.source;
2 |
3 | import java.io.IOException;
4 | import java.nio.charset.StandardCharsets;
5 | import java.nio.file.FileVisitResult;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.nio.file.SimpleFileVisitor;
9 | import java.nio.file.attribute.BasicFileAttributes;
10 | import java.util.ArrayList;
11 | import java.util.Comparator;
12 | import java.util.List;
13 | import java.util.Set;
14 | import java.util.Stack;
15 | import java.util.function.Function;
16 | import java.util.regex.MatchResult;
17 | import java.util.regex.Matcher;
18 | import java.util.regex.Pattern;
19 |
20 | public abstract class Source {
21 |
22 | public static final Pattern PACKAGE = Pattern.compile("package ([.*\\w]+);(\\r|)\\n");
23 | public static final Pattern IMPORT = Pattern.compile("import ([.*\\w]+);((\\r|)\\n)+");
24 |
25 | public static void modify(Path src, List extends Source> modify) throws IOException {
26 | Files.walkFileTree(src, new SimpleFileVisitor() {
27 | @Override
28 | public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
29 | if (!file.toString().endsWith(".java")) {
30 | return FileVisitResult.CONTINUE;
31 | }
32 | String className = file.toString().substring(0, file.toString().length() - 5);
33 | StringBuilder source = new StringBuilder(new String(Files.readAllBytes(file), StandardCharsets.UTF_8));
34 | for (Source srcModify : modify) {
35 | srcModify.apply(className, source);
36 | }
37 | Files.write(file, source.toString().getBytes());
38 | return FileVisitResult.CONTINUE;
39 | }
40 | });
41 | }
42 |
43 | private static boolean addMatch(StringBuilder source, Pattern pattern, String stringToAdd, boolean after, boolean onlyFirst) {
44 | Stack results = new Stack<>();
45 | String sourceString = source.toString();
46 | Matcher matcher = pattern.matcher(sourceString);
47 |
48 | while (matcher.find()) {
49 | MatchResult match = matcher.toMatchResult();
50 | results.push(match);
51 | if (onlyFirst) {
52 | break;
53 | }
54 | }
55 | boolean result = !results.isEmpty();
56 | while (!results.isEmpty()) {
57 | MatchResult match = results.pop();
58 | if (after && match.end() >= 0) {
59 | source.insert(match.end(), stringToAdd);
60 | } else if (!after && match.start() >= 0) {
61 | source.insert(match.start(), stringToAdd);
62 | }
63 | }
64 | return result;
65 | }
66 |
67 | public abstract void apply(String className, StringBuilder source);
68 |
69 | protected void updateImports(StringBuilder source, Set imports) {
70 | replaceTextOfMatchGroup(source, IMPORT, match -> {
71 | // Add import to the set
72 | imports.add(match.group(1));
73 | // Remove import from source
74 | return "";
75 | });
76 | List importsList = new ArrayList<>(imports);
77 | importsList.sort(Comparator.naturalOrder());
78 | StringBuilder sb = new StringBuilder();
79 | String n = System.lineSeparator();
80 | String lastPkg = "";
81 | for (String imp : importsList) {
82 | int dot = imp.indexOf('.');
83 | String pkg = dot == -1 ? imp : imp.substring(0, dot);
84 | if (!pkg.equals(lastPkg)) {
85 | sb.append(n);
86 | }
87 | lastPkg = pkg;
88 | sb.append("import ").append(imp).append(";").append(n);
89 | }
90 | // Re-add all imports
91 | String importsString = sb.toString();
92 | if (!addAfterFirstMatch(source, PACKAGE, importsString)) {
93 | source.insert(0, importsString);
94 | }
95 | }
96 |
97 | protected String replaceTextOfMatchGroup(String source, Pattern pattern, Function replaceStrategy) {
98 | StringBuilder sb = new StringBuilder(source);
99 | replaceTextOfMatchGroup(sb, pattern, replaceStrategy);
100 | return sb.toString();
101 | }
102 |
103 | protected void replaceTextOfMatchGroup(StringBuilder source, Pattern pattern, Function replaceStrategy) {
104 | Stack results = new Stack<>();
105 | String sourceString = source.toString();
106 | Matcher matcher = pattern.matcher(sourceString);
107 |
108 | while (matcher.find()) {
109 | results.push(matcher.toMatchResult());
110 | }
111 | while (!results.isEmpty()) {
112 | MatchResult match = results.pop();
113 | if (match.start() >= 0 && match.end() >= 0) {
114 | source.replace(match.start(), match.end(), replaceStrategy.apply(match));
115 | }
116 | }
117 | }
118 |
119 | protected boolean addAfterFirstMatch(StringBuilder source, Pattern pattern, String stringToAdd) {
120 | return addMatch(source, pattern, stringToAdd, true, true);
121 | }
122 |
123 | protected boolean addBeforeFirstMatch(StringBuilder source, Pattern pattern, String stringToAdd) {
124 | return addMatch(source, pattern, stringToAdd, false, true);
125 | }
126 |
127 | protected boolean addAfterMatch(StringBuilder source, Pattern pattern, String stringToAdd) {
128 | return addMatch(source, pattern, stringToAdd, true, false);
129 | }
130 |
131 | protected boolean addBeforeMatch(StringBuilder source, Pattern pattern, String stringToAdd) {
132 | return addMatch(source, pattern, stringToAdd, false, false);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/DownloadData.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions;
2 |
3 | import java.io.IOException;
4 | import java.net.URL;
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.Map.Entry;
10 |
11 | import org.json.JSONObject;
12 | import org.mcphackers.mcp.DownloadListener;
13 | import org.mcphackers.mcp.MCP;
14 | import org.mcphackers.mcp.MCPPaths;
15 | import org.mcphackers.mcp.tasks.Task.Side;
16 | import org.mcphackers.mcp.tools.FileUtil;
17 | import org.mcphackers.mcp.tools.Util;
18 | import org.mcphackers.mcp.tools.versions.json.AssetIndex;
19 | import org.mcphackers.mcp.tools.versions.json.AssetIndex.Asset;
20 | import org.mcphackers.mcp.tools.versions.json.DependDownload;
21 | import org.mcphackers.mcp.tools.versions.json.Rule;
22 | import org.mcphackers.mcp.tools.versions.json.Version;
23 |
24 | public class DownloadData {
25 |
26 | private final Path gameDir;
27 | public int totalSize;
28 | protected List downloadQueue = new ArrayList<>();
29 | protected AssetIndex assets;
30 |
31 | public DownloadData(MCP mcp, Version version) {
32 | this(MCPPaths.get(mcp, MCPPaths.LIB), MCPPaths.get(mcp, MCPPaths.JARS), MCPPaths.get(mcp, MCPPaths.JAR_ORIGINAL, Side.CLIENT), MCPPaths.get(mcp, MCPPaths.JAR_ORIGINAL, Side.SERVER), version);
33 | }
34 |
35 | public DownloadData(Path libraries, Path gameDir, Path client, Path server, Version version) {
36 | this.gameDir = gameDir;
37 | queueDownload(version.downloads.artifacts.get("client"), client);
38 | queueDownload(version.downloads.artifacts.get("server"), server);
39 | for (DependDownload dependencyDownload : version.libraries) {
40 | if (Rule.apply(dependencyDownload.rules)) {
41 | queueDownload(dependencyDownload.getDownload(null), libraries);
42 | queueDownload(dependencyDownload.getDownload(dependencyDownload.getNatives()), libraries);
43 | queueDownload(dependencyDownload.getDownload("sources"), libraries);
44 | }
45 | }
46 | try {
47 | Path assetIndex = gameDir.resolve("assets/indexes/" + version.assets + ".json");
48 | String assetIndexString;
49 | if (!Files.exists(assetIndex) || !version.assetIndex.sha1.equals(Util.getSHA1(assetIndex))) {
50 | assetIndexString = new String(Util.readAllBytes(FileUtil.openURLStream(new URL(version.assetIndex.url))));
51 | Files.write(assetIndex, assetIndexString.getBytes());
52 | } else {
53 | assetIndexString = new String(Files.readAllBytes(assetIndex));
54 | }
55 | setAssets(AssetIndex.from(new JSONObject(assetIndexString)));
56 | } catch (IOException ignored) {
57 | }
58 | }
59 |
60 | public static List getLibraries(Version version) {
61 | List retList = new ArrayList<>();
62 | for (DependDownload dependencyDownload : version.libraries) {
63 | if (Rule.apply(dependencyDownload.rules)) {
64 | String lib = dependencyDownload.getArtifactPath(null);
65 | retList.add(lib);
66 | }
67 | }
68 | return retList;
69 | }
70 |
71 | public static List getLibraries(Path libDir, Version version) {
72 | List retList = new ArrayList<>();
73 | for (DependDownload dependencyDownload : version.libraries) {
74 | if (Rule.apply(dependencyDownload.rules)) {
75 | String lib = dependencyDownload.getArtifactPath(null);
76 | if(lib == null) {
77 | continue;
78 | }
79 | retList.add((libDir.resolve(lib)));
80 | }
81 | }
82 | return retList;
83 | }
84 |
85 | public static List getNatives(Path libDir, Version version) {
86 | List retList = new ArrayList<>();
87 | for (DependDownload dependencyDownload : version.libraries) {
88 | if (Rule.apply(dependencyDownload.rules)) {
89 | String natives = dependencyDownload.getNatives();
90 | if (natives != null) {
91 | String lib = dependencyDownload.getArtifactPath(natives);
92 | if(lib == null) {
93 | continue;
94 | }
95 | retList.add(libDir.resolve(lib));
96 | }
97 | }
98 | }
99 | return retList;
100 | }
101 |
102 | public void setAssets(AssetIndex assets) {
103 | if (this.assets != null) {
104 | return;
105 | }
106 | this.assets = assets;
107 | Path dir = gameDir.resolve(assets.map_to_resources ? "resources/" : "assets/objects/");
108 | for (Entry entry : assets.objects.entrySet()) {
109 | queueDownload(entry.getValue(), dir);
110 | }
111 | }
112 |
113 | public void queueDownload(IDownload dl, Path baseDir) {
114 | if (dl == null) {
115 | return;
116 | }
117 | totalSize += dl.downloadSize();
118 | downloadQueue.add(new Download(dl, baseDir));
119 | }
120 |
121 | public void performDownload(DownloadListener listener) throws IOException {
122 | for (Download dl : downloadQueue) {
123 | IDownload download = dl.download;
124 | Path baseDir = dl.dir;
125 | String path = download.downloadPath();
126 | // if downloadPath is null then baseDir is the location of downloaded file.
127 | Path file = path == null ? baseDir : baseDir.resolve(path);
128 | listener.notify(dl.download, totalSize);
129 | if (!Files.exists(file) || (download.verify() && !download.downloadHash().equals(Util.getSHA1(file)))) {
130 | Path parent = file.getParent();
131 | if (parent != null) Files.createDirectories(parent);
132 | FileUtil.downloadFile(download.downloadURL(), file);
133 | }
134 | }
135 | }
136 |
137 | private static class Download {
138 | IDownload download;
139 | Path dir;
140 |
141 | public Download(IDownload dl, Path path) {
142 | download = dl;
143 | dir = path;
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/IDownload.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions;
2 |
3 | public interface IDownload {
4 |
5 | String downloadPath();
6 |
7 | String downloadURL();
8 |
9 | long downloadSize();
10 |
11 | String downloadHash();
12 |
13 | boolean verify();
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/VersionParser.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions;
2 |
3 | import java.io.InputStream;
4 | import java.net.URL;
5 | import java.net.URLConnection;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.nio.file.Paths;
9 | import java.time.Instant;
10 | import java.time.format.DateTimeFormatter;
11 | import java.util.ArrayList;
12 | import java.util.Comparator;
13 | import java.util.List;
14 |
15 | import org.json.JSONArray;
16 | import org.json.JSONObject;
17 | import org.mcphackers.mcp.tools.JSONUtil;
18 | import org.mcphackers.mcp.tools.versions.json.VersionMetadata;
19 |
20 | public class VersionParser {
21 |
22 | public static String mappingsJson = "https://mcphackers.github.io/versionsV2/versions.json";
23 | private static VersionParser INSTANCE;
24 |
25 | private final List versions = new ArrayList<>();
26 | public Exception failureCause;
27 |
28 | protected VersionParser() {
29 | JSONArray json;
30 | try {
31 | json = getJson();
32 | } catch (Exception e) {
33 | failureCause = e;
34 | return; // Couldn't init json
35 | }
36 | for (Object j : json) {
37 | if (!(j instanceof JSONObject)) {
38 | continue;
39 | }
40 | try {
41 | VersionData data = VersionData.from((JSONObject) j);
42 | versions.add(data);
43 | } catch (Exception e) {
44 | // Catching exception will skip to the next version
45 | e.printStackTrace();
46 | }
47 | }
48 | versions.sort(new VersionSorter());
49 | INSTANCE = this;
50 | }
51 |
52 | public static VersionParser getInstance() {
53 | if (INSTANCE == null) {
54 | return new VersionParser();
55 | }
56 | return INSTANCE;
57 | }
58 |
59 | private static JSONArray getJson() throws Exception {
60 | InputStream in;
61 | Path versions = Paths.get("versions.json");
62 | if (Files.exists(versions)) {
63 | in = Files.newInputStream(versions);
64 | } else {
65 | URLConnection connect = new URL(mappingsJson).openConnection();
66 | connect.setConnectTimeout(30000);
67 | in = connect.getInputStream();
68 | }
69 | return JSONUtil.parseJSONArray(in);
70 | }
71 |
72 | /**
73 | * Returns version data from version id/name
74 | *
75 | * @param id
76 | * @return VersionData
77 | */
78 | public VersionData getVersion(String id) {
79 | for (VersionData data : versions) {
80 | if (data.id.equals(id)) {
81 | return data;
82 | }
83 | }
84 | return null;
85 | }
86 |
87 | /**
88 | * @return All cached VersionData
89 | */
90 | public List getVersions() {
91 | return versions;
92 | }
93 |
94 | public static class VersionData extends VersionMetadata {
95 | public String resources;
96 |
97 | public static VersionData from(JSONObject obj) {
98 | if (obj == null) {
99 | return null;
100 | }
101 | return new VersionData() {
102 | {
103 | id = obj.getString("id");
104 | time = obj.getString("time");
105 | releaseTime = obj.getString("releaseTime");
106 | type = obj.getString("type");
107 | url = obj.getString("url");
108 | resources = obj.optString("resources", null);
109 | }
110 | };
111 | }
112 |
113 | @Override
114 | public String toString() {
115 | String typ;
116 | String ver;
117 | if (id.startsWith("rd") && "old_alpha".equals(type)) {
118 | typ = "Pre-Classic";
119 | ver = id;
120 | } else if (id.startsWith("c") && "old_alpha".equals(type)) {
121 | typ = "Classic";
122 | ver = id.substring(1);
123 | } else if (id.startsWith("inf-")) {
124 | typ = "Infdev";
125 | ver = id.substring(4);
126 | } else if (id.startsWith("in-")) {
127 | typ = "Indev";
128 | ver = id.substring(3);
129 | } else if (id.startsWith("a") && "old_alpha".equals(type)) {
130 | typ = "Alpha";
131 | ver = id.substring(1);
132 | } else if (id.startsWith("b")) {
133 | typ = "Beta";
134 | ver = id.substring(1);
135 | } else {
136 | typ = type.substring(0, 1).toUpperCase() + type.substring(1);
137 | ver = id;
138 | }
139 | return typ + " " + ver;
140 | }
141 | }
142 |
143 | /**
144 | * Sorts versions by date
145 | */
146 | public static class VersionSorter implements Comparator {
147 |
148 | @Override
149 | public int compare(VersionData t1, VersionData t2) {
150 | try {
151 | Instant i1 = Instant.from(DateTimeFormatter.ISO_DATE_TIME.parse(t1.releaseTime));
152 | Instant i2 = Instant.from(DateTimeFormatter.ISO_DATE_TIME.parse(t2.releaseTime));
153 | return i2.compareTo(i1);
154 | } catch (Exception e) {
155 | return -1;
156 | }
157 | }
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/json/Artifact.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions.json;
2 |
3 | import org.json.JSONObject;
4 | import org.mcphackers.mcp.tools.versions.IDownload;
5 |
6 | public class Artifact implements IDownload {
7 | public String path;
8 | public String name;
9 | public String sha1;
10 | public String url;
11 | public long size;
12 |
13 | public static Artifact from(JSONObject obj, String nameStr) {
14 | if (obj == null) {
15 | return null;
16 | }
17 | return new Artifact() {
18 | {
19 | name = nameStr;
20 | path = obj.optString("path", null);
21 | sha1 = obj.optString("sha1", null);
22 | url = obj.optString("url", null);
23 | size = obj.optLong("size");
24 | }
25 | };
26 | }
27 |
28 | @Override
29 | public String downloadPath() {
30 | return path;
31 | }
32 |
33 | @Override
34 | public String downloadURL() {
35 | return url;
36 | }
37 |
38 | @Override
39 | public long downloadSize() {
40 | return size;
41 | }
42 |
43 | @Override
44 | public String downloadHash() {
45 | return sha1;
46 | }
47 |
48 | @Override
49 | public boolean verify() {
50 | return true;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/json/AssetIndex.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions.json;
2 |
3 | import java.util.LinkedHashMap;
4 | import java.util.Map;
5 |
6 | import org.json.JSONObject;
7 | import org.mcphackers.mcp.tools.versions.IDownload;
8 |
9 | public class AssetIndex {
10 | public Map objects = new LinkedHashMap<>();
11 | public boolean virtual;
12 | public boolean map_to_resources;
13 |
14 | public static AssetIndex from(JSONObject obj) {
15 | if (obj == null) {
16 | return null;
17 | }
18 | return new AssetIndex() {
19 | {
20 | virtual = obj.optBoolean("virtual");
21 | map_to_resources = obj.optBoolean("map_to_resources");
22 | JSONObject obj2 = obj.optJSONObject("objects");
23 | if (obj2 != null) {
24 | for (String s : obj2.keySet()) {
25 | objects.put(s, assetFrom(obj2.getJSONObject(s)));
26 | }
27 | }
28 | }
29 | };
30 | }
31 |
32 | public Asset assetFrom(JSONObject obj) {
33 | if (obj == null) {
34 | return null;
35 | }
36 | return new Asset() {
37 | {
38 | hash = obj.getString("hash");
39 | size = obj.getLong("size");
40 | url = obj.optString("url", null);
41 | // reconstruct = obj.optBoolean("reconstruct");
42 | // compressedHash = obj.optString("compressedHash", null);
43 | // compressedSize = obj.optLong("compressedSize");
44 | }
45 | };
46 | }
47 |
48 | public static class Asset implements IDownload {
49 | public String hash;
50 | public String url;
51 | public long size;
52 | // public boolean reconstruct;
53 | // public String compressedHash;
54 | // public long compressedSize;
55 |
56 | @Override
57 | public String downloadURL() {
58 | return url != null ? url : "https://resources.download.minecraft.net/" + downloadPath();
59 | }
60 |
61 | @Override
62 | public long downloadSize() {
63 | return size;
64 | }
65 |
66 | @Override
67 | public String downloadPath() {
68 | return hash.substring(0, 2) + "/" + hash;
69 | }
70 |
71 | @Override
72 | public String downloadHash() {
73 | return hash;
74 | }
75 |
76 | @Override
77 | public boolean verify() {
78 | return true;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/json/AssetIndexMeta.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions.json;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class AssetIndexMeta {
6 | public String id;
7 | public String sha1;
8 | public long size;
9 | public long totalSize;
10 | public String url;
11 |
12 | public static AssetIndexMeta from(JSONObject obj) {
13 | if (obj == null) {
14 | return null;
15 | }
16 | return new AssetIndexMeta() {
17 | {
18 | id = obj.getString("id");
19 | sha1 = obj.getString("sha1");
20 | size = obj.getLong("size");
21 | totalSize = obj.getLong("totalSize");
22 | url = obj.getString("url");
23 | }
24 | };
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/json/Classifiers.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions.json;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import org.json.JSONObject;
7 |
8 | public class Classifiers {
9 | public Map artifacts = new HashMap<>();
10 |
11 | public static Classifiers from(JSONObject obj) {
12 | if (obj == null) {
13 | return null;
14 | }
15 | return new Classifiers() {
16 | {
17 | for(String key : obj.keySet()) {
18 | artifacts.put(key, getArtifact(obj, key));
19 | }
20 | }
21 | };
22 | }
23 |
24 | private static Artifact getArtifact(JSONObject root, String name) {
25 | return Artifact.from(root.optJSONObject(name), name);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/json/DependDownload.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions.json;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.json.JSONArray;
7 | import org.json.JSONObject;
8 | import org.mcphackers.mcp.tools.OS;
9 | import org.mcphackers.mcp.tools.versions.IDownload;
10 |
11 | public class DependDownload {
12 | public Downloads downloads;
13 | public String name;
14 | public String url;
15 | public List rules = new ArrayList<>();
16 |
17 | public static DependDownload from(JSONObject obj) {
18 | if (obj == null) {
19 | return null;
20 | }
21 | return new DependDownload() {
22 | {
23 | name = obj.getString("name");
24 | url = obj.optString("url", null);
25 | downloads = Downloads.from(obj.optJSONObject("downloads"));
26 | JSONArray a = obj.optJSONArray("rules");
27 | if (a != null) {
28 | for (Object o : a) {
29 | rules.add(Rule.from((JSONObject) o));
30 | }
31 | }
32 | }
33 | };
34 | }
35 |
36 | private static String getLibPath(String name, String classifierSuffix) {
37 | String[] artifact = name.split(":");
38 | return artifact[0].replace('.', '/') + "/" + artifact[1] + "/" + artifact[2] + "/" + artifact[1] + "-" + artifact[2] + (classifierSuffix == null ? "" : ("-" + classifierSuffix)) + ".jar";
39 | }
40 |
41 | public String getArtifactURL(String artifactName) {
42 | Artifact artifact = getArtifact(artifactName);
43 | if(artifact == null) {
44 | return null;
45 | }
46 | if(artifact.url != null) {
47 | return artifact.url;
48 | }
49 | String urlBase = "https://libraries.minecraft.net/";
50 | if(url != null) {
51 | urlBase = url.codePointAt(url.length() - 1) == '/' ? url : url + "/";
52 | }
53 | return urlBase + getLibPath(name, artifact.name);
54 | }
55 |
56 | public IDownload getDownload(String artifactName) {
57 | final Artifact artifact = getArtifact(artifactName);
58 | if(artifact == null) {
59 | return null;
60 | }
61 | final String url = getArtifactURL(artifactName);
62 | final String path = getArtifactPath(artifactName);
63 | return new IDownload() {
64 |
65 | @Override
66 | public String downloadPath() {
67 | return path;
68 | }
69 |
70 | @Override
71 | public String downloadURL() {
72 | return url;
73 | }
74 |
75 | @Override
76 | public long downloadSize() {
77 | return artifact.size;
78 | }
79 |
80 | @Override
81 | public String downloadHash() {
82 | return artifact.sha1;
83 | }
84 |
85 | @Override
86 | public boolean verify() {
87 | return true;
88 | }
89 |
90 | };
91 | }
92 |
93 | public String getArtifactPath(String artifactName) {
94 | Artifact artifact = getArtifact(artifactName);
95 | if(artifact == null) {
96 | return null;
97 | }
98 | if(artifact.path != null) {
99 | return artifact.path;
100 | }
101 | return getLibPath(name, artifact.name);
102 | }
103 |
104 | private Artifact getArtifact(String name) {
105 | if(name == null) {
106 | return downloads == null ? null : downloads.artifact;
107 | }
108 | return (downloads == null || downloads.classifiers == null) ? null : downloads.classifiers.artifacts.get(name);
109 | }
110 |
111 | public String getNatives() {
112 | switch (OS.getOs()) {
113 | case windows:
114 | if (getArtifact("natives-windows") != null) {
115 | return "natives-windows";
116 | }
117 | break;
118 | case linux:
119 | if (getArtifact("natives-linux") != null) {
120 | return "natives-linux";
121 | }
122 | break;
123 | case osx:
124 | if (getArtifact("natives-osx") != null) {
125 | return "natives-osx";
126 | }
127 | if (getArtifact("natives-macos") != null) {
128 | return "natives-macos";
129 | }
130 | break;
131 | default:
132 | break;
133 | }
134 | return null;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/json/Downloads.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions.json;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class Downloads {
6 | public Artifact artifact;
7 | public Classifiers classifiers;
8 |
9 | public static Downloads from(JSONObject obj) {
10 | if (obj == null) {
11 | return null;
12 | }
13 | return new Downloads() {
14 | {
15 | artifact = Artifact.from(obj.optJSONObject("artifact"), null);
16 | classifiers = Classifiers.from(obj.optJSONObject("classifiers"));
17 | }
18 | };
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/json/Manifest.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions.json;
2 |
3 | public class Manifest {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/json/Rule.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions.json;
2 |
3 | import java.util.Collections;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.Map.Entry;
8 | import java.util.regex.Matcher;
9 | import java.util.regex.Pattern;
10 |
11 | import org.json.JSONObject;
12 | import org.mcphackers.mcp.tools.OS;
13 |
14 | public class Rule {
15 | public Action action;
16 | public OSInfo os;
17 | public Map features = new HashMap<>();
18 |
19 | public static Rule from(JSONObject obj) {
20 | if (obj == null) {
21 | return null;
22 | }
23 | return new Rule() {
24 | {
25 | action = Action.valueOf(obj.getString("action"));
26 | os = OSInfo.from(obj.optJSONObject("os"));
27 | JSONObject obj2 = obj.optJSONObject("features");
28 | if (obj2 != null) {
29 | for (String s : obj2.keySet()) {
30 | features.put(s, obj2.getBoolean(s));
31 | }
32 | }
33 | }
34 | };
35 | }
36 |
37 | public static boolean apply(List rules) {
38 | return apply(rules, Collections.emptyList());
39 | }
40 |
41 | public static boolean apply(List rules, List features) {
42 | if (rules != null && !rules.isEmpty()) {
43 | Rule.Action lastAction = Rule.Action.disallow;
44 |
45 | for (Rule compatibilityRule : rules) {
46 | Rule.Action action = compatibilityRule.getAppliedAction(features);
47 | if (action != null) {
48 | lastAction = action;
49 | }
50 | }
51 | return lastAction == Rule.Action.allow;
52 | } else {
53 | return true;
54 | }
55 | }
56 |
57 | public Rule.Action getAppliedAction(List featuresList) {
58 | boolean featuresMatch;
59 | for (Entry entry : features.entrySet()) {
60 | featuresMatch = featuresList.contains(entry.getKey()) == entry.getValue();
61 | if (!featuresMatch) return Rule.Action.disallow;
62 | }
63 | return this.os != null && !this.os.equalsOS(OS.getOs()) ? null : this.action;
64 | }
65 |
66 | public enum Action {
67 | allow,
68 | disallow
69 | }
70 |
71 | public static class OSInfo {
72 | public OS name;
73 | public String version;
74 |
75 | public static OSInfo from(JSONObject obj) {
76 | if (obj == null) {
77 | return null;
78 | }
79 | return new OSInfo() {
80 | {
81 | name = OS.valueOf(obj.getString("name"));
82 | version = obj.optString("version");
83 | }
84 | };
85 | }
86 |
87 | public boolean equalsOS(OS os) {
88 | if (this.name != null && this.name != os) {
89 | return false;
90 | } else {
91 | if (this.version != null) {
92 | try {
93 | Pattern pattern = Pattern.compile(this.version);
94 | Matcher matcher = pattern.matcher(System.getProperty("os.version"));
95 | if (!matcher.matches()) {
96 | return false;
97 | }
98 | } catch (Throwable ignored) {
99 | }
100 | }
101 |
102 | return true;
103 | }
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/org/mcphackers/mcp/tools/versions/json/Version.java:
--------------------------------------------------------------------------------
1 | package org.mcphackers.mcp.tools.versions.json;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.json.JSONObject;
7 |
8 | /**
9 | * Deserialized version JSON
10 | */
11 | public class Version {
12 |
13 | public AssetIndexMeta assetIndex;
14 | public String assets;
15 | public Classifiers downloads;
16 | public String id;
17 | public String time;
18 | public String releaseTime;
19 | public String type;
20 | public List libraries;
21 | //public Logging logging;
22 | public String mainClass;
23 | public String minecraftArguments;
24 | public Arguments arguments;
25 |
26 | public static Version from(JSONObject obj) {
27 | if (obj == null) {
28 | return null;
29 | }
30 | return new Version() {
31 | {
32 | assetIndex = AssetIndexMeta.from(obj.getJSONObject("assetIndex"));
33 | assets = obj.getString("assets");
34 | downloads = Classifiers.from(obj.getJSONObject("downloads"));
35 | id = obj.getString("id");
36 | time = obj.getString("time");
37 | releaseTime = obj.getString("releaseTime");
38 | type = obj.getString("type");
39 | libraries = new ArrayList<>();
40 | for (Object o : obj.getJSONArray("libraries")) {
41 | if (o instanceof JSONObject) {
42 | libraries.add(DependDownload.from((JSONObject) o));
43 | }
44 | }
45 | mainClass = obj.getString("mainClass");
46 | minecraftArguments = obj.optString("minecraftArguments", null);
47 | }
48 | };
49 | }
50 |
51 | public static class Arguments {
52 | public List