├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── docs.yml │ ├── gradle-ci.yml │ └── gradle-publish.yml ├── .gitignore ├── LICENSE ├── README.MD ├── build.gradle ├── flowupdater-schema.json ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ └── fr │ └── flowarg │ └── flowupdater │ ├── FlowUpdater.java │ ├── download │ ├── DownloadList.java │ ├── IProgressCallback.java │ ├── Step.java │ ├── VanillaDownloader.java │ ├── VanillaReader.java │ ├── json │ │ ├── AssetDownloadable.java │ │ ├── AssetIndex.java │ │ ├── CurseFileInfo.java │ │ ├── CurseModPackInfo.java │ │ ├── Downloadable.java │ │ ├── ExternalFile.java │ │ ├── MCP.java │ │ ├── Mod.java │ │ ├── ModrinthModPackInfo.java │ │ ├── ModrinthVersionInfo.java │ │ ├── OptiFineInfo.java │ │ └── package-info.java │ └── package-info.java │ ├── integrations │ ├── Integration.java │ ├── IntegrationManager.java │ ├── curseforgeintegration │ │ ├── CurseForgeIntegration.java │ │ ├── CurseModPack.java │ │ ├── ICurseForgeCompatible.java │ │ └── package-info.java │ ├── modrinthintegration │ │ ├── IModrinthCompatible.java │ │ ├── ModrinthIntegration.java │ │ ├── ModrinthModPack.java │ │ └── package-info.java │ ├── optifineintegration │ │ ├── IOptiFineCompatible.java │ │ ├── OptiFine.java │ │ ├── OptiFineIntegration.java │ │ └── package-info.java │ └── package-info.java │ ├── package-info.java │ ├── utils │ ├── ExternalFileDeleter.java │ ├── FlowUpdaterException.java │ ├── IFileDeleter.java │ ├── IOUtils.java │ ├── ModFileDeleter.java │ ├── UpdaterOptions.java │ ├── Version.java │ ├── VersionChecker.java │ ├── builderapi │ │ ├── BuilderArgument.java │ │ ├── BuilderException.java │ │ ├── IBuilder.java │ │ └── package-info.java │ └── package-info.java │ └── versions │ ├── AbstractModLoaderVersion.java │ ├── IModLoaderVersion.java │ ├── ModLoaderUtils.java │ ├── ModLoaderVersionBuilder.java │ ├── ParsedLibrary.java │ ├── VanillaVersion.java │ ├── fabric │ ├── FabricBasedVersion.java │ ├── FabricVersion.java │ ├── FabricVersionBuilder.java │ ├── QuiltVersion.java │ ├── QuiltVersionBuilder.java │ └── package-info.java │ ├── forge │ ├── ForgeVersion.java │ ├── ForgeVersionBuilder.java │ └── package-info.java │ ├── neoforge │ ├── NeoForgeVersion.java │ ├── NeoForgeVersionBuilder.java │ └── package-info.java │ └── package-info.java └── test └── java └── fr └── flowarg └── flowupdater ├── IntegrationTests.java ├── Updates.java └── utils ├── VersionTest.java └── builderapi └── BuilderAPITest.java /.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: 'BUG :' 5 | labels: bug 6 | assignees: FlowArg 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Windows] 28 | - Version [e.g. 10] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'Feature Request :' 5 | labels: feature request 6 | assignees: FlowArg 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: CI Documentation 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | docs: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | packages: write 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up JDK 8 17 | uses: actions/setup-java@v4 18 | with: 19 | java-version: '8' 20 | distribution: 'zulu' 21 | 22 | - name: Build documentation 23 | run: gradle javadoc 24 | - name: Publish Github Pages 25 | uses: peaceiris/actions-gh-pages@v4 26 | with: 27 | personal_token: ${{ secrets.FLOW_TOKEN }} 28 | publish_dir: ./build/docs/javadoc 29 | -------------------------------------------------------------------------------- /.github/workflows/gradle-ci.yml: -------------------------------------------------------------------------------- 1 | name: Gradle CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | testjava: 9 | strategy: 10 | matrix: 11 | jdk: [8, 17, 21] 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read 15 | packages: write 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up JDK ${{ matrix.jdk }} 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: ${{ matrix.jdk }} 23 | distribution: 'zulu' 24 | 25 | - name: Build and test project with Java ${{ matrix.jdk }} 26 | run: gradle build javadoc 27 | -------------------------------------------------------------------------------- /.github/workflows/gradle-publish.yml: -------------------------------------------------------------------------------- 1 | name: Gradle Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | packages: write 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up JDK 8 17 | uses: actions/setup-java@v4 18 | with: 19 | java-version: '8' 20 | distribution: 'zulu' 21 | 22 | - name: Publish FlowUpdater to MavenCentral 23 | run: gradle publish 24 | env: 25 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 26 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 27 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 28 | SONATYPE_TOKEN: ${{ secrets.SONATYPE_TOKEN }} 29 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 30 | GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | updater 4 | run 5 | src/test/java/fr/flowarg/flowupdatertest 6 | .idea 7 | out 8 | forge-installer.jar.log 9 | forge-installer-patched.jar.log 10 | installer.log 11 | testing_directory 12 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | [version]: https://img.shields.io/maven-central/v/fr.flowarg/flowupdater.svg?label=Download 2 | [download]: https://search.maven.org/search?q=g:%22fr.flowarg%22%20AND%20a:%22flowupdater%22 3 | 4 | [discord-shield]: https://discordapp.com/api/guilds/730758985376071750/widget.png 5 | [discord-invite]: https://discord.gg/dN6HWHp 6 | 7 | [ ![version][] ][download] 8 | [ ![discord-shield][] ][discord-invite] 9 | 10 | # FlowUpdater 11 | Welcome on FlowUpdater's repository. FlowUpdater is a free and open source solution to update Minecraft in Java. 12 | It was mainly designed for launcher's purposes but can be used for other usages as well. FlowUpdater focuses on customization and reliability. 13 | The best documentation is the JavaDoc included in FlowUpdater's source code. The rest of the documentation (for instance this readme or the wiki tab on GitHub) has a chance of not being updated. 14 | 15 | ## Legal and fork notices :warning: 16 | The CurseForge integration works with an API Key which is mine at the moment. **You CAN'T use this key for other purposes outside FlowUpdater.** 17 | If you wish to fork this project, **you HAVE TO use your own API Key**. 18 | 19 | ## Alternatives 20 | If you are a developer or know a developer who has made a similar library in another programming language, 21 | feel free to ask to appear in this list: 22 | - [Rust Launcher Lib](https://github.com/knightmar/rust_launcher_lib) (Rust) 23 | 24 | ## Usage 25 | 26 | ### Vanilla 27 | 28 | First, create a new VanillaVersion, and build the version: 29 | ```java 30 | VanillaVersion version = new VanillaVersion.VanillaVersionBuilder().withName("1.20.4").build(); 31 | ``` 32 | `VanillaVersion` accepts some arguments to add more libraries, assets or to reach snapshots or custom version of the game. 33 | All accepted arguments are available in the `VanillaVersionBuilder` class. 34 | 35 | Add the version to a new `FlowUpdater` instance and build it: 36 | ```java 37 | FlowUpdater updater = new FlowUpdater.FlowUpdaterBuilder() 38 | .withVanillaVersion(version) 39 | .build(); 40 | ``` 41 | 42 | In the same way, `FlowUpdater` accepts many arguments that you can use as you want. 43 | The more important ones to know about are: the logger, the progress callback, the vanilla version, possibly a mod loader version. The full list is available in the `FlowUpdaterBuilder` class. 44 | 45 | 46 | Finally, call the update function: 47 | ```java 48 | updater.update(Paths.get("your/path/")); 49 | ``` 50 | This `update` method will start the whole checks-and-download pipeline and will return when all the work is done. 51 | You usually need to put this method in a new `Thread` / `ExecutorService` because apart from the assets part, all actions are run on the same thread. 52 | 53 | 54 | ### Forge 55 | 56 | (You need to setup a vanilla version like above!) 57 | 58 | Next, make a List of Mod objects (except if you don't need some). 59 | ```java 60 | List mods = new ArrayList<>(); 61 | mods.add(new Mod("OneMod.jar", "sha1ofmod", 85120, "https://link.com/of/mod.jar")); 62 | mods.add(new Mod("AnotherMod.jar", "sha1ofanothermod", 86120, "https://link.com/of/another/mod.jar")); 63 | ``` 64 | You can also get a list of mods by providing a json link: `List mods = Mod.getModsFromJson("https://url.com/launcher/mods.json");`. A template is available in Mod class. 65 | 66 | You can get mods from CurseForge too: 67 | ```java 68 | List modInfos = new ArrayList<>(); 69 | // project ID and file ID 70 | modInfos.add(new CurseFileInfo(238222, 2988823)); 71 | ``` 72 | You can also get a list of curse mods by providing a json link: `List mods = CurseFileInfo.getFilesFromJson("https://url.com/launcher/cursemods.json");`. 73 | 74 | On the same pattern, you can get mods from Modrinth. 75 | 76 | Then, build a forge version. For example, I will build a NewForgeVersion. 77 | ```java 78 | ForgeVersion forgeVersion = new ForgeVersionBuilder() 79 | .withForgeVersion("1.20.6-50.1.12") // mandatory 80 | .withCurseMods(modInfos) // optional 81 | .withOptiFine(new OptiFineInfo("preview_OptiFine_1.20.6_HD_U_I9_pre1")) // installing OptiFine (optional) 82 | .withFileDeleter(new ModFileDeleter("jei.jar")) // (optional, but recommended) delete bad mods, don't remove the file jei.jar if it's present in mods directory. You can also provide A `Pattern` with a regex rule. 83 | .build(); 84 | ``` 85 | 86 | Finally, set the Forge version object to your `FlowUpdaterBuilder`: 87 | ```java 88 | .withModLoaderVersion(forgeVersion); 89 | ``` 90 | 91 | ### MCP 92 | 93 | (You need to setup a vanilla updater!) 94 | 95 | There are two ways to setup an MCP version. You can either (1) provide an MCP object (for a simple client for example) or (2) a JSON link to a custom json version which can contains custom assets, custom libraries etc... 96 | 97 | (1) set to vanilla version builder a MCP version: 98 | ```java 99 | .withMCP(new MCP("clientURL", "clientSha1", 25008229)); 100 | ``` 101 | If you set an empty/null string in url and sha1 and 0 in size, the updater will use the default minecraft jar. 102 | Example for a client-only mcp downloading: 103 | ```java 104 | .withMCP(new MCP("https://mighya.eu/resources/Client.jar", "f2c219e485831af2bae9464eebbe4765128c6ad6", 23005862)); 105 | ``` 106 | You can get an MCP object instance by providing a json link too: `.withMCP("https://url.com/launcher/mcp.json");`. 107 | 108 | (2) 109 | Still in the vanilla version builder, set a json link to a custom MCP version: 110 | ```java 111 | .withCustomVersionJson(new URL("https://url.com/launcher/mcp.json")); 112 | ``` 113 | 114 | You can also provide some more additional libraries or assets with all methods in the `VanillaVersionBuilder` class 115 | (`withAnotherLibraries`, `withAnotherAssets`, `withCustomAssetIndex`). 116 | 117 | ### Fabric 118 | 119 | (You need to setup a vanilla updater!) 120 | 121 | Next, make a List of Mod objects like for a ForgeVersion if you need some. 122 | 123 | Then, build a Fabric version. 124 | ```java 125 | FabricVersion fabricVersion = new FabricVersionBuilder() 126 | .withFabricVersion("0.10.8") // optional, if you don't set one, it will take the latest fabric loader version available. 127 | .withCurseMods(modInfos) // optional 128 | .withMods(mods) // optional 129 | .withFileDeleter(new ModFileDeleter("sodium.jar")) // (optional but recommended) delete bad mods ; but it won't remove the file sodium.jar if it's present in the mods' dir. 130 | .build(); 131 | ``` 132 | 133 | Finally, set the Fabric version to your `FlowUpdaterBuilder`: 134 | ```java 135 | .withModLoaderVersion(fabricVersion); 136 | ``` 137 | 138 | ## External Files 139 | 140 | With FlowUpdater, you can download other files in your update dir! This system is designed mainly for configs, resource packs. 141 | You can also configure a keep-policy for these files (should the updater download the file again if it is modified?). 142 | In your FlowUpdaterBuilder, define an array list of ExternalFile (by `ExternalFile#getExternalFilesFromJson` for more convenience). 143 | 144 | ### About json files... 145 | 146 | **Deprecated**: All json files can be generated by the [FlowUpdaterJsonCreator](https://github.com/FlowArg/FlowUpdaterJsonCreator)! 147 | 148 | There are new tools made by the community that can help you generate some JSON files: 149 | - [FlowJsonCreator by Paulem79](https://github.com/Paulem79/FlowJsonCreator) (Java) 150 | - [FUJC by Zuygui](https://github.com/zuygui/flowupdater-json-creator) (Rust) 151 | 152 | ## Post executions 153 | 154 | With FlowUpdater, you can execute some actions after update, like patch a file, kill a process, launch a process, review a config etc... 155 | In your FlowUpdaterBuilder, you have to set a list of Runnable. 156 | It's not always relevant to use this feature, but it can be useful in some specific cases. 157 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'idea' 3 | apply plugin: 'maven-publish' 4 | apply plugin: 'signing' 5 | 6 | group = 'fr.flowarg' 7 | version = '1.9.3' 8 | archivesBaseName = "flowupdater" 9 | 10 | java { 11 | toolchain { 12 | languageVersion = JavaLanguageVersion.of(8) 13 | } 14 | } 15 | 16 | tasks.withType(JavaCompile).configureEach { 17 | options.encoding = 'UTF-8' 18 | sourceCompatibility = JavaVersion.VERSION_1_8 19 | } 20 | 21 | repositories { 22 | mavenCentral() 23 | mavenLocal() 24 | } 25 | 26 | java { 27 | withJavadocJar() 28 | withSourcesJar() 29 | } 30 | 31 | dependencies { 32 | api 'com.google.code.gson:gson:2.13.0' 33 | api 'fr.flowarg:flowmultitools:1.4.4' 34 | api 'org.jetbrains:annotations:26.0.2' 35 | 36 | // Only for internal tests 37 | testImplementation 'fr.flowarg:openlauncherlib:3.2.11' 38 | testImplementation 'org.junit.jupiter:junit-jupiter:5.12.2' 39 | } 40 | 41 | publishing { 42 | publications { 43 | mavenJava(MavenPublication) { 44 | from components.java 45 | 46 | pom { 47 | groupId = project.group 48 | version = project.version 49 | artifactId = 'flowupdater' 50 | name = project.name 51 | description = 'The free and open source solution to update Minecraft.' 52 | url = 'https://github.com/FlowArg/FlowUpdater' 53 | 54 | scm { 55 | connection = 'scm:git:git://github.com/FlowArg/FlowUpdater.git' 56 | developerConnection = 'scm:git:ssh://github.com:FlowArg/FlowUpdater.git' 57 | url = 'https://github.com/FlowArg/FlowUpdater/tree/master' 58 | } 59 | 60 | licenses { 61 | license { 62 | name = 'GNU General Public License v3.0' 63 | url = 'https://www.gnu.org/licenses/gpl-3.0.txt' 64 | } 65 | } 66 | 67 | developers { 68 | developer { 69 | id = 'flowarg' 70 | name = 'Flow Arg' 71 | email = 'flow@flowarg.fr' 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | repositories { 79 | maven { 80 | credentials { 81 | username = System.getenv("OSSRH_USERNAME") 82 | password = System.getenv("OSSRH_PASSWORD") 83 | } 84 | url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 85 | } 86 | } 87 | } 88 | 89 | signing { 90 | def signingKey = System.getenv("GPG_PRIVATE_KEY") 91 | def signingPassword = System.getenv("GPG_PASSPHRASE") 92 | useInMemoryPgpKeys(signingKey, signingPassword) 93 | sign publishing.publications.mavenJava 94 | } 95 | 96 | test { 97 | useJUnitPlatform() 98 | } 99 | -------------------------------------------------------------------------------- /flowupdater-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "", 4 | "type": "object", 5 | "properties": { 6 | "curseFiles": { 7 | "type": "array", 8 | "uniqueItems": true, 9 | "minItems": 1, 10 | "items": { 11 | "required": [ 12 | "projectID", 13 | "fileID" 14 | ], 15 | "properties": { 16 | "projectID": { 17 | "type": "number", 18 | "description": "Project ID of the mod on CurseForge" 19 | }, 20 | "fileID": { 21 | "type": "number", 22 | "description": "File ID of the mod on CurseForge" 23 | }, 24 | "required": { 25 | "type": "boolean", 26 | "description": "If true, the mod is required to be present in the mod folder." 27 | } 28 | } 29 | } 30 | }, 31 | "modrinthMods": { 32 | "type": "array", 33 | "uniqueItems": true, 34 | "minItems": 1, 35 | "items": { 36 | "required": [ 37 | "versionId", 38 | "projectReference", 39 | "versionNumber" 40 | ], 41 | "oneOf": [ 42 | { 43 | "required": [ 44 | "versionId" 45 | ], 46 | "properties": { 47 | "versionId": { 48 | "type": "number", 49 | "description": "Mod version file ID" 50 | } 51 | } 52 | }, 53 | { 54 | "required": [ 55 | "projectReference", 56 | "versionNumber" 57 | ], 58 | "properties": { 59 | "projectReference": { 60 | "type": "number", 61 | "description": "Projet ID of the mod on Modrinth" 62 | }, 63 | "versionNumber": { 64 | "type": "number", 65 | "description": "Version ID of the mod on Modrinth" 66 | } 67 | } 68 | } 69 | ] 70 | } 71 | }, 72 | "mods": { 73 | "type": "array", 74 | "uniqueItems": true, 75 | "minItems": 1, 76 | "items": { 77 | "required": [ 78 | "name", 79 | "downloadURL", 80 | "sha1", 81 | "size" 82 | ], 83 | "properties": { 84 | "name": { 85 | "type": "string", 86 | "minLength": 1, 87 | "description": "Name of mod file" 88 | }, 89 | "downloadURL": { 90 | "type": "string", 91 | "minLength": 1, 92 | "description": "Mod download URL" 93 | }, 94 | "sha1": { 95 | "type": "string", 96 | "minLength": 1, 97 | "description": "Sha1 of mod file" 98 | }, 99 | "size": { 100 | "type": "number", 101 | "description": "Size of mod file (in bytes)" 102 | } 103 | } 104 | } 105 | }, 106 | "extfiles": { 107 | "type": "array", 108 | "uniqueItems": true, 109 | "minItems": 1, 110 | "items": { 111 | "required": [ 112 | "path", 113 | "downloadURL", 114 | "sha1", 115 | "size" 116 | ], 117 | "properties": { 118 | "path": { 119 | "type": "string", 120 | "minLength": 1, 121 | "description": "Path of external file" 122 | }, 123 | "downloadURL": { 124 | "type": "string", 125 | "minLength": 1, 126 | "description": "external file URL" 127 | }, 128 | "sha1": { 129 | "type": "string", 130 | "minLength": 1, 131 | "description": "Sha1 of external file" 132 | }, 133 | "size": { 134 | "type": "number", 135 | "description": "Size of external file (in bytes)" 136 | }, 137 | "update": { 138 | "type": "boolean", 139 | "description": "If false, the file will not be checked again if the file is valid." 140 | } 141 | } 142 | } 143 | }, 144 | "clientURL": { 145 | "type": "string", 146 | "minLength": 1, 147 | "description": "URL of client.jar" 148 | }, 149 | "clientSha1": { 150 | "type": "string", 151 | "minLength": 1, 152 | "description": "SHA1 of client.jar" 153 | }, 154 | "clientSize": { 155 | "type": "number", 156 | "description": "Size of client.jar (in bytes)" 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowArg/FlowUpdater/64f083a6fd78c9e01b1fcc35d21a5bef74e9e86b/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-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'FlowUpdater' -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/DownloadList.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download; 2 | 3 | import fr.flowarg.flowupdater.download.json.AssetDownloadable; 4 | import fr.flowarg.flowupdater.download.json.Downloadable; 5 | import fr.flowarg.flowupdater.download.json.ExternalFile; 6 | import fr.flowarg.flowupdater.download.json.Mod; 7 | import fr.flowarg.flowupdater.integrations.optifineintegration.OptiFine; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | import java.util.concurrent.atomic.AtomicLong; 13 | import java.util.concurrent.locks.Lock; 14 | import java.util.concurrent.locks.ReentrantLock; 15 | 16 | /** 17 | * Represent information about download status. Used with {@link IProgressCallback} progress system. 18 | * 19 | * @author FlowArg 20 | */ 21 | public class DownloadList 22 | { 23 | private final List downloadableFiles = new ArrayList<>(); 24 | private final List downloadableAssets = new ArrayList<>(); 25 | private final List extFiles = new ArrayList<>(); 26 | private final List mods = new ArrayList<>(); 27 | private final Lock updateInfoLock = new ReentrantLock(); 28 | private OptiFine optiFine = null; 29 | private DownloadInfo downloadInfo; 30 | private boolean init = false; 31 | 32 | /** 33 | * This method initializes fields. 34 | */ 35 | public void init() 36 | { 37 | if (this.init) return; 38 | 39 | this.downloadInfo = new DownloadInfo(); 40 | this.downloadableFiles.forEach( 41 | downloadable -> this.downloadInfo.totalToDownloadBytes.set( 42 | this.downloadInfo.totalToDownloadBytes.get() + downloadable.getSize())); 43 | this.downloadableAssets.forEach( 44 | downloadable -> this.downloadInfo.totalToDownloadBytes.set( 45 | this.downloadInfo.totalToDownloadBytes.get() + downloadable.getSize())); 46 | this.extFiles.forEach( 47 | externalFile -> this.downloadInfo.totalToDownloadBytes.set( 48 | this.downloadInfo.totalToDownloadBytes.get() + externalFile.getSize())); 49 | this.mods.forEach( 50 | mod -> this.downloadInfo.totalToDownloadBytes.set(this.downloadInfo.totalToDownloadBytes.get() + mod.getSize())); 51 | 52 | this.downloadInfo.totalToDownloadFiles.set( 53 | this.downloadInfo.totalToDownloadFiles.get() + 54 | this.downloadableFiles.size() + 55 | this.downloadableAssets.size() + 56 | this.extFiles.size() + 57 | this.mods.size()); 58 | 59 | if (this.optiFine != null) 60 | { 61 | this.downloadInfo.totalToDownloadBytes.set(this.downloadInfo.totalToDownloadBytes.get() + this.optiFine.getSize()); 62 | this.downloadInfo.totalToDownloadFiles.incrementAndGet(); 63 | } 64 | this.init = true; 65 | } 66 | 67 | /** 68 | * This method increments the number of bytes downloaded by the number of bytes passed in parameter. 69 | * @param bytes number of bytes to add to downloaded bytes. 70 | */ 71 | public void incrementDownloaded(long bytes) 72 | { 73 | this.updateInfoLock.lock(); 74 | this.downloadInfo.downloadedFiles.incrementAndGet(); 75 | this.downloadInfo.downloadedBytes.set(this.downloadInfo.downloadedBytes.get() + bytes); 76 | this.updateInfoLock.unlock(); 77 | } 78 | 79 | /** 80 | * Get the new API to get information about the progress of the download. 81 | * @return the instance of {@link DownloadInfo}. 82 | */ 83 | public DownloadInfo getDownloadInfo() 84 | { 85 | return this.downloadInfo; 86 | } 87 | 88 | /** 89 | * Get the queue that contains all assets to download. 90 | * @return the queue that contains all assets to download. 91 | */ 92 | public List getDownloadableAssets() 93 | { 94 | return this.downloadableAssets; 95 | } 96 | 97 | /** 98 | * Get the list that contains all downloadable files. 99 | * @return the list that contains all downloadable files. 100 | */ 101 | public List getDownloadableFiles() 102 | { 103 | return this.downloadableFiles; 104 | } 105 | 106 | /** 107 | * Get the list that contains all external files. 108 | * @return the list that contains all external files. 109 | */ 110 | public List getExtFiles() 111 | { 112 | return this.extFiles; 113 | } 114 | 115 | /** 116 | * Get the list that contains all mods. 117 | * @return the list that contains all mods. 118 | */ 119 | public List getMods() 120 | { 121 | return this.mods; 122 | } 123 | 124 | /** 125 | * Get the OptiFine object. 126 | * @return the OptiFine object. 127 | */ 128 | public OptiFine getOptiFine() 129 | { 130 | return this.optiFine; 131 | } 132 | 133 | /** 134 | * Define the OptiFine object. 135 | * @param optiFine the OptiFine object to define. 136 | */ 137 | public void setOptiFine(OptiFine optiFine) 138 | { 139 | this.optiFine = optiFine; 140 | } 141 | 142 | /** 143 | * Clear and reset this download list object. 144 | */ 145 | public void clear() 146 | { 147 | this.downloadableFiles.clear(); 148 | this.extFiles.clear(); 149 | this.downloadableAssets.clear(); 150 | this.mods.clear(); 151 | this.optiFine = null; 152 | this.downloadInfo.reset(); 153 | this.init = false; 154 | } 155 | 156 | public static class DownloadInfo 157 | { 158 | private final AtomicLong totalToDownloadBytes = new AtomicLong(0); 159 | private final AtomicLong downloadedBytes = new AtomicLong(0); 160 | private final AtomicInteger totalToDownloadFiles = new AtomicInteger(0); 161 | private final AtomicInteger downloadedFiles = new AtomicInteger(0); 162 | 163 | /** 164 | * Reset this download info object. 165 | */ 166 | public void reset() 167 | { 168 | this.totalToDownloadBytes.set(0); 169 | this.downloadedBytes.set(0); 170 | this.totalToDownloadFiles.set(0); 171 | this.downloadedFiles.set(0); 172 | } 173 | 174 | /** 175 | * Get the total of bytes to download. 176 | * @return bytes to download. 177 | */ 178 | public long getTotalToDownloadBytes() 179 | { 180 | return this.totalToDownloadBytes.get(); 181 | } 182 | 183 | /** 184 | * Get the downloaded bytes. 185 | * @return the downloaded bytes. 186 | */ 187 | public long getDownloadedBytes() 188 | { 189 | return this.downloadedBytes.get(); 190 | } 191 | 192 | /** 193 | * Get the number of files to download. 194 | * @return number of files to download. 195 | */ 196 | public int getTotalToDownloadFiles() 197 | { 198 | return this.totalToDownloadFiles.get(); 199 | } 200 | 201 | /** 202 | * Get the number of downloaded files. 203 | * @return the number of downloaded files. 204 | */ 205 | public int getDownloadedFiles() 206 | { 207 | return this.downloadedFiles.get(); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/IProgressCallback.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download; 2 | 3 | import fr.flowarg.flowlogger.ILogger; 4 | import fr.flowarg.flowupdater.FlowUpdater; 5 | 6 | import java.nio.file.Path; 7 | 8 | /** 9 | * This interface provides useful methods to implement to access to download and update status. 10 | */ 11 | public interface IProgressCallback 12 | { 13 | /** 14 | * This method is called at {@link FlowUpdater} initialization. 15 | * @param logger {@link ILogger} of FlowUpdater instance. 16 | */ 17 | default void init(ILogger logger) {} 18 | 19 | /** 20 | * This method is called when a step started. 21 | * @param step Actual {@link Step}. 22 | */ 23 | default void step(Step step) {} 24 | 25 | /** 26 | * This method is called when a new file is downloaded. 27 | * @param info The {@link DownloadList.DownloadInfo} instance that contains all wanted information. 28 | */ 29 | default void update(DownloadList.DownloadInfo info) {} 30 | 31 | /** 32 | * This method is called before {@link #update(DownloadList.DownloadInfo)} when a file is downloaded. 33 | * @param path the file downloaded. 34 | */ 35 | default void onFileDownloaded(Path path) {} 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/Step.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download; 2 | 3 | import fr.flowarg.flowupdater.FlowUpdater; 4 | 5 | /** 6 | * Represent each step of a Minecraft Installation 7 | * @author flow 8 | */ 9 | public enum Step 10 | { 11 | /** Integration loading */ 12 | INTEGRATION, 13 | /** ModPack preparation */ 14 | MOD_PACK, 15 | /** JSON reading */ 16 | READ, 17 | /** Download libraries */ 18 | DL_LIBS, 19 | /** Download assets */ 20 | DL_ASSETS, 21 | /** Extract natives */ 22 | EXTRACT_NATIVES, 23 | /** Install a mod loader version. Skipped if {@link FlowUpdater#getModLoaderVersion()} is null. */ 24 | MOD_LOADER, 25 | /** Download mods. Skipped if {@link FlowUpdater#getModLoaderVersion()} is null. */ 26 | MODS, 27 | /** Download other files. */ 28 | EXTERNAL_FILES, 29 | /** Runs a list of runnable at the end of update. */ 30 | POST_EXECUTIONS, 31 | /** All tasks are finished */ 32 | END 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/VanillaDownloader.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download; 2 | 3 | import fr.flowarg.flowio.FileUtils; 4 | import fr.flowarg.flowlogger.ILogger; 5 | import fr.flowarg.flowstringer.StringUtils; 6 | import fr.flowarg.flowupdater.FlowUpdater; 7 | import fr.flowarg.flowupdater.download.json.Downloadable; 8 | import fr.flowarg.flowupdater.utils.IOUtils; 9 | import fr.flowarg.flowzipper.ZipUtils; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.net.URL; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | import java.util.Enumeration; 16 | import java.util.List; 17 | import java.util.concurrent.Executors; 18 | import java.util.jar.JarFile; 19 | import java.util.stream.Collectors; 20 | import java.util.stream.Stream; 21 | import java.util.zip.ZipEntry; 22 | 23 | /** 24 | * This class handles the downloading of vanilla files (client, assets, natives...). 25 | */ 26 | public class VanillaDownloader 27 | { 28 | private final Path dir; 29 | private final ILogger logger; 30 | private final IProgressCallback callback; 31 | private final DownloadList downloadList; 32 | private final Path natives; 33 | private final Path assets; 34 | private final String vanillaJsonURL; 35 | 36 | /** 37 | * Construct a new VanillaDownloader object. 38 | * @param dir the installation directory. 39 | * @param flowUpdater the flow updater object. 40 | * @throws Exception if an I/O error occurred. 41 | */ 42 | public VanillaDownloader(Path dir, @NotNull FlowUpdater flowUpdater) throws Exception 43 | { 44 | this.dir = dir; 45 | this.logger = flowUpdater.getLogger(); 46 | this.callback = flowUpdater.getCallback(); 47 | this.downloadList = flowUpdater.getDownloadList(); 48 | 49 | this.natives = this.dir.resolve("natives"); 50 | this.assets = this.dir.resolve("assets"); 51 | this.vanillaJsonURL = flowUpdater.getVanillaVersion().getJsonURL(); 52 | 53 | Files.createDirectories(this.dir.resolve("libraries")); 54 | Files.createDirectories(this.assets); 55 | Files.createDirectories(this.natives); 56 | 57 | this.downloadList.init(); 58 | } 59 | 60 | /** 61 | * This method downloads calls other methods to download and verify all files. 62 | * @throws Exception if an I/O error occurred. 63 | */ 64 | public void download() throws Exception 65 | { 66 | this.downloadLibraries(); 67 | this.downloadAssets(); 68 | this.extractNatives(); 69 | 70 | this.logger.info("All vanilla files were successfully downloaded!"); 71 | } 72 | 73 | private void downloadLibraries() throws Exception 74 | { 75 | this.logger.info("Checking library files..."); 76 | this.callback.step(Step.DL_LIBS); 77 | 78 | if(this.vanillaJsonURL != null) 79 | this.downloadVanillaJson(); 80 | 81 | for (Downloadable downloadable : this.downloadList.getDownloadableFiles()) 82 | { 83 | final Path filePath = this.dir.resolve(downloadable.getName()); 84 | 85 | if(Files.notExists(filePath) || 86 | !FileUtils.getSHA1(filePath).equalsIgnoreCase(downloadable.getSha1()) || 87 | Files.size(filePath) != downloadable.getSize()) 88 | { 89 | IOUtils.download(this.logger, new URL(downloadable.getUrl()), filePath); 90 | this.callback.onFileDownloaded(filePath); 91 | } 92 | 93 | this.downloadList.incrementDownloaded(downloadable.getSize()); 94 | this.callback.update(this.downloadList.getDownloadInfo()); 95 | } 96 | } 97 | 98 | private void downloadVanillaJson() throws Exception 99 | { 100 | final Path vanillaJsonTarget = this.dir.resolve(this.vanillaJsonURL.substring(this.vanillaJsonURL.lastIndexOf('/') + 1)); 101 | final String vanillaJsonResourceName = this.vanillaJsonURL.substring(this.vanillaJsonURL.lastIndexOf('/')); 102 | final String vanillaJsonPathUrl = StringUtils.empty(StringUtils.empty(this.vanillaJsonURL, "https://launchermeta.mojang.com/v1/packages/"), "https://piston-meta.mojang.com/v1/packages/"); 103 | 104 | if(Files.notExists(vanillaJsonTarget) || !FileUtils.getSHA1(vanillaJsonTarget) 105 | .equals(StringUtils.empty(vanillaJsonPathUrl, vanillaJsonResourceName))) 106 | IOUtils.download(this.logger, new URL(this.vanillaJsonURL), vanillaJsonTarget); 107 | } 108 | 109 | private void extractNatives() throws Exception 110 | { 111 | boolean flag = false; 112 | final List existingNatives = FileUtils.list(this.natives); 113 | if (!existingNatives.isEmpty()) 114 | { 115 | for (Path minecraftNative : FileUtils.list(this.natives) 116 | .stream() 117 | .filter(path -> path.getFileName().toString().endsWith(".jar")) 118 | .collect(Collectors.toList())) 119 | { 120 | final JarFile jarFile = new JarFile(minecraftNative.toFile()); 121 | final Enumeration entries = jarFile.entries(); 122 | while (entries.hasMoreElements()) 123 | { 124 | final ZipEntry entry = entries.nextElement(); 125 | if (entry.isDirectory() || 126 | entry.getName().endsWith(".git") || 127 | entry.getName().endsWith(".sha1") || 128 | entry.getName().contains("META-INF")) continue; 129 | 130 | final Path flPath = this.natives.resolve(entry.getName()); 131 | 132 | if(Files.exists(flPath) && entry.getCrc() == FileUtils.getCRC32(flPath)) continue; 133 | 134 | flag = true; 135 | break; 136 | } 137 | jarFile.close(); 138 | if (flag) break; 139 | } 140 | } 141 | 142 | if (flag) 143 | { 144 | this.logger.info("Extracting natives..."); 145 | this.callback.step(Step.EXTRACT_NATIVES); 146 | 147 | final Stream natives = FileUtils.list(this.natives).stream(); 148 | natives.filter(file -> !Files.isDirectory(file) && file.getFileName().toString().endsWith(".jar")) 149 | .forEach(file -> { 150 | try 151 | { 152 | ZipUtils.unzipJar(this.natives, file, "ignoreMetaInf"); 153 | } catch (Exception e) 154 | { 155 | this.logger.printStackTrace(e); 156 | } 157 | }); 158 | natives.close(); 159 | } 160 | 161 | try(Stream natives = Files.list(this.natives)) 162 | { 163 | natives.forEach(path -> { 164 | try 165 | { 166 | if (path.getFileName().toString().endsWith(".git") || path.getFileName().toString().endsWith(".sha1")) 167 | Files.delete(path); 168 | else if(Files.isDirectory(path)) 169 | FileUtils.deleteDirectory(path); 170 | } catch (Exception e) 171 | { 172 | this.logger.printStackTrace(e); 173 | } 174 | }); 175 | } 176 | } 177 | 178 | private void downloadAssets() 179 | { 180 | this.logger.info("Checking assets..."); 181 | this.callback.step(Step.DL_ASSETS); 182 | 183 | IOUtils.executeAsyncForEach(this.downloadList.getDownloadableAssets(), Executors.newWorkStealingPool(), assetDownloadable -> { 184 | try 185 | { 186 | final Path downloadPath = this.assets.resolve(assetDownloadable.getFile()); 187 | 188 | if (Files.notExists(downloadPath) || Files.size(downloadPath) != assetDownloadable.getSize()) 189 | { 190 | final Path localAssetPath = IOUtils.getMinecraftFolder().resolve("assets").resolve(assetDownloadable.getFile()); 191 | if (Files.exists(localAssetPath) && Files.size(localAssetPath) == assetDownloadable.getSize()) 192 | IOUtils.copy(this.logger, localAssetPath, downloadPath); 193 | else 194 | { 195 | IOUtils.download(this.logger, new URL(assetDownloadable.getUrl()), downloadPath); 196 | this.callback.onFileDownloaded(downloadPath); 197 | } 198 | } 199 | 200 | this.downloadList.incrementDownloaded(assetDownloadable.getSize()); 201 | this.callback.update(this.downloadList.getDownloadInfo()); 202 | } 203 | catch (Exception e) 204 | { 205 | this.logger.printStackTrace(e); 206 | } 207 | }); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/VanillaReader.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download; 2 | 3 | import com.google.gson.GsonBuilder; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import fr.flowarg.flowcompat.Platform; 7 | import fr.flowarg.flowupdater.FlowUpdater; 8 | import fr.flowarg.flowupdater.download.json.AssetDownloadable; 9 | import fr.flowarg.flowupdater.download.json.AssetIndex; 10 | import fr.flowarg.flowupdater.download.json.Downloadable; 11 | import fr.flowarg.flowupdater.utils.IOUtils; 12 | import fr.flowarg.flowupdater.versions.VanillaVersion; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.net.URL; 16 | import java.util.HashSet; 17 | import java.util.Set; 18 | import java.util.concurrent.atomic.AtomicBoolean; 19 | 20 | /** 21 | * This class handles all parsing stuff about vanilla files. 22 | */ 23 | public class VanillaReader 24 | { 25 | private final VanillaVersion version; 26 | private final IProgressCallback callback; 27 | private final DownloadList downloadList; 28 | 29 | /** 30 | * Construct a new VanillaReader. 31 | * @param flowUpdater the flow updater object. 32 | */ 33 | public VanillaReader(@NotNull FlowUpdater flowUpdater) 34 | { 35 | this.version = flowUpdater.getVanillaVersion(); 36 | this.callback = flowUpdater.getCallback(); 37 | this.downloadList = flowUpdater.getDownloadList(); 38 | } 39 | 40 | /** 41 | * This method calls other methods to parse each part of the given Minecraft Version. 42 | * @throws Exception if an I/O error occurred. 43 | */ 44 | public void read() throws Exception 45 | { 46 | this.callback.step(Step.READ); 47 | this.parseLibraries(); 48 | this.parseAssetIndex(); 49 | this.parseClient(); 50 | this.parseNatives(); 51 | this.parseAssets(); 52 | } 53 | 54 | private void parseLibraries() 55 | { 56 | this.version.getMinecraftLibrariesJson().forEach(jsonElement -> { 57 | final JsonObject element = jsonElement.getAsJsonObject(); 58 | 59 | if (element == null || !this.checkRules(element)) 60 | return; 61 | 62 | final JsonObject downloads = element.getAsJsonObject("downloads"); 63 | 64 | if(downloads == null) 65 | return; 66 | 67 | block: { 68 | final String name = element.getAsJsonPrimitive("name").getAsString(); 69 | 70 | if(!name.contains("lwjgl") || !name.contains("natives") || !name.contains("macos")) 71 | break block; 72 | 73 | boolean platformCheck = (Platform.isOnMac() && 74 | Platform.getArch().equals("64") && 75 | System.getProperty("os.arch").equals("aarch64")); 76 | 77 | if(platformCheck != name.contains("arm64")) 78 | return; 79 | } 80 | 81 | final JsonObject artifact = downloads.getAsJsonObject("artifact"); 82 | 83 | if (artifact == null) 84 | return; 85 | 86 | final String url = artifact.getAsJsonPrimitive("url").getAsString(); 87 | final int size = artifact.getAsJsonPrimitive("size").getAsInt(); 88 | final String path = "libraries/" + artifact.getAsJsonPrimitive("path").getAsString(); 89 | final String sha1 = artifact.getAsJsonPrimitive("sha1").getAsString(); 90 | final Downloadable downloadable = new Downloadable(url, size, sha1, path); 91 | 92 | if(!this.downloadList.getDownloadableFiles().contains(downloadable)) 93 | this.downloadList.getDownloadableFiles().add(downloadable); 94 | }); 95 | this.downloadList.getDownloadableFiles().addAll(this.version.getAnotherLibraries()); 96 | } 97 | 98 | private void parseAssetIndex() 99 | { 100 | if(this.version.getCustomAssetIndex() != null) 101 | return; 102 | 103 | final JsonObject assetIndex = this.version.getMinecraftAssetIndex(); 104 | final String url = assetIndex.getAsJsonPrimitive("url").getAsString(); 105 | final int size = assetIndex.getAsJsonPrimitive("size").getAsInt(); 106 | final String name = "assets/indexes/" + url.substring(url.lastIndexOf('/') + 1); 107 | final String sha1 = assetIndex.getAsJsonPrimitive("sha1").getAsString(); 108 | 109 | this.downloadList.getDownloadableFiles().add(new Downloadable(url, size, sha1, name)); 110 | } 111 | 112 | private void parseClient() 113 | { 114 | final JsonObject client = this.version.getMinecraftClient(); 115 | final String clientURL = client.getAsJsonPrimitive("url").getAsString(); 116 | final int clientSize = client.getAsJsonPrimitive("size").getAsInt(); 117 | final String clientName = clientURL.substring(clientURL.lastIndexOf('/') + 1); 118 | final String clientSha1 = client.getAsJsonPrimitive("sha1").getAsString(); 119 | 120 | this.downloadList.getDownloadableFiles().add(new Downloadable(clientURL, clientSize, clientSha1, clientName)); 121 | } 122 | 123 | private void parseNatives() 124 | { 125 | this.version.getMinecraftLibrariesJson().forEach(jsonElement -> { 126 | final JsonObject obj = jsonElement.getAsJsonObject() 127 | .getAsJsonObject("downloads") 128 | .getAsJsonObject("classifiers"); 129 | 130 | if (obj == null) 131 | return; 132 | 133 | final JsonObject macObj = obj.getAsJsonObject("natives-macos"); 134 | final JsonObject osxObj = obj.getAsJsonObject("natives-osx"); 135 | JsonObject windowsObj = obj.getAsJsonObject(String.format("natives-windows-%s", Platform.getArch())); 136 | if (windowsObj == null) windowsObj = obj.getAsJsonObject("natives-windows"); 137 | final JsonObject linuxObj = obj.getAsJsonObject("natives-linux"); 138 | 139 | if (macObj != null && Platform.isOnMac()) 140 | this.getNativeForOS("mac", macObj); 141 | else if (osxObj != null && Platform.isOnMac()) 142 | this.getNativeForOS("mac", osxObj); 143 | else if (windowsObj != null && Platform.isOnWindows()) 144 | this.getNativeForOS("win", windowsObj); 145 | else if (linuxObj != null && Platform.isOnLinux()) 146 | this.getNativeForOS("linux", linuxObj); 147 | }); 148 | } 149 | 150 | private void getNativeForOS(@NotNull String os, @NotNull JsonObject obj) 151 | { 152 | final String url = obj.getAsJsonPrimitive("url").getAsString(); 153 | final int size = obj.getAsJsonPrimitive("size").getAsInt(); 154 | final String path = obj.getAsJsonPrimitive("path").getAsString(); 155 | final String name = "natives/" + path.substring(path.lastIndexOf('/') + 1); 156 | final String sha1 = obj.getAsJsonPrimitive("sha1").getAsString(); 157 | 158 | if(!os.equals("mac")) 159 | { 160 | if (name.contains("-3.2.1-") && name.contains("lwjgl")) 161 | return; 162 | if (name.contains("-2.9.2-") && name.contains("lwjgl")) 163 | return; 164 | } 165 | else if(name.contains("-3.2.2-") && name.contains("lwjgl")) 166 | return; 167 | 168 | this.downloadList.getDownloadableFiles().add(new Downloadable(url, size, sha1, name)); 169 | } 170 | 171 | private void parseAssets() throws Exception 172 | { 173 | final Set toDownload = new HashSet<>(this.version.getAnotherAssets()); 174 | final AssetIndex assetIndex; 175 | 176 | if(this.version.getCustomAssetIndex() == null) 177 | assetIndex = new GsonBuilder() 178 | .disableHtmlEscaping() 179 | .create() 180 | .fromJson(IOUtils.getContent(new URL(this.version.getMinecraftAssetIndex().get("url").getAsString())), AssetIndex.class); 181 | else assetIndex = this.version.getCustomAssetIndex(); 182 | 183 | assetIndex.getUniqueObjects() 184 | .values() 185 | .forEach(assetDownloadable -> 186 | toDownload.add(new AssetDownloadable(assetDownloadable.getHash(), assetDownloadable.getSize()))); 187 | this.downloadList.getDownloadableAssets().addAll(toDownload); 188 | } 189 | 190 | private boolean checkRules(@NotNull JsonObject obj) 191 | { 192 | final JsonElement rulesElement = obj.get("rules"); 193 | 194 | if (rulesElement == null) 195 | return true; 196 | 197 | final AtomicBoolean canDownload = new AtomicBoolean(true); 198 | 199 | rulesElement.getAsJsonArray().forEach(jsonElement -> { 200 | final JsonObject object = jsonElement.getAsJsonObject(); 201 | final String actionValue = object.getAsJsonPrimitive("action").getAsString(); 202 | final JsonObject osObject = object.getAsJsonObject("os"); 203 | 204 | if (actionValue.equals("allow")) 205 | { 206 | if (osObject == null) return; 207 | 208 | final String os = osObject.getAsJsonPrimitive("name").getAsString(); 209 | canDownload.set(this.check(os)); 210 | } 211 | else if (actionValue.equals("disallow")) 212 | { 213 | final String os = osObject.getAsJsonPrimitive("name").getAsString(); 214 | canDownload.set(!this.check(os)); 215 | } 216 | }); 217 | 218 | return canDownload.get(); 219 | } 220 | 221 | private boolean check(@NotNull String os) 222 | { 223 | return (os.equalsIgnoreCase("osx") && Platform.isOnMac()) || 224 | (os.equalsIgnoreCase("macos") && Platform.isOnMac()) || 225 | (os.equalsIgnoreCase("windows") && Platform.isOnWindows()) || 226 | (os.equalsIgnoreCase("linux") && Platform.isOnLinux()); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/AssetDownloadable.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | /** 4 | * This class represents an asset. 5 | */ 6 | public class AssetDownloadable 7 | { 8 | private final String hash; 9 | private final long size; 10 | private final String url; 11 | private final String file; 12 | 13 | /** 14 | * Construct a new asset object. 15 | * @param hash the sha1 of the asset. 16 | * @param size the size of the asset. 17 | */ 18 | public AssetDownloadable(String hash, long size) 19 | { 20 | this.hash = hash; 21 | this.size = size; 22 | final String assetsPath = "/" + this.hash.substring(0, 2) + "/" + this.hash; 23 | this.url = "https://resources.download.minecraft.net" + assetsPath; 24 | this.file = "objects" + assetsPath; 25 | } 26 | 27 | /** 28 | * Get the hash of the asset. 29 | * @return the sha1 of the asset. 30 | */ 31 | public String getHash() 32 | { 33 | return this.hash; 34 | } 35 | 36 | /** 37 | * Get the length of the asset. 38 | * @return the size of the asset. 39 | */ 40 | public long getSize() 41 | { 42 | return this.size; 43 | } 44 | 45 | /** 46 | * Get the remote url of the asset. 47 | * @return the url of the asset. 48 | */ 49 | public String getUrl() 50 | { 51 | return this.url; 52 | } 53 | 54 | /** 55 | * Get the file path of the asset. 56 | * @return the relative local path of this asset. 57 | */ 58 | public String getFile() 59 | { 60 | return this.file; 61 | } 62 | 63 | @Override 64 | public boolean equals(Object o) 65 | { 66 | if (this == o) return true; 67 | if (o == null || getClass() != o.getClass()) return false; 68 | 69 | final AssetDownloadable that = (AssetDownloadable)o; 70 | return this.file.equals(that.file) && this.size == that.size && this.hash.equals(that.hash) && this.url.equals(that.url); 71 | } 72 | 73 | @Override 74 | public int hashCode() 75 | { 76 | int result = this.hash.hashCode(); 77 | result = 31 * result + Long.hashCode(this.size); 78 | result = 31 * result + this.url.hashCode(); 79 | return result; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/AssetIndex.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | import java.util.Collections; 4 | import java.util.LinkedHashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * This class represents an asset index of a Minecraft version. 9 | */ 10 | public class AssetIndex 11 | { 12 | private final Map objects = new LinkedHashMap<>(); 13 | 14 | /** 15 | * Internal getter. 16 | * @return asset objects 17 | */ 18 | private Map getObjects() 19 | { 20 | return this.objects; 21 | } 22 | 23 | /** 24 | * Get an immutable collection of asset objects. 25 | * @return asset objects. 26 | */ 27 | public Map getUniqueObjects() 28 | { 29 | return Collections.unmodifiableMap(this.getObjects()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/CurseFileInfo.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import fr.flowarg.flowupdater.utils.FlowUpdaterException; 6 | import fr.flowarg.flowupdater.utils.IOUtils; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.net.URL; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Objects; 13 | 14 | /** 15 | * This class represents a file in the CurseForge API. 16 | */ 17 | public class CurseFileInfo 18 | { 19 | private final int projectID; 20 | private final int fileID; 21 | 22 | /** 23 | * Construct a new CurseFileInfo object. 24 | * @param projectID the ID of the project. 25 | * @param fileID the ID of the file. 26 | */ 27 | public CurseFileInfo(int projectID, int fileID) 28 | { 29 | this.projectID = projectID; 30 | this.fileID = fileID; 31 | } 32 | 33 | /** 34 | * Retrieve a collection of {@link CurseFileInfo} by parsing a remote JSON file. 35 | * @param jsonUrl the url of the remote JSON file. 36 | * @return a collection of {@link CurseFileInfo}. 37 | */ 38 | public static @NotNull List getFilesFromJson(URL jsonUrl) 39 | { 40 | final List result = new ArrayList<>(); 41 | final JsonObject object = IOUtils.readJson(jsonUrl).getAsJsonObject(); 42 | final JsonArray mods = object.getAsJsonArray("curseFiles"); 43 | mods.forEach(curseModElement -> { 44 | final JsonObject obj = curseModElement.getAsJsonObject(); 45 | final int projectID = obj.get("projectID").getAsInt(); 46 | final int fileID = obj.get("fileID").getAsInt(); 47 | result.add(new CurseFileInfo(projectID, fileID)); 48 | }); 49 | return result; 50 | } 51 | 52 | /** 53 | * Retrieve a collection of {@link CurseFileInfo} by parsing a remote JSON file. 54 | * @param jsonUrl the url of the remote JSON file. 55 | * @return a collection of {@link CurseFileInfo}. 56 | */ 57 | public static @NotNull List getFilesFromJson(String jsonUrl) 58 | { 59 | try 60 | { 61 | return getFilesFromJson(new URL(jsonUrl)); 62 | } 63 | catch (Exception e) 64 | { 65 | throw new FlowUpdaterException(e); 66 | } 67 | } 68 | 69 | /** 70 | * Get the project ID. 71 | * @return the project ID. 72 | */ 73 | public int getProjectID() 74 | { 75 | return this.projectID; 76 | } 77 | 78 | /** 79 | * Get the file ID. 80 | * @return the file ID. 81 | */ 82 | public int getFileID() 83 | { 84 | return this.fileID; 85 | } 86 | 87 | @Override 88 | public boolean equals(Object o) 89 | { 90 | if (this == o) return true; 91 | if (o == null || this.getClass() != o.getClass()) return false; 92 | final CurseFileInfo that = (CurseFileInfo)o; 93 | return this.projectID == that.projectID && this.fileID == that.fileID; 94 | } 95 | 96 | @Override 97 | public int hashCode() 98 | { 99 | return Objects.hash(this.projectID, this.fileID); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/CurseModPackInfo.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | /** 4 | * This class represents a mod pack file in the CurseForge API. 5 | */ 6 | public class CurseModPackInfo extends CurseFileInfo 7 | { 8 | private final boolean installExtFiles; 9 | private final String[] excluded; 10 | 11 | private String url = ""; 12 | 13 | /** 14 | * Construct a new CurseModPackInfo object. 15 | * @param projectID the ID of the project. 16 | * @param fileID the ID of the file. 17 | * @param installExtFiles should install external files like config and resource packs. 18 | * @param excluded mods to exclude. 19 | */ 20 | public CurseModPackInfo(int projectID, int fileID, boolean installExtFiles, String... excluded) 21 | { 22 | super(projectID, fileID); 23 | this.installExtFiles = installExtFiles; 24 | this.excluded = excluded; 25 | } 26 | 27 | /** 28 | * Construct a new CurseModPackInfo object. 29 | * @param url the url of the custom mod pack endpoint. 30 | * @param installExtFiles should install external files like config and resource packs. 31 | * @param excluded mods to exclude. 32 | */ 33 | public CurseModPackInfo(String url, boolean installExtFiles, String... excluded) 34 | { 35 | super(0, 0); 36 | this.url = url; 37 | this.installExtFiles = installExtFiles; 38 | this.excluded = excluded; 39 | } 40 | 41 | /** 42 | * Get the {@link #installExtFiles} option. 43 | * @return the {@link #installExtFiles} option. 44 | */ 45 | public boolean isInstallExtFiles() 46 | { 47 | return this.installExtFiles; 48 | } 49 | 50 | /** 51 | * Get the excluded mods. 52 | * @return the excluded mods. 53 | */ 54 | public String[] getExcluded() 55 | { 56 | return this.excluded; 57 | } 58 | 59 | /** 60 | * Get the url of the mod pack endpoint. 61 | * Should be of the form: 62 | * { 63 | * "data": { 64 | * "fileName": "modpack.zip", 65 | * "downloadUrl": "https://site.com/modpack.zip", 66 | * "fileLength": 123456789, 67 | * "hashes": [ 68 | * { 69 | * "value": "a02b0499589bc6982fced96dcc85c3b3e33af119", 70 | * "algo": 1 71 | * } 72 | * ] 73 | * } 74 | * } 75 | * @return the url of the mod pack endpoint if it's not from CurseForge's servers. 76 | */ 77 | public String getUrl() 78 | { 79 | return this.url; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/Downloadable.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * This class represents a classic downloadable file such as a library, the client/server or natives. 7 | */ 8 | public class Downloadable 9 | { 10 | private final String url; 11 | private final long size; 12 | private final String sha1; 13 | private final String name; 14 | 15 | /** 16 | * Construct a new Downloadable object. 17 | * @param url the url where to download the file. 18 | * @param size the size of the file. 19 | * @param sha1 the sha1 of the file. 20 | * @param name the name (path) of the file. 21 | */ 22 | public Downloadable(String url, long size, String sha1, String name) 23 | { 24 | this.url = url; 25 | this.size = size; 26 | this.sha1 = sha1; 27 | this.name = name; 28 | } 29 | 30 | /** 31 | * Get the url of the file. 32 | * @return the url of the file. 33 | */ 34 | public String getUrl() 35 | { 36 | return this.url; 37 | } 38 | 39 | /** 40 | * Get the size of the file. 41 | * @return the size of the file. 42 | */ 43 | public long getSize() 44 | { 45 | return this.size; 46 | } 47 | 48 | /** 49 | * Get the sha1 of the file. 50 | * @return the sha1 of the file. 51 | */ 52 | public String getSha1() 53 | { 54 | return this.sha1; 55 | } 56 | 57 | /** 58 | * Get the relative path of the file. 59 | * @return the relative path of the file. 60 | */ 61 | public String getName() 62 | { 63 | return this.name; 64 | } 65 | 66 | @Override 67 | public boolean equals(Object o) 68 | { 69 | if (this == o) return true; 70 | if (o == null || getClass() != o.getClass()) return false; 71 | final Downloadable that = (Downloadable)o; 72 | return this.size == that.size && 73 | Objects.equals(this.url, that.url) && 74 | Objects.equals(this.sha1, that.sha1) && 75 | Objects.equals(this.name, that.name); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/ExternalFile.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import fr.flowarg.flowupdater.utils.FlowUpdaterException; 6 | import fr.flowarg.flowupdater.utils.IOUtils; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.net.URL; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * This class represents an external file object. 15 | */ 16 | public class ExternalFile 17 | { 18 | private final String path; 19 | private final String downloadURL; 20 | private final String sha1; 21 | private final long size; 22 | private final boolean update; 23 | 24 | /** 25 | * Construct a new ExternalFile object. 26 | * @param path Path of external file. 27 | * @param sha1 Sha1 of external file. 28 | * @param size Size of external file. 29 | * @param downloadURL external file URL. 30 | */ 31 | public ExternalFile(String path, String downloadURL, String sha1, long size) 32 | { 33 | this.path = path; 34 | this.downloadURL = downloadURL; 35 | this.sha1 = sha1; 36 | this.size = size; 37 | this.update = true; 38 | } 39 | 40 | /** 41 | * Construct a new ExternalFile object. 42 | * @param path Path of external file. 43 | * @param sha1 Sha1 of external file. 44 | * @param size Size of external file. 45 | * @param downloadURL external file URL. 46 | * @param update false: not checking if the file is valid. true: checking if the file is valid. 47 | */ 48 | public ExternalFile(String path, String downloadURL, String sha1, long size, boolean update) 49 | { 50 | this.path = path; 51 | this.downloadURL = downloadURL; 52 | this.sha1 = sha1; 53 | this.size = size; 54 | this.update = update; 55 | } 56 | 57 | /** 58 | * Provide a List of external file from a JSON file. 59 | * Template of a JSON file : 60 | *
 61 |      * {
 62 |      *   "extfiles": [
 63 |      *     {
 64 |      *       "path": "other/path/AnExternalFile.binpatch",
 65 |      *       "downloadURL": "https://url.com/launcher/extern/AnExtFile.binpatch",
 66 |      *       "sha1": "40f784892989du0fc6f45c895d4l6c5db9378f48",
 67 |      *       "size": 25652
 68 |      *     },
 69 |      *     {
 70 |      *       "path": "config/config.json",
 71 |      *       "downloadURL": "https://url.com/launcher/ext/modconfig.json",
 72 |      *       "sha1": "eef74b3fbab6400cb14b02439cf092cca3c2125c",
 73 |      *       "size": 19683,
 74 |      *       "update": false
 75 |      *     }
 76 |      *   ]
 77 |      * }
 78 |      * 
79 | * @param jsonUrl the JSON file URL. 80 | * @return an external file list. 81 | */ 82 | public static @NotNull List getExternalFilesFromJson(URL jsonUrl) 83 | { 84 | final List result = new ArrayList<>(); 85 | final JsonArray extfiles = IOUtils.readJson(jsonUrl).getAsJsonObject().getAsJsonArray("extfiles"); 86 | extfiles.forEach(extFileElement -> { 87 | final JsonObject obj = extFileElement.getAsJsonObject(); 88 | final String path = obj.get("path").getAsString(); 89 | final String sha1 = obj.get("sha1").getAsString(); 90 | final String downloadURL = obj.get("downloadURL").getAsString(); 91 | final long size = obj.get("size").getAsLong(); 92 | if(obj.get("update") != null) 93 | result.add(new ExternalFile(path, downloadURL, sha1, size, obj.get("update").getAsBoolean())); 94 | else result.add(new ExternalFile(path, downloadURL, sha1, size)); 95 | }); 96 | return result; 97 | } 98 | 99 | /** 100 | * Provide a List of external file from a JSON file. 101 | * @param jsonUrl the JSON file URL. 102 | * @return an external file list. 103 | */ 104 | public static @NotNull List getExternalFilesFromJson(String jsonUrl) 105 | { 106 | try 107 | { 108 | return getExternalFilesFromJson(new URL(jsonUrl)); 109 | } catch (Exception e) 110 | { 111 | throw new FlowUpdaterException(e); 112 | } 113 | } 114 | 115 | /** 116 | * Get the path of the external file. 117 | * @return the path of the external file. 118 | */ 119 | public String getPath() 120 | { 121 | return this.path; 122 | } 123 | 124 | /** 125 | * Get the url of the external file. 126 | * @return the url of the external file. 127 | */ 128 | public String getDownloadURL() 129 | { 130 | return this.downloadURL; 131 | } 132 | 133 | /** 134 | * Get the sha1 of the external file. 135 | * @return the sha1 of the external file. 136 | */ 137 | public String getSha1() 138 | { 139 | return this.sha1; 140 | } 141 | 142 | /** 143 | * Get the size of the external file. 144 | * @return the size of the external file. 145 | */ 146 | public long getSize() 147 | { 148 | return this.size; 149 | } 150 | 151 | /** 152 | * Should {@link fr.flowarg.flowupdater.utils.ExternalFileDeleter} check the file? 153 | * @return if the external file deleter should check and delete the file. 154 | */ 155 | public boolean isUpdate() 156 | { 157 | return this.update; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/MCP.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | import com.google.gson.JsonObject; 4 | import fr.flowarg.flowupdater.utils.FlowUpdaterException; 5 | import fr.flowarg.flowupdater.utils.IOUtils; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.net.MalformedURLException; 9 | import java.net.URL; 10 | 11 | /** 12 | * This class represents an MCP object. 13 | */ 14 | public class MCP 15 | { 16 | private final String clientURL; 17 | private final String clientSha1; 18 | private final long clientSize; 19 | 20 | /** 21 | * Construct a new MCP object. 22 | * @param clientURL URL of client.jar 23 | * @param clientSha1 SHA1 of client.jar 24 | * @param clientSize Size (bytes) of client.jar 25 | */ 26 | public MCP(String clientURL, String clientSha1, long clientSize) 27 | { 28 | this.clientURL = clientURL; 29 | this.clientSha1 = clientSha1; 30 | this.clientSize = clientSize; 31 | } 32 | 33 | /** 34 | * Provide an MCP instance from a JSON file. 35 | * Template of a JSON file : 36 | *
37 |      * {
38 |      *   "clientURL": "https://url.com/launcher/client.jar",
39 |      *   "clientSha1": "9b0a9d70320811e7af2e8741653f029151a6719a",
40 |      *   "clientSize": 1234
41 |      * }
42 |      * 
43 | * @param jsonUrl the JSON file URL. 44 | * @return the MCP instance. 45 | */ 46 | public static @NotNull MCP getMCPFromJson(URL jsonUrl) 47 | { 48 | final JsonObject object = IOUtils.readJson(jsonUrl).getAsJsonObject(); 49 | return new MCP(object.get("clientURL").getAsString(), object.get("clientSha1").getAsString(), object.get("clientSize").getAsLong()); 50 | } 51 | 52 | /** 53 | * Provide an MCP instance from a JSON file. 54 | * @param jsonUrl the JSON file URL. 55 | * @return the MCP instance. 56 | */ 57 | public static @NotNull MCP getMCPFromJson(String jsonUrl) 58 | { 59 | try 60 | { 61 | return getMCPFromJson(new URL(jsonUrl)); 62 | } catch (Exception e) 63 | { 64 | throw new FlowUpdaterException(e); 65 | } 66 | } 67 | 68 | /** 69 | * Return the client url. 70 | * @return the client url. 71 | */ 72 | public String getClientURL() 73 | { 74 | return this.clientURL; 75 | } 76 | 77 | /** 78 | * Return the client sha1. 79 | * @return the client sha1. 80 | */ 81 | public String getClientSha1() 82 | { 83 | return this.clientSha1; 84 | } 85 | 86 | /** 87 | * Return the client size. 88 | * @return the client size. 89 | */ 90 | public long getClientSize() 91 | { 92 | return this.clientSize; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/Mod.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import fr.flowarg.flowupdater.utils.FlowUpdaterException; 7 | import fr.flowarg.flowupdater.utils.IOUtils; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.net.URL; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * This class represents a Mod object. 16 | */ 17 | public class Mod 18 | { 19 | private final String name; 20 | private final String sha1; 21 | private final long size; 22 | private final String downloadURL; 23 | 24 | /** 25 | * Construct a new Mod object. 26 | * @param name Name of mod file. 27 | * @param downloadURL Mod download URL. 28 | * @param sha1 Sha1 of mod file. 29 | * @param size Size of mod file. 30 | */ 31 | public Mod(String name, String downloadURL, String sha1, long size) 32 | { 33 | this.name = name; 34 | this.downloadURL = downloadURL; 35 | this.sha1 = sha1; 36 | this.size = size; 37 | } 38 | 39 | /** 40 | * Provide a List of Mods from a JSON file. 41 | * Template of a JSON file : 42 | *
 43 |      * {
 44 |      *   "mods": [
 45 |      *     {
 46 |      *       "name": "KeyStroke",
 47 |      *       "downloadURL": "https://url.com/launcher/mods/KeyStroke.jar",
 48 |      *       "sha1": "70e564892989d8bbc6f45c895df56c5db9378f48",
 49 |      *       "size": 1234
 50 |      *     },
 51 |      *     {
 52 |      *       "name": "JourneyMap",
 53 |      *       "downloadURL": "https://url.com/launcher/mods/JourneyMap.jar",
 54 |      *       "sha1": "eef74b3fbab6400cb14b02439cf092cca3c2125c",
 55 |      *       "size": 1234
 56 |      *     }
 57 |      *   ]
 58 |      * }
 59 |      * 
60 | * @param jsonUrl the JSON file URL. 61 | * @return a Mod list. 62 | */ 63 | public static @NotNull List getModsFromJson(URL jsonUrl) 64 | { 65 | final List result = new ArrayList<>(); 66 | final JsonObject object = IOUtils.readJson(jsonUrl).getAsJsonObject(); 67 | final JsonArray mods = object.getAsJsonArray("mods"); 68 | mods.forEach(modElement -> result.add(fromJson(modElement))); 69 | return result; 70 | } 71 | 72 | public static Mod fromJson(JsonElement modElement) 73 | { 74 | final JsonObject obj = modElement.getAsJsonObject(); 75 | 76 | return new Mod( 77 | obj.get("name").getAsString(), 78 | obj.get("downloadURL").getAsString(), 79 | obj.get("sha1").getAsString(), 80 | obj.get("size").getAsLong() 81 | ); 82 | } 83 | 84 | /** 85 | * Provide a List of Mods from a JSON file. 86 | * Template of a JSON file : 87 | * @param jsonUrl the JSON file URL. 88 | * @return a Mod list. 89 | */ 90 | public static @NotNull List getModsFromJson(String jsonUrl) 91 | { 92 | try 93 | { 94 | return getModsFromJson(new URL(jsonUrl)); 95 | } 96 | catch (Exception e) 97 | { 98 | throw new FlowUpdaterException(e); 99 | } 100 | } 101 | 102 | /** 103 | * Get the mod name. 104 | * @return the mod name. 105 | */ 106 | public String getName() 107 | { 108 | return this.name; 109 | } 110 | 111 | /** 112 | * Get the sha1 of the mod. 113 | * @return the sha1 of the mod. 114 | */ 115 | public String getSha1() 116 | { 117 | return this.sha1; 118 | } 119 | 120 | /** 121 | * Get the mod size. 122 | * @return the mod size. 123 | */ 124 | public long getSize() 125 | { 126 | return this.size; 127 | } 128 | 129 | /** 130 | * Get the mod url. 131 | * @return the mod url. 132 | */ 133 | public String getDownloadURL() 134 | { 135 | return this.downloadURL; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/ModrinthModPackInfo.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | public class ModrinthModPackInfo extends ModrinthVersionInfo 4 | { 5 | private final boolean installExtFiles; 6 | private final String[] excluded; 7 | 8 | public ModrinthModPackInfo(String projectReference, String versionNumber, boolean installExtFiles, String... excluded) 9 | { 10 | super(projectReference, versionNumber); 11 | this.installExtFiles = installExtFiles; 12 | this.excluded = excluded; 13 | } 14 | 15 | public ModrinthModPackInfo(String versionId, boolean installExtFiles, String... excluded) 16 | { 17 | super(versionId); 18 | this.installExtFiles = installExtFiles; 19 | this.excluded = excluded; 20 | } 21 | 22 | /** 23 | * Get the {@link #installExtFiles} option. 24 | * @return the {@link #installExtFiles} option. 25 | */ 26 | public boolean isInstallExtFiles() 27 | { 28 | return this.installExtFiles; 29 | } 30 | 31 | /** 32 | * Get the excluded mods. 33 | * @return the excluded mods. 34 | */ 35 | public String[] getExcluded() 36 | { 37 | return this.excluded; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/ModrinthVersionInfo.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonNull; 6 | import com.google.gson.JsonObject; 7 | import fr.flowarg.flowupdater.utils.FlowUpdaterException; 8 | import fr.flowarg.flowupdater.utils.IOUtils; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class ModrinthVersionInfo 17 | { 18 | private String projectReference = ""; 19 | private String versionNumber = ""; 20 | private String versionId = ""; 21 | 22 | /** 23 | * Construct a new ModrinthVersionInfo object. 24 | * @param projectReference the project reference can be slug or id. 25 | * @param versionNumber the version number (and NOT the version name unless they are the same). 26 | */ 27 | public ModrinthVersionInfo(String projectReference, String versionNumber) 28 | { 29 | this.projectReference = projectReference.trim(); 30 | this.versionNumber = versionNumber.trim(); 31 | } 32 | 33 | /** 34 | * Construct a new ModrinthVersionInfo object. 35 | * This constructor doesn't need a project reference because 36 | * we can access the version without any project information. 37 | * @param versionId the version id. 38 | */ 39 | public ModrinthVersionInfo(String versionId) 40 | { 41 | this.versionId = versionId.trim(); 42 | } 43 | 44 | public static @NotNull List getModrinthVersionsFromJson(URL jsonUrl) 45 | { 46 | final List result = new ArrayList<>(); 47 | final JsonObject object = IOUtils.readJson(jsonUrl).getAsJsonObject(); 48 | final JsonArray mods = object.getAsJsonArray("modrinthMods"); 49 | mods.forEach(modElement -> { 50 | final JsonObject obj = modElement.getAsJsonObject(); 51 | final JsonElement versionIdElement = obj.get("versionId"); 52 | 53 | if(versionIdElement instanceof JsonNull) 54 | result.add(new ModrinthVersionInfo(obj.get("projectReference").getAsString(), obj.get("versionNumber").getAsString())); 55 | else result.add(new ModrinthVersionInfo(versionIdElement.getAsString())); 56 | }); 57 | return result; 58 | } 59 | 60 | public static @NotNull List getModrinthVersionsFromJson(String jsonUrl) 61 | { 62 | try 63 | { 64 | return getModrinthVersionsFromJson(new URL(jsonUrl)); 65 | } catch (Exception e) 66 | { 67 | throw new FlowUpdaterException(e); 68 | } 69 | } 70 | 71 | public String getProjectReference() 72 | { 73 | return this.projectReference; 74 | } 75 | 76 | public String getVersionNumber() 77 | { 78 | return this.versionNumber; 79 | } 80 | 81 | public String getVersionId() 82 | { 83 | return this.versionId; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/OptiFineInfo.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.download.json; 2 | 3 | /** 4 | * This class represents an OptiFineInfo object. 5 | */ 6 | public class OptiFineInfo 7 | { 8 | private final String version; 9 | private final boolean preview; 10 | 11 | /** 12 | * Construct a new OptiFineInfo object. 13 | * @param version the OptiFine's version. 14 | * @param preview if the version is a preview. 15 | */ 16 | public OptiFineInfo(String version, boolean preview) 17 | { 18 | this.version = version; 19 | this.preview = preview; 20 | } 21 | 22 | /** 23 | * Construct a new OptiFineInfo object, use {@link OptiFineInfo#OptiFineInfo(String, boolean)} . 24 | * @param version the OptiFine's version. 25 | */ 26 | public OptiFineInfo(String version) 27 | { 28 | this(version, version.startsWith("preview_")); 29 | } 30 | 31 | /** 32 | * Get the OptiFine's version. 33 | * @return the OptiFine's version. 34 | */ 35 | public String getVersion() 36 | { 37 | return this.version; 38 | } 39 | 40 | /** 41 | * Is the version a preview? 42 | * @return if the version is a preview or not. 43 | */ 44 | public boolean isPreview() 45 | { 46 | return this.preview; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/json/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains some objects that can be/are parsed as a JSON. 3 | */ 4 | package fr.flowarg.flowupdater.download.json; -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/download/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains some things about download stuff. 3 | */ 4 | package fr.flowarg.flowupdater.download; -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/Integration.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.integrations; 2 | 3 | import fr.flowarg.flowlogger.ILogger; 4 | 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | 8 | /** 9 | * The new Integration system replaces an old plugin system 10 | * which had some problems such as unavailability to communicate directly with FlowUpdater. 11 | * This new system is easier to use: no more annoying updater's options, no more extra-dependencies. 12 | * Polymorphism and inheritance can now be used to avoid code duplication. 13 | */ 14 | public abstract class Integration 15 | { 16 | protected final ILogger logger; 17 | protected final Path folder; 18 | 19 | /** 20 | * Default constructor of a basic Integration. 21 | * @param logger the logger used. 22 | * @param folder the folder where the plugin can work. 23 | * @throws Exception if an error occurred. 24 | */ 25 | public Integration(ILogger logger, Path folder) throws Exception 26 | { 27 | this.logger = logger; 28 | this.folder = folder; 29 | Files.createDirectories(this.folder); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/IntegrationManager.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.integrations; 2 | 3 | import fr.flowarg.flowio.FileUtils; 4 | import fr.flowarg.flowlogger.ILogger; 5 | import fr.flowarg.flowupdater.FlowUpdater; 6 | import fr.flowarg.flowupdater.download.DownloadList; 7 | import fr.flowarg.flowupdater.download.IProgressCallback; 8 | import fr.flowarg.flowupdater.download.Step; 9 | import fr.flowarg.flowupdater.download.json.*; 10 | import fr.flowarg.flowupdater.integrations.curseforgeintegration.CurseForgeIntegration; 11 | import fr.flowarg.flowupdater.integrations.curseforgeintegration.CurseModPack; 12 | import fr.flowarg.flowupdater.integrations.curseforgeintegration.ICurseForgeCompatible; 13 | import fr.flowarg.flowupdater.integrations.modrinthintegration.IModrinthCompatible; 14 | import fr.flowarg.flowupdater.integrations.modrinthintegration.ModrinthIntegration; 15 | import fr.flowarg.flowupdater.integrations.modrinthintegration.ModrinthModPack; 16 | import fr.flowarg.flowupdater.integrations.optifineintegration.IOptiFineCompatible; 17 | import fr.flowarg.flowupdater.integrations.optifineintegration.OptiFine; 18 | import fr.flowarg.flowupdater.integrations.optifineintegration.OptiFineIntegration; 19 | import fr.flowarg.flowupdater.utils.FlowUpdaterException; 20 | import org.jetbrains.annotations.NotNull; 21 | 22 | import java.nio.file.Files; 23 | import java.nio.file.Path; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | /** 28 | * The integration manager loads integration's stuff at the startup of FlowUpdater. 29 | */ 30 | public class IntegrationManager 31 | { 32 | private final IProgressCallback progressCallback; 33 | private final ILogger logger; 34 | private final DownloadList downloadList; 35 | 36 | /** 37 | * Construct a new Integration Manager. 38 | * @param updater a {@link FlowUpdater} instance. 39 | */ 40 | public IntegrationManager(@NotNull FlowUpdater updater) 41 | { 42 | this.progressCallback = updater.getCallback(); 43 | this.logger = updater.getLogger(); 44 | this.downloadList = updater.getDownloadList(); 45 | } 46 | 47 | /** 48 | * This method loads the CurseForge integration and fetches some data. 49 | * @param dir the installation directory. 50 | * @param curseForgeCompatible a version that accepts CurseForge's feature stuff. 51 | */ 52 | public void loadCurseForgeIntegration(Path dir, ICurseForgeCompatible curseForgeCompatible) 53 | { 54 | this.progressCallback.step(Step.INTEGRATION); 55 | try 56 | { 57 | final CurseModPackInfo modPackInfo = curseForgeCompatible.getCurseModPackInfo(); 58 | final List allCurseMods = new ArrayList<>(); 59 | 60 | if(curseForgeCompatible.getCurseMods().isEmpty() && modPackInfo == null) 61 | { 62 | curseForgeCompatible.setAllCurseMods(allCurseMods); 63 | return; 64 | } 65 | 66 | final CurseForgeIntegration curseForgeIntegration = new CurseForgeIntegration(this.logger, dir.getParent().resolve(".cfp")); 67 | 68 | for (CurseFileInfo info : curseForgeCompatible.getCurseMods()) 69 | { 70 | try { 71 | final Mod mod = curseForgeIntegration.fetchMod(info); 72 | 73 | if(mod == null) 74 | break; 75 | 76 | this.checkMod(mod, allCurseMods, dir); 77 | } 78 | catch (Exception e) 79 | { 80 | this.logger.printStackTrace(e); 81 | } 82 | } 83 | 84 | if (modPackInfo == null) 85 | { 86 | curseForgeCompatible.setAllCurseMods(allCurseMods); 87 | return; 88 | } 89 | 90 | this.progressCallback.step(Step.MOD_PACK); 91 | final CurseModPack modPack = curseForgeIntegration.getCurseModPack(modPackInfo); 92 | this.logger.info(String.format("Loading mod pack: %s (%s) by %s.", modPack.getName(), modPack.getVersion(), modPack.getAuthor())); 93 | modPack.getMods().forEach(mod -> { 94 | allCurseMods.add(mod); 95 | try 96 | { 97 | final Path filePath = dir.resolve(mod.getName()); 98 | boolean flag = false; 99 | for (String exclude : modPackInfo.getExcluded()) 100 | { 101 | if (!mod.getName().equalsIgnoreCase(exclude)) continue; 102 | 103 | flag = !mod.isRequired(); 104 | break; 105 | } 106 | 107 | if(flag) return; 108 | 109 | if(Files.exists(filePath) 110 | && Files.size(filePath) == mod.getSize() 111 | && (mod.getSha1().isEmpty() || FileUtils.getSHA1(filePath).equalsIgnoreCase(mod.getSha1()))) 112 | return; 113 | 114 | Files.deleteIfExists(filePath); 115 | this.downloadList.getMods().add(mod); 116 | } catch (Exception e) 117 | { 118 | this.logger.printStackTrace(e); 119 | } 120 | }); 121 | 122 | curseForgeCompatible.setAllCurseMods(allCurseMods); 123 | } 124 | catch (Exception e) 125 | { 126 | throw new FlowUpdaterException(e); 127 | } 128 | } 129 | 130 | public void loadModrinthIntegration(Path dir, IModrinthCompatible modrinthCompatible) 131 | { 132 | try 133 | { 134 | final ModrinthModPackInfo modPackInfo = modrinthCompatible.getModrinthModPackInfo(); 135 | final List allModrinthMods = new ArrayList<>(); 136 | 137 | if(modrinthCompatible.getModrinthMods().isEmpty() && modPackInfo == null) 138 | { 139 | modrinthCompatible.setAllModrinthMods(allModrinthMods); 140 | return; 141 | } 142 | 143 | final ModrinthIntegration modrinthIntegration = new ModrinthIntegration(this.logger, dir.getParent().resolve(".modrinth")); 144 | 145 | for (ModrinthVersionInfo info : modrinthCompatible.getModrinthMods()) 146 | { 147 | final Mod mod = modrinthIntegration.fetchMod(info); 148 | this.checkMod(mod, allModrinthMods, dir); 149 | } 150 | 151 | if (modPackInfo == null) 152 | { 153 | modrinthCompatible.setAllModrinthMods(allModrinthMods); 154 | return; 155 | } 156 | 157 | this.progressCallback.step(Step.MOD_PACK); 158 | final ModrinthModPack modPack = modrinthIntegration.getCurseModPack(modPackInfo); 159 | this.logger.info(String.format("Loading mod pack: %s (%s).", modPack.getName(), modPack.getVersion())); 160 | modrinthCompatible.setModrinthModPack(modPack); 161 | 162 | for (Mod mod : modPack.getMods()) 163 | this.checkMod(mod, allModrinthMods, dir); 164 | 165 | modrinthCompatible.setAllModrinthMods(allModrinthMods); 166 | } 167 | catch (Exception e) 168 | { 169 | throw new FlowUpdaterException(e); 170 | } 171 | } 172 | 173 | /** 174 | * This method loads the OptiFine integration and fetches OptiFine data. 175 | * @param dir the installation directory. 176 | * @param optiFineCompatible the current Forge version. 177 | */ 178 | public void loadOptiFineIntegration(Path dir, @NotNull IOptiFineCompatible optiFineCompatible) 179 | { 180 | final OptiFineInfo info = optiFineCompatible.getOptiFineInfo(); 181 | 182 | if(info == null) 183 | return; 184 | 185 | try 186 | { 187 | final OptiFineIntegration optifineIntegration = new OptiFineIntegration(this.logger, dir.getParent().resolve(".op")); 188 | final OptiFine optifine = optifineIntegration.getOptiFine(info.getVersion(), info.isPreview()); 189 | this.downloadList.setOptiFine(optifine); 190 | } catch (Exception e) 191 | { 192 | throw new FlowUpdaterException(e); 193 | } 194 | } 195 | 196 | private void checkMod(Mod mod, @NotNull List allMods, @NotNull Path dir) throws Exception 197 | { 198 | allMods.add(mod); 199 | 200 | final Path filePath = dir.resolve(mod.getName()); 201 | 202 | if(Files.exists(filePath) 203 | && Files.size(filePath) == mod.getSize() 204 | && (mod.getSha1().isEmpty() || FileUtils.getSHA1(filePath).equalsIgnoreCase(mod.getSha1()))) 205 | return; 206 | 207 | Files.deleteIfExists(filePath); 208 | this.downloadList.getMods().add(mod); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/CurseModPack.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.integrations.curseforgeintegration; 2 | 3 | import fr.flowarg.flowupdater.download.json.Mod; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Basic object that represents a CurseForge's mod pack. 10 | */ 11 | public class CurseModPack 12 | { 13 | private final String name; 14 | private final String version; 15 | private final String author; 16 | private final List mods; 17 | 18 | CurseModPack(String name, String version, String author, List mods) 19 | { 20 | this.name = name; 21 | this.version = version; 22 | this.author = author; 23 | this.mods = mods; 24 | } 25 | 26 | /** 27 | * Get the mod pack's name. 28 | * @return the mod pack's name. 29 | */ 30 | public String getName() 31 | { 32 | return this.name; 33 | } 34 | 35 | 36 | /** 37 | * Get the mod pack's version. 38 | * @return the mod pack's version. 39 | */ 40 | public String getVersion() 41 | { 42 | return this.version; 43 | } 44 | 45 | /** 46 | * Get the mod pack's author. 47 | * @return the mod pack's author. 48 | */ 49 | public String getAuthor() 50 | { 51 | return this.author; 52 | } 53 | 54 | /** 55 | * Get the mods in the mod pack. 56 | * @return the mods in the mod pack. 57 | */ 58 | public List getMods() 59 | { 60 | return this.mods; 61 | } 62 | 63 | /** 64 | * A Curse Forge's mod from a mod pack. 65 | */ 66 | public static class CurseModPackMod extends Mod 67 | { 68 | private final boolean required; 69 | 70 | CurseModPackMod(String name, String downloadURL, String sha1, long size, boolean required) 71 | { 72 | super(name, downloadURL, sha1, size); 73 | this.required = required; 74 | } 75 | 76 | CurseModPackMod(@NotNull Mod base, boolean required) 77 | { 78 | this(base.getName(), base.getDownloadURL(), base.getSha1(), base.getSize(), required); 79 | } 80 | 81 | /** 82 | * Is the mod required. 83 | * @return if the mod is required. 84 | */ 85 | public boolean isRequired() 86 | { 87 | return this.required; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/ICurseForgeCompatible.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.integrations.curseforgeintegration; 2 | 3 | import fr.flowarg.flowupdater.download.json.CurseFileInfo; 4 | import fr.flowarg.flowupdater.download.json.CurseModPackInfo; 5 | import fr.flowarg.flowupdater.download.json.Mod; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * This class represents an object that using CurseForge features. 11 | */ 12 | public interface ICurseForgeCompatible 13 | { 14 | /** 15 | * Get all curse mods to update. 16 | * @return all curse mods. 17 | */ 18 | List getCurseMods(); 19 | 20 | /** 21 | * Get information about the mod pack to update. 22 | * @return mod pack's information. 23 | */ 24 | CurseModPackInfo getCurseModPackInfo(); 25 | 26 | /** 27 | * Define all curse mods to update. 28 | * @param curseMods curse mods to define. 29 | */ 30 | void setAllCurseMods(List curseMods); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * CurseForge Integration package. 3 | */ 4 | package fr.flowarg.flowupdater.integrations.curseforgeintegration; -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/IModrinthCompatible.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.integrations.modrinthintegration; 2 | 3 | import fr.flowarg.flowupdater.download.json.Mod; 4 | import fr.flowarg.flowupdater.download.json.ModrinthModPackInfo; 5 | import fr.flowarg.flowupdater.download.json.ModrinthVersionInfo; 6 | 7 | import java.util.List; 8 | 9 | public interface IModrinthCompatible 10 | { 11 | /** 12 | * Get all modrinth mods to update. 13 | * @return all modrinth mods. 14 | */ 15 | List getModrinthMods(); 16 | 17 | /** 18 | * Get information about the mod pack to update. 19 | * @return mod pack's information. 20 | */ 21 | ModrinthModPackInfo getModrinthModPackInfo(); 22 | 23 | /** 24 | * Get the modrinth mod pack. 25 | * @return the modrinth mod pack. 26 | */ 27 | ModrinthModPack getModrinthModPack(); 28 | 29 | /** 30 | * Define the modrinth mod pack. 31 | * @param modrinthModPack the modrinth mod pack. 32 | */ 33 | void setModrinthModPack(ModrinthModPack modrinthModPack); 34 | 35 | /** 36 | * Define all modrinth mods to update. 37 | * @param modrinthMods modrinth mods to define. 38 | */ 39 | void setAllModrinthMods(List modrinthMods); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/ModrinthIntegration.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.integrations.modrinthintegration; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import fr.flowarg.flowio.FileUtils; 8 | import fr.flowarg.flowlogger.ILogger; 9 | import fr.flowarg.flowstringer.StringUtils; 10 | import fr.flowarg.flowupdater.download.json.Mod; 11 | import fr.flowarg.flowupdater.download.json.ModrinthModPackInfo; 12 | import fr.flowarg.flowupdater.download.json.ModrinthVersionInfo; 13 | import fr.flowarg.flowupdater.integrations.Integration; 14 | import fr.flowarg.flowupdater.utils.FlowUpdaterException; 15 | import fr.flowarg.flowupdater.utils.IOUtils; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.net.URL; 20 | import java.nio.charset.StandardCharsets; 21 | import java.nio.file.Files; 22 | import java.nio.file.Path; 23 | import java.nio.file.StandardCopyOption; 24 | import java.util.ArrayList; 25 | import java.util.Enumeration; 26 | import java.util.List; 27 | import java.util.zip.ZipEntry; 28 | import java.util.zip.ZipFile; 29 | 30 | public class ModrinthIntegration extends Integration 31 | { 32 | private static final String MODRINTH_URL = "https://api.modrinth.com/v2/"; 33 | private static final String MODRINTH_VERSION_ENDPOINT = "version/{versionId}"; 34 | private static final String MODRINTH_PROJECT_VERSION_ENDPOINT = "project/{projectId}/version"; 35 | 36 | private final List builtInMods = new ArrayList<>(); 37 | 38 | /** 39 | * Default constructor of a basic Integration. 40 | * 41 | * @param logger the logger used. 42 | * @param folder the folder where the plugin can work. 43 | * @throws Exception if an error occurred. 44 | */ 45 | public ModrinthIntegration(ILogger logger, Path folder) throws Exception 46 | { 47 | super(logger, folder); 48 | } 49 | 50 | public Mod fetchMod(@NotNull ModrinthVersionInfo versionInfo) throws Exception 51 | { 52 | if(!versionInfo.getVersionId().isEmpty()) 53 | { 54 | final URL url = new URL(MODRINTH_URL + MODRINTH_VERSION_ENDPOINT 55 | .replace("{versionId}", versionInfo.getVersionId())); 56 | 57 | return this.parseModFile(JsonParser.parseString(IOUtils.getContent(url)).getAsJsonObject()); 58 | } 59 | 60 | final URL url = new URL(MODRINTH_URL + MODRINTH_PROJECT_VERSION_ENDPOINT.replace("{projectId}", versionInfo.getProjectReference())); 61 | final JsonArray versions = JsonParser.parseString(IOUtils.getContent(url)).getAsJsonArray(); 62 | JsonObject version = null; 63 | for (JsonElement jsonElement : versions) 64 | { 65 | if(!jsonElement.getAsJsonObject().get("version_number").getAsString().equals(versionInfo.getVersionNumber())) 66 | continue; 67 | 68 | version = jsonElement.getAsJsonObject(); 69 | break; 70 | } 71 | 72 | if(version == null) 73 | throw new FlowUpdaterException( 74 | "No version found for " + versionInfo.getVersionNumber() + 75 | " in project " + versionInfo.getProjectReference()); 76 | 77 | return this.parseModFile(version); 78 | } 79 | 80 | public Mod parseModFile(@NotNull JsonObject version) 81 | { 82 | final JsonObject fileJson = version.getAsJsonArray("files").get(0).getAsJsonObject(); 83 | final String fileName = fileJson.get("filename").getAsString(); 84 | final String downloadURL = fileJson.get("url").getAsString(); 85 | final String sha1 = fileJson.getAsJsonObject("hashes").get("sha1").getAsString(); 86 | final long size = fileJson.get("size").getAsLong(); 87 | 88 | return new Mod(fileName, downloadURL, sha1, size); 89 | } 90 | 91 | /** 92 | * Get a CurseForge's mod pack object with a project identifier and a file identifier. 93 | * @param info CurseForge's mod pack info. 94 | * @return the curse's mod pack corresponding to given parameters. 95 | * @throws Exception if an error occurred. 96 | */ 97 | public ModrinthModPack getCurseModPack(ModrinthModPackInfo info) throws Exception 98 | { 99 | final Path modPackFile = this.checkForUpdate(info); 100 | if(modPackFile == null) 101 | throw new FlowUpdaterException("Can't find the mod pack file with the provided Modrinth mod pack info."); 102 | this.extractModPack(modPackFile, info.isInstallExtFiles()); 103 | return this.parseMods(); 104 | } 105 | 106 | private @Nullable Path checkForUpdate(@NotNull ModrinthModPackInfo info) throws Exception 107 | { 108 | final Mod modPackFile = this.fetchMod(info); 109 | 110 | if(modPackFile == null) 111 | { 112 | this.logger.err("This mod pack isn't available anymore on Modrinth (for 3rd parties maybe). "); 113 | return null; 114 | } 115 | 116 | final Path outPath = this.folder.resolve(modPackFile.getName()); 117 | 118 | if(Files.notExists(outPath) || !FileUtils.getSHA1(outPath).equalsIgnoreCase(modPackFile.getSha1())) 119 | IOUtils.download(this.logger, new URL(modPackFile.getDownloadURL()), outPath); 120 | 121 | return outPath; 122 | } 123 | 124 | private void extractModPack(@NotNull Path out, boolean installExtFiles) throws Exception 125 | { 126 | this.logger.info("Extracting mod pack..."); 127 | final ZipFile zipFile = new ZipFile(out.toFile(), ZipFile.OPEN_READ, StandardCharsets.UTF_8); 128 | final Path dirPath = this.folder.getParent(); 129 | final Enumeration entries = zipFile.entries(); 130 | while (entries.hasMoreElements()) 131 | { 132 | final ZipEntry entry = entries.nextElement(); 133 | final String entryName = entry.getName(); 134 | final Path flPath = dirPath.resolve(StringUtils.empty(StringUtils.empty(entryName, "client-overrides/"), "overrides/")); 135 | 136 | if(entryName.equalsIgnoreCase("modrinth.index.json")) 137 | { 138 | if(Files.notExists(flPath) || entry.getCrc() != FileUtils.getCRC32(flPath)) 139 | this.transferAndClose(flPath, zipFile, entry); 140 | continue; 141 | } 142 | 143 | final String withoutOverrides = StringUtils.empty(StringUtils.empty(entryName, "overrides/"), "client-overrides/"); 144 | 145 | if(withoutOverrides.startsWith("mods/") || withoutOverrides.startsWith("mods\\")) 146 | { 147 | final String modName = withoutOverrides.substring(withoutOverrides.lastIndexOf('/') + 1); 148 | final Mod mod = new Mod(modName, "", "", entry.getSize()); 149 | this.builtInMods.add(mod); 150 | } 151 | 152 | if(!installExtFiles || Files.exists(flPath)) continue; 153 | 154 | if (flPath.getFileName().toString().endsWith(flPath.getFileSystem().getSeparator())) 155 | Files.createDirectories(flPath); 156 | 157 | if (entry.isDirectory()) continue; 158 | 159 | this.transferAndClose(flPath, zipFile, entry); 160 | } 161 | zipFile.close(); 162 | } 163 | 164 | private @NotNull ModrinthModPack parseMods() throws Exception 165 | { 166 | this.logger.info("Fetching mods..."); 167 | 168 | final Path dirPath = this.folder.getParent(); 169 | final String manifestJson = StringUtils.toString(Files.readAllLines(dirPath.resolve("modrinth.index.json"))); 170 | final JsonObject manifestObj = JsonParser.parseString(manifestJson).getAsJsonObject(); 171 | final String modPackName = manifestObj.get("name").getAsString(); 172 | final String modPackVersion = manifestObj.get("versionId").getAsString(); 173 | final List mods = this.parseManifest(manifestObj); 174 | 175 | return new ModrinthModPack(modPackName, modPackVersion, mods, this.builtInMods); 176 | } 177 | 178 | private @NotNull List parseManifest(@NotNull JsonObject manifestObject) 179 | { 180 | final List mods = new ArrayList<>(); 181 | 182 | final JsonArray files = manifestObject.getAsJsonArray("files"); 183 | 184 | files.forEach(jsonElement -> { 185 | final JsonObject file = jsonElement.getAsJsonObject(); 186 | 187 | if(file.getAsJsonObject("env").get("client").getAsString().equals("unsupported")) 188 | return; 189 | 190 | final String name = StringUtils.empty(StringUtils.empty(file.get("path").getAsString(), "mods/"), "mods\\"); 191 | final String downloadURL = file.getAsJsonArray("downloads").get(0).getAsString(); 192 | final String sha1 = file.getAsJsonObject("hashes").get("sha1").getAsString(); 193 | final long size = file.get("fileSize").getAsLong(); 194 | 195 | mods.add(new Mod(name, downloadURL, sha1, size)); 196 | }); 197 | 198 | return mods; 199 | } 200 | 201 | private void transferAndClose(@NotNull Path flPath, ZipFile zipFile, ZipEntry entry) throws Exception 202 | { 203 | if(Files.notExists(flPath.getParent())) 204 | Files.createDirectories(flPath.getParent()); 205 | 206 | Files.copy(zipFile.getInputStream(entry), flPath, StandardCopyOption.REPLACE_EXISTING); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/ModrinthModPack.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.integrations.modrinthintegration; 2 | 3 | import fr.flowarg.flowupdater.download.json.Mod; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class ModrinthModPack 9 | { 10 | private final String name; 11 | private final String version; 12 | private final List mods; 13 | private final List builtInMods; 14 | 15 | ModrinthModPack(String name, String version, List mods) 16 | { 17 | this(name, version, mods, new ArrayList<>()); 18 | } 19 | 20 | ModrinthModPack(String name, String version, List mods, List builtInMods) 21 | { 22 | this.name = name; 23 | this.version = version; 24 | this.mods = mods; 25 | this.builtInMods = builtInMods; 26 | } 27 | 28 | /** 29 | * Get the mod pack's name. 30 | * @return the mod pack's name. 31 | */ 32 | public String getName() 33 | { 34 | return this.name; 35 | } 36 | 37 | 38 | /** 39 | * Get the mod pack's version. 40 | * @return the mod pack's version. 41 | */ 42 | public String getVersion() 43 | { 44 | return this.version; 45 | } 46 | 47 | /** 48 | * Get the mods in the mod pack. 49 | * @return the mods in the mod pack. 50 | */ 51 | public List getMods() 52 | { 53 | return this.mods; 54 | } 55 | 56 | /** 57 | * Get the built-in mods in the mod pack. 58 | * Built-in mods are mods directly put in the mods folder in the .mrpack file. 59 | * They are not downloaded from a remote server. 60 | * This is not a very good way to add mods because it disables some mod verification on these mods. 61 | * We recommend mod pack creators to use the built-in mods feature only for mods that are not available remotely. 62 | * @return the built-in mods in the mod pack. 63 | */ 64 | public List getBuiltInMods() 65 | { 66 | return this.builtInMods; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Modrinth Integration package. 3 | */ 4 | package fr.flowarg.flowupdater.integrations.modrinthintegration; -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/IOptiFineCompatible.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.integrations.optifineintegration; 2 | 3 | import fr.flowarg.flowupdater.download.json.OptiFineInfo; 4 | 5 | public interface IOptiFineCompatible 6 | { 7 | /** 8 | * Get information about OptiFine to update. 9 | * @return OptiFine's information. 10 | */ 11 | OptiFineInfo getOptiFineInfo(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/OptiFine.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.integrations.optifineintegration; 2 | 3 | /** 4 | * This class represents a basic OptiFine object. 5 | */ 6 | public class OptiFine 7 | { 8 | private final String name; 9 | private final long size; 10 | 11 | OptiFine(String name, long size) 12 | { 13 | this.name = name; 14 | this.size = size; 15 | } 16 | 17 | /** 18 | * Get the OptiFine filename. 19 | * @return the OptiFine filename. 20 | */ 21 | public String getName() { 22 | return this.name; 23 | } 24 | 25 | /** 26 | * Get the OptiFine file size. 27 | * @return the OptiFine file size. 28 | */ 29 | public long getSize() { 30 | return this.size; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/OptiFineIntegration.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.integrations.optifineintegration; 2 | 3 | import fr.flowarg.flowlogger.ILogger; 4 | import fr.flowarg.flowupdater.integrations.Integration; 5 | import fr.flowarg.flowupdater.utils.FlowUpdaterException; 6 | import fr.flowarg.flowupdater.utils.IOUtils; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.net.URL; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.util.Arrays; 13 | import java.util.Optional; 14 | 15 | /** 16 | * This integration supports the download of OptiFine in any version from the official site 17 | * (OptiFine). 18 | */ 19 | public class OptiFineIntegration extends Integration 20 | { 21 | public OptiFineIntegration(ILogger logger, Path folder) throws Exception 22 | { 23 | super(logger, folder); 24 | } 25 | 26 | /** 27 | * Get an OptiFine object from the official website. 28 | * @param optiFineVersion the version of OptiFine 29 | * @param preview if the OptiFine version is a preview. 30 | * @return the object that defines the plugin 31 | */ 32 | public OptiFine getOptiFine(String optiFineVersion, boolean preview) 33 | { 34 | try 35 | { 36 | final String fixedVersion = preview ? (optiFineVersion.startsWith("preview_OptiFine_") ? 37 | optiFineVersion : optiFineVersion.startsWith("OptiFine_") ? 38 | "preview_" + optiFineVersion : "preview_OptiFine_" + optiFineVersion) : 39 | optiFineVersion.startsWith("OptiFine_") ? optiFineVersion : "OptiFine_" + optiFineVersion; 40 | final String name = fixedVersion + ".jar"; 41 | final String newUrl = this.getNewURL(name, preview, fixedVersion); 42 | 43 | return new OptiFine(name, this.checkForUpdatesAndGetSize(name, newUrl)); 44 | } 45 | catch (FlowUpdaterException e) 46 | { 47 | throw e; 48 | } 49 | catch (Exception e) 50 | { 51 | throw new FlowUpdaterException(e); 52 | } 53 | } 54 | 55 | private @NotNull String getNewURL(String name, boolean preview, String optiFineVersion) 56 | { 57 | return "https://optifine.net/downloadx?f=" + 58 | name + 59 | "&x=" + 60 | (preview ? this.getJsonPreview(optiFineVersion) : this.getJson(optiFineVersion)); 61 | } 62 | 63 | private long checkForUpdatesAndGetSize(String name, String newUrl) throws Exception 64 | { 65 | final Path outputPath = this.folder.resolve(name); 66 | if(Files.notExists(outputPath)) 67 | IOUtils.download(this.logger, new URL(newUrl), outputPath); 68 | return Files.size(outputPath); 69 | } 70 | 71 | private @NotNull String getJson(String optiFineVersion) 72 | { 73 | try 74 | { 75 | final String[] respLine = IOUtils.getContent(new URL("https://optifine.net/adloadx?f=OptiFine_" + optiFineVersion)) 76 | .split("\n"); 77 | final Optional result = Arrays.stream(respLine).filter(s -> s.contains("downloadx?f=OptiFine")).findFirst(); 78 | if(result.isPresent()) 79 | return result.get() 80 | .replace("' onclick='onDownload()'>OptiFine " + optiFineVersion.replace("_", " ") + 81 | "", "") 82 | .replace("" + optiFineVersion.replace("_", " ") + 102 | "", "") 103 | .replace(" mods, OptiFine optiFine, ModrinthModPack modrinthModPack) throws Exception 58 | { 59 | if(!this.isUseFileDeleter()) 60 | return; 61 | 62 | final Set badFiles = new HashSet<>(); 63 | final List verifiedFiles = new ArrayList<>(); 64 | 65 | if(this.modsToIgnore != null) 66 | Arrays.stream(this.modsToIgnore).forEach(fileName -> verifiedFiles.add(modsDir.resolve(fileName))); 67 | else if(this.modsToIgnorePattern != null) 68 | { 69 | FileUtils.list(modsDir).stream().filter(path -> !Files.isDirectory(path)).forEach(path -> { 70 | if(this.modsToIgnorePattern.matcher(path.getFileName().toString()).matches()) 71 | verifiedFiles.add(path); 72 | }); 73 | } 74 | 75 | if(modrinthModPack != null) 76 | modrinthModPack.getBuiltInMods().forEach(mod -> verifiedFiles.add(modsDir.resolve(mod.getName()))); 77 | 78 | for(Path fileInDir : FileUtils.list(modsDir).stream().filter(path -> !Files.isDirectory(path)).collect(Collectors.toList())) 79 | { 80 | if(verifiedFiles.contains(fileInDir)) 81 | continue; 82 | 83 | if(mods.isEmpty() && optiFine == null) 84 | { 85 | if (!verifiedFiles.contains(fileInDir)) 86 | badFiles.add(fileInDir); 87 | } 88 | else 89 | { 90 | if (optiFine != null) 91 | { 92 | if (optiFine.getName().equalsIgnoreCase(fileInDir.getFileName().toString())) 93 | { 94 | if (Files.size(fileInDir) == optiFine.getSize()) 95 | { 96 | badFiles.remove(fileInDir); 97 | verifiedFiles.add(fileInDir); 98 | } 99 | else badFiles.add(fileInDir); 100 | } 101 | else 102 | { 103 | if (!verifiedFiles.contains(fileInDir)) badFiles.add(fileInDir); 104 | } 105 | } 106 | 107 | for (Mod mod : mods) 108 | { 109 | if (mod.getName().equalsIgnoreCase(fileInDir.getFileName().toString())) 110 | { 111 | if (Files.size(fileInDir) == mod.getSize() && (mod.getSha1().isEmpty() || FileUtils.getSHA1(fileInDir).equalsIgnoreCase(mod.getSha1()))) 112 | { 113 | badFiles.remove(fileInDir); 114 | verifiedFiles.add(fileInDir); 115 | } 116 | else badFiles.add(fileInDir); 117 | } 118 | else 119 | { 120 | if (!verifiedFiles.contains(fileInDir)) 121 | badFiles.add(fileInDir); 122 | } 123 | } 124 | } 125 | } 126 | 127 | badFiles.forEach(path -> { 128 | try 129 | { 130 | Files.deleteIfExists(path); 131 | } catch (Exception e) 132 | { 133 | logger.printStackTrace(e); 134 | } 135 | }); 136 | badFiles.clear(); 137 | } 138 | 139 | public boolean isUseFileDeleter() 140 | { 141 | return this.useFileDeleter; 142 | } 143 | 144 | public String[] getModsToIgnore() 145 | { 146 | return this.modsToIgnore; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/utils/UpdaterOptions.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.utils; 2 | 3 | import fr.flowarg.flowupdater.utils.builderapi.BuilderArgument; 4 | import fr.flowarg.flowupdater.utils.builderapi.BuilderException; 5 | import fr.flowarg.flowupdater.utils.builderapi.IBuilder; 6 | 7 | import java.nio.file.Paths; 8 | 9 | /** 10 | * Represent some settings for FlowUpdater 11 | * 12 | * @author FlowArg 13 | */ 14 | public class UpdaterOptions 15 | { 16 | public static final UpdaterOptions DEFAULT = new UpdaterOptions(new ExternalFileDeleter(), true, System.getProperty("java.home") != null ? Paths.get(System.getProperty("java.home")) 17 | .resolve("bin") 18 | .resolve("java") 19 | .toAbsolutePath() 20 | .toString() : "java"); 21 | 22 | private final ExternalFileDeleter externalFileDeleter; 23 | private final boolean versionChecker; 24 | private final String javaPath; 25 | 26 | private UpdaterOptions(ExternalFileDeleter externalFileDeleter, boolean versionChecker, String javaPath) 27 | { 28 | this.externalFileDeleter = externalFileDeleter; 29 | this.versionChecker = versionChecker; 30 | this.javaPath = javaPath; 31 | } 32 | 33 | /** 34 | * The external file deleter is used to check if some external files need to be downloaded. 35 | * Default: {@link fr.flowarg.flowupdater.utils.ExternalFileDeleter} 36 | * @return externalFileDeleter value. 37 | */ 38 | public ExternalFileDeleter getExternalFileDeleter() 39 | { 40 | return this.externalFileDeleter; 41 | } 42 | 43 | /** 44 | * Should check the version of FlowUpdater. 45 | * @return true or false. 46 | */ 47 | public boolean isVersionChecker() 48 | { 49 | return this.versionChecker; 50 | } 51 | 52 | /** 53 | * The path to the java executable to use with Forge and Fabric installers. 54 | * By default, it's taken from System.getProperty("java.home"). 55 | * @return the path to the java executable. 56 | */ 57 | public String getJavaPath() 58 | { 59 | return this.javaPath; 60 | } 61 | 62 | /** 63 | * Builder of {@link UpdaterOptions} 64 | */ 65 | public static class UpdaterOptionsBuilder implements IBuilder 66 | { 67 | private final BuilderArgument externalFileDeleterArgument = new BuilderArgument<>("External FileDeleter", ExternalFileDeleter::new).optional(); 68 | private final BuilderArgument versionChecker = new BuilderArgument<>("VersionChecker", () -> true).optional(); 69 | private final BuilderArgument javaPath = new BuilderArgument<>("JavaPath", () -> 70 | System.getProperty("java.home") != null ? Paths.get(System.getProperty("java.home")) 71 | .resolve("bin") 72 | .resolve("java") 73 | .toAbsolutePath() 74 | .toString() : "java") 75 | .optional(); 76 | 77 | /** 78 | * Append an {@link ExternalFileDeleter} object. 79 | * @param externalFileDeleter the file deleter to define. 80 | * @return the builder. 81 | */ 82 | public UpdaterOptionsBuilder withExternalFileDeleter(ExternalFileDeleter externalFileDeleter) 83 | { 84 | this.externalFileDeleterArgument.set(externalFileDeleter); 85 | return this; 86 | } 87 | 88 | /** 89 | * Enable or disable the version checker. 90 | * @param versionChecker the value to define. 91 | * @return the builder. 92 | */ 93 | public UpdaterOptionsBuilder withVersionChecker(boolean versionChecker) 94 | { 95 | this.versionChecker.set(versionChecker); 96 | return this; 97 | } 98 | 99 | /** 100 | * Set the path to the java executable to use with Forge and Fabric installers. 101 | * (Directly the java executable, not the java home) 102 | * If you wish to set up the java home, you should use the {@link System#setProperty(String, String)} method 103 | * with the "java.home" key. 104 | * By default, it's taken from {@code System.getProperty("java.home")}. 105 | * @param javaPath the path to the java executable. 106 | * @return the builder. 107 | */ 108 | public UpdaterOptionsBuilder withJavaPath(String javaPath) 109 | { 110 | this.javaPath.set(javaPath); 111 | return this; 112 | } 113 | 114 | /** 115 | * Build an {@link UpdaterOptions} object. 116 | */ 117 | @Override 118 | public UpdaterOptions build() throws BuilderException 119 | { 120 | return new UpdaterOptions( 121 | this.externalFileDeleterArgument.get(), 122 | this.versionChecker.get(), 123 | this.javaPath.get() 124 | ); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/utils/Version.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.utils; 2 | 3 | import org.jetbrains.annotations.Contract; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Objects; 9 | 10 | public class Version implements Comparable 11 | { 12 | private final List version; 13 | 14 | public Version(List version) 15 | { 16 | this.version = version; 17 | } 18 | 19 | @Contract("_ -> new") 20 | public static @NotNull Version gen(@NotNull String version) 21 | { 22 | if(version.isEmpty()) 23 | throw new IllegalArgumentException("Version cannot be empty."); 24 | final String[] parts = version.split("\\."); 25 | final List versionList = new ArrayList<>(); 26 | for (String part : parts) 27 | versionList.add(Integer.parseInt(part)); 28 | return new Version(versionList); 29 | } 30 | 31 | @Override 32 | public int compareTo(@NotNull Version o) 33 | { 34 | final int thisSize = this.version.size(); 35 | final int oSize = o.version.size(); 36 | 37 | for (int i = 0; i < Math.min(thisSize, oSize); i++) 38 | if (!Objects.equals(this.version.get(i), o.version.get(i))) 39 | return Integer.compare(this.version.get(i), o.version.get(i)); 40 | 41 | return Integer.compare(thisSize, oSize); 42 | } 43 | 44 | @Override 45 | public String toString() 46 | { 47 | final StringBuilder builder = new StringBuilder(); 48 | for (int i = 0; i < this.version.size(); i++) 49 | { 50 | builder.append(this.version.get(i)); 51 | if (i < this.version.size() - 1) 52 | builder.append("."); 53 | } 54 | return builder.toString(); 55 | } 56 | 57 | public boolean isNewerThan(@NotNull Version o) 58 | { 59 | return this.compareTo(o) > 0; 60 | } 61 | 62 | public boolean isNewerOrEqualTo(@NotNull Version o) 63 | { 64 | return this.compareTo(o) >= 0; 65 | } 66 | 67 | public boolean isOlderThan(@NotNull Version o) 68 | { 69 | return this.compareTo(o) < 0; 70 | } 71 | 72 | public boolean isOlderOrEqualTo(@NotNull Version o) 73 | { 74 | return this.compareTo(o) <= 0; 75 | } 76 | 77 | public boolean isEqualTo(@NotNull Version o) 78 | { 79 | return this.compareTo(o) == 0; 80 | } 81 | 82 | public boolean isBetweenOrEqual(@NotNull Version min, @NotNull Version max) 83 | { 84 | return this.isNewerOrEqualTo(min) && this.isOlderOrEqualTo(max); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/utils/VersionChecker.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.utils; 2 | 3 | import fr.flowarg.flowlogger.ILogger; 4 | import fr.flowarg.flowupdater.FlowUpdater; 5 | 6 | public class VersionChecker 7 | { 8 | public static void run(ILogger logger) 9 | { 10 | new Thread(() -> { 11 | final String version = IOUtils.getLatestArtifactVersion("https://repo1.maven.org/maven2/fr/flowarg/flowupdater/maven-metadata.xml"); 12 | 13 | if (version == null) 14 | { 15 | logger.err("Couldn't get the latest version of FlowUpdater."); 16 | logger.err("Maybe the maven repository is down? Or your internet connection sucks?"); 17 | return; 18 | } 19 | 20 | final int compare = Version.gen(FlowUpdater.FU_VERSION).compareTo(Version.gen(version)); 21 | 22 | if(compare > 0) 23 | { 24 | logger.info("You're running on an unpublished version of FlowUpdater. Are you in a dev environment?"); 25 | return; 26 | } 27 | 28 | if(compare < 0) 29 | logger.warn(String.format("Detected a new version of FlowUpdater (%s). You should update!", version)); 30 | }).start(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/utils/builderapi/BuilderArgument.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.utils.builderapi; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.function.Supplier; 6 | 7 | /** 8 | * Builder API; Represent an argument for a Builder implementation. 9 | * @version 1.6 10 | * @author flow 11 | * 12 | * @param Object Argument 13 | */ 14 | public class BuilderArgument 15 | { 16 | private final String objectName; 17 | private T badObject = null; 18 | private T object = null; 19 | private boolean isRequired; 20 | 21 | /** 22 | * Construct a new BuilderArgument. 23 | * @param objectName The name of the object. 24 | * @param initialValue The initial value's wrapper. 25 | */ 26 | public BuilderArgument(String objectName, @NotNull Supplier initialValue) 27 | { 28 | this.objectName = objectName; 29 | this.object = initialValue.get(); 30 | } 31 | 32 | /** 33 | * Construct a new basic BuilderArgument. 34 | * @param objectName The name of the object. 35 | */ 36 | public BuilderArgument(String objectName) 37 | { 38 | this.objectName = objectName; 39 | } 40 | 41 | /** 42 | * Construct a new BuilderArgument. 43 | * @param objectName The name of the object. 44 | * @param initialValue The initial value's wrapper. 45 | * @param badObject The initial bad value's wrapper. 46 | */ 47 | public BuilderArgument(String objectName, @NotNull Supplier initialValue, @NotNull Supplier badObject) 48 | { 49 | this.objectName = objectName; 50 | this.object = initialValue.get(); 51 | this.badObject = badObject.get(); 52 | } 53 | 54 | /** 55 | * Construct a new BuilderArgument. 56 | * @param badObject The initial bad value's wrapper. 57 | * @param objectName The name of the object. 58 | */ 59 | public BuilderArgument(@NotNull Supplier badObject, String objectName) 60 | { 61 | this.objectName = objectName; 62 | this.badObject = badObject.get(); 63 | } 64 | 65 | /** 66 | * Check and get the wrapped object. 67 | * @return the wrapper object. 68 | * @throws BuilderException it the builder configuration is invalid. 69 | */ 70 | public T get() throws BuilderException 71 | { 72 | if(this.object == this.badObject && this.badObject != null) 73 | throw new BuilderException("Argument" + this.objectName + " is a bad object!"); 74 | 75 | if(this.isRequired) 76 | { 77 | if(this.object == null) 78 | throw new BuilderException("Argument" + this.objectName + " is null!"); 79 | else return this.object; 80 | } 81 | else return this.object; 82 | } 83 | 84 | /** 85 | * Define the new wrapped object. 86 | * @param object the new wrapper object to define. 87 | */ 88 | public void set(T object) 89 | { 90 | this.object = object; 91 | } 92 | 93 | /** 94 | * Indicate that provided arguments are required if this argument is built. 95 | * @param required required arguments. 96 | * @return this. 97 | */ 98 | public BuilderArgument require(BuilderArgument @NotNull ... required) 99 | { 100 | for (BuilderArgument arg : required) 101 | arg.isRequired = true; 102 | return this; 103 | } 104 | 105 | /** 106 | * Indicate that argument is required. 107 | * @return this. 108 | */ 109 | public BuilderArgument required() 110 | { 111 | this.isRequired = true; 112 | return this; 113 | } 114 | 115 | /** 116 | * Indicate that argument is optional. 117 | * @return this. 118 | */ 119 | public BuilderArgument optional() 120 | { 121 | this.isRequired = false; 122 | return this; 123 | } 124 | 125 | /** 126 | * Get the name of the current object's name. 127 | * @return the object's name. 128 | */ 129 | public String getObjectName() 130 | { 131 | return this.objectName; 132 | } 133 | 134 | /** 135 | * Get the bad object. 136 | * @return the bad object. 137 | */ 138 | public T badObject() 139 | { 140 | return this.badObject; 141 | } 142 | 143 | @Override 144 | public String toString() 145 | { 146 | return "BuilderArgument{" + "objectName='" + this.objectName + '\'' + ", isRequired=" + this.isRequired + '}'; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/utils/builderapi/BuilderException.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.utils.builderapi; 2 | 3 | /** 4 | * Builder API; This exception is thrown when an error occurred with Builder API. 5 | * @version 1.6 6 | * @author flow 7 | */ 8 | public class BuilderException extends RuntimeException 9 | { 10 | private static final long serialVersionUID = 1L; 11 | 12 | public BuilderException() 13 | { 14 | super(); 15 | } 16 | 17 | public BuilderException(String reason) 18 | { 19 | super(reason); 20 | } 21 | 22 | public BuilderException(String reason, Throwable cause) 23 | { 24 | super(reason, cause); 25 | } 26 | 27 | public BuilderException(Throwable cause) 28 | { 29 | super(cause); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/utils/builderapi/IBuilder.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.utils.builderapi; 2 | 3 | /** 4 | * Builder API ; Builder interface. 5 | * @version 1.6 6 | * @author flow 7 | * 8 | * @param Object returned. 9 | */ 10 | @FunctionalInterface 11 | public interface IBuilder 12 | { 13 | /** 14 | * Build a {@link T} object. 15 | * @return a {@link T} object. 16 | * @throws BuilderException if an error occurred when building an object. 17 | */ 18 | T build() throws BuilderException; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/utils/builderapi/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Builder API package. 3 | */ 4 | package fr.flowarg.flowupdater.utils.builderapi; -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/utils/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility package. 3 | */ 4 | package fr.flowarg.flowupdater.utils; -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/AbstractModLoaderVersion.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions; 2 | 3 | import fr.flowarg.flowlogger.ILogger; 4 | import fr.flowarg.flowupdater.FlowUpdater; 5 | import fr.flowarg.flowupdater.download.DownloadList; 6 | import fr.flowarg.flowupdater.download.IProgressCallback; 7 | import fr.flowarg.flowupdater.download.Step; 8 | import fr.flowarg.flowupdater.download.json.*; 9 | import fr.flowarg.flowupdater.integrations.curseforgeintegration.ICurseForgeCompatible; 10 | import fr.flowarg.flowupdater.integrations.modrinthintegration.IModrinthCompatible; 11 | import fr.flowarg.flowupdater.integrations.modrinthintegration.ModrinthModPack; 12 | import fr.flowarg.flowupdater.integrations.optifineintegration.IOptiFineCompatible; 13 | import fr.flowarg.flowupdater.integrations.optifineintegration.OptiFine; 14 | import fr.flowarg.flowupdater.utils.IOUtils; 15 | import fr.flowarg.flowupdater.utils.ModFileDeleter; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import java.nio.file.Files; 19 | import java.nio.file.Path; 20 | import java.util.List; 21 | 22 | public abstract class AbstractModLoaderVersion implements IModLoaderVersion, ICurseForgeCompatible, IModrinthCompatible, IOptiFineCompatible 23 | { 24 | protected final List mods; 25 | protected final List curseMods; 26 | protected final List modrinthMods; 27 | protected final ModFileDeleter fileDeleter; 28 | protected final CurseModPackInfo curseModPackInfo; 29 | protected final ModrinthModPackInfo modrinthModPackInfo; 30 | protected final OptiFineInfo optiFineInfo; 31 | 32 | protected String modLoaderVersion; 33 | protected ILogger logger; 34 | protected VanillaVersion vanilla; 35 | protected DownloadList downloadList; 36 | protected IProgressCallback callback; 37 | protected String javaPath; 38 | protected ModrinthModPack modrinthModPack; 39 | 40 | public AbstractModLoaderVersion(String modLoaderVersion, List mods, List curseMods, 41 | List modrinthMods, ModFileDeleter fileDeleter, CurseModPackInfo curseModPackInfo, 42 | ModrinthModPackInfo modrinthModPackInfo, OptiFineInfo optiFineInfo) 43 | { 44 | this.modLoaderVersion = modLoaderVersion; 45 | this.mods = mods; 46 | this.curseMods = curseMods; 47 | this.modrinthMods = modrinthMods; 48 | this.fileDeleter = fileDeleter; 49 | this.curseModPackInfo = curseModPackInfo; 50 | this.modrinthModPackInfo = modrinthModPackInfo; 51 | this.optiFineInfo = optiFineInfo; 52 | } 53 | 54 | /** 55 | * {@inheritDoc} 56 | */ 57 | @Override 58 | public void attachFlowUpdater(@NotNull FlowUpdater flowUpdater) 59 | { 60 | this.logger = flowUpdater.getLogger(); 61 | this.vanilla = flowUpdater.getVanillaVersion(); 62 | this.downloadList = flowUpdater.getDownloadList(); 63 | this.callback = flowUpdater.getCallback(); 64 | this.javaPath = flowUpdater.getUpdaterOptions().getJavaPath(); 65 | } 66 | 67 | /** 68 | * {@inheritDoc} 69 | */ 70 | @Override 71 | public List getMods() 72 | { 73 | return this.mods; 74 | } 75 | 76 | /** 77 | * {@inheritDoc} 78 | */ 79 | @Override 80 | public DownloadList getDownloadList() 81 | { 82 | return this.downloadList; 83 | } 84 | 85 | /** 86 | * {@inheritDoc} 87 | */ 88 | @Override 89 | public IProgressCallback getCallback() 90 | { 91 | return this.callback; 92 | } 93 | 94 | /** 95 | * {@inheritDoc} 96 | */ 97 | @Override 98 | public List getCurseMods() 99 | { 100 | return this.curseMods; 101 | } 102 | 103 | /** 104 | * {@inheritDoc} 105 | */ 106 | @Override 107 | public List getModrinthMods() 108 | { 109 | return this.modrinthMods; 110 | } 111 | 112 | /** 113 | * {@inheritDoc} 114 | */ 115 | @Override 116 | public void setAllCurseMods(List allCurseMods) 117 | { 118 | this.mods.addAll(allCurseMods); 119 | } 120 | 121 | /** 122 | * {@inheritDoc} 123 | */ 124 | @Override 125 | public CurseModPackInfo getCurseModPackInfo() 126 | { 127 | return this.curseModPackInfo; 128 | } 129 | 130 | /** 131 | * {@inheritDoc} 132 | */ 133 | @Override 134 | public ModrinthModPackInfo getModrinthModPackInfo() 135 | { 136 | return this.modrinthModPackInfo; 137 | } 138 | 139 | /** 140 | * {@inheritDoc} 141 | */ 142 | @Override 143 | public OptiFineInfo getOptiFineInfo() 144 | { 145 | return this.optiFineInfo; 146 | } 147 | 148 | /** 149 | * {@inheritDoc} 150 | */ 151 | @Override 152 | public void setAllModrinthMods(List modrinthMods) 153 | { 154 | this.mods.addAll(modrinthMods); 155 | } 156 | 157 | /** 158 | * {@inheritDoc} 159 | */ 160 | @Override 161 | public ILogger getLogger() 162 | { 163 | return this.logger; 164 | } 165 | 166 | /** 167 | * {@inheritDoc} 168 | */ 169 | @Override 170 | public String getModLoaderVersion() 171 | { 172 | return this.modLoaderVersion; 173 | } 174 | 175 | /** 176 | * {@inheritDoc} 177 | */ 178 | @Override 179 | public ModFileDeleter getFileDeleter() 180 | { 181 | return this.fileDeleter; 182 | } 183 | 184 | @Override 185 | public void setModrinthModPack(ModrinthModPack modrinthModPack) 186 | { 187 | this.modrinthModPack = modrinthModPack; 188 | } 189 | 190 | @Override 191 | public ModrinthModPack getModrinthModPack() 192 | { 193 | return this.modrinthModPack; 194 | } 195 | 196 | @Override 197 | public void installMods(@NotNull Path modsDir) throws Exception 198 | { 199 | this.callback.step(Step.MODS); 200 | this.installAllMods(modsDir); 201 | 202 | final OptiFine ofObj = this.downloadList.getOptiFine(); 203 | 204 | if(ofObj != null) 205 | { 206 | try 207 | { 208 | final Path optiFineFilePath = modsDir.resolve(ofObj.getName()); 209 | 210 | if (Files.notExists(optiFineFilePath) || Files.size(optiFineFilePath) != ofObj.getSize()) 211 | IOUtils.copy(this.logger, modsDir.getParent().resolve(".op").resolve(ofObj.getName()), optiFineFilePath); 212 | } catch (Exception e) 213 | { 214 | this.logger.printStackTrace(e); 215 | } 216 | this.downloadList.incrementDownloaded(ofObj.getSize()); 217 | this.callback.update(this.downloadList.getDownloadInfo()); 218 | } 219 | 220 | this.fileDeleter.delete(this.logger, modsDir, this.mods, ofObj, this.modrinthModPack); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/IModLoaderVersion.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions; 2 | 3 | import fr.flowarg.flowlogger.ILogger; 4 | import fr.flowarg.flowupdater.FlowUpdater; 5 | import fr.flowarg.flowupdater.download.DownloadList; 6 | import fr.flowarg.flowupdater.download.IProgressCallback; 7 | import fr.flowarg.flowupdater.download.Step; 8 | import fr.flowarg.flowupdater.download.json.Mod; 9 | import fr.flowarg.flowupdater.utils.IOUtils; 10 | import fr.flowarg.flowupdater.utils.ModFileDeleter; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.net.URL; 14 | import java.nio.file.Path; 15 | import java.util.List; 16 | 17 | public interface IModLoaderVersion 18 | { 19 | /** 20 | * Attach {@link FlowUpdater} object to mod loaders, allow them to retrieve some information. 21 | * @param flowUpdater flow updater object. 22 | */ 23 | void attachFlowUpdater(@NotNull FlowUpdater flowUpdater); 24 | 25 | /** 26 | * Check if the current mod loader is already installed. 27 | * @param installDir the dir to check. 28 | * @return if the current mod loader is already installed. 29 | */ 30 | boolean isModLoaderAlreadyInstalled(@NotNull Path installDir); 31 | 32 | /** 33 | * Install the current mod loader in a specified directory. 34 | * @param installDir folder where the mod loader is going to be installed. 35 | * @throws Exception if an I/O error occurred. 36 | */ 37 | default void install(@NotNull Path installDir) throws Exception 38 | { 39 | this.getCallback().step(Step.MOD_LOADER); 40 | this.getLogger().info("Installing " + this.name() + ", version: " + this.getModLoaderVersion() + "..."); 41 | } 42 | 43 | /** 44 | * Install all mods in the mods' directory. 45 | * @param modsDir mods directory. 46 | * @throws Exception if an I/O error occurred. 47 | */ 48 | void installMods(@NotNull Path modsDir) throws Exception; 49 | 50 | /** 51 | * Get the mod loader version. 52 | * @return the mod loader version. 53 | */ 54 | String getModLoaderVersion(); 55 | 56 | /** 57 | * Get all processed mods / mods to process. 58 | * @return all processed mods / mods to process. 59 | */ 60 | List getMods(); 61 | 62 | /** 63 | * Download mods in the mods' folder. 64 | * @param modsDir mods' folder 65 | */ 66 | default void installAllMods(@NotNull Path modsDir) 67 | { 68 | this.getDownloadList().getMods().forEach(mod -> { 69 | try 70 | { 71 | final Path modFilePath = modsDir.resolve(mod.getName()); 72 | IOUtils.download(this.getLogger(), new URL(mod.getDownloadURL()), modFilePath); 73 | this.getCallback().onFileDownloaded(modFilePath); 74 | } 75 | catch (Exception e) 76 | { 77 | this.getLogger().printStackTrace(e); 78 | } 79 | this.getDownloadList().incrementDownloaded(mod.getSize()); 80 | this.getCallback().update(this.getDownloadList().getDownloadInfo()); 81 | }); 82 | } 83 | 84 | /** 85 | * Get the {@link DownloadList} object. 86 | * @return download info. 87 | */ 88 | DownloadList getDownloadList(); 89 | 90 | /** 91 | * Get the {@link ILogger} object. 92 | * @return the logger. 93 | */ 94 | ILogger getLogger(); 95 | 96 | /** 97 | * Get the {@link IProgressCallback} object. 98 | * @return the progress callback. 99 | */ 100 | IProgressCallback getCallback(); 101 | 102 | /** 103 | * Get the attached {@link ModFileDeleter} instance; 104 | * @return this mod file deleter; 105 | */ 106 | ModFileDeleter getFileDeleter(); 107 | 108 | /** 109 | * Get the mod loader name. 110 | * @return the mod loader name. 111 | */ 112 | String name(); 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/ModLoaderUtils.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import fr.flowarg.flowio.FileUtils; 7 | import org.jetbrains.annotations.Contract; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.net.URL; 11 | import java.nio.charset.StandardCharsets; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.nio.file.StandardCopyOption; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | public class ModLoaderUtils 19 | { 20 | @Contract(pure = true) 21 | public static @NotNull String buildJarUrl(String baseUrl, @NotNull String group, String artifact, String version) 22 | { 23 | return buildJarUrl(baseUrl, group, artifact, version, ""); 24 | } 25 | 26 | @Contract(pure = true) 27 | public static @NotNull String buildJarUrl(String baseUrl, @NotNull String group, String artifact, String version, String classifier) 28 | { 29 | return baseUrl + group.replace(".", "/") + "/" + artifact + "/" + version + "/" + artifact + "-" + version + classifier + ".jar"; 30 | } 31 | 32 | public static @NotNull Path buildLibraryPath(@NotNull Path installDir, @NotNull String group, String artifact, String version) 33 | { 34 | return installDir.resolve("libraries") 35 | .resolve(group.replace(".", installDir.getFileSystem().getSeparator())) 36 | .resolve(artifact) 37 | .resolve(version) 38 | .resolve(artifact + "-" + version + ".jar"); 39 | } 40 | 41 | public static void fakeContext(@NotNull Path dirToInstall, String vanilla) throws Exception 42 | { 43 | final Path fakeProfiles = dirToInstall.resolve("launcher_profiles.json"); 44 | 45 | Files.write(fakeProfiles, "{}".getBytes(StandardCharsets.UTF_8)); 46 | 47 | final Path versions = dirToInstall.resolve("versions"); 48 | if(Files.notExists(versions)) 49 | Files.createDirectories(versions); 50 | 51 | final Path vanillaVersion = versions.resolve(vanilla); 52 | if(Files.notExists(vanillaVersion)) 53 | Files.createDirectories(vanillaVersion); 54 | 55 | Files.copy( 56 | dirToInstall.resolve("client.jar"), 57 | vanillaVersion.resolve(vanilla + ".jar"), 58 | StandardCopyOption.REPLACE_EXISTING 59 | ); 60 | } 61 | 62 | public static void removeFakeContext(@NotNull Path dirToInstall) throws Exception 63 | { 64 | FileUtils.deleteDirectory(dirToInstall.resolve("versions")); 65 | Files.deleteIfExists(dirToInstall.resolve("launcher_profiles.json")); 66 | } 67 | 68 | public static @NotNull List parseNewVersionInfo(Path installDir, @NotNull JsonObject versionInfo) throws Exception 69 | { 70 | final List parsedLibraries = new ArrayList<>(); 71 | 72 | final JsonArray libraries = versionInfo.getAsJsonArray("libraries"); 73 | 74 | for (final JsonElement libraryElement : libraries) 75 | { 76 | final JsonObject library = libraryElement.getAsJsonObject(); 77 | final String name = library.get("name").getAsString(); 78 | final JsonObject downloads = library.getAsJsonObject("downloads"); 79 | final JsonObject artifact = downloads.getAsJsonObject("artifact"); 80 | 81 | final String path = artifact.get("path").getAsString(); 82 | final String sha1 = artifact.get("sha1").getAsString(); 83 | final String url = artifact.get("url").getAsString(); 84 | 85 | final Path libraryPath = installDir.resolve("libraries") 86 | .resolve(path.replace("/", installDir.getFileSystem().getSeparator())); 87 | 88 | final boolean installed = Files.exists(libraryPath) && 89 | FileUtils.getSHA1(libraryPath).equalsIgnoreCase(sha1); 90 | 91 | parsedLibraries.add(new ParsedLibrary(libraryPath, url.isEmpty() ? null : new URL(url), name, installed)); 92 | } 93 | 94 | return parsedLibraries; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/ModLoaderVersionBuilder.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions; 2 | 3 | import fr.flowarg.flowupdater.download.json.*; 4 | import fr.flowarg.flowupdater.utils.ModFileDeleter; 5 | import fr.flowarg.flowupdater.utils.builderapi.BuilderArgument; 6 | import fr.flowarg.flowupdater.utils.builderapi.BuilderException; 7 | import fr.flowarg.flowupdater.utils.builderapi.IBuilder; 8 | 9 | import java.net.URL; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.Collection; 13 | import java.util.List; 14 | 15 | @SuppressWarnings("unchecked") 16 | public abstract class ModLoaderVersionBuilder> implements IBuilder 17 | { 18 | protected final BuilderArgument> modsArgument = new BuilderArgument>("Mods", ArrayList::new).optional(); 19 | protected final BuilderArgument> curseModsArgument = new BuilderArgument>("CurseMods", ArrayList::new).optional(); 20 | protected final BuilderArgument> modrinthModsArgument = new BuilderArgument>("ModrinthMods", ArrayList::new).optional(); 21 | protected final BuilderArgument fileDeleterArgument = new BuilderArgument<>("ModFileDeleter", () -> new ModFileDeleter(false)).optional(); 22 | protected final BuilderArgument curseModPackArgument = new BuilderArgument("CurseModPack").optional(); 23 | protected final BuilderArgument modrinthPackArgument = new BuilderArgument("ModrinthModPack").optional(); 24 | 25 | /** 26 | * Append a mod list to the version. 27 | * @param mods mods to append. 28 | * @return the builder. 29 | */ 30 | public B withMods(List mods) 31 | { 32 | this.modsArgument.get().addAll(mods); 33 | return (B) this; 34 | } 35 | 36 | /** 37 | * Append a single mod or a mod array to the version. 38 | * @param mods mods to append. 39 | * @return the builder. 40 | */ 41 | public B withMods(Mod... mods) 42 | { 43 | return this.withMods(Arrays.asList(mods)); 44 | } 45 | 46 | /** 47 | * Append mods contained in the provided JSON url. 48 | * @param jsonUrl The json URL of mods to append. 49 | * @return the builder. 50 | */ 51 | public B withMods(URL jsonUrl) 52 | { 53 | return this.withMods(Mod.getModsFromJson(jsonUrl)); 54 | } 55 | 56 | /** 57 | * Append mods contained in the provided JSON url. 58 | * @param jsonUrl The json URL of mods to append. 59 | * @return the builder. 60 | */ 61 | public B withMods(String jsonUrl) 62 | { 63 | return this.withMods(Mod.getModsFromJson(jsonUrl)); 64 | } 65 | 66 | /** 67 | * Append a mod list to the version. 68 | * @param curseMods CurseForge's mods to append. 69 | * @return the builder. 70 | */ 71 | public B withCurseMods(Collection curseMods) 72 | { 73 | this.curseModsArgument.get().addAll(curseMods); 74 | return (B) this; 75 | } 76 | 77 | /** 78 | * Append a single mod or a mod array to the version. 79 | * @param curseMods CurseForge's mods to append. 80 | * @return the builder. 81 | */ 82 | public B withCurseMods(CurseFileInfo... curseMods) 83 | { 84 | return this.withCurseMods(Arrays.asList(curseMods)); 85 | } 86 | 87 | /** 88 | * Append mods contained in the provided JSON url. 89 | * @param jsonUrl The json URL of mods to append. 90 | * @return the builder. 91 | */ 92 | public B withCurseMods(URL jsonUrl) 93 | { 94 | return this.withCurseMods(CurseFileInfo.getFilesFromJson(jsonUrl)); 95 | } 96 | 97 | /** 98 | * Append mods contained in the provided JSON url. 99 | * @param jsonUrl The json URL of mods to append. 100 | * @return the builder. 101 | */ 102 | public B withCurseMods(String jsonUrl) 103 | { 104 | return this.withCurseMods(CurseFileInfo.getFilesFromJson(jsonUrl)); 105 | } 106 | 107 | /** 108 | * Append a mod list to the version. 109 | * @param modrinthMods Modrinth's mods to append. 110 | * @return the builder. 111 | */ 112 | public B withModrinthMods(Collection modrinthMods) 113 | { 114 | this.modrinthModsArgument.get().addAll(modrinthMods); 115 | return (B) this; 116 | } 117 | 118 | /** 119 | * Append a single mod or a mod array to the version. 120 | * @param modrinthMods Modrinth's mods to append. 121 | * @return the builder. 122 | */ 123 | public B withModrinthMods(ModrinthVersionInfo... modrinthMods) 124 | { 125 | return this.withModrinthMods(Arrays.asList(modrinthMods)); 126 | } 127 | 128 | /** 129 | * Append mods contained in the provided JSON url. 130 | * @param jsonUrl The json URL of mods to append. 131 | * @return the builder. 132 | */ 133 | public B withModrinthMods(URL jsonUrl) 134 | { 135 | return this.withModrinthMods(ModrinthVersionInfo.getModrinthVersionsFromJson(jsonUrl)); 136 | } 137 | 138 | /** 139 | * Append mods contained in the provided JSON url. 140 | * @param jsonUrl The json URL of mods to append. 141 | * @return the builder. 142 | */ 143 | public B withModrinthMods(String jsonUrl) 144 | { 145 | return this.withModrinthMods(ModrinthVersionInfo.getModrinthVersionsFromJson(jsonUrl)); 146 | } 147 | 148 | /** 149 | * Assign to the future forge version a mod pack. 150 | * @param modPackInfo the mod pack information to assign. 151 | * @return the builder. 152 | */ 153 | public B withCurseModPack(CurseModPackInfo modPackInfo) 154 | { 155 | this.curseModPackArgument.set(modPackInfo); 156 | return (B) this; 157 | } 158 | 159 | /** 160 | * Assign to the future forge version a mod pack. 161 | * @param modPackInfo the mod pack information to assign. 162 | * @return the builder. 163 | */ 164 | public B withModrinthModPack(ModrinthModPackInfo modPackInfo) 165 | { 166 | this.modrinthPackArgument.set(modPackInfo); 167 | return (B) this; 168 | } 169 | 170 | /** 171 | * Append a file deleter to the version. 172 | * @param fileDeleter the file deleter to append. 173 | * @return the builder. 174 | */ 175 | public B withFileDeleter(ModFileDeleter fileDeleter) 176 | { 177 | this.fileDeleterArgument.set(fileDeleter); 178 | return (B) this; 179 | } 180 | 181 | @Override 182 | public abstract T build() throws BuilderException; 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/ParsedLibrary.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions; 2 | 3 | import fr.flowarg.flowlogger.ILogger; 4 | import fr.flowarg.flowupdater.utils.IOUtils; 5 | 6 | import java.net.URL; 7 | import java.nio.file.Path; 8 | import java.util.Optional; 9 | 10 | public class ParsedLibrary 11 | { 12 | private final Path path; 13 | private final URL url; 14 | private final String artifact; 15 | private final boolean installed; 16 | 17 | public ParsedLibrary(Path path, URL url, String artifact, boolean installed) 18 | { 19 | this.path = path; 20 | this.url = url; 21 | this.artifact = artifact; 22 | this.installed = installed; 23 | } 24 | 25 | public void download(ILogger logger) 26 | { 27 | if(this.url != null) 28 | IOUtils.download(logger, this.url, this.path); 29 | } 30 | 31 | public Path getPath() 32 | { 33 | return this.path; 34 | } 35 | 36 | public Optional getUrl() 37 | { 38 | return Optional.ofNullable(this.url); 39 | } 40 | 41 | public String getArtifact() 42 | { 43 | return this.artifact; 44 | } 45 | 46 | public boolean isInstalled() 47 | { 48 | return this.installed; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/fabric/FabricBasedVersion.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions.fabric; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import fr.flowarg.flowio.FileUtils; 8 | import fr.flowarg.flowupdater.download.json.*; 9 | import fr.flowarg.flowupdater.utils.IOUtils; 10 | import fr.flowarg.flowupdater.utils.ModFileDeleter; 11 | import fr.flowarg.flowupdater.versions.AbstractModLoaderVersion; 12 | import fr.flowarg.flowupdater.versions.ModLoaderUtils; 13 | import fr.flowarg.flowupdater.versions.ParsedLibrary; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.net.URL; 17 | import java.nio.file.Files; 18 | import java.nio.file.Path; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.concurrent.Callable; 22 | 23 | public abstract class FabricBasedVersion extends AbstractModLoaderVersion 24 | { 25 | protected final String metaApi; 26 | protected String versionId; 27 | 28 | public FabricBasedVersion(String modLoaderVersion, List mods, List curseMods, 29 | List modrinthMods, ModFileDeleter fileDeleter, CurseModPackInfo curseModPackInfo, 30 | ModrinthModPackInfo modrinthModPackInfo, OptiFineInfo optiFineInfo, String metaApi) 31 | { 32 | super(modLoaderVersion, mods, curseMods, modrinthMods, fileDeleter, curseModPackInfo, modrinthModPackInfo, optiFineInfo); 33 | this.metaApi = metaApi; 34 | } 35 | 36 | @Override 37 | public boolean isModLoaderAlreadyInstalled(@NotNull Path installDir) 38 | { 39 | final Path versionJsonFile = installDir.resolve(this.versionId + ".json"); 40 | 41 | if(Files.notExists(versionJsonFile)) 42 | return false; 43 | 44 | try { 45 | return this.parseLibraries(versionJsonFile, installDir).stream().allMatch(ParsedLibrary::isInstalled); 46 | } 47 | catch (Exception e) 48 | { 49 | this.logger.err("An error occurred while checking if the mod loader is already installed."); 50 | return false; 51 | } 52 | } 53 | 54 | /** 55 | * {@inheritDoc} 56 | */ 57 | @Override 58 | public void install(final @NotNull Path installDir) throws Exception 59 | { 60 | super.install(installDir); 61 | 62 | final Path versionJsonFile = installDir.resolve(this.versionId + ".json"); 63 | 64 | IOUtils.download(this.logger, new URL(String.format(this.metaApi, this.vanilla.getName(), this.modLoaderVersion)), versionJsonFile); 65 | 66 | try { 67 | final List parsedLibraries = this.parseLibraries(versionJsonFile, installDir); 68 | parsedLibraries.stream() 69 | .filter(parsedLibrary -> !parsedLibrary.isInstalled()) 70 | .forEach(parsedLibrary -> parsedLibrary.download(this.logger)); 71 | } 72 | catch (Exception e) 73 | { 74 | this.logger.err("An error occurred while installing the mod loader."); 75 | } 76 | } 77 | 78 | protected List parseLibraries(Path versionJsonFile, Path installDir) throws Exception 79 | { 80 | final List parsedLibraries = new ArrayList<>(); 81 | final JsonObject object = JsonParser.parseReader(Files.newBufferedReader(versionJsonFile)) 82 | .getAsJsonObject(); 83 | final JsonArray libraries = object.getAsJsonArray("libraries"); 84 | 85 | for (final JsonElement libraryElement : libraries) 86 | { 87 | final JsonObject library = libraryElement.getAsJsonObject(); 88 | final String url = library.get("url").getAsString(); 89 | final String completeArtifact = library.get("name").getAsString(); 90 | final String[] name = completeArtifact.split(":"); 91 | final String group = name[0]; 92 | final String artifact = name[1]; 93 | final String version = name[2]; 94 | 95 | final String builtJarUrl = ModLoaderUtils.buildJarUrl(url, group, artifact, version); 96 | final Path builtLibaryPath = ModLoaderUtils.buildLibraryPath(installDir, group, artifact, version); 97 | final Callable sha1 = this.getSha1FromLibrary(library, builtJarUrl); 98 | final boolean installed = Files.exists(builtLibaryPath) && 99 | FileUtils.getSHA1(builtLibaryPath).equalsIgnoreCase(sha1.call()); 100 | 101 | parsedLibraries.add(new ParsedLibrary(builtLibaryPath, new URL(builtJarUrl), completeArtifact, installed)); 102 | } 103 | return parsedLibraries; 104 | } 105 | 106 | protected Callable getSha1FromLibrary(@NotNull JsonObject library, String builtJarUrl) 107 | { 108 | final JsonElement sha1Elem = library.get("sha1"); 109 | if (sha1Elem != null) 110 | return sha1Elem::getAsString; 111 | 112 | return () -> IOUtils.getContent(new URL(builtJarUrl + ".sha1")); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/fabric/FabricVersion.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions.fabric; 2 | 3 | import fr.flowarg.flowupdater.FlowUpdater; 4 | import fr.flowarg.flowupdater.download.json.*; 5 | import fr.flowarg.flowupdater.utils.ModFileDeleter; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * The object that contains Fabric's stuff. 12 | */ 13 | public class FabricVersion extends FabricBasedVersion 14 | { 15 | private static final String FABRIC_META_API = "https://meta.fabricmc.net/v2/versions/loader/%s/%s/profile/json"; 16 | 17 | /** 18 | * Use {@link FabricVersionBuilder} to instantiate this class. 19 | * @param mods {@link List} to install. 20 | * @param curseMods {@link List} to install. 21 | * @param fabricVersion to install. 22 | * @param fileDeleter {@link ModFileDeleter} used to clean up mods' dir. 23 | * @param curseModPackInfo {@link CurseModPackInfo} the mod pack you want to install. 24 | */ 25 | FabricVersion(String fabricVersion, List mods, List curseMods, 26 | List modrinthMods, ModFileDeleter fileDeleter, CurseModPackInfo curseModPackInfo, 27 | ModrinthModPackInfo modrinthModPackInfo, OptiFineInfo optiFineInfo) 28 | { 29 | super(fabricVersion, mods, curseMods, modrinthMods, fileDeleter, curseModPackInfo, modrinthModPackInfo, optiFineInfo, FABRIC_META_API); 30 | } 31 | 32 | @Override 33 | public void attachFlowUpdater(@NotNull FlowUpdater flowUpdater) 34 | { 35 | super.attachFlowUpdater(flowUpdater); 36 | this.versionId = "fabric-loader-" + this.modLoaderVersion + "-" + this.vanilla.getName(); 37 | } 38 | 39 | @Override 40 | public String name() 41 | { 42 | return "Fabric"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/fabric/FabricVersionBuilder.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions.fabric; 2 | 3 | import fr.flowarg.flowupdater.download.json.OptiFineInfo; 4 | import fr.flowarg.flowupdater.utils.IOUtils; 5 | import fr.flowarg.flowupdater.utils.builderapi.BuilderArgument; 6 | import fr.flowarg.flowupdater.utils.builderapi.BuilderException; 7 | import fr.flowarg.flowupdater.versions.ModLoaderVersionBuilder; 8 | 9 | public class FabricVersionBuilder extends ModLoaderVersionBuilder 10 | { 11 | private static final String FABRIC_VERSION_METADATA = 12 | "https://maven.fabricmc.net/net/fabricmc/fabric-loader/maven-metadata.xml"; 13 | 14 | private final BuilderArgument fabricVersionArgument = 15 | new BuilderArgument<>("FabricVersion", () -> 16 | IOUtils.getLatestArtifactVersion(FABRIC_VERSION_METADATA)) 17 | .optional(); 18 | private final BuilderArgument optiFineArgument = new BuilderArgument("OptiFine").optional(); 19 | 20 | 21 | /** 22 | * @param fabricVersion the Fabric version you want to install 23 | * (don't use this function if you want to use the latest fabric version). 24 | * @return the builder. 25 | */ 26 | public FabricVersionBuilder withFabricVersion(String fabricVersion) 27 | { 28 | this.fabricVersionArgument.set(fabricVersion); 29 | return this; 30 | } 31 | 32 | /** 33 | * Append some OptiFine download's information. 34 | * @param optiFineInfo OptiFine info. 35 | * @return the builder. 36 | */ 37 | public FabricVersionBuilder withOptiFine(OptiFineInfo optiFineInfo) 38 | { 39 | this.optiFineArgument.set(optiFineInfo); 40 | return this; 41 | } 42 | 43 | /** 44 | * Build a new {@link FabricVersion} instance with provided arguments. 45 | * @return the freshly created instance. 46 | * @throws BuilderException if an error occurred. 47 | */ 48 | @Override 49 | public FabricVersion build() throws BuilderException 50 | { 51 | return new FabricVersion( 52 | this.fabricVersionArgument.get(), 53 | this.modsArgument.get(), 54 | this.curseModsArgument.get(), 55 | this.modrinthModsArgument.get(), 56 | this.fileDeleterArgument.get(), 57 | this.curseModPackArgument.get(), 58 | this.modrinthPackArgument.get(), 59 | this.optiFineArgument.get() 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/fabric/QuiltVersion.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions.fabric; 2 | 3 | import fr.flowarg.flowupdater.FlowUpdater; 4 | import fr.flowarg.flowupdater.download.json.*; 5 | import fr.flowarg.flowupdater.utils.ModFileDeleter; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * The object that contains Quilt's stuff. 12 | */ 13 | public class QuiltVersion extends FabricBasedVersion 14 | { 15 | private static final String QUILT_META_API = "https://meta.quiltmc.org/v3/versions/loader/%s/%s/profile/json"; 16 | 17 | /** 18 | * Use {@link QuiltVersionBuilder} to instantiate this class. 19 | * @param mods {@link List} to install. 20 | * @param curseMods {@link List} to install. 21 | * @param quiltVersion to install. 22 | * @param fileDeleter {@link ModFileDeleter} used to clean up mods' dir. 23 | * @param curseModPackInfo {@link CurseModPackInfo} the mod pack you want to install. 24 | */ 25 | QuiltVersion(String quiltVersion, List mods, List curseMods, 26 | List modrinthMods, ModFileDeleter fileDeleter, CurseModPackInfo curseModPackInfo, 27 | ModrinthModPackInfo modrinthModPackInfo, OptiFineInfo optiFineInfo) 28 | { 29 | super(quiltVersion, mods, curseMods, modrinthMods, fileDeleter, curseModPackInfo, modrinthModPackInfo, optiFineInfo, QUILT_META_API); 30 | } 31 | 32 | @Override 33 | public void attachFlowUpdater(@NotNull FlowUpdater flowUpdater) 34 | { 35 | super.attachFlowUpdater(flowUpdater); 36 | this.versionId = "quilt-loader-" + this.modLoaderVersion + "-" + this.vanilla.getName(); 37 | } 38 | 39 | @Override 40 | public String name() 41 | { 42 | return "Quilt"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/fabric/QuiltVersionBuilder.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions.fabric; 2 | 3 | import fr.flowarg.flowupdater.download.json.OptiFineInfo; 4 | import fr.flowarg.flowupdater.utils.IOUtils; 5 | import fr.flowarg.flowupdater.utils.builderapi.BuilderArgument; 6 | import fr.flowarg.flowupdater.utils.builderapi.BuilderException; 7 | import fr.flowarg.flowupdater.versions.ModLoaderVersionBuilder; 8 | 9 | public class QuiltVersionBuilder extends ModLoaderVersionBuilder 10 | { 11 | private static final String QUILT_VERSION_METADATA = 12 | "https://maven.quiltmc.org/repository/release/org/quiltmc/quilt-loader/maven-metadata.xml"; 13 | 14 | private final BuilderArgument quiltVersionArgument = 15 | new BuilderArgument<>("QuiltVersion", () -> IOUtils.getLatestArtifactVersion(QUILT_VERSION_METADATA)).optional(); 16 | private final BuilderArgument optiFineArgument = new BuilderArgument("OptiFine").optional(); 17 | 18 | /** 19 | * @param quiltVersion the Quilt version you want to install 20 | * (don't use this function if you want to use the latest Quilt version). 21 | * @return the builder. 22 | */ 23 | public QuiltVersionBuilder withQuiltVersion(String quiltVersion) 24 | { 25 | this.quiltVersionArgument.set(quiltVersion); 26 | return this; 27 | } 28 | 29 | /** 30 | * Append some OptiFine download's information. 31 | * @param optiFineInfo OptiFine info. 32 | * @return the builder. 33 | */ 34 | public QuiltVersionBuilder withOptiFine(OptiFineInfo optiFineInfo) 35 | { 36 | this.optiFineArgument.set(optiFineInfo); 37 | return this; 38 | } 39 | 40 | /** 41 | * Build a new {@link QuiltVersion} instance with provided arguments. 42 | * @return the freshly created instance. 43 | * @throws BuilderException if an error occurred. 44 | */ 45 | @Override 46 | public QuiltVersion build() throws BuilderException 47 | { 48 | return new QuiltVersion( 49 | this.quiltVersionArgument.get(), 50 | this.modsArgument.get(), 51 | this.curseModsArgument.get(), 52 | this.modrinthModsArgument.get(), 53 | this.fileDeleterArgument.get(), 54 | this.curseModPackArgument.get(), 55 | this.modrinthPackArgument.get(), 56 | this.optiFineArgument.get() 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/fabric/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains all the classes that are used to install Fabric-based mod loaders (Fabric and Quilt at the moment). 3 | */ 4 | package fr.flowarg.flowupdater.versions.fabric; -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/forge/ForgeVersionBuilder.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions.forge; 2 | 3 | import fr.flowarg.flowupdater.download.json.OptiFineInfo; 4 | import fr.flowarg.flowupdater.utils.builderapi.BuilderArgument; 5 | import fr.flowarg.flowupdater.utils.builderapi.BuilderException; 6 | import fr.flowarg.flowupdater.versions.ModLoaderVersionBuilder; 7 | 8 | /** 9 | * Builder for {@link ForgeVersion} 10 | * @author Flow Arg (FlowArg) 11 | */ 12 | public class ForgeVersionBuilder extends ModLoaderVersionBuilder 13 | { 14 | private final BuilderArgument forgeVersionArgument = new BuilderArgument("ForgeVersion").required(); 15 | private final BuilderArgument optiFineArgument = new BuilderArgument("OptiFine").optional(); 16 | 17 | /** 18 | * @param forgeVersion the Forge version you want to install. You should be very precise with the string you give. 19 | * For instance, "1.18.2-40.2.21", "1.12.2-14.23.5.2860", "1.8.9-11.15.1.2318-1.8.9", "1.7.10-10.13.4.1614-1.7.10" are correct. 20 | * Download an installer and check the name of it to get the correct version you should provide here if you are not sure. 21 | * @return the builder. 22 | */ 23 | public ForgeVersionBuilder withForgeVersion(String forgeVersion) 24 | { 25 | this.forgeVersionArgument.set(forgeVersion); 26 | return this; 27 | } 28 | 29 | /** 30 | * Append some OptiFine download's information. 31 | * @param optiFineInfo provided information. 32 | * @return the builder. 33 | */ 34 | public ForgeVersionBuilder withOptiFine(OptiFineInfo optiFineInfo) 35 | { 36 | this.optiFineArgument.set(optiFineInfo); 37 | return this; 38 | } 39 | 40 | @Override 41 | public ForgeVersion build() throws BuilderException 42 | { 43 | return new ForgeVersion( 44 | this.forgeVersionArgument.get(), 45 | this.modsArgument.get(), 46 | this.curseModsArgument.get(), 47 | this.modrinthModsArgument.get(), 48 | this.fileDeleterArgument.get(), 49 | this.curseModPackArgument.get(), 50 | this.modrinthPackArgument.get(), 51 | this.optiFineArgument.get() 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/forge/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains all the classes that are used to install Forge. 3 | */ 4 | package fr.flowarg.flowupdater.versions.forge; -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/neoforge/NeoForgeVersion.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions.neoforge; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonParser; 5 | import fr.flowarg.flowupdater.download.json.*; 6 | import fr.flowarg.flowupdater.utils.IOUtils; 7 | import fr.flowarg.flowupdater.utils.ModFileDeleter; 8 | import fr.flowarg.flowupdater.versions.AbstractModLoaderVersion; 9 | import fr.flowarg.flowupdater.versions.ModLoaderUtils; 10 | import fr.flowarg.flowupdater.versions.ParsedLibrary; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.net.URL; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.StandardCopyOption; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | public class NeoForgeVersion extends AbstractModLoaderVersion 21 | { 22 | private final boolean isOldNeoForge; // 1.20.1 neo forge versions only are "old" 23 | private final String versionId; 24 | 25 | NeoForgeVersion(String modLoaderVersion, List mods, List curseMods, 26 | List modrinthMods, ModFileDeleter fileDeleter, 27 | CurseModPackInfo curseModPackInfo, ModrinthModPackInfo modrinthModPackInfo, OptiFineInfo optiFineInfo) 28 | { 29 | super(modLoaderVersion, mods, curseMods, modrinthMods, fileDeleter, curseModPackInfo, modrinthModPackInfo, optiFineInfo); 30 | this.isOldNeoForge = this.modLoaderVersion.startsWith("1."); 31 | 32 | if(this.isOldNeoForge) 33 | { 34 | final String[] oldNeoForgeVersionData = this.modLoaderVersion.split("-"); 35 | final String vanillaVersion = oldNeoForgeVersionData[0]; 36 | final String oldNeoForgeVersion = oldNeoForgeVersionData[1]; 37 | 38 | this.versionId = String.format("%s-forge-%s", vanillaVersion, oldNeoForgeVersion); 39 | } 40 | else this.versionId = String.format("neoforge-%s", this.modLoaderVersion); 41 | } 42 | 43 | @Override 44 | public boolean isModLoaderAlreadyInstalled(@NotNull Path installDir) 45 | { 46 | final Path versionJsonFile = installDir.resolve(this.versionId + ".json"); 47 | 48 | if(Files.notExists(versionJsonFile)) 49 | return false; 50 | 51 | try { 52 | final JsonObject object = JsonParser.parseReader(Files.newBufferedReader(versionJsonFile)) 53 | .getAsJsonObject(); 54 | 55 | final boolean firstPass = ModLoaderUtils.parseNewVersionInfo(installDir, object).stream().allMatch(ParsedLibrary::isInstalled); 56 | 57 | if(!firstPass) 58 | return false; 59 | } 60 | catch (Exception e) 61 | { 62 | this.logger.warn("An error occurred while checking if the mod loader is already installed."); 63 | return false; 64 | } 65 | 66 | final Path neoForgeDirectory = installDir.resolve("libraries") 67 | .resolve("net") 68 | .resolve("neoforged") 69 | .resolve(this.isOldNeoForge ? "forge" : "neoforge") 70 | .resolve(this.modLoaderVersion); 71 | 72 | final Path universalNeoForgeJar = neoForgeDirectory.resolve(this.versionId + "-universal.jar"); 73 | final Path clientNeoForgeJar = neoForgeDirectory.resolve(this.versionId + "-client.jar"); 74 | 75 | return Files.exists(universalNeoForgeJar) && Files.exists(clientNeoForgeJar); 76 | } 77 | 78 | @Override 79 | public void install(@NotNull Path installDir) throws Exception 80 | { 81 | super.install(installDir); 82 | 83 | final String installerUrl = String.format("https://maven.neoforged.net/net/neoforged/%s/%s/%s-installer.jar", 84 | this.isOldNeoForge ? "forge" : "neoforge", this.modLoaderVersion, this.versionId); 85 | 86 | ModLoaderUtils.fakeContext(installDir, this.vanilla.getName()); 87 | 88 | final String[] installerUrlParts = installerUrl.split("/"); 89 | final Path installerFile = installDir.resolve(installerUrlParts[installerUrlParts.length - 1]); 90 | 91 | IOUtils.download(this.logger, new URL(installerUrl), installerFile); 92 | 93 | final List command = new ArrayList<>(); 94 | command.add(this.javaPath); 95 | command.add("-jar"); 96 | command.add(installerFile.toAbsolutePath().toString()); 97 | command.add("--installClient"); 98 | command.add(installDir.toAbsolutePath().toString()); 99 | 100 | final ProcessBuilder processBuilder = new ProcessBuilder(command); 101 | 102 | processBuilder.directory(installDir.toFile()); 103 | processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT); 104 | 105 | final Process process = processBuilder.start(); 106 | process.waitFor(); 107 | 108 | Files.copy( 109 | installDir.resolve("versions") 110 | .resolve(this.versionId) 111 | .resolve(this.versionId + ".json"), 112 | installDir.resolve(this.versionId + ".json"), 113 | StandardCopyOption.REPLACE_EXISTING 114 | ); 115 | 116 | Files.deleteIfExists(installerFile); 117 | ModLoaderUtils.removeFakeContext(installDir); 118 | } 119 | 120 | @Override 121 | public String name() 122 | { 123 | return "NeoForge"; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/neoforge/NeoForgeVersionBuilder.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.versions.neoforge; 2 | 3 | import fr.flowarg.flowupdater.download.json.OptiFineInfo; 4 | import fr.flowarg.flowupdater.utils.builderapi.BuilderArgument; 5 | import fr.flowarg.flowupdater.utils.builderapi.BuilderException; 6 | import fr.flowarg.flowupdater.versions.ModLoaderVersionBuilder; 7 | 8 | public class NeoForgeVersionBuilder extends ModLoaderVersionBuilder 9 | { 10 | private final BuilderArgument neoForgeVersionArgument = new BuilderArgument("NeoForgeVersion").required(); 11 | private final BuilderArgument optiFineArgument = new BuilderArgument("OptiFine").optional(); 12 | 13 | /** 14 | * @param neoForgeVersion the NeoForge version you want to install. 15 | * @return the builder. 16 | */ 17 | public NeoForgeVersionBuilder withNeoForgeVersion(String neoForgeVersion) 18 | { 19 | this.neoForgeVersionArgument.set(neoForgeVersion); 20 | return this; 21 | } 22 | 23 | /** 24 | * Append some OptiFine download's information. 25 | * @param optiFineInfo OptiFine info. 26 | * @return the builder. 27 | */ 28 | public NeoForgeVersionBuilder withOptiFine(OptiFineInfo optiFineInfo) 29 | { 30 | this.optiFineArgument.set(optiFineInfo); 31 | return this; 32 | } 33 | 34 | @Override 35 | public NeoForgeVersion build() throws BuilderException 36 | { 37 | return new NeoForgeVersion( 38 | this.neoForgeVersionArgument.get(), 39 | this.modsArgument.get(), 40 | this.curseModsArgument.get(), 41 | this.modrinthModsArgument.get(), 42 | this.fileDeleterArgument.get(), 43 | this.curseModPackArgument.get(), 44 | this.modrinthPackArgument.get(), 45 | this.optiFineArgument.get() 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/neoforge/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains all the classes that are used to install NeoForge. 3 | */ 4 | package fr.flowarg.flowupdater.versions.neoforge; -------------------------------------------------------------------------------- /src/main/java/fr/flowarg/flowupdater/versions/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains all common classes to the versions system. 3 | */ 4 | package fr.flowarg.flowupdater.versions; -------------------------------------------------------------------------------- /src/test/java/fr/flowarg/flowupdater/IntegrationTests.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater; 2 | 3 | import fr.flowarg.flowio.FileUtils; 4 | import fr.flowarg.flowupdater.utils.UpdaterOptions; 5 | import org.junit.jupiter.api.*; 6 | 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertFalse; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 15 | public class IntegrationTests 16 | { 17 | private static final Path UPDATE_DIR = Paths.get("testing_directory"); 18 | 19 | @BeforeAll 20 | public static void setup() throws Exception 21 | { 22 | Updates.UPDATE_DIR = UPDATE_DIR; 23 | Files.createDirectory(UPDATE_DIR); 24 | } 25 | 26 | @AfterAll 27 | public static void cleanup() throws Exception 28 | { 29 | FileUtils.deleteDirectory(UPDATE_DIR); 30 | } 31 | 32 | @Order(1) 33 | @Test 34 | public void testWithVanillaUsage() throws Exception 35 | { 36 | final Updates.Result result = Updates.vanillaUsage(); 37 | this.basicAssertions(result.updateDir, result.error, result.version); 38 | } 39 | 40 | @Order(2) 41 | @Test 42 | public void testWithNewForgeUsage() throws Exception 43 | { 44 | final Updates.Result result = Updates.testWithNewForgeUsage(); 45 | final String vanillaForge = result.version + "-" + result.modLoaderVersion; 46 | 47 | this.basicAssertions(result.updateDir, result.error, result.version); 48 | 49 | assertTrue(Files.exists(result.updateDir.resolve(String.format("%s-forge-%s.json", result.version, result.modLoaderVersion)))); 50 | assertTrue(Files.exists(result.updateDir.resolve("libraries").resolve("net").resolve("minecraftforge").resolve("forge").resolve(vanillaForge).resolve("forge-" + vanillaForge + "-universal.jar"))); 51 | } 52 | 53 | @Order(3) 54 | @Test 55 | public void testWithVeryOldForgeUsage() throws Exception 56 | { 57 | final Updates.Result result = Updates.testWithVeryOldForgeUsage(); 58 | final String full = result.version + '-' + result.modLoaderVersion + '-' + result.version; 59 | 60 | this.basicAssertions(result.updateDir, result.error, result.version); 61 | 62 | assertTrue(Files.exists(result.updateDir.resolve(result.version + "-Forge" + result.modLoaderVersion + "-" + result.version + ".json"))); 63 | assertTrue(Files.exists(result.updateDir.resolve("libraries").resolve("net").resolve("minecraftforge").resolve("forge").resolve(full).resolve("forge-" + full + ".jar"))); 64 | } 65 | 66 | @Order(4) 67 | @Test 68 | public void testWithOldForgeUsage() throws Exception 69 | { 70 | final Updates.Result result = Updates.testWithOldForgeUsage(); 71 | final String full = result.version + '-' + result.modLoaderVersion; 72 | 73 | this.basicAssertions(result.updateDir, result.error, result.version); 74 | 75 | assertTrue(Files.exists(result.updateDir.resolve(result.version + "-forge" + full + ".json"))); 76 | assertTrue(Files.exists(result.updateDir.resolve("libraries").resolve("net").resolve("minecraftforge").resolve("forge").resolve(full).resolve("forge-" + full + ".jar"))); 77 | } 78 | 79 | @Order(5) 80 | @Test 81 | public void testWithFabric() throws Exception 82 | { 83 | final Updates.Result result = Updates.testWithFabric(); 84 | 85 | this.basicAssertions(result.updateDir, result.error, result.version); 86 | 87 | assertTrue(Files.exists(result.updateDir.resolve("libraries").resolve("net").resolve("fabricmc").resolve("fabric-loader"))); 88 | } 89 | 90 | @Order(6) 91 | @Test 92 | public void testWithQuilt() throws Exception 93 | { 94 | if(Integer.parseInt(System.getProperty("java.version").split("\\.")[0]) < 17) 95 | { 96 | System.out.println("Skipping test with Quilt because Java version is < 17"); 97 | return; 98 | } 99 | 100 | final Updates.Result result = Updates.testWithQuilt(new UpdaterOptions.UpdaterOptionsBuilder().build()); 101 | 102 | this.basicAssertions(result.updateDir, result.error, result.version); 103 | 104 | assertTrue(Files.exists(result.updateDir.resolve("libraries").resolve("org").resolve("quiltmc").resolve("quilt-loader"))); 105 | } 106 | 107 | @Order(6) 108 | @Test 109 | public void testWithFabric119() throws Exception 110 | { 111 | final Updates.Result result = Updates.testWithFabric119(); 112 | 113 | this.basicAssertions(result.updateDir, result.error, result.version, false); 114 | assertTrue(Files.exists(result.updateDir.resolve("libraries").resolve("net").resolve("fabricmc").resolve("fabric-loader"))); 115 | } 116 | 117 | @Order(8) 118 | @Test 119 | public void testWithNeoForgeUsage() throws Exception 120 | { 121 | final Updates.Result result = Updates.testWithNeoForgeUsage(); 122 | 123 | this.basicAssertions(result.updateDir, result.error, result.version, false); 124 | 125 | assertTrue(Files.exists(result.updateDir.resolve(String.format("neoforge-%s.json", result.modLoaderVersion)))); 126 | assertTrue(Files.exists(result.updateDir.resolve("libraries").resolve("net").resolve("neoforged").resolve("neoforge").resolve(result.modLoaderVersion).resolve("neoforge-" + result.modLoaderVersion + "-universal.jar"))); 127 | } 128 | 129 | private void basicAssertions(Path updateDir, boolean error, String version) throws Exception 130 | { 131 | this.basicAssertions(updateDir, error, version, true); 132 | } 133 | 134 | private void basicAssertions(Path updateDir, boolean error, String version, boolean natives) throws Exception 135 | { 136 | assertFalse(error); 137 | assertTrue(Files.exists(updateDir.resolve(version + ".json"))); 138 | assertTrue(Files.exists(updateDir.resolve("client.jar"))); 139 | 140 | if(natives) 141 | { 142 | final Path nativesDir = updateDir.resolve("natives"); 143 | assertTrue(Files.exists(nativesDir)); 144 | assertTrue(Files.isDirectory(nativesDir)); 145 | assertFalse(FileUtils.list(nativesDir).isEmpty()); 146 | } 147 | 148 | final Path librariesDir = updateDir.resolve("libraries"); 149 | assertTrue(Files.exists(librariesDir)); 150 | assertTrue(Files.isDirectory(librariesDir)); 151 | assertFalse(FileUtils.list(librariesDir).isEmpty()); 152 | FileUtils.list(librariesDir).forEach(path -> assertTrue(Files.isDirectory(path))); 153 | assertTrue(FileUtils.list(updateDir.resolve("assets").resolve("objects")).size() > 200); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/fr/flowarg/flowupdater/utils/VersionTest.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.utils; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Arrays; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | public class VersionTest 10 | { 11 | @Test 12 | public void testVersionCompareWithSameSize() 13 | { 14 | final Version version = Version.gen("1.0.0"); 15 | final Version version2 = Version.gen("1.0.0"); 16 | final Version version3 = Version.gen("1.0.1"); 17 | final Version version4 = Version.gen("1.1.0"); 18 | final Version version5 = Version.gen("2.0.0"); 19 | final Version version6 = Version.gen("0.1.0"); 20 | final Version version7 = new Version(Arrays.asList(1, 1, 1)); 21 | 22 | assertEquals(0, version.compareTo(version2)); 23 | assertEquals(-1, version.compareTo(version3)); 24 | assertEquals(-1, version.compareTo(version4)); 25 | assertEquals(-1, version.compareTo(version5)); 26 | assertEquals(1, version.compareTo(version6)); 27 | assertEquals(-1, version.compareTo(version7)); 28 | } 29 | 30 | @Test 31 | public void testVersionCompareWithDifferentSize() 32 | { 33 | final Version version = Version.gen("1.0.0"); 34 | final Version version2 = Version.gen("1.1"); 35 | final Version version3 = Version.gen("1.0"); 36 | final Version version4 = Version.gen("3"); 37 | final Version version5 = Version.gen("0"); 38 | final Version version6 = Version.gen("0.1"); 39 | final Version version7 = new Version(Arrays.asList(1, 2, 3, 4, 5)); 40 | 41 | assertEquals(-1, version.compareTo(version2)); 42 | assertEquals(1, version.compareTo(version3)); 43 | assertEquals(-1, version.compareTo(version4)); 44 | assertEquals(1, version.compareTo(version5)); 45 | assertEquals(1, version.compareTo(version6)); 46 | assertEquals(-1, version.compareTo(version7)); 47 | } 48 | 49 | @Test 50 | public void testVersionBetween() 51 | { 52 | final Version version = Version.gen("1.0.0"); 53 | final Version version2 = Version.gen("1.1"); 54 | final Version version3 = Version.gen("1.0"); 55 | final Version version4 = Version.gen("1.0.1"); 56 | final Version version5 = Version.gen("1.0.2"); 57 | 58 | assertTrue(version.isBetweenOrEqual(version3, version2)); 59 | assertTrue(version4.isBetweenOrEqual(version, version5)); 60 | } 61 | 62 | @Test 63 | public void testVersionEmpty() 64 | { 65 | assertThrows(IllegalArgumentException.class, () -> Version.gen("")); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/fr/flowarg/flowupdater/utils/builderapi/BuilderAPITest.java: -------------------------------------------------------------------------------- 1 | package fr.flowarg.flowupdater.utils.builderapi; 2 | 3 | import org.jetbrains.annotations.Contract; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | public class BuilderAPITest 10 | { 11 | @Test 12 | public void shouldFailBecauseMissingRequiredArgument() 13 | { 14 | assertThrows(BuilderException.class, () -> new TestBuilder().build()); 15 | } 16 | 17 | @Test 18 | public void shouldWorkBecauseRequiredArgumentIsFilled() 19 | { 20 | final TestObject object = new TestBuilder().withAnArgument("AnArgument").build(); 21 | assertEquals("AnArgument", object.str); 22 | } 23 | 24 | @Test 25 | public void shouldFailBecauseOfBadObject() 26 | { 27 | assertThrows(BuilderException.class, () -> new TestBuilder().withAnArgument("AnArgument").withAnInt(-1).build()); 28 | } 29 | 30 | @Test 31 | public void shouldFailBecauseUndefinedParentArgument() 32 | { 33 | assertThrows(BuilderException.class, () -> new AnotherTestBuilder().withAnotherBoolean(true).build()); 34 | } 35 | 36 | @Test 37 | public void shouldWorkBecauseDefinedParentArgument() 38 | { 39 | final AnotherTestObject anotherTestObject = new AnotherTestBuilder().withAnotherBoolean(true).withABoolean(false).build(); 40 | assertTrue(anotherTestObject.anotherBoolean); 41 | assertFalse(anotherTestObject.aBoolean); 42 | } 43 | 44 | private static class TestObject 45 | { 46 | public final String str; 47 | public final int anInt; 48 | 49 | public TestObject(String str, int anInt) 50 | { 51 | this.str = str; 52 | this.anInt = anInt; 53 | } 54 | } 55 | 56 | private static class AnotherTestObject 57 | { 58 | public final boolean aBoolean; 59 | public final boolean anotherBoolean; 60 | 61 | public AnotherTestObject(boolean aBoolean, boolean anotherBoolean) 62 | { 63 | this.aBoolean = aBoolean; 64 | this.anotherBoolean = anotherBoolean; 65 | } 66 | } 67 | 68 | private static class TestBuilder implements IBuilder 69 | { 70 | private final BuilderArgument anArgument = new BuilderArgument("AnArgument").required(); 71 | private final BuilderArgument anIntArgument = new BuilderArgument<>("AnIntArgument", () -> 0, () -> -1).optional(); 72 | 73 | public TestBuilder withAnArgument(String anArgument) 74 | { 75 | this.anArgument.set(anArgument); 76 | return this; 77 | } 78 | 79 | public TestBuilder withAnInt(int anInt) 80 | { 81 | this.anIntArgument.set(anInt); 82 | return this; 83 | } 84 | 85 | @Contract(" -> new") 86 | @Override 87 | public @NotNull TestObject build() throws BuilderException 88 | { 89 | return new TestObject( 90 | this.anArgument.get(), 91 | this.anIntArgument.get() 92 | ); 93 | } 94 | } 95 | 96 | private static class AnotherTestBuilder implements IBuilder 97 | { 98 | private final BuilderArgument aBooleanArgument = new BuilderArgument("ABooleanArgument").optional(); 99 | private final BuilderArgument anotherBooleanArgument = new BuilderArgument("AnotherBooleanArgument").require(this.aBooleanArgument).optional(); 100 | 101 | public AnotherTestBuilder withABoolean(boolean aBoolean) 102 | { 103 | this.aBooleanArgument.set(aBoolean); 104 | return this; 105 | } 106 | 107 | public AnotherTestBuilder withAnotherBoolean(boolean anotherBoolean) 108 | { 109 | this.anotherBooleanArgument.set(anotherBoolean); 110 | return this; 111 | } 112 | 113 | @Contract(" -> new") 114 | @Override 115 | public @NotNull AnotherTestObject build() throws BuilderException 116 | { 117 | return new AnotherTestObject( 118 | this.aBooleanArgument.get(), 119 | this.anotherBooleanArgument.get() 120 | ); 121 | } 122 | } 123 | } 124 | --------------------------------------------------------------------------------