Showing preview only (329K chars total). Download the full file or copy to clipboard to get everything.
Repository: FlowArg/FlowUpdater
Branch: master
Commit: 97b0ead23dff
Files: 89
Total size: 300.9 KB
Directory structure:
gitextract_dp3i_z3s/
├── .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/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.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
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: 'BUG :'
labels: bug
assignees: FlowArg
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows]
- Version [e.g. 10]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: 'Feature Request :'
labels: feature request
assignees: FlowArg
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/docs.yml
================================================
name: CI Documentation
on:
push:
branches: [ master ]
jobs:
docs:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v5
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'zulu'
- name: Build documentation
run: gradle javadoc
- name: Publish Github Pages
uses: peaceiris/actions-gh-pages@v4
with:
personal_token: ${{ secrets.FLOW_TOKEN }}
publish_dir: ./build/docs/javadoc
================================================
FILE: .github/workflows/gradle-ci.yml
================================================
name: Gradle CI
on:
push:
branches: [ master ]
jobs:
testjava:
strategy:
matrix:
jdk: [17, 21]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v5
- name: Set up JDK ${{ matrix.jdk }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.jdk }}
distribution: 'zulu'
- name: Build and test project with Java ${{ matrix.jdk }}
run: gradle build javadoc
================================================
FILE: .github/workflows/gradle-publish.yml
================================================
name: Gradle Package
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v5
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'zulu'
- name: Publish FlowUpdater to MavenCentral
run: gradle publish
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_TOKEN: ${{ secrets.SONATYPE_TOKEN }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
NEW_CENTRAL_ID: ${{ secrets.NEW_CENTRAL_ID }}
NEW_CENTRAL_TOKEN: ${{ secrets.NEW_CENTRAL_TOKEN }}
================================================
FILE: .gitignore
================================================
.gradle
build
updater
run
src/test/java/fr/flowarg/flowupdatertest
.idea
out
forge-installer.jar.log
forge-installer-patched.jar.log
installer.log
testing_directory
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
FlowUpdater - The free and opensource solution to update Minecraft.
Copyright (C) 2020 Flow Arg (FlowArg)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
FlowUpdater Copyright (C) 2020 Flow Arg (FlowArg)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: README.MD
================================================
[version]: https://img.shields.io/maven-central/v/fr.flowarg/flowupdater.svg?label=Download
[download]: https://search.maven.org/search?q=g:%22fr.flowarg%22%20AND%20a:%22flowupdater%22
[discord-shield]: https://discordapp.com/api/guilds/730758985376071750/widget.png
[discord-invite]: https://discord.gg/dN6HWHp
[ ![version][] ][download]
[ ![discord-shield][] ][discord-invite]
# FlowUpdater
Welcome on FlowUpdater's repository. FlowUpdater is a free and open source solution to update Minecraft in Java.
It was mainly designed for launcher's purposes but can be used for other usages as well. FlowUpdater focuses on customization and reliability.
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.
## Legal and fork notices :warning:
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.**
If you wish to fork this project, **you HAVE TO use your own API Key**.
## Alternatives
If you are a developer or know a developer who has made a similar library in another programming language,
feel free to ask to appear in this list:
- [Rust Launcher Lib](https://github.com/knightmar/rust_launcher_lib) (Rust)
## Usage
### Vanilla
First, create a new VanillaVersion, and build the version:
```java
VanillaVersion version = new VanillaVersion.VanillaVersionBuilder().withName("1.20.4").build();
```
`VanillaVersion` accepts some arguments to add more libraries, assets or to reach snapshots or custom version of the game.
All accepted arguments are available in the `VanillaVersionBuilder` class.
Add the version to a new `FlowUpdater` instance and build it:
```java
FlowUpdater updater = new FlowUpdater.FlowUpdaterBuilder()
.withVanillaVersion(version)
.build();
```
In the same way, `FlowUpdater` accepts many arguments that you can use as you want.
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.
Finally, call the update function:
```java
updater.update(Paths.get("your/path/"));
```
This `update` method will start the whole checks-and-download pipeline and will return when all the work is done.
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.
### Forge
(You need to setup a vanilla version like above!)
Next, make a List of Mod objects (except if you don't need some).
```java
List<Mod> mods = new ArrayList<>();
mods.add(new Mod("OneMod.jar", "sha1ofmod", 85120, "https://link.com/of/mod.jar"));
mods.add(new Mod("AnotherMod.jar", "sha1ofanothermod", 86120, "https://link.com/of/another/mod.jar"));
```
You can also get a list of mods by providing a json link: `List<Mod> mods = Mod.getModsFromJson("https://url.com/launcher/mods.json");`. A template is available in Mod class.
You can get mods from CurseForge too:
```java
List<CurseFileInfo> modInfos = new ArrayList<>();
// project ID and file ID
modInfos.add(new CurseFileInfo(238222, 2988823));
```
You can also get a list of curse mods by providing a json link: `List<CurseFileInfo> mods = CurseFileInfo.getFilesFromJson("https://url.com/launcher/cursemods.json");`.
On the same pattern, you can get mods from Modrinth.
Then, build a forge version. For example, I will build a NewForgeVersion.
```java
ForgeVersion forgeVersion = new ForgeVersionBuilder()
.withForgeVersion("1.20.6-50.1.12") // mandatory
.withCurseMods(modInfos) // optional
.withOptiFine(new OptiFineInfo("preview_OptiFine_1.20.6_HD_U_I9_pre1")) // installing OptiFine (optional)
.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.
.build();
```
Finally, set the Forge version object to your `FlowUpdaterBuilder`:
```java
.withModLoaderVersion(forgeVersion);
```
### NeoForge
Works almost the same way as Forge, but you need to use `NeoForgeVersion` instead of `ForgeVersion` and `NeoForgeVersionBuilder` instead of `ForgeVersionBuilder`.
Be careful when passing the neoforge version, it must be in the forge format for 1.20.1 (1.20.1-47.1.5 for example) ; but you should only pass the neoforge version for versions >= 1.21 (21.8.31 for example).
### Fabric
(You need to setup a vanilla updater!)
Next, make a List of Mod objects like for a ForgeVersion if you need some.
Then, build a Fabric version.
```java
FabricVersion fabricVersion = new FabricVersionBuilder()
.withFabricVersion("0.10.8") // optional, if you don't set one, it will take the latest fabric loader version available.
.withCurseMods(modInfos) // optional
.withMods(mods) // optional
.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.
.build();
```
Finally, set the Fabric version to your `FlowUpdaterBuilder`:
```java
.withModLoaderVersion(fabricVersion);
```
### MCP
(You need to setup a vanilla updater!)
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...
(1) set to vanilla version builder a MCP version:
```java
.withMCP(new MCP("clientURL", "clientSha1", 25008229));
```
If you set an empty/null string in url and sha1 and 0 in size, the updater will use the default minecraft jar.
Example for a client-only mcp downloading:
```java
.withMCP(new MCP("https://mighya.eu/resources/Client.jar", "f2c219e485831af2bae9464eebbe4765128c6ad6", 23005862));
```
You can get an MCP object instance by providing a json link too: `.withMCP("https://url.com/launcher/mcp.json");`.
(2)
Still in the vanilla version builder, set a json link to a custom MCP version:
```java
.withCustomVersionJson(new URL("https://url.com/launcher/mcp.json"));
```
You can also provide some more additional libraries or assets with all methods in the `VanillaVersionBuilder` class
(`withAnotherLibraries`, `withAnotherAssets`, `withCustomAssetIndex`).
## External Files
With FlowUpdater, you can download other files in your update dir! This system is designed mainly for configs, resource packs.
You can also configure a keep-policy for these files (should the updater download the file again if it is modified?).
In your FlowUpdaterBuilder, define an array list of ExternalFile (by `ExternalFile#getExternalFilesFromJson` for more convenience).
### About json files...
**Deprecated**: All json files can be generated by the [FlowUpdaterJsonCreator](https://github.com/FlowArg/FlowUpdaterJsonCreator)!
There are new tools made by the community that can help you generate some JSON files:
- [FlowJsonCreator by Paulem79](https://github.com/Paulem79/FlowJsonCreator) (Java)
- [FUJC by Zuygui](https://github.com/zuygui/flowupdater-json-creator) (Rust)
## Post executions
With FlowUpdater, you can execute some actions after update, like patch a file, kill a process, launch a process, review a config etc...
In your FlowUpdaterBuilder, you have to set a list of Runnable.
It's not always relevant to use this feature, but it can be useful in some specific cases.
================================================
FILE: build.gradle
================================================
plugins {
id 'java-library'
id 'idea'
id 'maven-publish'
id 'signing'
}
group = 'fr.flowarg'
version = '1.9.4'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
withJavadocJar()
withSourcesJar()
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
}
tasks.named("compileJava").configure {
options.release.set(8)
}
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
api libs.gson
api libs.flowmultitools
api libs.annotations
// Only for internal tests
testImplementation libs.oll
testImplementation libs.junit.jupiter
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.withType(Jar).configureEach {
archiveBaseName.set("flowupdater")
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
pom {
groupId = project.group
version = project.version
artifactId = 'flowupdater'
name = project.name
description = 'The free and open source solution to update Minecraft.'
url = 'https://github.com/FlowArg/FlowUpdater'
scm {
connection = 'scm:git:git://github.com/FlowArg/FlowUpdater.git'
developerConnection = 'scm:git:ssh://github.com:FlowArg/FlowUpdater.git'
url = 'https://github.com/FlowArg/FlowUpdater/tree/master'
}
licenses {
license {
name = 'GNU General Public License v3.0'
url = 'https://www.gnu.org/licenses/gpl-3.0.txt'
}
}
developers {
developer {
id = 'flowarg'
name = 'Flow Arg'
email = 'flow@flowarg.fr'
}
}
}
}
}
repositories {
maven {
credentials {
username = System.getenv("NEW_CENTRAL_ID")
password = System.getenv("NEW_CENTRAL_TOKEN")
}
url = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
}
}
}
signing {
def signingKey = System.getenv("GPG_PRIVATE_KEY")
def signingPassword = System.getenv("GPG_PASSPHRASE")
useInMemoryPgpKeys(signingKey, signingPassword)
sign publishing.publications.mavenJava
}
test {
useJUnitPlatform()
}
================================================
FILE: flowupdater-schema.json
================================================
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "",
"type": "object",
"properties": {
"curseFiles": {
"type": "array",
"uniqueItems": true,
"minItems": 1,
"items": {
"required": [
"projectID",
"fileID"
],
"properties": {
"projectID": {
"type": "number",
"description": "Project ID of the mod on CurseForge"
},
"fileID": {
"type": "number",
"description": "File ID of the mod on CurseForge"
},
"required": {
"type": "boolean",
"description": "If true, the mod is required to be present in the mod folder."
}
}
}
},
"modrinthMods": {
"type": "array",
"uniqueItems": true,
"minItems": 1,
"items": {
"required": [
"versionId",
"projectReference",
"versionNumber"
],
"oneOf": [
{
"required": [
"versionId"
],
"properties": {
"versionId": {
"type": "number",
"description": "Mod version file ID"
}
}
},
{
"required": [
"projectReference",
"versionNumber"
],
"properties": {
"projectReference": {
"type": "number",
"description": "Projet ID of the mod on Modrinth"
},
"versionNumber": {
"type": "number",
"description": "Version ID of the mod on Modrinth"
}
}
}
]
}
},
"mods": {
"type": "array",
"uniqueItems": true,
"minItems": 1,
"items": {
"required": [
"name",
"downloadURL",
"sha1",
"size"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Name of mod file"
},
"downloadURL": {
"type": "string",
"minLength": 1,
"description": "Mod download URL"
},
"sha1": {
"type": "string",
"minLength": 1,
"description": "Sha1 of mod file"
},
"size": {
"type": "number",
"description": "Size of mod file (in bytes)"
}
}
}
},
"extfiles": {
"type": "array",
"uniqueItems": true,
"minItems": 1,
"items": {
"required": [
"path",
"downloadURL",
"sha1",
"size"
],
"properties": {
"path": {
"type": "string",
"minLength": 1,
"description": "Path of external file"
},
"downloadURL": {
"type": "string",
"minLength": 1,
"description": "external file URL"
},
"sha1": {
"type": "string",
"minLength": 1,
"description": "Sha1 of external file"
},
"size": {
"type": "number",
"description": "Size of external file (in bytes)"
},
"update": {
"type": "boolean",
"description": "If false, the file will not be checked again if the file is valid."
}
}
}
},
"clientURL": {
"type": "string",
"minLength": 1,
"description": "URL of client.jar"
},
"clientSha1": {
"type": "string",
"minLength": 1,
"description": "SHA1 of client.jar"
},
"clientSize": {
"type": "number",
"description": "Size of client.jar (in bytes)"
}
}
}
================================================
FILE: gradle/libs.versions.toml
================================================
[versions]
gson = "2.13.2"
flowmultitools = "1.4.5"
annotations = "26.1.0"
oll = "3.2.11"
junit = "6.0.3"
[libraries]
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
flowmultitools = { module = "fr.flowarg:flowmultitools", version.ref = "flowmultitools" }
annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" }
oll = { module = "fr.flowarg:openlauncherlib", version.ref = "oll" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: gradle.properties
================================================
org.gradle.configuration-cache=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
rootProject.name = 'FlowUpdater'
================================================
FILE: src/main/java/fr/flowarg/flowupdater/FlowUpdater.java
================================================
package fr.flowarg.flowupdater;
import fr.flowarg.flowio.FileUtils;
import fr.flowarg.flowlogger.ILogger;
import fr.flowarg.flowlogger.Logger;
import fr.flowarg.flowupdater.download.*;
import fr.flowarg.flowupdater.download.json.ExternalFile;
import fr.flowarg.flowupdater.download.json.Mod;
import fr.flowarg.flowupdater.integrations.IntegrationManager;
import fr.flowarg.flowupdater.integrations.curseforgeintegration.ICurseForgeCompatible;
import fr.flowarg.flowupdater.integrations.modrinthintegration.IModrinthCompatible;
import fr.flowarg.flowupdater.integrations.optifineintegration.IOptiFineCompatible;
import fr.flowarg.flowupdater.utils.IOUtils;
import fr.flowarg.flowupdater.utils.UpdaterOptions;
import fr.flowarg.flowupdater.utils.VersionChecker;
import fr.flowarg.flowupdater.utils.builderapi.BuilderArgument;
import fr.flowarg.flowupdater.utils.builderapi.BuilderException;
import fr.flowarg.flowupdater.utils.builderapi.IBuilder;
import fr.flowarg.flowupdater.versions.IModLoaderVersion;
import fr.flowarg.flowupdater.versions.VanillaVersion;
import org.jetbrains.annotations.NotNull;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
/**
* Represent the base class of the updater.<br>
* You can define some parameters about your version (Forge, Vanilla, MCP, Fabric...).
* @author FlowArg
*/
public class FlowUpdater
{
/** FlowUpdater's version string constant */
public static final String FU_VERSION = "1.9.4";
/** Vanilla version's object to update/install */
private final VanillaVersion vanillaVersion;
/** Logger object */
private final ILogger logger;
/** Mod loader version to install, can be null if you want a vanilla or MCP version */
private final IModLoaderVersion modLoaderVersion;
/** Progress callback to notify installation progress */
private final IProgressCallback callback;
/** Information about download status */
private final DownloadList downloadList;
/** Represent some settings for FlowUpdater */
private final UpdaterOptions updaterOptions;
/** Represent a list of ExternalFile. External files are downloaded before post-executions.*/
private final List<ExternalFile> externalFiles;
/** Represent a list of Runnable. Post-Executions are called after update. */
private final List<Runnable> postExecutions;
/** The integration manager object */
private final IntegrationManager integrationManager;
/** Default callback */
public static final IProgressCallback NULL_CALLBACK = new IProgressCallback()
{
@Override
public void init(@NotNull ILogger logger)
{
logger.info("Default callback will be used.");
}
};
/** Default logger, null for path argument = no file logger */
public static final ILogger DEFAULT_LOGGER = new Logger("[FlowUpdater]", null);
/**
* Basic constructor for {@link FlowUpdater}, use {@link FlowUpdaterBuilder} to instantiate a new {@link FlowUpdater}.
* @param vanillaVersion {@link VanillaVersion} to update.
* @param logger {@link ILogger} used for log information.
* @param updaterOptions {@link UpdaterOptions} for this updater
* @param callback {@link IProgressCallback} used for update progression. If it's null, it will be
* automatically assigned to {@link FlowUpdater#NULL_CALLBACK}.
* @param externalFiles {@link List<ExternalFile>} are downloaded before postExecutions.
* @param postExecutions {@link List<Runnable>} are called after update.
* @param modLoaderVersion {@link IModLoaderVersion} to install can be null.
*/
private FlowUpdater(VanillaVersion vanillaVersion, ILogger logger,
UpdaterOptions updaterOptions, IProgressCallback callback,
List<ExternalFile> externalFiles, List<Runnable> postExecutions,
IModLoaderVersion modLoaderVersion)
{
this.logger = logger;
this.vanillaVersion = vanillaVersion;
this.externalFiles = externalFiles;
this.postExecutions = postExecutions;
this.modLoaderVersion = modLoaderVersion;
this.updaterOptions = updaterOptions;
this.callback = callback;
this.downloadList = new DownloadList();
this.integrationManager = new IntegrationManager(this);
this.logger.info(String.format(
"------------------------- FlowUpdater for Minecraft %s v%s -------------------------",
this.vanillaVersion.getName(), FU_VERSION));
if(this.updaterOptions.isVersionChecker())
VersionChecker.run(this.logger);
this.callback.init(this.logger);
}
/**
* This method updates the Minecraft Installation in the given directory.
* If the {@link #vanillaVersion} is {@link VanillaVersion#NULL_VERSION}, the updater will
* only run external files and post-executions.
* @param dir Directory where is the Minecraft installation.
* @throws Exception if an I/O problem occurred.
* @throws fr.flowarg.flowupdater.utils.FlowUpdaterException if an important error occurred during the update.
*/
public void update(Path dir) throws Exception
{
this.checkExtFiles(dir);
this.updateMinecraft(dir);
this.updateExtFiles(dir);
this.runPostExecutions();
this.endUpdate();
}
private void checkExtFiles(Path dir) throws Exception
{
this.updaterOptions.getExternalFileDeleter().delete(this.externalFiles, this.downloadList, dir);
}
private void updateMinecraft(@NotNull Path dir) throws Exception
{
this.loadVanillaStuff();
if(this.modLoaderVersion != null)
this.loadModLoader(dir);
this.startVanillaDownload(dir);
if(this.modLoaderVersion != null)
this.installModLoader(dir);
}
private void loadVanillaStuff() throws Exception
{
if(this.vanillaVersion == VanillaVersion.NULL_VERSION)
{
this.downloadList.init();
return;
}
this.logger.info(String.format("Reading data about %s Minecraft version...", this.vanillaVersion.getName()));
new VanillaReader(this).read();
}
private void loadModLoader(@NotNull Path dir) throws Exception
{
final Path modsDirPath = dir.resolve("mods");
this.checkMods(this.modLoaderVersion, modsDirPath);
if(this.modLoaderVersion instanceof ICurseForgeCompatible)
this.integrationManager.loadCurseForgeIntegration(modsDirPath, (ICurseForgeCompatible)this.modLoaderVersion);
if(this.modLoaderVersion instanceof IModrinthCompatible)
this.integrationManager.loadModrinthIntegration(modsDirPath, (IModrinthCompatible)this.modLoaderVersion);
if(this.modLoaderVersion instanceof IOptiFineCompatible)
this.integrationManager.loadOptiFineIntegration(modsDirPath, (IOptiFineCompatible)this.modLoaderVersion);
}
private void checkMods(@NotNull IModLoaderVersion modLoader, Path modsDir) throws Exception
{
for(Mod mod : modLoader.getMods())
{
final Path filePath = modsDir.resolve(mod.getName());
if(Files.notExists(filePath) ||
Files.size(filePath) != mod.getSize() ||
(!mod.getSha1().isEmpty() && !FileUtils.getSHA1(filePath).equalsIgnoreCase(mod.getSha1())))
this.downloadList.getMods().add(mod);
}
}
private void startVanillaDownload(Path dir) throws Exception
{
if (Files.notExists(dir))
Files.createDirectories(dir);
new VanillaDownloader(dir, this).download();
}
private void installModLoader(Path dir) throws Exception
{
if(this.modLoaderVersion == null)
return;
this.modLoaderVersion.attachFlowUpdater(this);
if(!this.modLoaderVersion.isModLoaderAlreadyInstalled(dir))
{
this.modLoaderVersion.install(dir);
this.logger.info(this.modLoaderVersion.name() + ", version: " + this.modLoaderVersion.getModLoaderVersion() + " has been successfully installed!");
}
else this.logger.info(this.modLoaderVersion.name() + " is already installed! Skipping installation...");
this.modLoaderVersion.installMods(dir.resolve("mods"));
}
private void updateExtFiles(Path dir)
{
if(this.downloadList.getExtFiles().isEmpty()) return;
this.callback.step(Step.EXTERNAL_FILES);
this.logger.info("Downloading external file(s)...");
final Consumer<ExternalFile> extFileDownloadConsumer = extFile -> {
try
{
final Path filePath = dir.resolve(extFile.getPath());
IOUtils.download(this.logger, new URL(extFile.getDownloadURL()), filePath);
this.callback.onFileDownloaded(filePath);
}
catch (Exception e)
{
this.logger.printStackTrace(e);
}
this.downloadList.incrementDownloaded(extFile.getSize());
this.callback.update(this.downloadList.getDownloadInfo());
};
if(this.updaterOptions.shouldDisableExtFilesAsyncDownload())
this.downloadList.getExtFiles().forEach(extFileDownloadConsumer);
else IOUtils.executeAsyncForEach(this.downloadList.getExtFiles(), Executors.newWorkStealingPool(), extFileDownloadConsumer);
}
private void runPostExecutions()
{
if(this.postExecutions.isEmpty()) return;
this.callback.step(Step.POST_EXECUTIONS);
this.logger.info("Running post executions...");
this.postExecutions.forEach(Runnable::run);
}
private void endUpdate()
{
this.callback.step(Step.END);
this.callback.update(this.downloadList.getDownloadInfo());
this.downloadList.clear();
}
/**
* Builder of {@link FlowUpdater}.
* @author Flow Arg (FlowArg)
*/
public static class FlowUpdaterBuilder implements IBuilder<FlowUpdater>
{
private final BuilderArgument<VanillaVersion> versionArgument = new BuilderArgument<>("VanillaVersion", () -> VanillaVersion.NULL_VERSION).optional();
private final BuilderArgument<ILogger> loggerArgument = new BuilderArgument<>("Logger", () -> DEFAULT_LOGGER).optional();
private final BuilderArgument<UpdaterOptions> updaterOptionsArgument = new BuilderArgument<>("UpdaterOptions", () -> UpdaterOptions.DEFAULT).optional();
private final BuilderArgument<IProgressCallback> progressCallbackArgument = new BuilderArgument<>("Callback", () -> NULL_CALLBACK).optional();
private final BuilderArgument<List<ExternalFile>> externalFilesArgument = new BuilderArgument<List<ExternalFile>>("External Files", ArrayList::new).optional();
private final BuilderArgument<List<Runnable>> postExecutionsArgument = new BuilderArgument<List<Runnable>>("Post Executions", ArrayList::new).optional();
private final BuilderArgument<IModLoaderVersion> modLoaderVersionArgument = new BuilderArgument<IModLoaderVersion>("ModLoader").optional().require(this.versionArgument);
/**
* Append a {@link VanillaVersion} object in the final FlowUpdater instance.
* @param version the {@link VanillaVersion} to append and install.
* @return the builder.
*/
public FlowUpdaterBuilder withVanillaVersion(VanillaVersion version)
{
this.versionArgument.set(version);
return this;
}
/**
* Append a {@link ILogger} object in the final FlowUpdater instance.
* @param logger the {@link ILogger} to append and use.
* @return the builder.
*/
public FlowUpdaterBuilder withLogger(ILogger logger)
{
this.loggerArgument.set(logger);
return this;
}
/**
* Append a {@link UpdaterOptions} object in the final FlowUpdater instance.
* @param updaterOptions the {@link UpdaterOptions} to append and propagate.
* @return the builder.
*/
public FlowUpdaterBuilder withUpdaterOptions(UpdaterOptions updaterOptions)
{
this.updaterOptionsArgument.set(updaterOptions);
return this;
}
/**
* Append a {@link IProgressCallback} object in the final FlowUpdater instance.
* @param callback the {@link IProgressCallback} to append and use.
* @return the builder.
*/
public FlowUpdaterBuilder withProgressCallback(IProgressCallback callback)
{
this.progressCallbackArgument.set(callback);
return this;
}
/**
* Append a {@link List} object in the final FlowUpdater instance.
* @param externalFiles the {@link List} to append and update.
* @return the builder.
*/
public FlowUpdaterBuilder withExternalFiles(Collection<ExternalFile> externalFiles)
{
this.externalFilesArgument.get().addAll(externalFiles);
return this;
}
/**
* Append an array object in the final FlowUpdater instance.
* @param externalFiles the array to append and update.
* @return the builder.
*/
public FlowUpdaterBuilder withExternalFiles(ExternalFile... externalFiles)
{
return withExternalFiles(Arrays.asList(externalFiles));
}
/**
* Append external files in the final FlowUpdater instance.
* @param externalFilesJsonUrl the URL of the json of external files append and update.
* @return the builder.
*/
public FlowUpdaterBuilder withExternalFiles(URL externalFilesJsonUrl)
{
return withExternalFiles(ExternalFile.getExternalFilesFromJson(externalFilesJsonUrl));
}
/**
* Append external files in the final FlowUpdater instance.
* @param externalFilesJsonUrl the URL of the json of external files append and update.
* @return the builder.
*/
public FlowUpdaterBuilder withExternalFiles(String externalFilesJsonUrl)
{
return withExternalFiles(ExternalFile.getExternalFilesFromJson(externalFilesJsonUrl));
}
/**
* Append a {@link List} object in the final FlowUpdater instance.
* @param postExecutions the {@link List} to append and run after the update.
* @return the builder.
*/
public FlowUpdaterBuilder withPostExecutions(Collection<Runnable> postExecutions)
{
this.postExecutionsArgument.get().addAll(postExecutions);
return this;
}
/**
* Append an array object in the final FlowUpdater instance.
* @param postExecutions the array to append and run after the update.
* @return the builder.
*/
public FlowUpdaterBuilder withPostExecutions(Runnable... postExecutions)
{
return withPostExecutions(Arrays.asList(postExecutions));
}
/**
* Necessary if you want to install a mod loader like Forge or Fabric, for instance.
* Append a {@link IModLoaderVersion} object in the final FlowUpdater instance.
* @param modLoaderVersion the {@link IModLoaderVersion} to append and install.
* @return the builder.
*/
public FlowUpdaterBuilder withModLoaderVersion(IModLoaderVersion modLoaderVersion)
{
this.modLoaderVersionArgument.set(modLoaderVersion);
return this;
}
/**
* Build a new {@link FlowUpdater} instance with provided arguments.
* @return the new {@link FlowUpdater} instance.
* @throws BuilderException if an error occurred on FlowUpdater instance building.
*/
@Override
public FlowUpdater build() throws BuilderException
{
return new FlowUpdater(
this.versionArgument.get(),
this.loggerArgument.get(),
this.updaterOptionsArgument.get(),
this.progressCallbackArgument.get(),
this.externalFilesArgument.get(),
this.postExecutionsArgument.get(),
this.modLoaderVersionArgument.get()
);
}
}
// Some getters
/**
* Get the {@link VanillaVersion} attached to this {@link FlowUpdater} instance.
* @return a vanilla version.
*/
public VanillaVersion getVanillaVersion()
{
return this.vanillaVersion;
}
/**
* Get th {@link IModLoaderVersion} attached to this {@link FlowUpdater} instance.
* @return a mod loader version.
*/
public IModLoaderVersion getModLoaderVersion()
{
return this.modLoaderVersion;
}
/**
* Get the current logger.
* @return a logger.
*/
public ILogger getLogger()
{
return this.logger;
}
/**
* Get the current callback.
* @return a callback.
*/
public IProgressCallback getCallback()
{
return this.callback;
}
/**
* Get the list of external files.
* @return external files.
*/
public List<ExternalFile> getExternalFiles()
{
return this.externalFiles;
}
/**
* Get the list of post-executions.
* @return all post-executions
*/
public List<Runnable> getPostExecutions()
{
return this.postExecutions;
}
/**
* Get the download list which contains all download information.
* @return a {@link DownloadList} instance.
*/
public DownloadList getDownloadList()
{
return this.downloadList;
}
/**
* Get the FlowUpdater's options.
* @return some useful settings.
*/
public UpdaterOptions getUpdaterOptions()
{
return this.updaterOptions;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/DownloadList.java
================================================
package fr.flowarg.flowupdater.download;
import fr.flowarg.flowupdater.download.json.AssetDownloadable;
import fr.flowarg.flowupdater.download.json.Downloadable;
import fr.flowarg.flowupdater.download.json.ExternalFile;
import fr.flowarg.flowupdater.download.json.Mod;
import fr.flowarg.flowupdater.integrations.optifineintegration.OptiFine;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Represent information about download status. Used with {@link IProgressCallback} progress system.
*
* @author FlowArg
*/
public class DownloadList
{
private final List<Downloadable> downloadableFiles = new ArrayList<>();
private final List<AssetDownloadable> downloadableAssets = new ArrayList<>();
private final List<ExternalFile> extFiles = new ArrayList<>();
private final List<Mod> mods = new ArrayList<>();
private final Lock updateInfoLock = new ReentrantLock();
private OptiFine optiFine = null;
private DownloadInfo downloadInfo;
private boolean init = false;
/**
* This method initializes fields.
*/
public void init()
{
if (this.init) return;
this.downloadInfo = new DownloadInfo();
this.downloadableFiles.forEach(
downloadable -> this.downloadInfo.totalToDownloadBytes.set(
this.downloadInfo.totalToDownloadBytes.get() + downloadable.getSize()));
this.downloadableAssets.forEach(
downloadable -> this.downloadInfo.totalToDownloadBytes.set(
this.downloadInfo.totalToDownloadBytes.get() + downloadable.getSize()));
this.extFiles.forEach(
externalFile -> this.downloadInfo.totalToDownloadBytes.set(
this.downloadInfo.totalToDownloadBytes.get() + externalFile.getSize()));
this.mods.forEach(
mod -> this.downloadInfo.totalToDownloadBytes.set(this.downloadInfo.totalToDownloadBytes.get() + mod.getSize()));
this.downloadInfo.totalToDownloadFiles.set(
this.downloadInfo.totalToDownloadFiles.get() +
this.downloadableFiles.size() +
this.downloadableAssets.size() +
this.extFiles.size() +
this.mods.size());
if (this.optiFine != null)
{
this.downloadInfo.totalToDownloadBytes.set(this.downloadInfo.totalToDownloadBytes.get() + this.optiFine.getSize());
this.downloadInfo.totalToDownloadFiles.incrementAndGet();
}
this.init = true;
}
/**
* This method increments the number of bytes downloaded by the number of bytes passed in parameter.
* @param bytes number of bytes to add to downloaded bytes.
*/
public void incrementDownloaded(long bytes)
{
this.updateInfoLock.lock();
this.downloadInfo.downloadedFiles.incrementAndGet();
this.downloadInfo.downloadedBytes.set(this.downloadInfo.downloadedBytes.get() + bytes);
this.updateInfoLock.unlock();
}
/**
* Get the new API to get information about the progress of the download.
* @return the instance of {@link DownloadInfo}.
*/
public DownloadInfo getDownloadInfo()
{
return this.downloadInfo;
}
/**
* Get the queue that contains all assets to download.
* @return the queue that contains all assets to download.
*/
public List<AssetDownloadable> getDownloadableAssets()
{
return this.downloadableAssets;
}
/**
* Get the list that contains all downloadable files.
* @return the list that contains all downloadable files.
*/
public List<Downloadable> getDownloadableFiles()
{
return this.downloadableFiles;
}
/**
* Get the list that contains all external files.
* @return the list that contains all external files.
*/
public List<ExternalFile> getExtFiles()
{
return this.extFiles;
}
/**
* Get the list that contains all mods.
* @return the list that contains all mods.
*/
public List<Mod> getMods()
{
return this.mods;
}
/**
* Get the OptiFine object.
* @return the OptiFine object.
*/
public OptiFine getOptiFine()
{
return this.optiFine;
}
/**
* Define the OptiFine object.
* @param optiFine the OptiFine object to define.
*/
public void setOptiFine(OptiFine optiFine)
{
this.optiFine = optiFine;
}
/**
* Clear and reset this download list object.
*/
public void clear()
{
this.downloadableFiles.clear();
this.extFiles.clear();
this.downloadableAssets.clear();
this.mods.clear();
this.optiFine = null;
this.downloadInfo.reset();
this.init = false;
}
public static class DownloadInfo
{
private final AtomicLong totalToDownloadBytes = new AtomicLong(0);
private final AtomicLong downloadedBytes = new AtomicLong(0);
private final AtomicInteger totalToDownloadFiles = new AtomicInteger(0);
private final AtomicInteger downloadedFiles = new AtomicInteger(0);
/**
* Reset this download info object.
*/
public void reset()
{
this.totalToDownloadBytes.set(0);
this.downloadedBytes.set(0);
this.totalToDownloadFiles.set(0);
this.downloadedFiles.set(0);
}
/**
* Get the total of bytes to download.
* @return bytes to download.
*/
public long getTotalToDownloadBytes()
{
return this.totalToDownloadBytes.get();
}
/**
* Get the downloaded bytes.
* @return the downloaded bytes.
*/
public long getDownloadedBytes()
{
return this.downloadedBytes.get();
}
/**
* Get the number of files to download.
* @return number of files to download.
*/
public int getTotalToDownloadFiles()
{
return this.totalToDownloadFiles.get();
}
/**
* Get the number of downloaded files.
* @return the number of downloaded files.
*/
public int getDownloadedFiles()
{
return this.downloadedFiles.get();
}
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/IProgressCallback.java
================================================
package fr.flowarg.flowupdater.download;
import fr.flowarg.flowlogger.ILogger;
import fr.flowarg.flowupdater.FlowUpdater;
import java.nio.file.Path;
/**
* This interface provides useful methods to implement to access to download and update status.
*/
public interface IProgressCallback
{
/**
* This method is called at {@link FlowUpdater} initialization.
* @param logger {@link ILogger} of FlowUpdater instance.
*/
default void init(ILogger logger) {}
/**
* This method is called when a step started.
* @param step Actual {@link Step}.
*/
default void step(Step step) {}
/**
* This method is called when a new file is downloaded.
* @param info The {@link DownloadList.DownloadInfo} instance that contains all wanted information.
*/
default void update(DownloadList.DownloadInfo info) {}
/**
* This method is called before {@link #update(DownloadList.DownloadInfo)} when a file is downloaded.
* @param path the file downloaded.
*/
default void onFileDownloaded(Path path) {}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/Step.java
================================================
package fr.flowarg.flowupdater.download;
import fr.flowarg.flowupdater.FlowUpdater;
/**
* Represent each step of a Minecraft Installation
* @author flow
*/
public enum Step
{
/** Integration loading */
INTEGRATION,
/** ModPack preparation */
MOD_PACK,
/** JSON reading */
READ,
/** Download libraries */
DL_LIBS,
/** Download assets */
DL_ASSETS,
/** Extract natives */
EXTRACT_NATIVES,
/** Install a mod loader version. Skipped if {@link FlowUpdater#getModLoaderVersion()} is null. */
MOD_LOADER,
/** Download mods. Skipped if {@link FlowUpdater#getModLoaderVersion()} is null. */
MODS,
/** Download other files. */
EXTERNAL_FILES,
/** Runs a list of runnable at the end of update. */
POST_EXECUTIONS,
/** All tasks are finished */
END
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/VanillaDownloader.java
================================================
package fr.flowarg.flowupdater.download;
import fr.flowarg.flowio.FileUtils;
import fr.flowarg.flowlogger.ILogger;
import fr.flowarg.flowstringer.StringUtils;
import fr.flowarg.flowupdater.FlowUpdater;
import fr.flowarg.flowupdater.download.json.Downloadable;
import fr.flowarg.flowupdater.utils.IOUtils;
import fr.flowarg.flowzipper.ZipUtils;
import org.jetbrains.annotations.NotNull;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
/**
* This class handles the downloading of vanilla files (client, assets, natives...).
*/
public class VanillaDownloader
{
private final Path dir;
private final ILogger logger;
private final IProgressCallback callback;
private final DownloadList downloadList;
private final Path natives;
private final Path assets;
private final String vanillaJsonURL;
/**
* Construct a new VanillaDownloader object.
* @param dir the installation directory.
* @param flowUpdater the flow updater object.
* @throws Exception if an I/O error occurred.
*/
public VanillaDownloader(Path dir, @NotNull FlowUpdater flowUpdater) throws Exception
{
this.dir = dir;
this.logger = flowUpdater.getLogger();
this.callback = flowUpdater.getCallback();
this.downloadList = flowUpdater.getDownloadList();
this.natives = this.dir.resolve("natives");
this.assets = this.dir.resolve("assets");
this.vanillaJsonURL = flowUpdater.getVanillaVersion().getJsonURL();
Files.createDirectories(this.dir.resolve("libraries"));
Files.createDirectories(this.assets);
Files.createDirectories(this.natives);
this.downloadList.init();
}
/**
* This method downloads calls other methods to download and verify all files.
* @throws Exception if an I/O error occurred.
*/
public void download() throws Exception
{
this.downloadLibraries();
this.downloadAssets();
this.extractNatives();
this.logger.info("All vanilla files were successfully downloaded!");
}
private void downloadLibraries() throws Exception
{
this.logger.info("Checking library files...");
this.callback.step(Step.DL_LIBS);
if(this.vanillaJsonURL != null)
this.downloadVanillaJson();
for (Downloadable downloadable : this.downloadList.getDownloadableFiles())
{
final Path filePath = this.dir.resolve(downloadable.getName());
if(Files.notExists(filePath) ||
!FileUtils.getSHA1(filePath).equalsIgnoreCase(downloadable.getSha1()) ||
Files.size(filePath) != downloadable.getSize())
{
IOUtils.download(this.logger, new URL(downloadable.getUrl()), filePath);
this.callback.onFileDownloaded(filePath);
}
this.downloadList.incrementDownloaded(downloadable.getSize());
this.callback.update(this.downloadList.getDownloadInfo());
}
}
private void downloadVanillaJson() throws Exception
{
final Path vanillaJsonTarget = this.dir.resolve(this.vanillaJsonURL.substring(this.vanillaJsonURL.lastIndexOf('/') + 1));
final String vanillaJsonResourceName = this.vanillaJsonURL.substring(this.vanillaJsonURL.lastIndexOf('/'));
final String vanillaJsonPathUrl = StringUtils.empty(StringUtils.empty(this.vanillaJsonURL, "https://launchermeta.mojang.com/v1/packages/"), "https://piston-meta.mojang.com/v1/packages/");
if(Files.notExists(vanillaJsonTarget) || !FileUtils.getSHA1(vanillaJsonTarget)
.equals(StringUtils.empty(vanillaJsonPathUrl, vanillaJsonResourceName)))
IOUtils.download(this.logger, new URL(this.vanillaJsonURL), vanillaJsonTarget);
}
private void extractNatives() throws Exception
{
boolean flag = false;
final List<Path> existingNatives = FileUtils.list(this.natives);
if (!existingNatives.isEmpty())
{
for (Path minecraftNative : FileUtils.list(this.natives)
.stream()
.filter(path -> path.getFileName().toString().endsWith(".jar"))
.collect(Collectors.toList()))
{
final JarFile jarFile = new JarFile(minecraftNative.toFile());
final Enumeration<? extends ZipEntry> entries = jarFile.entries();
while (entries.hasMoreElements())
{
final ZipEntry entry = entries.nextElement();
if (entry.isDirectory() ||
entry.getName().endsWith(".git") ||
entry.getName().endsWith(".sha1") ||
entry.getName().contains("META-INF")) continue;
final Path flPath = this.natives.resolve(entry.getName());
if(Files.exists(flPath) && entry.getCrc() == FileUtils.getCRC32(flPath)) continue;
flag = true;
break;
}
jarFile.close();
if (flag) break;
}
}
if (flag)
{
this.logger.info("Extracting natives...");
this.callback.step(Step.EXTRACT_NATIVES);
final Stream<Path> natives = FileUtils.list(this.natives).stream();
natives.filter(file -> !Files.isDirectory(file) && file.getFileName().toString().endsWith(".jar"))
.forEach(file -> {
try
{
ZipUtils.unzipJar(this.natives, file, "ignoreMetaInf");
} catch (Exception e)
{
this.logger.printStackTrace(e);
}
});
natives.close();
}
try(Stream<Path> natives = Files.list(this.natives))
{
natives.forEach(path -> {
try
{
if (path.getFileName().toString().endsWith(".git") || path.getFileName().toString().endsWith(".sha1"))
Files.delete(path);
else if(Files.isDirectory(path))
FileUtils.deleteDirectory(path);
} catch (Exception e)
{
this.logger.printStackTrace(e);
}
});
}
}
private void downloadAssets()
{
this.logger.info("Checking assets...");
this.callback.step(Step.DL_ASSETS);
IOUtils.executeAsyncForEach(this.downloadList.getDownloadableAssets(), Executors.newWorkStealingPool(), assetDownloadable -> {
try
{
final Path downloadPath = this.assets.resolve(assetDownloadable.getFile());
if (Files.notExists(downloadPath) || Files.size(downloadPath) != assetDownloadable.getSize())
{
final Path localAssetPath = IOUtils.getMinecraftFolder().resolve("assets").resolve(assetDownloadable.getFile());
if (Files.exists(localAssetPath) && Files.size(localAssetPath) == assetDownloadable.getSize())
IOUtils.copy(this.logger, localAssetPath, downloadPath);
else
{
IOUtils.download(this.logger, new URL(assetDownloadable.getUrl()), downloadPath);
this.callback.onFileDownloaded(downloadPath);
}
}
this.downloadList.incrementDownloaded(assetDownloadable.getSize());
this.callback.update(this.downloadList.getDownloadInfo());
}
catch (Exception e)
{
this.logger.printStackTrace(e);
}
});
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/VanillaReader.java
================================================
package fr.flowarg.flowupdater.download;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import fr.flowarg.flowcompat.Platform;
import fr.flowarg.flowupdater.FlowUpdater;
import fr.flowarg.flowupdater.download.json.AssetDownloadable;
import fr.flowarg.flowupdater.download.json.AssetIndex;
import fr.flowarg.flowupdater.download.json.Downloadable;
import fr.flowarg.flowupdater.utils.IOUtils;
import fr.flowarg.flowupdater.versions.VanillaVersion;
import org.jetbrains.annotations.NotNull;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This class handles all parsing stuff about vanilla files.
*/
public class VanillaReader
{
private final VanillaVersion version;
private final IProgressCallback callback;
private final DownloadList downloadList;
/**
* Construct a new VanillaReader.
* @param flowUpdater the flow updater object.
*/
public VanillaReader(@NotNull FlowUpdater flowUpdater)
{
this.version = flowUpdater.getVanillaVersion();
this.callback = flowUpdater.getCallback();
this.downloadList = flowUpdater.getDownloadList();
}
/**
* This method calls other methods to parse each part of the given Minecraft Version.
* @throws Exception if an I/O error occurred.
*/
public void read() throws Exception
{
this.callback.step(Step.READ);
this.parseLibraries();
this.parseAssetIndex();
this.parseClient();
this.parseNatives();
this.parseAssets();
}
private void parseLibraries()
{
this.version.getMinecraftLibrariesJson().forEach(jsonElement -> {
final JsonObject element = jsonElement.getAsJsonObject();
if (element == null || !this.checkRules(element))
return;
final JsonObject downloads = element.getAsJsonObject("downloads");
if(downloads == null)
return;
block: {
final String name = element.getAsJsonPrimitive("name").getAsString();
if(!name.contains("lwjgl") || !name.contains("natives") || !name.contains("macos"))
break block;
boolean platformCheck = (Platform.isOnMac() &&
Platform.getArch().equals("64") &&
System.getProperty("os.arch").equals("aarch64"));
if(platformCheck != name.contains("arm64"))
return;
}
final JsonObject artifact = downloads.getAsJsonObject("artifact");
if (artifact == null)
return;
final String url = artifact.getAsJsonPrimitive("url").getAsString();
final int size = artifact.getAsJsonPrimitive("size").getAsInt();
final String path = "libraries/" + artifact.getAsJsonPrimitive("path").getAsString();
final String sha1 = artifact.getAsJsonPrimitive("sha1").getAsString();
final Downloadable downloadable = new Downloadable(url, size, sha1, path);
if(!this.downloadList.getDownloadableFiles().contains(downloadable))
this.downloadList.getDownloadableFiles().add(downloadable);
});
this.downloadList.getDownloadableFiles().addAll(this.version.getAnotherLibraries());
}
private void parseAssetIndex()
{
if(this.version.getCustomAssetIndex() != null)
return;
final JsonObject assetIndex = this.version.getMinecraftAssetIndex();
final String url = assetIndex.getAsJsonPrimitive("url").getAsString();
final int size = assetIndex.getAsJsonPrimitive("size").getAsInt();
final String name = "assets/indexes/" + url.substring(url.lastIndexOf('/') + 1);
final String sha1 = assetIndex.getAsJsonPrimitive("sha1").getAsString();
this.downloadList.getDownloadableFiles().add(new Downloadable(url, size, sha1, name));
}
private void parseClient()
{
final JsonObject client = this.version.getMinecraftClient();
final String clientURL = client.getAsJsonPrimitive("url").getAsString();
final int clientSize = client.getAsJsonPrimitive("size").getAsInt();
final String clientName = clientURL.substring(clientURL.lastIndexOf('/') + 1);
final String clientSha1 = client.getAsJsonPrimitive("sha1").getAsString();
this.downloadList.getDownloadableFiles().add(new Downloadable(clientURL, clientSize, clientSha1, clientName));
}
private void parseNatives()
{
this.version.getMinecraftLibrariesJson().forEach(jsonElement -> {
final JsonObject obj = jsonElement.getAsJsonObject()
.getAsJsonObject("downloads")
.getAsJsonObject("classifiers");
if (obj == null)
return;
final JsonObject macObj = obj.getAsJsonObject("natives-macos");
final JsonObject osxObj = obj.getAsJsonObject("natives-osx");
JsonObject windowsObj = obj.getAsJsonObject(String.format("natives-windows-%s", Platform.getArch()));
if (windowsObj == null) windowsObj = obj.getAsJsonObject("natives-windows");
final JsonObject linuxObj = obj.getAsJsonObject("natives-linux");
if (macObj != null && Platform.isOnMac())
this.getNativeForOS("mac", macObj);
else if (osxObj != null && Platform.isOnMac())
this.getNativeForOS("mac", osxObj);
else if (windowsObj != null && Platform.isOnWindows())
this.getNativeForOS("win", windowsObj);
else if (linuxObj != null && Platform.isOnLinux())
this.getNativeForOS("linux", linuxObj);
});
}
private void getNativeForOS(@NotNull String os, @NotNull JsonObject obj)
{
final String url = obj.getAsJsonPrimitive("url").getAsString();
final int size = obj.getAsJsonPrimitive("size").getAsInt();
final String path = obj.getAsJsonPrimitive("path").getAsString();
final String name = "natives/" + path.substring(path.lastIndexOf('/') + 1);
final String sha1 = obj.getAsJsonPrimitive("sha1").getAsString();
if(!os.equals("mac"))
{
if (name.contains("-3.2.1-") && name.contains("lwjgl"))
return;
if (name.contains("-2.9.2-") && name.contains("lwjgl"))
return;
}
else if(name.contains("-3.2.2-") && name.contains("lwjgl"))
return;
this.downloadList.getDownloadableFiles().add(new Downloadable(url, size, sha1, name));
}
private void parseAssets() throws Exception
{
final Set<AssetDownloadable> toDownload = new HashSet<>(this.version.getAnotherAssets());
final AssetIndex assetIndex;
if(this.version.getCustomAssetIndex() == null)
assetIndex = new GsonBuilder()
.disableHtmlEscaping()
.create()
.fromJson(IOUtils.getContent(new URL(this.version.getMinecraftAssetIndex().get("url").getAsString())), AssetIndex.class);
else assetIndex = this.version.getCustomAssetIndex();
assetIndex.getUniqueObjects()
.values()
.forEach(assetDownloadable ->
toDownload.add(new AssetDownloadable(assetDownloadable.getHash(), assetDownloadable.getSize())));
this.downloadList.getDownloadableAssets().addAll(toDownload);
}
private boolean checkRules(@NotNull JsonObject obj)
{
final JsonElement rulesElement = obj.get("rules");
if (rulesElement == null)
return true;
final AtomicBoolean canDownload = new AtomicBoolean(true);
rulesElement.getAsJsonArray().forEach(jsonElement -> {
final JsonObject object = jsonElement.getAsJsonObject();
final String actionValue = object.getAsJsonPrimitive("action").getAsString();
final JsonObject osObject = object.getAsJsonObject("os");
if (actionValue.equals("allow"))
{
if (osObject == null) return;
final String os = osObject.getAsJsonPrimitive("name").getAsString();
canDownload.set(this.check(os));
}
else if (actionValue.equals("disallow"))
{
final String os = osObject.getAsJsonPrimitive("name").getAsString();
canDownload.set(!this.check(os));
}
});
return canDownload.get();
}
private boolean check(@NotNull String os)
{
return (os.equalsIgnoreCase("osx") && Platform.isOnMac()) ||
(os.equalsIgnoreCase("macos") && Platform.isOnMac()) ||
(os.equalsIgnoreCase("windows") && Platform.isOnWindows()) ||
(os.equalsIgnoreCase("linux") && Platform.isOnLinux());
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/AssetDownloadable.java
================================================
package fr.flowarg.flowupdater.download.json;
/**
* This class represents an asset.
*/
public class AssetDownloadable
{
private final String hash;
private final long size;
private final String url;
private final String file;
/**
* Construct a new asset object.
* @param hash the sha1 of the asset.
* @param size the size of the asset.
*/
public AssetDownloadable(String hash, long size)
{
this.hash = hash;
this.size = size;
final String assetsPath = "/" + this.hash.substring(0, 2) + "/" + this.hash;
this.url = "https://resources.download.minecraft.net" + assetsPath;
this.file = "objects" + assetsPath;
}
/**
* Get the hash of the asset.
* @return the sha1 of the asset.
*/
public String getHash()
{
return this.hash;
}
/**
* Get the length of the asset.
* @return the size of the asset.
*/
public long getSize()
{
return this.size;
}
/**
* Get the remote url of the asset.
* @return the url of the asset.
*/
public String getUrl()
{
return this.url;
}
/**
* Get the file path of the asset.
* @return the relative local path of this asset.
*/
public String getFile()
{
return this.file;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final AssetDownloadable that = (AssetDownloadable)o;
return this.file.equals(that.file) && this.size == that.size && this.hash.equals(that.hash) && this.url.equals(that.url);
}
@Override
public int hashCode()
{
int result = this.hash.hashCode();
result = 31 * result + Long.hashCode(this.size);
result = 31 * result + this.url.hashCode();
return result;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/AssetIndex.java
================================================
package fr.flowarg.flowupdater.download.json;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* This class represents an asset index of a Minecraft version.
*/
public class AssetIndex
{
private final Map<String, AssetDownloadable> objects = new LinkedHashMap<>();
/**
* Internal getter.
* @return asset objects
*/
private Map<String, AssetDownloadable> getObjects()
{
return this.objects;
}
/**
* Get an immutable collection of asset objects.
* @return asset objects.
*/
public Map<String, AssetDownloadable> getUniqueObjects()
{
return Collections.unmodifiableMap(this.getObjects());
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/CurseFileInfo.java
================================================
package fr.flowarg.flowupdater.download.json;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import fr.flowarg.flowupdater.utils.FlowUpdaterException;
import fr.flowarg.flowupdater.utils.IOUtils;
import org.jetbrains.annotations.NotNull;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* This class represents a file in the CurseForge API.
*/
public class CurseFileInfo
{
private final int projectID;
private final int fileID;
/**
* Construct a new CurseFileInfo object.
* @param projectID the ID of the project.
* @param fileID the ID of the file.
*/
public CurseFileInfo(int projectID, int fileID)
{
this.projectID = projectID;
this.fileID = fileID;
}
/**
* Retrieve a collection of {@link CurseFileInfo} by parsing a remote JSON file.
* @param jsonUrl the url of the remote JSON file.
* @return a collection of {@link CurseFileInfo}.
*/
public static @NotNull List<CurseFileInfo> getFilesFromJson(URL jsonUrl)
{
final List<CurseFileInfo> result = new ArrayList<>();
final JsonObject object = IOUtils.readJson(jsonUrl).getAsJsonObject();
final JsonArray mods = object.getAsJsonArray("curseFiles");
mods.forEach(curseModElement -> {
final JsonObject obj = curseModElement.getAsJsonObject();
final int projectID = obj.get("projectID").getAsInt();
final int fileID = obj.get("fileID").getAsInt();
result.add(new CurseFileInfo(projectID, fileID));
});
return result;
}
/**
* Retrieve a collection of {@link CurseFileInfo} by parsing a remote JSON file.
* @param jsonUrl the url of the remote JSON file.
* @return a collection of {@link CurseFileInfo}.
*/
public static @NotNull List<CurseFileInfo> getFilesFromJson(String jsonUrl)
{
try
{
return getFilesFromJson(new URL(jsonUrl));
}
catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
/**
* Get the project ID.
* @return the project ID.
*/
public int getProjectID()
{
return this.projectID;
}
/**
* Get the file ID.
* @return the file ID.
*/
public int getFileID()
{
return this.fileID;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || this.getClass() != o.getClass()) return false;
final CurseFileInfo that = (CurseFileInfo)o;
return this.projectID == that.projectID && this.fileID == that.fileID;
}
@Override
public int hashCode()
{
return Objects.hash(this.projectID, this.fileID);
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/CurseModPackInfo.java
================================================
package fr.flowarg.flowupdater.download.json;
/**
* This class represents a mod pack file in the CurseForge API.
*/
public class CurseModPackInfo extends CurseFileInfo
{
private final boolean installExtFiles;
private final String[] excluded;
private String url = "";
/**
* Construct a new CurseModPackInfo object.
* @param projectID the ID of the project.
* @param fileID the ID of the file.
* @param installExtFiles should install external files like config and resource packs.
* @param excluded mods to exclude.
*/
public CurseModPackInfo(int projectID, int fileID, boolean installExtFiles, String... excluded)
{
super(projectID, fileID);
this.installExtFiles = installExtFiles;
this.excluded = excluded;
}
/**
* Construct a new CurseModPackInfo object.
* @param url the url of the custom mod pack endpoint.
* @param installExtFiles should install external files like config and resource packs.
* @param excluded mods to exclude.
*/
public CurseModPackInfo(String url, boolean installExtFiles, String... excluded)
{
super(0, 0);
this.url = url;
this.installExtFiles = installExtFiles;
this.excluded = excluded;
}
/**
* Get the {@link #installExtFiles} option.
* @return the {@link #installExtFiles} option.
*/
public boolean isInstallExtFiles()
{
return this.installExtFiles;
}
/**
* Get the excluded mods.
* @return the excluded mods.
*/
public String[] getExcluded()
{
return this.excluded;
}
/**
* Get the url of the mod pack endpoint.
* Should be of the form:
* {
* "data": {
* "fileName": "modpack.zip",
* "downloadUrl": "https://site.com/modpack.zip",
* "fileLength": 123456789,
* "hashes": [
* {
* "value": "a02b0499589bc6982fced96dcc85c3b3e33af119",
* "algo": 1
* }
* ]
* }
* }
* @return the url of the mod pack endpoint if it's not from CurseForge's servers.
*/
public String getUrl()
{
return this.url;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/Downloadable.java
================================================
package fr.flowarg.flowupdater.download.json;
import java.util.Objects;
/**
* This class represents a classic downloadable file such as a library, the client/server or natives.
*/
public class Downloadable
{
private final String url;
private final long size;
private final String sha1;
private final String name;
/**
* Construct a new Downloadable object.
* @param url the url where to download the file.
* @param size the size of the file.
* @param sha1 the sha1 of the file.
* @param name the name (path) of the file.
*/
public Downloadable(String url, long size, String sha1, String name)
{
this.url = url;
this.size = size;
this.sha1 = sha1;
this.name = name;
}
/**
* Get the url of the file.
* @return the url of the file.
*/
public String getUrl()
{
return this.url;
}
/**
* Get the size of the file.
* @return the size of the file.
*/
public long getSize()
{
return this.size;
}
/**
* Get the sha1 of the file.
* @return the sha1 of the file.
*/
public String getSha1()
{
return this.sha1;
}
/**
* Get the relative path of the file.
* @return the relative path of the file.
*/
public String getName()
{
return this.name;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Downloadable that = (Downloadable)o;
return this.size == that.size &&
Objects.equals(this.url, that.url) &&
Objects.equals(this.sha1, that.sha1) &&
Objects.equals(this.name, that.name);
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/ExternalFile.java
================================================
package fr.flowarg.flowupdater.download.json;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import fr.flowarg.flowupdater.utils.FlowUpdaterException;
import fr.flowarg.flowupdater.utils.IOUtils;
import org.jetbrains.annotations.NotNull;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* This class represents an external file object.
*/
public class ExternalFile
{
private final String path;
private final String downloadURL;
private final String sha1;
private final long size;
private final boolean update;
/**
* Construct a new ExternalFile object.
* @param path Path of external file.
* @param sha1 Sha1 of external file.
* @param size Size of external file.
* @param downloadURL external file URL.
*/
public ExternalFile(String path, String downloadURL, String sha1, long size)
{
this.path = path;
this.downloadURL = downloadURL;
this.sha1 = sha1;
this.size = size;
this.update = true;
}
/**
* Construct a new ExternalFile object.
* @param path Path of external file.
* @param sha1 Sha1 of external file.
* @param size Size of external file.
* @param downloadURL external file URL.
* @param update false: not checking if the file is valid. true: checking if the file is valid.
*/
public ExternalFile(String path, String downloadURL, String sha1, long size, boolean update)
{
this.path = path;
this.downloadURL = downloadURL;
this.sha1 = sha1;
this.size = size;
this.update = update;
}
/**
* Provide a List of external file from a JSON file.
* Template of a JSON file :
* <pre>
* {
* "extfiles": [
* {
* "path": "other/path/AnExternalFile.binpatch",
* "downloadURL": "https://url.com/launcher/extern/AnExtFile.binpatch",
* "sha1": "40f784892989du0fc6f45c895d4l6c5db9378f48",
* "size": 25652
* },
* {
* "path": "config/config.json",
* "downloadURL": "https://url.com/launcher/ext/modconfig.json",
* "sha1": "eef74b3fbab6400cb14b02439cf092cca3c2125c",
* "size": 19683,
* "update": false
* }
* ]
* }
* </pre>
* @param jsonUrl the JSON file URL.
* @return an external file list.
*/
public static @NotNull List<ExternalFile> getExternalFilesFromJson(URL jsonUrl)
{
final List<ExternalFile> result = new ArrayList<>();
final JsonArray extfiles = IOUtils.readJson(jsonUrl).getAsJsonObject().getAsJsonArray("extfiles");
extfiles.forEach(extFileElement -> {
final JsonObject obj = extFileElement.getAsJsonObject();
final String path = obj.get("path").getAsString();
final String sha1 = obj.get("sha1").getAsString();
final String downloadURL = obj.get("downloadURL").getAsString();
final long size = obj.get("size").getAsLong();
if(obj.get("update") != null)
result.add(new ExternalFile(path, downloadURL, sha1, size, obj.get("update").getAsBoolean()));
else result.add(new ExternalFile(path, downloadURL, sha1, size));
});
return result;
}
/**
* Provide a List of external file from a JSON file.
* @param jsonUrl the JSON file URL.
* @return an external file list.
*/
public static @NotNull List<ExternalFile> getExternalFilesFromJson(String jsonUrl)
{
try
{
return getExternalFilesFromJson(new URL(jsonUrl));
} catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
/**
* Get the path of the external file.
* @return the path of the external file.
*/
public String getPath()
{
return this.path;
}
/**
* Get the url of the external file.
* @return the url of the external file.
*/
public String getDownloadURL()
{
return this.downloadURL;
}
/**
* Get the sha1 of the external file.
* @return the sha1 of the external file.
*/
public String getSha1()
{
return this.sha1;
}
/**
* Get the size of the external file.
* @return the size of the external file.
*/
public long getSize()
{
return this.size;
}
/**
* Should {@link fr.flowarg.flowupdater.utils.ExternalFileDeleter} check the file?
* @return if the external file deleter should check and delete the file.
*/
public boolean isUpdate()
{
return this.update;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/MCP.java
================================================
package fr.flowarg.flowupdater.download.json;
import com.google.gson.JsonObject;
import fr.flowarg.flowupdater.utils.FlowUpdaterException;
import fr.flowarg.flowupdater.utils.IOUtils;
import org.jetbrains.annotations.NotNull;
import java.net.MalformedURLException;
import java.net.URL;
/**
* This class represents an MCP object.
*/
public class MCP
{
private final String clientURL;
private final String clientSha1;
private final long clientSize;
/**
* Construct a new MCP object.
* @param clientURL URL of client.jar
* @param clientSha1 SHA1 of client.jar
* @param clientSize Size (bytes) of client.jar
*/
public MCP(String clientURL, String clientSha1, long clientSize)
{
this.clientURL = clientURL;
this.clientSha1 = clientSha1;
this.clientSize = clientSize;
}
/**
* Provide an MCP instance from a JSON file.
* Template of a JSON file :
* <pre>
* {
* "clientURL": "https://url.com/launcher/client.jar",
* "clientSha1": "9b0a9d70320811e7af2e8741653f029151a6719a",
* "clientSize": 1234
* }
* </pre>
* @param jsonUrl the JSON file URL.
* @return the MCP instance.
*/
public static @NotNull MCP getMCPFromJson(URL jsonUrl)
{
final JsonObject object = IOUtils.readJson(jsonUrl).getAsJsonObject();
return new MCP(object.get("clientURL").getAsString(), object.get("clientSha1").getAsString(), object.get("clientSize").getAsLong());
}
/**
* Provide an MCP instance from a JSON file.
* @param jsonUrl the JSON file URL.
* @return the MCP instance.
*/
public static @NotNull MCP getMCPFromJson(String jsonUrl)
{
try
{
return getMCPFromJson(new URL(jsonUrl));
} catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
/**
* Return the client url.
* @return the client url.
*/
public String getClientURL()
{
return this.clientURL;
}
/**
* Return the client sha1.
* @return the client sha1.
*/
public String getClientSha1()
{
return this.clientSha1;
}
/**
* Return the client size.
* @return the client size.
*/
public long getClientSize()
{
return this.clientSize;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/Mod.java
================================================
package fr.flowarg.flowupdater.download.json;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import fr.flowarg.flowupdater.utils.FlowUpdaterException;
import fr.flowarg.flowupdater.utils.IOUtils;
import org.jetbrains.annotations.NotNull;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* This class represents a Mod object.
*/
public class Mod
{
private final String name;
private final String sha1;
private final long size;
private final String downloadURL;
/**
* Construct a new Mod object.
* @param name Name of mod file.
* @param downloadURL Mod download URL.
* @param sha1 Sha1 of mod file.
* @param size Size of mod file.
*/
public Mod(String name, String downloadURL, String sha1, long size)
{
this.name = name;
this.downloadURL = downloadURL;
this.sha1 = sha1;
this.size = size;
}
/**
* Provide a List of Mods from a JSON file.
* Template of a JSON file :
* <pre>
* {
* "mods": [
* {
* "name": "KeyStroke",
* "downloadURL": "https://url.com/launcher/mods/KeyStroke.jar",
* "sha1": "70e564892989d8bbc6f45c895df56c5db9378f48",
* "size": 1234
* },
* {
* "name": "JourneyMap",
* "downloadURL": "https://url.com/launcher/mods/JourneyMap.jar",
* "sha1": "eef74b3fbab6400cb14b02439cf092cca3c2125c",
* "size": 1234
* }
* ]
* }
* </pre>
* @param jsonUrl the JSON file URL.
* @return a Mod list.
*/
public static @NotNull List<Mod> getModsFromJson(URL jsonUrl)
{
final List<Mod> result = new ArrayList<>();
final JsonObject object = IOUtils.readJson(jsonUrl).getAsJsonObject();
final JsonArray mods = object.getAsJsonArray("mods");
mods.forEach(modElement -> result.add(fromJson(modElement)));
return result;
}
public static Mod fromJson(JsonElement modElement)
{
final JsonObject obj = modElement.getAsJsonObject();
return new Mod(
obj.get("name").getAsString(),
obj.get("downloadURL").getAsString(),
obj.get("sha1").getAsString(),
obj.get("size").getAsLong()
);
}
/**
* Provide a List of Mods from a JSON file.
* Template of a JSON file :
* @param jsonUrl the JSON file URL.
* @return a Mod list.
*/
public static @NotNull List<Mod> getModsFromJson(String jsonUrl)
{
try
{
return getModsFromJson(new URL(jsonUrl));
}
catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
/**
* Get the mod name.
* @return the mod name.
*/
public String getName()
{
return this.name;
}
/**
* Get the sha1 of the mod.
* @return the sha1 of the mod.
*/
public String getSha1()
{
return this.sha1;
}
/**
* Get the mod size.
* @return the mod size.
*/
public long getSize()
{
return this.size;
}
/**
* Get the mod url.
* @return the mod url.
*/
public String getDownloadURL()
{
return this.downloadURL;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/ModrinthModPackInfo.java
================================================
package fr.flowarg.flowupdater.download.json;
public class ModrinthModPackInfo extends ModrinthVersionInfo
{
private final boolean installExtFiles;
private final String[] excluded;
public ModrinthModPackInfo(String projectReference, String versionNumber, boolean installExtFiles, String... excluded)
{
super(projectReference, versionNumber);
this.installExtFiles = installExtFiles;
this.excluded = excluded;
}
public ModrinthModPackInfo(String versionId, boolean installExtFiles, String... excluded)
{
super(versionId);
this.installExtFiles = installExtFiles;
this.excluded = excluded;
}
/**
* Get the {@link #installExtFiles} option.
* @return the {@link #installExtFiles} option.
*/
public boolean isInstallExtFiles()
{
return this.installExtFiles;
}
/**
* Get the excluded mods.
* @return the excluded mods.
*/
public String[] getExcluded()
{
return this.excluded;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/ModrinthVersionInfo.java
================================================
package fr.flowarg.flowupdater.download.json;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import fr.flowarg.flowupdater.utils.FlowUpdaterException;
import fr.flowarg.flowupdater.utils.IOUtils;
import org.jetbrains.annotations.NotNull;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class ModrinthVersionInfo
{
private String projectReference = "";
private String versionNumber = "";
private String versionId = "";
/**
* Construct a new ModrinthVersionInfo object.
* @param projectReference the project reference can be slug or id.
* @param versionNumber the version number (and NOT the version name unless they are the same).
*/
public ModrinthVersionInfo(String projectReference, String versionNumber)
{
this.projectReference = projectReference.trim();
this.versionNumber = versionNumber.trim();
}
/**
* Construct a new ModrinthVersionInfo object.
* This constructor doesn't need a project reference because
* we can access the version without any project information.
* @param versionId the version id.
*/
public ModrinthVersionInfo(String versionId)
{
this.versionId = versionId.trim();
}
public static @NotNull List<ModrinthVersionInfo> getModrinthVersionsFromJson(URL jsonUrl)
{
final List<ModrinthVersionInfo> result = new ArrayList<>();
final JsonObject object = IOUtils.readJson(jsonUrl).getAsJsonObject();
final JsonArray mods = object.getAsJsonArray("modrinthMods");
mods.forEach(modElement -> {
final JsonObject obj = modElement.getAsJsonObject();
final JsonElement versionIdElement = obj.get("versionId");
if(versionIdElement instanceof JsonNull)
result.add(new ModrinthVersionInfo(obj.get("projectReference").getAsString(), obj.get("versionNumber").getAsString()));
else result.add(new ModrinthVersionInfo(versionIdElement.getAsString()));
});
return result;
}
public static @NotNull List<ModrinthVersionInfo> getModrinthVersionsFromJson(String jsonUrl)
{
try
{
return getModrinthVersionsFromJson(new URL(jsonUrl));
} catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
public String getProjectReference()
{
return this.projectReference;
}
public String getVersionNumber()
{
return this.versionNumber;
}
public String getVersionId()
{
return this.versionId;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/OptiFineInfo.java
================================================
package fr.flowarg.flowupdater.download.json;
/**
* This class represents an OptiFineInfo object.
*/
public class OptiFineInfo
{
private final String version;
private final boolean preview;
/**
* Construct a new OptiFineInfo object.
* @param version the OptiFine's version.
* @param preview if the version is a preview.
*/
public OptiFineInfo(String version, boolean preview)
{
this.version = version;
this.preview = preview;
}
/**
* Construct a new OptiFineInfo object, use {@link OptiFineInfo#OptiFineInfo(String, boolean)} .
* @param version the OptiFine's version.
*/
public OptiFineInfo(String version)
{
this(version, version.startsWith("preview_"));
}
/**
* Get the OptiFine's version.
* @return the OptiFine's version.
*/
public String getVersion()
{
return this.version;
}
/**
* Is the version a preview?
* @return if the version is a preview or not.
*/
public boolean isPreview()
{
return this.preview;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/json/package-info.java
================================================
/**
* This package contains some objects that can be/are parsed as a JSON.
*/
package fr.flowarg.flowupdater.download.json;
================================================
FILE: src/main/java/fr/flowarg/flowupdater/download/package-info.java
================================================
/**
* This package contains some things about download stuff.
*/
package fr.flowarg.flowupdater.download;
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/Integration.java
================================================
package fr.flowarg.flowupdater.integrations;
import fr.flowarg.flowlogger.ILogger;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* The new Integration system replaces an old plugin system
* which had some problems such as unavailability to communicate directly with FlowUpdater.
* This new system is easier to use: no more annoying updater's options, no more extra-dependencies.
* Polymorphism and inheritance can now be used to avoid code duplication.
*/
public abstract class Integration
{
protected final ILogger logger;
protected final Path folder;
/**
* Default constructor of a basic Integration.
* @param logger the logger used.
* @param folder the folder where the plugin can work.
* @throws Exception if an error occurred.
*/
public Integration(ILogger logger, Path folder) throws Exception
{
this.logger = logger;
this.folder = folder;
Files.createDirectories(this.folder);
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/IntegrationManager.java
================================================
package fr.flowarg.flowupdater.integrations;
import fr.flowarg.flowio.FileUtils;
import fr.flowarg.flowlogger.ILogger;
import fr.flowarg.flowupdater.FlowUpdater;
import fr.flowarg.flowupdater.download.DownloadList;
import fr.flowarg.flowupdater.download.IProgressCallback;
import fr.flowarg.flowupdater.download.Step;
import fr.flowarg.flowupdater.download.json.*;
import fr.flowarg.flowupdater.integrations.curseforgeintegration.CurseForgeIntegration;
import fr.flowarg.flowupdater.integrations.curseforgeintegration.CurseModPack;
import fr.flowarg.flowupdater.integrations.curseforgeintegration.ICurseForgeCompatible;
import fr.flowarg.flowupdater.integrations.modrinthintegration.IModrinthCompatible;
import fr.flowarg.flowupdater.integrations.modrinthintegration.ModrinthIntegration;
import fr.flowarg.flowupdater.integrations.modrinthintegration.ModrinthModPack;
import fr.flowarg.flowupdater.integrations.optifineintegration.IOptiFineCompatible;
import fr.flowarg.flowupdater.integrations.optifineintegration.OptiFine;
import fr.flowarg.flowupdater.integrations.optifineintegration.OptiFineIntegration;
import fr.flowarg.flowupdater.utils.FlowUpdaterException;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
/**
* The integration manager loads integration's stuff at the startup of FlowUpdater.
*/
public class IntegrationManager
{
private final IProgressCallback progressCallback;
private final ILogger logger;
private final DownloadList downloadList;
/**
* Construct a new Integration Manager.
* @param updater a {@link FlowUpdater} instance.
*/
public IntegrationManager(@NotNull FlowUpdater updater)
{
this.progressCallback = updater.getCallback();
this.logger = updater.getLogger();
this.downloadList = updater.getDownloadList();
}
/**
* This method loads the CurseForge integration and fetches some data.
* @param dir the installation directory.
* @param curseForgeCompatible a version that accepts CurseForge's feature stuff.
*/
public void loadCurseForgeIntegration(Path dir, ICurseForgeCompatible curseForgeCompatible)
{
this.progressCallback.step(Step.INTEGRATION);
try
{
final CurseModPackInfo modPackInfo = curseForgeCompatible.getCurseModPackInfo();
final List<Mod> allCurseMods = new ArrayList<>();
if(curseForgeCompatible.getCurseMods().isEmpty() && modPackInfo == null)
{
curseForgeCompatible.setAllCurseMods(allCurseMods);
return;
}
final CurseForgeIntegration curseForgeIntegration = new CurseForgeIntegration(this.logger, dir.getParent().resolve(".cfp"));
for (CurseFileInfo info : curseForgeCompatible.getCurseMods())
{
try {
final Mod mod = curseForgeIntegration.fetchMod(info);
if(mod == null)
break;
this.checkMod(mod, allCurseMods, dir);
}
catch (Exception e)
{
this.logger.printStackTrace(e);
}
}
if (modPackInfo == null)
{
curseForgeCompatible.setAllCurseMods(allCurseMods);
return;
}
this.progressCallback.step(Step.MOD_PACK);
final CurseModPack modPack = curseForgeIntegration.getCurseModPack(modPackInfo);
this.logger.info(String.format("Loading mod pack: %s (%s) by %s.", modPack.getName(), modPack.getVersion(), modPack.getAuthor()));
modPack.getMods().forEach(mod -> {
allCurseMods.add(mod);
try
{
final Path filePath = dir.resolve(mod.getName());
boolean flag = false;
for (String exclude : modPackInfo.getExcluded())
{
if (!mod.getName().equalsIgnoreCase(exclude)) continue;
flag = !mod.isRequired();
break;
}
if(flag) return;
if(Files.exists(filePath)
&& Files.size(filePath) == mod.getSize()
&& (mod.getSha1().isEmpty() || FileUtils.getSHA1(filePath).equalsIgnoreCase(mod.getSha1())))
return;
Files.deleteIfExists(filePath);
this.downloadList.getMods().add(mod);
} catch (Exception e)
{
this.logger.printStackTrace(e);
}
});
curseForgeCompatible.setAllCurseMods(allCurseMods);
}
catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
public void loadModrinthIntegration(Path dir, IModrinthCompatible modrinthCompatible)
{
try
{
final ModrinthModPackInfo modPackInfo = modrinthCompatible.getModrinthModPackInfo();
final List<Mod> allModrinthMods = new ArrayList<>();
if(modrinthCompatible.getModrinthMods().isEmpty() && modPackInfo == null)
{
modrinthCompatible.setAllModrinthMods(allModrinthMods);
return;
}
final ModrinthIntegration modrinthIntegration = new ModrinthIntegration(this.logger, dir.getParent().resolve(".modrinth"));
for (ModrinthVersionInfo info : modrinthCompatible.getModrinthMods())
{
final Mod mod = modrinthIntegration.fetchMod(info);
this.checkMod(mod, allModrinthMods, dir);
}
if (modPackInfo == null)
{
modrinthCompatible.setAllModrinthMods(allModrinthMods);
return;
}
this.progressCallback.step(Step.MOD_PACK);
final ModrinthModPack modPack = modrinthIntegration.getCurseModPack(modPackInfo);
this.logger.info(String.format("Loading mod pack: %s (%s).", modPack.getName(), modPack.getVersion()));
modrinthCompatible.setModrinthModPack(modPack);
for (Mod mod : modPack.getMods())
this.checkMod(mod, allModrinthMods, dir);
modrinthCompatible.setAllModrinthMods(allModrinthMods);
}
catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
/**
* This method loads the OptiFine integration and fetches OptiFine data.
* @param dir the installation directory.
* @param optiFineCompatible the current Forge version.
*/
public void loadOptiFineIntegration(Path dir, @NotNull IOptiFineCompatible optiFineCompatible)
{
final OptiFineInfo info = optiFineCompatible.getOptiFineInfo();
if(info == null)
return;
try
{
final OptiFineIntegration optifineIntegration = new OptiFineIntegration(this.logger, dir.getParent().resolve(".op"));
final OptiFine optifine = optifineIntegration.getOptiFine(info.getVersion(), info.isPreview());
this.downloadList.setOptiFine(optifine);
} catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
private void checkMod(Mod mod, @NotNull List<Mod> allMods, @NotNull Path dir) throws Exception
{
allMods.add(mod);
final Path filePath = dir.resolve(mod.getName());
if(Files.exists(filePath)
&& Files.size(filePath) == mod.getSize()
&& (mod.getSha1().isEmpty() || FileUtils.getSHA1(filePath).equalsIgnoreCase(mod.getSha1())))
return;
Files.deleteIfExists(filePath);
this.downloadList.getMods().add(mod);
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/CurseForgeIntegration.java
================================================
package fr.flowarg.flowupdater.integrations.curseforgeintegration;
import com.google.gson.*;
import fr.flowarg.flowio.FileUtils;
import fr.flowarg.flowlogger.ILogger;
import fr.flowarg.flowstringer.StringUtils;
import fr.flowarg.flowupdater.download.json.CurseFileInfo;
import fr.flowarg.flowupdater.download.json.CurseModPackInfo;
import fr.flowarg.flowupdater.download.json.Mod;
import fr.flowarg.flowupdater.integrations.Integration;
import fr.flowarg.flowupdater.utils.IOUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* This integration supports all CurseForge stuff that FlowUpdater needs such as retrieve mods and mod packs from CurseForge.
*/
public class CurseForgeIntegration extends Integration
{
private static final String CF_API_URL = "https://api.curseforge.com";
private static final String CF_API_KEY = "JDJhJDEwJHBFZjhacXFwWE4zbVdtLm5aZ2pBMC5kdm9ibnhlV3hQZWZma2Q5ZEhCRWFid2VaUWh2cUtpJDJhJ";
private static final String MOD_FILE_ENDPOINT = "/v1/mods/{modId}/files/{fileId}";
private boolean manifestChanged = false;
/**
* Default constructor of a basic Integration.
*
* @param logger the logger used.
* @param folder the folder where the plugin can work.
* @throws Exception if an error occurred.
*/
public CurseForgeIntegration(ILogger logger, Path folder) throws Exception
{
super(logger, folder);
}
public Mod fetchMod(CurseFileInfo curseFileInfo) throws CurseForgeException
{
try
{
return this.parseModFile(this.fetchModLink(curseFileInfo));
} catch (Exception e)
{
throw new CurseForgeException(String.format("Failed to fetch mod project id: %d file id: %d", curseFileInfo.getProjectID(), curseFileInfo.getFileID()), e);
}
}
public static class CurseForgeException extends Exception
{
public CurseForgeException(String message, Throwable cause)
{
super(message, cause);
}
}
public String fetchModLink(@NotNull CurseFileInfo curseFileInfo)
{
final String url = CF_API_URL + MOD_FILE_ENDPOINT
.replace("{modId}", String.valueOf(curseFileInfo.getProjectID()))
.replace("{fileId}", String.valueOf(curseFileInfo.getFileID()));
return this.makeRequest(url);
}
/**
* Make a request to the CurseForge API.
* Oh my god, fuck Java 8 HTTP API, it's so fucking bad. Hope we drop Java 8 as soon as possible.
*
* @param url the url to request.
* @return the response of the request.
*/
private @NotNull String makeRequest(String url)
{
HttpURLConnection connection = null;
try
{
connection = (HttpURLConnection)new URL(url).openConnection();
connection.setRequestMethod("GET");
connection.setInstanceFollowRedirects(true);
connection.setUseCaches(false);
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("x-api-key", this.getCurseForgeAPIKey());
return IOUtils.getContent(connection.getInputStream());
}
catch (Exception e)
{
return "";
}
finally
{
if(connection != null)
connection.disconnect();
}
}
/**
* Parse the CurseForge API to retrieve the mod file.
*/
private @NotNull Mod parseModFile(String jsonResponse)
{
final JsonObject data = JsonParser.parseString(jsonResponse).getAsJsonObject().getAsJsonObject("data");
final String fileName = data.get("fileName").getAsString();
final JsonElement downloadURLElement = data.get("downloadUrl");
String downloadURL;
if(downloadURLElement instanceof JsonNull)
{
logger.warn(String.format("Mod file %s not available. The download can fail because of this! %s", data.get("displayName").getAsString(), jsonResponse));
final String id = Integer.toString(data.get("id").getAsInt());
downloadURL = String.format("https://edge.forgecdn.net/files/%s/%s/%s", id.substring(0, 4), id.substring(4), fileName);
}
else downloadURL = downloadURLElement.getAsString();
final long fileLength = data.get("fileLength").getAsLong();
final AtomicReference<String> sha1 = new AtomicReference<>("");
data.getAsJsonArray("hashes").forEach(hashObject -> {
final String hash = hashObject.getAsJsonObject().get("value").getAsString();
if(hash.length() == 40)
sha1.set(hash);
});
return new Mod(fileName, downloadURL, sha1.get(), fileLength);
}
/**
* Get a CurseForge's mod pack object with a project identifier and a file identifier.
* @param info CurseForge's mod pack info.
* @return the curse's mod pack corresponding to given parameters.
* @throws Exception if an error occurred.
*/
public CurseModPack getCurseModPack(CurseModPackInfo info) throws Exception
{
final Path modPackFile = this.checkForUpdate(info);
this.extractModPack(modPackFile, info.isInstallExtFiles());
return this.parseMods();
}
private @NotNull Path checkForUpdate(@NotNull CurseModPackInfo info) throws Exception
{
final String responseData = info.getUrl().isEmpty() ? this.fetchModLink(info) : this.makeRequest(info.getUrl());
final Mod modPackFile = this.parseModFile(responseData);
final Path outPath = this.folder.resolve(modPackFile.getName());
if(Files.notExists(outPath) || (!modPackFile.getSha1().isEmpty() && !FileUtils.getSHA1(outPath).equalsIgnoreCase(modPackFile.getSha1())) || Files.size(outPath) != modPackFile.getSize())
IOUtils.download(this.logger, new URL(modPackFile.getDownloadURL()), outPath);
return outPath;
}
private void extractModPack(@NotNull Path out, boolean installExtFiles) throws Exception
{
this.logger.info("Extracting mod pack...");
final ZipFile zipFile = new ZipFile(out.toFile(), ZipFile.OPEN_READ, StandardCharsets.UTF_8);
final Path dirPath = this.folder.getParent();
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements())
{
final ZipEntry entry = entries.nextElement();
final Path flPath = dirPath.resolve(StringUtils.empty(entry.getName(), "overrides/"));
final String entryName = entry.getName();
if(entryName.equalsIgnoreCase("manifest.json"))
{
if(Files.notExists(flPath) || entry.getCrc() != FileUtils.getCRC32(flPath))
{
this.manifestChanged = true;
this.transferAndClose(flPath, zipFile, entry);
}
if(!installExtFiles)
break;
continue;
}
if(!installExtFiles)
continue;
if(entryName.equals("modlist.html"))
continue;
if(Files.exists(flPath))
{
if(!Files.isDirectory(flPath))
{
if(entry.getCrc() == FileUtils.getCRC32(flPath))
continue;
}
}
else if (flPath.getFileName().toString().endsWith(flPath.getFileSystem().getSeparator()))
Files.createDirectories(flPath);
if (entry.isDirectory()) continue;
this.transferAndClose(flPath, zipFile, entry);
}
zipFile.close();
}
private @NotNull CurseModPack parseMods() throws Exception
{
this.logger.info("Fetching mods...");
final Path dirPath = this.folder.getParent();
final String manifestJson = StringUtils.toString(Files.readAllLines(dirPath.resolve("manifest.json")));
final JsonObject manifestObj = JsonParser.parseString(manifestJson).getAsJsonObject();
final String modPackName = manifestObj.get("name").getAsString();
final String modPackVersion = manifestObj.get("version").getAsString();
final String modPackAuthor = manifestObj.get("author").getAsString();
final List<CurseModPack.CurseModPackMod> mods = this.processCacheFile(dirPath, this.populateManifest(manifestObj));
return new CurseModPack(modPackName, modPackVersion, modPackAuthor, mods);
}
private @NotNull List<ProjectMod> populateManifest(@NotNull JsonObject manifestObj)
{
final List<ProjectMod> manifestFiles = new ArrayList<>();
manifestObj.getAsJsonArray("files")
.forEach(jsonElement -> manifestFiles.add(ProjectMod.fromJson(jsonElement.getAsJsonObject())));
return manifestFiles;
}
private @NotNull List<CurseModPack.CurseModPackMod> processCacheFile(@NotNull Path dirPath, List<ProjectMod> manifestFiles) throws Exception
{
final Path cachePath = dirPath.resolve("manifest.cache.json");
if(Files.notExists(cachePath))
{
Files.createFile(cachePath);
Files.write(cachePath, Collections.singletonList("[]"), StandardCharsets.UTF_8);
}
String json = StringUtils.toString(Files.readAllLines(cachePath, StandardCharsets.UTF_8));
if(this.manifestChanged || json.contains("\"md5\"") || json.contains("\"length\""))
{
Files.delete(cachePath);
Files.createFile(cachePath);
Files.write(cachePath, Collections.singletonList("[]"), StandardCharsets.UTF_8);
json = StringUtils.toString(Files.readAllLines(cachePath, StandardCharsets.UTF_8));
}
return this.deserializeWriteCache(json, manifestFiles, cachePath);
}
@Contract("_, _, _ -> new")
private @NotNull List<CurseModPack.CurseModPackMod> deserializeWriteCache(String json,
List<ProjectMod> manifestFiles, Path cachePath) throws Exception
{
final JsonArray cacheArray = JsonParser.parseString(json).getAsJsonArray();
final Queue<CurseModPack.CurseModPackMod> mods = new ConcurrentLinkedQueue<>();
cacheArray.forEach(jsonElement -> {
final JsonObject object = jsonElement.getAsJsonObject();
final Mod mod = Mod.fromJson(jsonElement);
final ProjectMod projectMod = ProjectMod.fromJson(object);
mods.add(new CurseModPack.CurseModPackMod(mod, projectMod.isRequired()));
manifestFiles.remove(projectMod);
});
IOUtils.executeAsyncForEach(manifestFiles, Executors.newWorkStealingPool(), projectMod -> this.fetchAndSerializeProjectMod(projectMod, cacheArray, mods));
Files.write(cachePath, Collections.singletonList(cacheArray.toString()), StandardCharsets.UTF_8);
return new ArrayList<>(mods);
}
private void fetchAndSerializeProjectMod(@NotNull ProjectMod projectMod, JsonArray cacheArray,
Queue<CurseModPack.CurseModPackMod> mods)
{
final boolean required = projectMod.isRequired();
try
{
final Mod retrievedMod = this.fetchMod(projectMod);
if(retrievedMod == null)
return;
final CurseModPack.CurseModPackMod mod = new CurseModPack.CurseModPackMod(retrievedMod, required);
final JsonObject inCache = new JsonObject();
inCache.addProperty("name", mod.getName());
inCache.addProperty("downloadURL", mod.getDownloadURL());
inCache.addProperty("sha1", mod.getSha1());
inCache.addProperty("size", mod.getSize());
inCache.addProperty("required", required);
inCache.addProperty("projectID", projectMod.getProjectID());
inCache.addProperty("fileID", projectMod.getFileID());
cacheArray.add(inCache);
mods.add(mod);
}
catch (Exception e)
{
this.logger.printStackTrace(e);
}
}
private void transferAndClose(@NotNull Path flPath, ZipFile zipFile, ZipEntry entry) throws Exception
{
if(Files.notExists(flPath.getParent()))
Files.createDirectories(flPath.getParent());
Files.copy(zipFile.getInputStream(entry), flPath, StandardCopyOption.REPLACE_EXISTING);
}
private static class ProjectMod extends CurseFileInfo
{
private final boolean required;
public ProjectMod(int projectID, int fileID, boolean required)
{
super(projectID, fileID);
this.required = required;
}
private static @NotNull ProjectMod fromJson(@NotNull JsonObject object)
{
return new ProjectMod(object.get("projectID").getAsInt(),
object.get("fileID").getAsInt(),
object.get("required").getAsBoolean());
}
public boolean isRequired()
{
return this.required;
}
}
/**
* Get the CurseForge API Key.
*/
private static String cacheKey = "";
private String getCurseForgeAPIKey()
{
return cacheKey.isEmpty() ? cacheKey = StringUtils.toString(Base64.getDecoder().decode(CF_API_KEY.substring(0, CF_API_KEY.length() - 5))) : cacheKey;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/CurseModPack.java
================================================
package fr.flowarg.flowupdater.integrations.curseforgeintegration;
import fr.flowarg.flowupdater.download.json.Mod;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Basic object that represents a CurseForge's mod pack.
*/
public class CurseModPack
{
private final String name;
private final String version;
private final String author;
private final List<CurseModPackMod> mods;
CurseModPack(String name, String version, String author, List<CurseModPackMod> mods)
{
this.name = name;
this.version = version;
this.author = author;
this.mods = mods;
}
/**
* Get the mod pack's name.
* @return the mod pack's name.
*/
public String getName()
{
return this.name;
}
/**
* Get the mod pack's version.
* @return the mod pack's version.
*/
public String getVersion()
{
return this.version;
}
/**
* Get the mod pack's author.
* @return the mod pack's author.
*/
public String getAuthor()
{
return this.author;
}
/**
* Get the mods in the mod pack.
* @return the mods in the mod pack.
*/
public List<CurseModPackMod> getMods()
{
return this.mods;
}
/**
* A Curse Forge's mod from a mod pack.
*/
public static class CurseModPackMod extends Mod
{
private final boolean required;
CurseModPackMod(String name, String downloadURL, String sha1, long size, boolean required)
{
super(name, downloadURL, sha1, size);
this.required = required;
}
CurseModPackMod(@NotNull Mod base, boolean required)
{
this(base.getName(), base.getDownloadURL(), base.getSha1(), base.getSize(), required);
}
/**
* Is the mod required.
* @return if the mod is required.
*/
public boolean isRequired()
{
return this.required;
}
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/ICurseForgeCompatible.java
================================================
package fr.flowarg.flowupdater.integrations.curseforgeintegration;
import fr.flowarg.flowupdater.download.json.CurseFileInfo;
import fr.flowarg.flowupdater.download.json.CurseModPackInfo;
import fr.flowarg.flowupdater.download.json.Mod;
import java.util.List;
/**
* This class represents an object that using CurseForge features.
*/
public interface ICurseForgeCompatible
{
/**
* Get all curse mods to update.
* @return all curse mods.
*/
List<CurseFileInfo> getCurseMods();
/**
* Get information about the mod pack to update.
* @return mod pack's information.
*/
CurseModPackInfo getCurseModPackInfo();
/**
* Define all curse mods to update.
* @param curseMods curse mods to define.
*/
void setAllCurseMods(List<Mod> curseMods);
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/package-info.java
================================================
/**
* CurseForge Integration package.
*/
package fr.flowarg.flowupdater.integrations.curseforgeintegration;
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/IModrinthCompatible.java
================================================
package fr.flowarg.flowupdater.integrations.modrinthintegration;
import fr.flowarg.flowupdater.download.json.Mod;
import fr.flowarg.flowupdater.download.json.ModrinthModPackInfo;
import fr.flowarg.flowupdater.download.json.ModrinthVersionInfo;
import java.util.List;
public interface IModrinthCompatible
{
/**
* Get all modrinth mods to update.
* @return all modrinth mods.
*/
List<ModrinthVersionInfo> getModrinthMods();
/**
* Get information about the mod pack to update.
* @return mod pack's information.
*/
ModrinthModPackInfo getModrinthModPackInfo();
/**
* Get the modrinth mod pack.
* @return the modrinth mod pack.
*/
ModrinthModPack getModrinthModPack();
/**
* Define the modrinth mod pack.
* @param modrinthModPack the modrinth mod pack.
*/
void setModrinthModPack(ModrinthModPack modrinthModPack);
/**
* Define all modrinth mods to update.
* @param modrinthMods modrinth mods to define.
*/
void setAllModrinthMods(List<Mod> modrinthMods);
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/ModrinthIntegration.java
================================================
package fr.flowarg.flowupdater.integrations.modrinthintegration;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import fr.flowarg.flowio.FileUtils;
import fr.flowarg.flowlogger.ILogger;
import fr.flowarg.flowstringer.StringUtils;
import fr.flowarg.flowupdater.download.json.Mod;
import fr.flowarg.flowupdater.download.json.ModrinthModPackInfo;
import fr.flowarg.flowupdater.download.json.ModrinthVersionInfo;
import fr.flowarg.flowupdater.integrations.Integration;
import fr.flowarg.flowupdater.utils.FlowUpdaterException;
import fr.flowarg.flowupdater.utils.IOUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class ModrinthIntegration extends Integration
{
private static final String MODRINTH_URL = "https://api.modrinth.com/v2/";
private static final String MODRINTH_VERSION_ENDPOINT = "version/{versionId}";
private static final String MODRINTH_PROJECT_VERSION_ENDPOINT = "project/{projectId}/version";
private final List<Mod> builtInMods = new ArrayList<>();
/**
* Default constructor of a basic Integration.
*
* @param logger the logger used.
* @param folder the folder where the plugin can work.
* @throws Exception if an error occurred.
*/
public ModrinthIntegration(ILogger logger, Path folder) throws Exception
{
super(logger, folder);
}
public Mod fetchMod(@NotNull ModrinthVersionInfo versionInfo) throws Exception
{
if(!versionInfo.getVersionId().isEmpty())
{
final URL url = new URL(MODRINTH_URL + MODRINTH_VERSION_ENDPOINT
.replace("{versionId}", versionInfo.getVersionId()));
return this.parseModFile(JsonParser.parseString(IOUtils.getContent(url)).getAsJsonObject());
}
final URL url = new URL(MODRINTH_URL + MODRINTH_PROJECT_VERSION_ENDPOINT.replace("{projectId}", versionInfo.getProjectReference()));
final JsonArray versions = JsonParser.parseString(IOUtils.getContent(url)).getAsJsonArray();
JsonObject version = null;
for (JsonElement jsonElement : versions)
{
if(!jsonElement.getAsJsonObject().get("version_number").getAsString().equals(versionInfo.getVersionNumber()))
continue;
version = jsonElement.getAsJsonObject();
break;
}
if(version == null)
throw new FlowUpdaterException(
"No version found for " + versionInfo.getVersionNumber() +
" in project " + versionInfo.getProjectReference());
return this.parseModFile(version);
}
public Mod parseModFile(@NotNull JsonObject version)
{
final JsonObject fileJson = version.getAsJsonArray("files").get(0).getAsJsonObject();
final String fileName = fileJson.get("filename").getAsString();
final String downloadURL = fileJson.get("url").getAsString();
final String sha1 = fileJson.getAsJsonObject("hashes").get("sha1").getAsString();
final long size = fileJson.get("size").getAsLong();
return new Mod(fileName, downloadURL, sha1, size);
}
/**
* Get a CurseForge's mod pack object with a project identifier and a file identifier.
* @param info CurseForge's mod pack info.
* @return the curse's mod pack corresponding to given parameters.
* @throws Exception if an error occurred.
*/
public ModrinthModPack getCurseModPack(ModrinthModPackInfo info) throws Exception
{
final Path modPackFile = this.checkForUpdate(info);
if(modPackFile == null)
throw new FlowUpdaterException("Can't find the mod pack file with the provided Modrinth mod pack info.");
this.extractModPack(modPackFile, info.isInstallExtFiles());
return this.parseMods();
}
private @Nullable Path checkForUpdate(@NotNull ModrinthModPackInfo info) throws Exception
{
final Mod modPackFile = this.fetchMod(info);
if(modPackFile == null)
{
this.logger.err("This mod pack isn't available anymore on Modrinth (for 3rd parties maybe). ");
return null;
}
final Path outPath = this.folder.resolve(modPackFile.getName());
if(Files.notExists(outPath) || !FileUtils.getSHA1(outPath).equalsIgnoreCase(modPackFile.getSha1()))
IOUtils.download(this.logger, new URL(modPackFile.getDownloadURL()), outPath);
return outPath;
}
private void extractModPack(@NotNull Path out, boolean installExtFiles) throws Exception
{
this.logger.info("Extracting mod pack...");
final ZipFile zipFile = new ZipFile(out.toFile(), ZipFile.OPEN_READ, StandardCharsets.UTF_8);
final Path dirPath = this.folder.getParent();
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements())
{
final ZipEntry entry = entries.nextElement();
final String entryName = entry.getName();
final Path flPath = dirPath.resolve(StringUtils.empty(StringUtils.empty(entryName, "client-overrides/"), "overrides/"));
if(entryName.equalsIgnoreCase("modrinth.index.json"))
{
if(Files.notExists(flPath) || entry.getCrc() != FileUtils.getCRC32(flPath))
this.transferAndClose(flPath, zipFile, entry);
continue;
}
final String withoutOverrides = StringUtils.empty(StringUtils.empty(entryName, "overrides/"), "client-overrides/");
if(withoutOverrides.startsWith("mods/") || withoutOverrides.startsWith("mods\\"))
{
final String modName = withoutOverrides.substring(withoutOverrides.lastIndexOf('/') + 1);
final Mod mod = new Mod(modName, "", "", entry.getSize());
this.builtInMods.add(mod);
}
if(!installExtFiles || Files.exists(flPath)) continue;
if (flPath.getFileName().toString().endsWith(flPath.getFileSystem().getSeparator()))
Files.createDirectories(flPath);
if (entry.isDirectory()) continue;
this.transferAndClose(flPath, zipFile, entry);
}
zipFile.close();
}
private @NotNull ModrinthModPack parseMods() throws Exception
{
this.logger.info("Fetching mods...");
final Path dirPath = this.folder.getParent();
final String manifestJson = StringUtils.toString(Files.readAllLines(dirPath.resolve("modrinth.index.json")));
final JsonObject manifestObj = JsonParser.parseString(manifestJson).getAsJsonObject();
final String modPackName = manifestObj.get("name").getAsString();
final String modPackVersion = manifestObj.get("versionId").getAsString();
final List<Mod> mods = this.parseManifest(manifestObj);
return new ModrinthModPack(modPackName, modPackVersion, mods, this.builtInMods);
}
private @NotNull List<Mod> parseManifest(@NotNull JsonObject manifestObject)
{
final List<Mod> mods = new ArrayList<>();
final JsonArray files = manifestObject.getAsJsonArray("files");
files.forEach(jsonElement -> {
final JsonObject file = jsonElement.getAsJsonObject();
if(file.getAsJsonObject("env").get("client").getAsString().equals("unsupported"))
return;
final String name = StringUtils.empty(StringUtils.empty(file.get("path").getAsString(), "mods/"), "mods\\");
final String downloadURL = file.getAsJsonArray("downloads").get(0).getAsString();
final String sha1 = file.getAsJsonObject("hashes").get("sha1").getAsString();
final long size = file.get("fileSize").getAsLong();
mods.add(new Mod(name, downloadURL, sha1, size));
});
return mods;
}
private void transferAndClose(@NotNull Path flPath, ZipFile zipFile, ZipEntry entry) throws Exception
{
if(Files.notExists(flPath.getParent()))
Files.createDirectories(flPath.getParent());
Files.copy(zipFile.getInputStream(entry), flPath, StandardCopyOption.REPLACE_EXISTING);
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/ModrinthModPack.java
================================================
package fr.flowarg.flowupdater.integrations.modrinthintegration;
import fr.flowarg.flowupdater.download.json.Mod;
import java.util.ArrayList;
import java.util.List;
public class ModrinthModPack
{
private final String name;
private final String version;
private final List<Mod> mods;
private final List<Mod> builtInMods;
ModrinthModPack(String name, String version, List<Mod> mods)
{
this(name, version, mods, new ArrayList<>());
}
ModrinthModPack(String name, String version, List<Mod> mods, List<Mod> builtInMods)
{
this.name = name;
this.version = version;
this.mods = mods;
this.builtInMods = builtInMods;
}
/**
* Get the mod pack's name.
* @return the mod pack's name.
*/
public String getName()
{
return this.name;
}
/**
* Get the mod pack's version.
* @return the mod pack's version.
*/
public String getVersion()
{
return this.version;
}
/**
* Get the mods in the mod pack.
* @return the mods in the mod pack.
*/
public List<Mod> getMods()
{
return this.mods;
}
/**
* Get the built-in mods in the mod pack.
* Built-in mods are mods directly put in the mods folder in the .mrpack file.
* They are not downloaded from a remote server.
* This is not a very good way to add mods because it disables some mod verification on these mods.
* We recommend mod pack creators to use the built-in mods feature only for mods that are not available remotely.
* @return the built-in mods in the mod pack.
*/
public List<Mod> getBuiltInMods()
{
return this.builtInMods;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/package-info.java
================================================
/**
* Modrinth Integration package.
*/
package fr.flowarg.flowupdater.integrations.modrinthintegration;
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/IOptiFineCompatible.java
================================================
package fr.flowarg.flowupdater.integrations.optifineintegration;
import fr.flowarg.flowupdater.download.json.OptiFineInfo;
public interface IOptiFineCompatible
{
/**
* Get information about OptiFine to update.
* @return OptiFine's information.
*/
OptiFineInfo getOptiFineInfo();
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/OptiFine.java
================================================
package fr.flowarg.flowupdater.integrations.optifineintegration;
/**
* This class represents a basic OptiFine object.
*/
public class OptiFine
{
private final String name;
private final long size;
OptiFine(String name, long size)
{
this.name = name;
this.size = size;
}
/**
* Get the OptiFine filename.
* @return the OptiFine filename.
*/
public String getName() {
return this.name;
}
/**
* Get the OptiFine file size.
* @return the OptiFine file size.
*/
public long getSize() {
return this.size;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/OptiFineIntegration.java
================================================
package fr.flowarg.flowupdater.integrations.optifineintegration;
import fr.flowarg.flowlogger.ILogger;
import fr.flowarg.flowupdater.integrations.Integration;
import fr.flowarg.flowupdater.utils.FlowUpdaterException;
import fr.flowarg.flowupdater.utils.IOUtils;
import org.jetbrains.annotations.NotNull;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
/**
* This integration supports the download of OptiFine in any version from the official site
* (<a href="https://optifine.net">OptiFine</a>).
*/
public class OptiFineIntegration extends Integration
{
public OptiFineIntegration(ILogger logger, Path folder) throws Exception
{
super(logger, folder);
}
/**
* Get an OptiFine object from the official website.
* @param optiFineVersion the version of OptiFine
* @param preview if the OptiFine version is a preview.
* @return the object that defines the plugin
*/
public OptiFine getOptiFine(String optiFineVersion, boolean preview)
{
try
{
final String fixedVersion = preview ? (optiFineVersion.startsWith("preview_OptiFine_") ?
optiFineVersion : optiFineVersion.startsWith("OptiFine_") ?
"preview_" + optiFineVersion : "preview_OptiFine_" + optiFineVersion) :
optiFineVersion.startsWith("OptiFine_") ? optiFineVersion : "OptiFine_" + optiFineVersion;
final String name = fixedVersion + ".jar";
final String newUrl = this.getNewURL(name, preview, fixedVersion);
return new OptiFine(name, this.checkForUpdatesAndGetSize(name, newUrl));
}
catch (FlowUpdaterException e)
{
throw e;
}
catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
private @NotNull String getNewURL(String name, boolean preview, String optiFineVersion)
{
return "https://optifine.net/downloadx?f=" +
name +
"&x=" +
(preview ? this.getJsonPreview(optiFineVersion) : this.getJson(optiFineVersion));
}
private long checkForUpdatesAndGetSize(String name, String newUrl) throws Exception
{
final Path outputPath = this.folder.resolve(name);
if(Files.notExists(outputPath))
IOUtils.download(this.logger, new URL(newUrl), outputPath);
return Files.size(outputPath);
}
private @NotNull String getJson(String optiFineVersion)
{
try
{
final String[] respLine = IOUtils.getContent(new URL("https://optifine.net/adloadx?f=OptiFine_" + optiFineVersion))
.split("\n");
final Optional<String> result = Arrays.stream(respLine).filter(s -> s.contains("downloadx?f=OptiFine")).findFirst();
if(result.isPresent())
return result.get()
.replace("' onclick='onDownload()'>OptiFine " + optiFineVersion.replace("_", " ") +
"</a>", "")
.replace("<a href='downloadx?f=OptiFine_" + optiFineVersion + "&x=", "")
.replace(" ", "");
else throw new FlowUpdaterException("No line found in: " + Arrays.toString(respLine));
}
catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
private @NotNull String getJsonPreview(String optiFineVersion)
{
try
{
final String[] respLine = IOUtils.getContent(new URL("https://optifine.net/adloadx?f=" + optiFineVersion))
.split("\n");
final Optional<String> result = Arrays.stream(respLine).filter(s -> s.contains("downloadx?f=preview")).findFirst();
if(result.isPresent())
return result.get()
.replace("' onclick='onDownload()'>" + optiFineVersion.replace("_", " ") +
"</a>", "")
.replace("<a href='downloadx?f=" + optiFineVersion + "&x=", "")
.replace(" ", "");
else throw new FlowUpdaterException("No line found in: " + Arrays.toString(respLine));
}
catch (Exception e)
{
throw new FlowUpdaterException(e);
}
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/package-info.java
================================================
/**
* OptiFine Integration package.
*/
package fr.flowarg.flowupdater.integrations.optifineintegration;
================================================
FILE: src/main/java/fr/flowarg/flowupdater/integrations/package-info.java
================================================
/**
* Integration API package.
*/
package fr.flowarg.flowupdater.integrations;
================================================
FILE: src/main/java/fr/flowarg/flowupdater/package-info.java
================================================
/**
* Main package of FlowUpdater. The only class contained in this package is {@link fr.flowarg.flowupdater.FlowUpdater}.
*/
package fr.flowarg.flowupdater;
================================================
FILE: src/main/java/fr/flowarg/flowupdater/utils/ExternalFileDeleter.java
================================================
package fr.flowarg.flowupdater.utils;
import fr.flowarg.flowio.FileUtils;
import fr.flowarg.flowupdater.download.DownloadList;
import fr.flowarg.flowupdater.download.json.ExternalFile;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
/**
* A file deleter designed to check external files.
*/
public class ExternalFileDeleter implements IFileDeleter
{
/**
* Delete all bad files in the provided directory.
* @param externalFiles the list of external files.
* @param downloadList the download list.
* @param dir the base dir.
* @throws Exception thrown if an error occurred
*/
public void delete(@NotNull List<ExternalFile> externalFiles, DownloadList downloadList, Path dir) throws Exception
{
if(externalFiles.isEmpty()) return;
for(ExternalFile extFile : externalFiles)
{
final Path filePath = dir.resolve(extFile.getPath());
if (Files.exists(filePath))
{
if(!extFile.isUpdate()) continue;
if (FileUtils.getSHA1(filePath).equalsIgnoreCase(extFile.getSha1())) continue;
Files.delete(filePath);
}
downloadList.getExtFiles().add(extFile);
}
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/utils/FlowUpdaterException.java
================================================
package fr.flowarg.flowupdater.utils;
/**
* A simple runtime exception class that represents a fatal FlowUpdater error.
*/
public class FlowUpdaterException extends RuntimeException
{
/**
* Initialize the exception.
*/
public FlowUpdaterException()
{
super();
}
/**
* Initialize the exception with an error message.
* @param message error message.
*/
public FlowUpdaterException(String message)
{
super(message);
}
/**
* Initialize the exception with an error message and a cause.
* @param message error message.
* @param cause cause.
*/
public FlowUpdaterException(String message, Throwable cause)
{
super(message, cause);
}
/**
* Initialize the exception with a cause.
* @param cause cause.
*/
public FlowUpdaterException(Throwable cause)
{
super(cause);
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/utils/IFileDeleter.java
================================================
package fr.flowarg.flowupdater.utils;
/**
* Just a marker class that is extended by all file deleter classes.
*/
public interface IFileDeleter {}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/utils/IOUtils.java
================================================
package fr.flowarg.flowupdater.utils;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonParser;
import fr.flowarg.flowcompat.Platform;
import fr.flowarg.flowlogger.ILogger;
import fr.flowarg.flowupdater.FlowUpdater;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.net.ssl.SSLHandshakeException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* A basic I/O utility class.
*/
public class IOUtils
{
private static Path cachedMinecraftPath = null;
private static final Map<String, Integer> DOWNLOAD_RETRIES_CAUSED_BY_SSL_HANDSHAKE_EXCEPTION = new HashMap<>();
/**
* Download a remote file to a destination file.
* @param logger a valid logger instance.
* @param in the input url.
* @param out the output file.
*/
public static void download(@NotNull ILogger logger, @NotNull URL in, @NotNull Path out)
{
final String url = in.toExternalForm();
try
{
logger.info(String.format("Downloading %s from %s...", out.getFileName().toString(), url));
Files.createDirectories(out.toAbsolutePath().getParent());
Files.copy(catchForbidden(in), out, StandardCopyOption.REPLACE_EXISTING);
}
catch (SSLHandshakeException e)
{
if(DOWNLOAD_RETRIES_CAUSED_BY_SSL_HANDSHAKE_EXCEPTION.getOrDefault(url, 0) > 3)
{
logger.err("Too many retries caused by SSLHandshakeException when downloading file from: " + url);
logger.printStackTrace(e);
DOWNLOAD_RETRIES_CAUSED_BY_SSL_HANDSHAKE_EXCEPTION.remove(url);
return;
}
download(logger, in, out);
DOWNLOAD_RETRIES_CAUSED_BY_SSL_HANDSHAKE_EXCEPTION.put(
url,
DOWNLOAD_RETRIES_CAUSED_BY_SSL_HANDSHAKE_EXCEPTION.getOrDefault(url, 0) + 1
);
}
catch (Exception e)
{
logger.printStackTrace(e);
}
}
/**
* Copy a local file to a destination file.
* @param logger a valid logger instance.
* @param in the input file.
* @param out the output file.
*/
public static void copy(@NotNull ILogger logger, @NotNull Path in, @NotNull Path out)
{
try
{
logger.info(String.format("Copying %s to %s...", in, out));
Files.createDirectories(out.getParent());
Files.copy(in, out, StandardCopyOption.REPLACE_EXISTING);
}
catch (Exception e)
{
logger.printStackTrace(e);
}
}
/**
* Get the content from a remote url.
* @param url the destination url
* @return the content.
*/
public static @NotNull String getContent(URL url)
{
try
{
return getContent(catchForbidden(url));
} catch (Exception e)
{
FlowUpdater.DEFAULT_LOGGER.printStackTrace(e);
return "";
}
}
/**
* Get the content from a remote stream.
* @param remote the remote stream
* @return the content.
*/
public static @NotNull String getContent(InputStream remote)
{
final StringBuilder sb = new StringBuilder();
try(InputStream stream = new BufferedInputStream(remote))
{
final ReadableByteChannel rbc = Channels.newChannel(stream);
final Reader enclosedReader = Channels.newReader(rbc, StandardCharsets.UTF_8.newDecoder(), -1);
final BufferedReader reader = new BufferedReader(enclosedReader);
int character;
while ((character = reader.read()) != -1) sb.append((char)character);
reader.close();
enclosedReader.close();
rbc.close();
} catch (Exception e)
{
FlowUpdater.DEFAULT_LOGGER.printStackTrace(e);
}
return sb.toString();
}
/**
* Reading an url in a json element
* @param jsonURL json input
* @return a json element
*/
public static JsonElement readJson(URL jsonURL)
{
try
{
return readJson(catchForbidden(jsonURL));
} catch (Exception e)
{
FlowUpdater.DEFAULT_LOGGER.printStackTrace(e);
}
return JsonNull.INSTANCE;
}
/**
* Reading an inputStream in a json element
* @param inputStream json input
* @return a json element
*/
public static JsonElement readJson(InputStream inputStream)
{
JsonElement element = JsonNull.INSTANCE;
try(InputStream stream = new BufferedInputStream(inputStream))
{
final ReadableByteChannel rbc = Channels.newChannel(stream);
final Reader enclosedReader = Channels.newReader(rbc, StandardCharsets.UTF_8.newDecoder(), -1);
final BufferedReader reader = new BufferedReader(enclosedReader);
final StringBuilder sb = new StringBuilder();
int character;
while ((character = reader.read()) != -1) sb.append((char)character);
element = JsonParser.parseString(sb.toString());
reader.close();
enclosedReader.close();
rbc.close();
} catch (Exception e)
{
FlowUpdater.DEFAULT_LOGGER.printStackTrace(e);
}
return element.getAsJsonObject();
}
/**
* A trick to avoid some forbidden response.
* @param url the destination url.
* @return the opened connection.
* @throws Exception if an I/O error occurred.
*/
public static InputStream catchForbidden(@NotNull URL url) throws Exception
{
final HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.addRequestProperty("User-Agent", "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36");
connection.setInstanceFollowRedirects(true);
return connection.getInputStream();
}
/**
* Execute asynchronously a task for a collection of items.
* @param iterable the collection of items.
* @param service the executor service.
* @param runnable the task to execute.
* @param <T> the type of the items.
*/
public static <T> void executeAsyncForEach(@NotNull Iterable<T> iterable, @NotNull ExecutorService service, Consumer<T> runnable)
{
try
{
iterable.forEach(t -> service.submit(() -> runnable.accept(t)));
service.shutdown();
service.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
} catch (InterruptedException e)
{
throw new FlowUpdaterException(e);
}
}
public static @Nullable String getLatestArtifactVersion(String mavenMetadataUrl)
{
try
{
final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
final Document doc = dBuilder.parse(new URL(mavenMetadataUrl).openStream());
return getLatestArtifactVersion(doc);
}
catch (Exception e)
{
FlowUpdater.DEFAULT_LOGGER.printStackTrace(e);
return null;
}
}
public static String getLatestArtifactVersion(@NotNull Document doc)
{
doc.getDocumentElement().normalize();
final Element root = doc.getDocumentElement();
final NodeList nList = root.getElementsByTagName("versioning");
String version = "";
for (int temp = 0; temp < nList.getLength(); temp++)
{
final Node node = nList.item(temp);
if (node.getNodeType() != Node.ELEMENT_NODE)
continue;
version = ((Element) node).getElementsByTagName("release").item(0).getTextContent();
}
return version;
}
/**
* Retrieve the local Minecraft folder path.
* @return the Minecraft folder path.
*/
public static Path getMinecraftFolder()
{
return cachedMinecraftPath == null ?
cachedMinecraftPath = Paths.get(
Platform.isOnWindows() ? System.getenv("APPDATA")
: (Platform.isOnMac() ? System.getProperty("user.home") + "/Library/Application Support/"
: System.getProperty("user.home")), ".minecraft")
: cachedMinecraftPath;
}
}
================================================
FILE: src/main/java/fr/flowarg/flowupdater/utils/ModFileDeleter.java
================================================
package fr.flowarg.flowupdater.utils;
import fr.flowarg.flowio.FileUtils;
import fr.flowarg.flowlogger.ILogger;
import fr.flowarg.flowupdater.download.json.Mod;
import fr.flowarg.flowupdater.integrations.modrinthintegration.ModrinthModPack;
import fr.flowarg.flowupdater.integrations.optifineintegration.OptiFine;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* A file deleter designed to check mods.
*/
public class ModFileDeleter implements IFileDeleter
{
private final boolean useFileDeleter;
private final String[] modsToIgnore;
private final Pattern modsToIgnorePattern;
public ModFileDeleter(boolean useFileDeleter, String... modsToIgnore)
{
this.useFileDeleter = useFileDeleter;
this.modsToIgnore = modsToIgnore;
this.modsToIgnorePattern = null;
}
public ModFileDeleter(String... modsToIgnore)
{
this(true, modsToIgnore);
}
public ModFileDeleter(boolean useFileDeleter, Pattern modsToIgnorePattern)
{
this.useFileDeleter = useFileDeleter;
this.modsToIgnore = null;
this.modsToIgnorePattern = modsToIgnorePattern;
}
public ModFileDeleter(
gitextract_dp3i_z3s/
├── .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/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.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
SYMBOL INDEX (492 symbols across 58 files)
FILE: src/main/java/fr/flowarg/flowupdater/FlowUpdater.java
class FlowUpdater (line 39) | public class FlowUpdater
method init (line 74) | @Override
method FlowUpdater (line 95) | private FlowUpdater(VanillaVersion vanillaVersion, ILogger logger,
method update (line 127) | public void update(Path dir) throws Exception
method checkExtFiles (line 136) | private void checkExtFiles(Path dir) throws Exception
method updateMinecraft (line 141) | private void updateMinecraft(@NotNull Path dir) throws Exception
method loadVanillaStuff (line 154) | private void loadVanillaStuff() throws Exception
method loadModLoader (line 166) | private void loadModLoader(@NotNull Path dir) throws Exception
method checkMods (line 182) | private void checkMods(@NotNull IModLoaderVersion modLoader, Path mods...
method startVanillaDownload (line 195) | private void startVanillaDownload(Path dir) throws Exception
method installModLoader (line 203) | private void installModLoader(Path dir) throws Exception
method updateExtFiles (line 218) | private void updateExtFiles(Path dir)
method runPostExecutions (line 245) | private void runPostExecutions()
method endUpdate (line 254) | private void endUpdate()
class FlowUpdaterBuilder (line 265) | public static class FlowUpdaterBuilder implements IBuilder<FlowUpdater>
method withVanillaVersion (line 280) | public FlowUpdaterBuilder withVanillaVersion(VanillaVersion version)
method withLogger (line 291) | public FlowUpdaterBuilder withLogger(ILogger logger)
method withUpdaterOptions (line 302) | public FlowUpdaterBuilder withUpdaterOptions(UpdaterOptions updaterO...
method withProgressCallback (line 313) | public FlowUpdaterBuilder withProgressCallback(IProgressCallback cal...
method withExternalFiles (line 324) | public FlowUpdaterBuilder withExternalFiles(Collection<ExternalFile>...
method withExternalFiles (line 335) | public FlowUpdaterBuilder withExternalFiles(ExternalFile... external...
method withExternalFiles (line 345) | public FlowUpdaterBuilder withExternalFiles(URL externalFilesJsonUrl)
method withExternalFiles (line 355) | public FlowUpdaterBuilder withExternalFiles(String externalFilesJson...
method withPostExecutions (line 365) | public FlowUpdaterBuilder withPostExecutions(Collection<Runnable> po...
method withPostExecutions (line 376) | public FlowUpdaterBuilder withPostExecutions(Runnable... postExecuti...
method withModLoaderVersion (line 387) | public FlowUpdaterBuilder withModLoaderVersion(IModLoaderVersion mod...
method build (line 398) | @Override
method getVanillaVersion (line 419) | public VanillaVersion getVanillaVersion()
method getModLoaderVersion (line 428) | public IModLoaderVersion getModLoaderVersion()
method getLogger (line 437) | public ILogger getLogger()
method getCallback (line 446) | public IProgressCallback getCallback()
method getExternalFiles (line 455) | public List<ExternalFile> getExternalFiles()
method getPostExecutions (line 464) | public List<Runnable> getPostExecutions()
method getDownloadList (line 473) | public DownloadList getDownloadList()
method getUpdaterOptions (line 482) | public UpdaterOptions getUpdaterOptions()
FILE: src/main/java/fr/flowarg/flowupdater/download/DownloadList.java
class DownloadList (line 21) | public class DownloadList
method init (line 35) | public void init()
method incrementDownloaded (line 71) | public void incrementDownloaded(long bytes)
method getDownloadInfo (line 83) | public DownloadInfo getDownloadInfo()
method getDownloadableAssets (line 92) | public List<AssetDownloadable> getDownloadableAssets()
method getDownloadableFiles (line 101) | public List<Downloadable> getDownloadableFiles()
method getExtFiles (line 110) | public List<ExternalFile> getExtFiles()
method getMods (line 119) | public List<Mod> getMods()
method getOptiFine (line 128) | public OptiFine getOptiFine()
method setOptiFine (line 137) | public void setOptiFine(OptiFine optiFine)
method clear (line 145) | public void clear()
class DownloadInfo (line 156) | public static class DownloadInfo
method reset (line 166) | public void reset()
method getTotalToDownloadBytes (line 178) | public long getTotalToDownloadBytes()
method getDownloadedBytes (line 187) | public long getDownloadedBytes()
method getTotalToDownloadFiles (line 196) | public int getTotalToDownloadFiles()
method getDownloadedFiles (line 205) | public int getDownloadedFiles()
FILE: src/main/java/fr/flowarg/flowupdater/download/IProgressCallback.java
type IProgressCallback (line 11) | public interface IProgressCallback
method init (line 17) | default void init(ILogger logger) {}
method step (line 23) | default void step(Step step) {}
method update (line 29) | default void update(DownloadList.DownloadInfo info) {}
method onFileDownloaded (line 35) | default void onFileDownloaded(Path path) {}
FILE: src/main/java/fr/flowarg/flowupdater/download/Step.java
type Step (line 9) | public enum Step
FILE: src/main/java/fr/flowarg/flowupdater/download/VanillaDownloader.java
class VanillaDownloader (line 26) | public class VanillaDownloader
method VanillaDownloader (line 42) | public VanillaDownloader(Path dir, @NotNull FlowUpdater flowUpdater) t...
method download (line 64) | public void download() throws Exception
method downloadLibraries (line 73) | private void downloadLibraries() throws Exception
method downloadVanillaJson (line 98) | private void downloadVanillaJson() throws Exception
method extractNatives (line 109) | private void extractNatives() throws Exception
method downloadAssets (line 178) | private void downloadAssets()
FILE: src/main/java/fr/flowarg/flowupdater/download/VanillaReader.java
class VanillaReader (line 23) | public class VanillaReader
method VanillaReader (line 33) | public VanillaReader(@NotNull FlowUpdater flowUpdater)
method read (line 44) | public void read() throws Exception
method parseLibraries (line 54) | private void parseLibraries()
method parseAssetIndex (line 98) | private void parseAssetIndex()
method parseClient (line 112) | private void parseClient()
method parseNatives (line 123) | private void parseNatives()
method getNativeForOS (line 150) | private void getNativeForOS(@NotNull String os, @NotNull JsonObject obj)
method parseAssets (line 171) | private void parseAssets() throws Exception
method checkRules (line 190) | private boolean checkRules(@NotNull JsonObject obj)
method check (line 221) | private boolean check(@NotNull String os)
FILE: src/main/java/fr/flowarg/flowupdater/download/json/AssetDownloadable.java
class AssetDownloadable (line 6) | public class AssetDownloadable
method AssetDownloadable (line 18) | public AssetDownloadable(String hash, long size)
method getHash (line 31) | public String getHash()
method getSize (line 40) | public long getSize()
method getUrl (line 49) | public String getUrl()
method getFile (line 58) | public String getFile()
method equals (line 63) | @Override
method hashCode (line 73) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/download/json/AssetIndex.java
class AssetIndex (line 10) | public class AssetIndex
method getObjects (line 18) | private Map<String, AssetDownloadable> getObjects()
method getUniqueObjects (line 27) | public Map<String, AssetDownloadable> getUniqueObjects()
FILE: src/main/java/fr/flowarg/flowupdater/download/json/CurseFileInfo.java
class CurseFileInfo (line 17) | public class CurseFileInfo
method CurseFileInfo (line 27) | public CurseFileInfo(int projectID, int fileID)
method getFilesFromJson (line 38) | public static @NotNull List<CurseFileInfo> getFilesFromJson(URL jsonUrl)
method getFilesFromJson (line 57) | public static @NotNull List<CurseFileInfo> getFilesFromJson(String jso...
method getProjectID (line 73) | public int getProjectID()
method getFileID (line 82) | public int getFileID()
method equals (line 87) | @Override
method hashCode (line 96) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/download/json/CurseModPackInfo.java
class CurseModPackInfo (line 6) | public class CurseModPackInfo extends CurseFileInfo
method CurseModPackInfo (line 20) | public CurseModPackInfo(int projectID, int fileID, boolean installExtF...
method CurseModPackInfo (line 33) | public CurseModPackInfo(String url, boolean installExtFiles, String......
method isInstallExtFiles (line 45) | public boolean isInstallExtFiles()
method getExcluded (line 54) | public String[] getExcluded()
method getUrl (line 77) | public String getUrl()
FILE: src/main/java/fr/flowarg/flowupdater/download/json/Downloadable.java
class Downloadable (line 8) | public class Downloadable
method Downloadable (line 22) | public Downloadable(String url, long size, String sha1, String name)
method getUrl (line 34) | public String getUrl()
method getSize (line 43) | public long getSize()
method getSha1 (line 52) | public String getSha1()
method getName (line 61) | public String getName()
method equals (line 66) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/download/json/ExternalFile.java
class ExternalFile (line 16) | public class ExternalFile
method ExternalFile (line 31) | public ExternalFile(String path, String downloadURL, String sha1, long...
method ExternalFile (line 48) | public ExternalFile(String path, String downloadURL, String sha1, long...
method getExternalFilesFromJson (line 82) | public static @NotNull List<ExternalFile> getExternalFilesFromJson(URL...
method getExternalFilesFromJson (line 104) | public static @NotNull List<ExternalFile> getExternalFilesFromJson(Str...
method getPath (line 119) | public String getPath()
method getDownloadURL (line 128) | public String getDownloadURL()
method getSha1 (line 137) | public String getSha1()
method getSize (line 146) | public long getSize()
method isUpdate (line 155) | public boolean isUpdate()
FILE: src/main/java/fr/flowarg/flowupdater/download/json/MCP.java
class MCP (line 14) | public class MCP
method MCP (line 26) | public MCP(String clientURL, String clientSha1, long clientSize)
method getMCPFromJson (line 46) | public static @NotNull MCP getMCPFromJson(URL jsonUrl)
method getMCPFromJson (line 57) | public static @NotNull MCP getMCPFromJson(String jsonUrl)
method getClientURL (line 72) | public String getClientURL()
method getClientSha1 (line 81) | public String getClientSha1()
method getClientSize (line 90) | public long getClientSize()
FILE: src/main/java/fr/flowarg/flowupdater/download/json/Mod.java
class Mod (line 17) | public class Mod
method Mod (line 31) | public Mod(String name, String downloadURL, String sha1, long size)
method getModsFromJson (line 63) | public static @NotNull List<Mod> getModsFromJson(URL jsonUrl)
method fromJson (line 72) | public static Mod fromJson(JsonElement modElement)
method getModsFromJson (line 90) | public static @NotNull List<Mod> getModsFromJson(String jsonUrl)
method getName (line 106) | public String getName()
method getSha1 (line 115) | public String getSha1()
method getSize (line 124) | public long getSize()
method getDownloadURL (line 133) | public String getDownloadURL()
FILE: src/main/java/fr/flowarg/flowupdater/download/json/ModrinthModPackInfo.java
class ModrinthModPackInfo (line 3) | public class ModrinthModPackInfo extends ModrinthVersionInfo
method ModrinthModPackInfo (line 8) | public ModrinthModPackInfo(String projectReference, String versionNumb...
method ModrinthModPackInfo (line 15) | public ModrinthModPackInfo(String versionId, boolean installExtFiles, ...
method isInstallExtFiles (line 26) | public boolean isInstallExtFiles()
method getExcluded (line 35) | public String[] getExcluded()
FILE: src/main/java/fr/flowarg/flowupdater/download/json/ModrinthVersionInfo.java
class ModrinthVersionInfo (line 16) | public class ModrinthVersionInfo
method ModrinthVersionInfo (line 27) | public ModrinthVersionInfo(String projectReference, String versionNumber)
method ModrinthVersionInfo (line 39) | public ModrinthVersionInfo(String versionId)
method getModrinthVersionsFromJson (line 44) | public static @NotNull List<ModrinthVersionInfo> getModrinthVersionsFr...
method getModrinthVersionsFromJson (line 60) | public static @NotNull List<ModrinthVersionInfo> getModrinthVersionsFr...
method getProjectReference (line 71) | public String getProjectReference()
method getVersionNumber (line 76) | public String getVersionNumber()
method getVersionId (line 81) | public String getVersionId()
FILE: src/main/java/fr/flowarg/flowupdater/download/json/OptiFineInfo.java
class OptiFineInfo (line 6) | public class OptiFineInfo
method OptiFineInfo (line 16) | public OptiFineInfo(String version, boolean preview)
method OptiFineInfo (line 26) | public OptiFineInfo(String version)
method getVersion (line 35) | public String getVersion()
method isPreview (line 44) | public boolean isPreview()
FILE: src/main/java/fr/flowarg/flowupdater/integrations/Integration.java
class Integration (line 14) | public abstract class Integration
method Integration (line 25) | public Integration(ILogger logger, Path folder) throws Exception
FILE: src/main/java/fr/flowarg/flowupdater/integrations/IntegrationManager.java
class IntegrationManager (line 30) | public class IntegrationManager
method IntegrationManager (line 40) | public IntegrationManager(@NotNull FlowUpdater updater)
method loadCurseForgeIntegration (line 52) | public void loadCurseForgeIntegration(Path dir, ICurseForgeCompatible ...
method loadModrinthIntegration (line 130) | public void loadModrinthIntegration(Path dir, IModrinthCompatible modr...
method loadOptiFineIntegration (line 178) | public void loadOptiFineIntegration(Path dir, @NotNull IOptiFineCompat...
method checkMod (line 196) | private void checkMod(Mod mod, @NotNull List<Mod> allMods, @NotNull Pa...
FILE: src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/CurseForgeIntegration.java
class CurseForgeIntegration (line 31) | public class CurseForgeIntegration extends Integration
method CurseForgeIntegration (line 46) | public CurseForgeIntegration(ILogger logger, Path folder) throws Excep...
method fetchMod (line 51) | public Mod fetchMod(CurseFileInfo curseFileInfo) throws CurseForgeExce...
class CurseForgeException (line 62) | public static class CurseForgeException extends Exception
method CurseForgeException (line 64) | public CurseForgeException(String message, Throwable cause)
method fetchModLink (line 70) | public String fetchModLink(@NotNull CurseFileInfo curseFileInfo)
method makeRequest (line 86) | private @NotNull String makeRequest(String url)
method parseModFile (line 114) | private @NotNull Mod parseModFile(String jsonResponse)
method getCurseModPack (line 147) | public CurseModPack getCurseModPack(CurseModPackInfo info) throws Exce...
method checkForUpdate (line 154) | private @NotNull Path checkForUpdate(@NotNull CurseModPackInfo info) t...
method extractModPack (line 166) | private void extractModPack(@NotNull Path out, boolean installExtFiles...
method parseMods (line 214) | private @NotNull CurseModPack parseMods() throws Exception
method populateManifest (line 229) | private @NotNull List<ProjectMod> populateManifest(@NotNull JsonObject...
method processCacheFile (line 239) | private @NotNull List<CurseModPack.CurseModPackMod> processCacheFile(@...
method deserializeWriteCache (line 262) | @Contract("_, _, _ -> new")
method fetchAndSerializeProjectMod (line 284) | private void fetchAndSerializeProjectMod(@NotNull ProjectMod projectMo...
method transferAndClose (line 316) | private void transferAndClose(@NotNull Path flPath, ZipFile zipFile, Z...
class ProjectMod (line 324) | private static class ProjectMod extends CurseFileInfo
method ProjectMod (line 328) | public ProjectMod(int projectID, int fileID, boolean required)
method fromJson (line 334) | private static @NotNull ProjectMod fromJson(@NotNull JsonObject object)
method isRequired (line 341) | public boolean isRequired()
method getCurseForgeAPIKey (line 352) | private String getCurseForgeAPIKey()
FILE: src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/CurseModPack.java
class CurseModPack (line 11) | public class CurseModPack
method CurseModPack (line 18) | CurseModPack(String name, String version, String author, List<CurseMod...
method getName (line 30) | public String getName()
method getVersion (line 40) | public String getVersion()
method getAuthor (line 49) | public String getAuthor()
method getMods (line 58) | public List<CurseModPackMod> getMods()
class CurseModPackMod (line 66) | public static class CurseModPackMod extends Mod
method CurseModPackMod (line 70) | CurseModPackMod(String name, String downloadURL, String sha1, long s...
method CurseModPackMod (line 76) | CurseModPackMod(@NotNull Mod base, boolean required)
method isRequired (line 85) | public boolean isRequired()
FILE: src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/ICurseForgeCompatible.java
type ICurseForgeCompatible (line 12) | public interface ICurseForgeCompatible
method getCurseMods (line 18) | List<CurseFileInfo> getCurseMods();
method getCurseModPackInfo (line 24) | CurseModPackInfo getCurseModPackInfo();
method setAllCurseMods (line 30) | void setAllCurseMods(List<Mod> curseMods);
FILE: src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/IModrinthCompatible.java
type IModrinthCompatible (line 9) | public interface IModrinthCompatible
method getModrinthMods (line 15) | List<ModrinthVersionInfo> getModrinthMods();
method getModrinthModPackInfo (line 21) | ModrinthModPackInfo getModrinthModPackInfo();
method getModrinthModPack (line 27) | ModrinthModPack getModrinthModPack();
method setModrinthModPack (line 33) | void setModrinthModPack(ModrinthModPack modrinthModPack);
method setAllModrinthMods (line 39) | void setAllModrinthMods(List<Mod> modrinthMods);
FILE: src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/ModrinthIntegration.java
class ModrinthIntegration (line 30) | public class ModrinthIntegration extends Integration
method ModrinthIntegration (line 45) | public ModrinthIntegration(ILogger logger, Path folder) throws Exception
method fetchMod (line 50) | public Mod fetchMod(@NotNull ModrinthVersionInfo versionInfo) throws E...
method parseModFile (line 80) | public Mod parseModFile(@NotNull JsonObject version)
method getCurseModPack (line 97) | public ModrinthModPack getCurseModPack(ModrinthModPackInfo info) throw...
method checkForUpdate (line 106) | private @Nullable Path checkForUpdate(@NotNull ModrinthModPackInfo inf...
method extractModPack (line 124) | private void extractModPack(@NotNull Path out, boolean installExtFiles...
method parseMods (line 164) | private @NotNull ModrinthModPack parseMods() throws Exception
method parseManifest (line 178) | private @NotNull List<Mod> parseManifest(@NotNull JsonObject manifestO...
method transferAndClose (line 201) | private void transferAndClose(@NotNull Path flPath, ZipFile zipFile, Z...
FILE: src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/ModrinthModPack.java
class ModrinthModPack (line 8) | public class ModrinthModPack
method ModrinthModPack (line 15) | ModrinthModPack(String name, String version, List<Mod> mods)
method ModrinthModPack (line 20) | ModrinthModPack(String name, String version, List<Mod> mods, List<Mod>...
method getName (line 32) | public String getName()
method getVersion (line 42) | public String getVersion()
method getMods (line 51) | public List<Mod> getMods()
method getBuiltInMods (line 64) | public List<Mod> getBuiltInMods()
FILE: src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/IOptiFineCompatible.java
type IOptiFineCompatible (line 5) | public interface IOptiFineCompatible
method getOptiFineInfo (line 11) | OptiFineInfo getOptiFineInfo();
FILE: src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/OptiFine.java
class OptiFine (line 6) | public class OptiFine
method OptiFine (line 11) | OptiFine(String name, long size)
method getName (line 21) | public String getName() {
method getSize (line 29) | public long getSize() {
FILE: src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/OptiFineIntegration.java
class OptiFineIntegration (line 19) | public class OptiFineIntegration extends Integration
method OptiFineIntegration (line 21) | public OptiFineIntegration(ILogger logger, Path folder) throws Exception
method getOptiFine (line 32) | public OptiFine getOptiFine(String optiFineVersion, boolean preview)
method getNewURL (line 55) | private @NotNull String getNewURL(String name, boolean preview, String...
method checkForUpdatesAndGetSize (line 63) | private long checkForUpdatesAndGetSize(String name, String newUrl) thr...
method getJson (line 71) | private @NotNull String getJson(String optiFineVersion)
method getJsonPreview (line 92) | private @NotNull String getJsonPreview(String optiFineVersion)
FILE: src/main/java/fr/flowarg/flowupdater/utils/ExternalFileDeleter.java
class ExternalFileDeleter (line 15) | public class ExternalFileDeleter implements IFileDeleter
method delete (line 24) | public void delete(@NotNull List<ExternalFile> externalFiles, Download...
FILE: src/main/java/fr/flowarg/flowupdater/utils/FlowUpdaterException.java
class FlowUpdaterException (line 6) | public class FlowUpdaterException extends RuntimeException
method FlowUpdaterException (line 11) | public FlowUpdaterException()
method FlowUpdaterException (line 20) | public FlowUpdaterException(String message)
method FlowUpdaterException (line 30) | public FlowUpdaterException(String message, Throwable cause)
method FlowUpdaterException (line 39) | public FlowUpdaterException(Throwable cause)
FILE: src/main/java/fr/flowarg/flowupdater/utils/IFileDeleter.java
type IFileDeleter (line 6) | public interface IFileDeleter {}
FILE: src/main/java/fr/flowarg/flowupdater/utils/IOUtils.java
class IOUtils (line 38) | public class IOUtils
method download (line 49) | public static void download(@NotNull ILogger logger, @NotNull URL in, ...
method copy (line 85) | public static void copy(@NotNull ILogger logger, @NotNull Path in, @No...
method getContent (line 104) | public static @NotNull String getContent(URL url)
method getContent (line 121) | public static @NotNull String getContent(InputStream remote)
method readJson (line 150) | public static JsonElement readJson(URL jsonURL)
method readJson (line 167) | public static JsonElement readJson(InputStream inputStream)
method catchForbidden (line 199) | public static InputStream catchForbidden(@NotNull URL url) throws Exce...
method executeAsyncForEach (line 214) | public static <T> void executeAsyncForEach(@NotNull Iterable<T> iterab...
method getLatestArtifactVersion (line 227) | public static @Nullable String getLatestArtifactVersion(String mavenMe...
method getLatestArtifactVersion (line 244) | public static String getLatestArtifactVersion(@NotNull Document doc)
method getMinecraftFolder (line 266) | public static Path getMinecraftFolder()
FILE: src/main/java/fr/flowarg/flowupdater/utils/ModFileDeleter.java
class ModFileDeleter (line 18) | public class ModFileDeleter implements IFileDeleter
method ModFileDeleter (line 24) | public ModFileDeleter(boolean useFileDeleter, String... modsToIgnore)
method ModFileDeleter (line 31) | public ModFileDeleter(String... modsToIgnore)
method ModFileDeleter (line 36) | public ModFileDeleter(boolean useFileDeleter, Pattern modsToIgnorePatt...
method ModFileDeleter (line 43) | public ModFileDeleter(Pattern modsToIgnorePattern)
method delete (line 57) | public void delete(ILogger logger, Path modsDir, List<Mod> mods, OptiF...
method isUseFileDeleter (line 139) | public boolean isUseFileDeleter()
method getModsToIgnore (line 144) | public String[] getModsToIgnore()
FILE: src/main/java/fr/flowarg/flowupdater/utils/UpdaterOptions.java
class UpdaterOptions (line 14) | public class UpdaterOptions
method UpdaterOptions (line 28) | private UpdaterOptions(ExternalFileDeleter externalFileDeleter, boolea...
method getExternalFileDeleter (line 41) | public ExternalFileDeleter getExternalFileDeleter()
method isVersionChecker (line 50) | public boolean isVersionChecker()
method getJavaPath (line 60) | public String getJavaPath()
method shouldDisableExtFilesAsyncDownload (line 71) | public boolean shouldDisableExtFilesAsyncDownload()
class UpdaterOptionsBuilder (line 79) | public static class UpdaterOptionsBuilder implements IBuilder<UpdaterO...
method withExternalFileDeleter (line 97) | public UpdaterOptionsBuilder withExternalFileDeleter(ExternalFileDel...
method withVersionChecker (line 108) | public UpdaterOptionsBuilder withVersionChecker(boolean versionChecker)
method withJavaPath (line 123) | public UpdaterOptionsBuilder withJavaPath(String javaPath)
method withDisableExtFilesAsyncDownload (line 134) | public UpdaterOptionsBuilder withDisableExtFilesAsyncDownload(boolea...
method build (line 143) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/utils/Version.java
class Version (line 10) | public class Version implements Comparable<Version>
method Version (line 14) | public Version(List<Integer> version)
method gen (line 19) | @Contract("_ -> new")
method compareTo (line 31) | @Override
method toString (line 44) | @Override
method isNewerThan (line 57) | public boolean isNewerThan(@NotNull Version o)
method isNewerOrEqualTo (line 62) | public boolean isNewerOrEqualTo(@NotNull Version o)
method isOlderThan (line 67) | public boolean isOlderThan(@NotNull Version o)
method isOlderOrEqualTo (line 72) | public boolean isOlderOrEqualTo(@NotNull Version o)
method isEqualTo (line 77) | public boolean isEqualTo(@NotNull Version o)
method isBetweenOrEqual (line 82) | public boolean isBetweenOrEqual(@NotNull Version min, @NotNull Version...
FILE: src/main/java/fr/flowarg/flowupdater/utils/VersionChecker.java
class VersionChecker (line 6) | public class VersionChecker
method run (line 8) | public static void run(ILogger logger)
FILE: src/main/java/fr/flowarg/flowupdater/utils/builderapi/BuilderArgument.java
class BuilderArgument (line 14) | public class BuilderArgument<T>
method BuilderArgument (line 26) | public BuilderArgument(String objectName, @NotNull Supplier<T> initial...
method BuilderArgument (line 36) | public BuilderArgument(String objectName)
method BuilderArgument (line 47) | public BuilderArgument(String objectName, @NotNull Supplier<T> initial...
method BuilderArgument (line 59) | public BuilderArgument(@NotNull Supplier<T> badObject, String objectName)
method get (line 70) | public T get() throws BuilderException
method set (line 88) | public void set(T object)
method require (line 98) | public BuilderArgument<T> require(BuilderArgument<?> @NotNull ... requ...
method required (line 109) | public BuilderArgument<T> required()
method optional (line 119) | public BuilderArgument<T> optional()
method getObjectName (line 129) | public String getObjectName()
method badObject (line 138) | public T badObject()
method toString (line 143) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/utils/builderapi/BuilderException.java
class BuilderException (line 8) | public class BuilderException extends RuntimeException
method BuilderException (line 12) | public BuilderException()
method BuilderException (line 17) | public BuilderException(String reason)
method BuilderException (line 22) | public BuilderException(String reason, Throwable cause)
method BuilderException (line 27) | public BuilderException(Throwable cause)
FILE: src/main/java/fr/flowarg/flowupdater/utils/builderapi/IBuilder.java
type IBuilder (line 10) | @FunctionalInterface
method build (line 18) | T build() throws BuilderException;
FILE: src/main/java/fr/flowarg/flowupdater/versions/AbstractModLoaderVersion.java
class AbstractModLoaderVersion (line 22) | public abstract class AbstractModLoaderVersion implements IModLoaderVers...
method AbstractModLoaderVersion (line 40) | public AbstractModLoaderVersion(String modLoaderVersion, List<Mod> mod...
method attachFlowUpdater (line 57) | @Override
method getMods (line 70) | @Override
method getDownloadList (line 79) | @Override
method getCallback (line 88) | @Override
method getCurseMods (line 97) | @Override
method getModrinthMods (line 106) | @Override
method setAllCurseMods (line 115) | @Override
method getCurseModPackInfo (line 124) | @Override
method getModrinthModPackInfo (line 133) | @Override
method getOptiFineInfo (line 142) | @Override
method setAllModrinthMods (line 151) | @Override
method getLogger (line 160) | @Override
method getModLoaderVersion (line 169) | @Override
method getFileDeleter (line 178) | @Override
method setModrinthModPack (line 184) | @Override
method getModrinthModPack (line 190) | @Override
method installMods (line 196) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/versions/IModLoaderVersion.java
type IModLoaderVersion (line 17) | public interface IModLoaderVersion
method attachFlowUpdater (line 23) | void attachFlowUpdater(@NotNull FlowUpdater flowUpdater);
method isModLoaderAlreadyInstalled (line 30) | boolean isModLoaderAlreadyInstalled(@NotNull Path installDir);
method install (line 37) | default void install(@NotNull Path installDir) throws Exception
method installMods (line 48) | void installMods(@NotNull Path modsDir) throws Exception;
method getModLoaderVersion (line 54) | String getModLoaderVersion();
method getMods (line 60) | List<Mod> getMods();
method installAllMods (line 66) | default void installAllMods(@NotNull Path modsDir)
method getDownloadList (line 88) | DownloadList getDownloadList();
method getLogger (line 94) | ILogger getLogger();
method getCallback (line 100) | IProgressCallback getCallback();
method getFileDeleter (line 106) | ModFileDeleter getFileDeleter();
method name (line 112) | String name();
FILE: src/main/java/fr/flowarg/flowupdater/versions/ModLoaderUtils.java
class ModLoaderUtils (line 18) | public class ModLoaderUtils
method buildJarUrl (line 20) | @Contract(pure = true)
method buildJarUrl (line 26) | @Contract(pure = true)
method buildLibraryPath (line 32) | public static @NotNull Path buildLibraryPath(@NotNull Path installDir,...
method fakeContext (line 41) | public static void fakeContext(@NotNull Path dirToInstall, String vani...
method removeFakeContext (line 62) | public static void removeFakeContext(@NotNull Path dirToInstall) throw...
method parseNewVersionInfo (line 68) | public static @NotNull List<ParsedLibrary> parseNewVersionInfo(Path in...
FILE: src/main/java/fr/flowarg/flowupdater/versions/ModLoaderVersionBuilder.java
class ModLoaderVersionBuilder (line 15) | @SuppressWarnings("unchecked")
method withMods (line 30) | public B withMods(List<Mod> mods)
method withMods (line 41) | public B withMods(Mod... mods)
method withMods (line 51) | public B withMods(URL jsonUrl)
method withMods (line 61) | public B withMods(String jsonUrl)
method withCurseMods (line 71) | public B withCurseMods(Collection<CurseFileInfo> curseMods)
method withCurseMods (line 82) | public B withCurseMods(CurseFileInfo... curseMods)
method withCurseMods (line 92) | public B withCurseMods(URL jsonUrl)
method withCurseMods (line 102) | public B withCurseMods(String jsonUrl)
method withModrinthMods (line 112) | public B withModrinthMods(Collection<ModrinthVersionInfo> modrinthMods)
method withModrinthMods (line 123) | public B withModrinthMods(ModrinthVersionInfo... modrinthMods)
method withModrinthMods (line 133) | public B withModrinthMods(URL jsonUrl)
method withModrinthMods (line 143) | public B withModrinthMods(String jsonUrl)
method withCurseModPack (line 153) | public B withCurseModPack(CurseModPackInfo modPackInfo)
method withModrinthModPack (line 164) | public B withModrinthModPack(ModrinthModPackInfo modPackInfo)
method withFileDeleter (line 175) | public B withFileDeleter(ModFileDeleter fileDeleter)
method build (line 181) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/versions/ParsedLibrary.java
class ParsedLibrary (line 10) | public class ParsedLibrary
method ParsedLibrary (line 17) | public ParsedLibrary(Path path, URL url, String artifact, boolean inst...
method download (line 25) | public void download(ILogger logger)
method getPath (line 31) | public Path getPath()
method getUrl (line 36) | public Optional<URL> getUrl()
method getArtifact (line 41) | public String getArtifact()
method isInstalled (line 46) | public boolean isInstalled()
FILE: src/main/java/fr/flowarg/flowupdater/versions/VanillaVersion.java
class VanillaVersion (line 26) | public class VanillaVersion
method VanillaVersion (line 46) | private VanillaVersion(String name, MCP mcp,
method getMinecraftLibrariesJson (line 66) | public JsonArray getMinecraftLibrariesJson()
method getMinecraftClient (line 75) | public JsonObject getMinecraftClient()
method getMinecraftAssetIndex (line 99) | public JsonObject getMinecraftAssetIndex()
method getJsonVersion (line 107) | private InputStream getJsonVersion()
method getName (line 151) | public @NotNull String getName()
method getMCP (line 160) | public MCP getMCP()
method isSnapshot (line 169) | public boolean isSnapshot()
method getCustomAssetIndex (line 178) | public AssetIndex getCustomAssetIndex()
method getAnotherAssets (line 187) | public List<AssetDownloadable> getAnotherAssets()
method getAnotherLibraries (line 196) | public List<Downloadable> getAnotherLibraries()
method getJsonURL (line 205) | public String getJsonURL()
class VanillaVersionBuilder (line 214) | public static class VanillaVersionBuilder implements IBuilder<VanillaV...
method withName (line 229) | public VanillaVersionBuilder withName(String name)
method withMCP (line 240) | public VanillaVersionBuilder withMCP(MCP mcp)
method withMCP (line 251) | public VanillaVersionBuilder withMCP(URL mcpJsonUrl)
method withMCP (line 261) | public VanillaVersionBuilder withMCP(String mcpJsonUrl)
method withSnapshot (line 271) | public VanillaVersionBuilder withSnapshot(boolean snapshot)
method withCustomAssetIndex (line 282) | public VanillaVersionBuilder withCustomAssetIndex(AssetIndex assetIn...
method withAnotherAssets (line 293) | public VanillaVersionBuilder withAnotherAssets(Collection<AssetDownl...
method withAnotherAssets (line 304) | public VanillaVersionBuilder withAnotherAssets(AssetDownloadable... ...
method withAnotherLibraries (line 314) | public VanillaVersionBuilder withAnotherLibraries(Collection<Downloa...
method withAnotherLibraries (line 325) | public VanillaVersionBuilder withAnotherLibraries(Downloadable... an...
method withCustomVersionJson (line 335) | public VanillaVersionBuilder withCustomVersionJson(JsonObject custom...
method withCustomVersionJson (line 346) | public VanillaVersionBuilder withCustomVersionJson(URL customVersion...
method build (line 357) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/versions/fabric/FabricBasedVersion.java
class FabricBasedVersion (line 23) | public abstract class FabricBasedVersion extends AbstractModLoaderVersion
method FabricBasedVersion (line 28) | public FabricBasedVersion(String modLoaderVersion, List<Mod> mods, Lis...
method isModLoaderAlreadyInstalled (line 36) | @Override
method install (line 57) | @Override
method parseLibraries (line 78) | protected List<ParsedLibrary> parseLibraries(Path versionJsonFile, Pat...
method getSha1FromLibrary (line 106) | protected Callable<String> getSha1FromLibrary(@NotNull JsonObject libr...
FILE: src/main/java/fr/flowarg/flowupdater/versions/fabric/FabricVersion.java
class FabricVersion (line 13) | public class FabricVersion extends FabricBasedVersion
method FabricVersion (line 25) | FabricVersion(String fabricVersion, List<Mod> mods, List<CurseFileInfo...
method attachFlowUpdater (line 32) | @Override
method name (line 39) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/versions/fabric/FabricVersionBuilder.java
class FabricVersionBuilder (line 9) | public class FabricVersionBuilder extends ModLoaderVersionBuilder<Fabric...
method withFabricVersion (line 26) | public FabricVersionBuilder withFabricVersion(String fabricVersion)
method withOptiFine (line 37) | public FabricVersionBuilder withOptiFine(OptiFineInfo optiFineInfo)
method build (line 48) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/versions/fabric/QuiltVersion.java
class QuiltVersion (line 13) | public class QuiltVersion extends FabricBasedVersion
method QuiltVersion (line 25) | QuiltVersion(String quiltVersion, List<Mod> mods, List<CurseFileInfo> ...
method attachFlowUpdater (line 32) | @Override
method name (line 39) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/versions/fabric/QuiltVersionBuilder.java
class QuiltVersionBuilder (line 9) | public class QuiltVersionBuilder extends ModLoaderVersionBuilder<QuiltVe...
method withQuiltVersion (line 23) | public QuiltVersionBuilder withQuiltVersion(String quiltVersion)
method withOptiFine (line 34) | public QuiltVersionBuilder withOptiFine(OptiFineInfo optiFineInfo)
method build (line 45) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/versions/forge/ForgeVersion.java
class ForgeVersion (line 28) | public class ForgeVersion extends AbstractModLoaderVersion implements IO...
method ForgeVersion (line 35) | public ForgeVersion(String modLoaderVersion, List<Mod> mods, List<Curs...
method isModLoaderAlreadyInstalled (line 75) | @Override
method getMcpVersion (line 184) | private String getMcpVersion(@NotNull JsonObject object)
method isSlimOrExtraSha1Wrong (line 197) | private boolean isSlimOrExtraSha1Wrong(Path extraJar, Path extraJarCac...
method getSha1FromLibrary (line 231) | private @NotNull Callable<String> getSha1FromLibrary(@NotNull JsonObje...
method install (line 245) | @Override
method useInstaller (line 330) | private void useInstaller(Path installDir, @NotNull Path installerFile...
method parseOldVersionInfo (line 361) | private @NotNull List<ParsedLibrary> parseOldVersionInfo(Path installD...
method getOptiFineInfo (line 395) | @Override
method name (line 401) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/versions/forge/ForgeVersionBuilder.java
class ForgeVersionBuilder (line 12) | public class ForgeVersionBuilder extends ModLoaderVersionBuilder<ForgeVe...
method withForgeVersion (line 23) | public ForgeVersionBuilder withForgeVersion(String forgeVersion)
method withOptiFine (line 34) | public ForgeVersionBuilder withOptiFine(OptiFineInfo optiFineInfo)
method build (line 40) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/versions/neoforge/NeoForgeVersion.java
class NeoForgeVersion (line 21) | public class NeoForgeVersion extends AbstractModLoaderVersion
method NeoForgeVersion (line 26) | NeoForgeVersion(String modLoaderVersion, List<Mod> mods, List<CurseFil...
method isModLoaderAlreadyInstalled (line 44) | @Override
method install (line 91) | @Override
method name (line 137) | @Override
FILE: src/main/java/fr/flowarg/flowupdater/versions/neoforge/NeoForgeVersionBuilder.java
class NeoForgeVersionBuilder (line 8) | public class NeoForgeVersionBuilder extends ModLoaderVersionBuilder<NeoF...
method withNeoForgeVersion (line 19) | public NeoForgeVersionBuilder withNeoForgeVersion(String neoForgeVersion)
method withOptiFine (line 30) | public NeoForgeVersionBuilder withOptiFine(OptiFineInfo optiFineInfo)
method build (line 36) | @Override
FILE: src/test/java/fr/flowarg/flowupdater/IntegrationTests.java
class IntegrationTests (line 14) | @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
method setup (line 19) | @BeforeAll
method cleanup (line 26) | @AfterAll
method testWithVanillaUsage (line 32) | @Order(1)
method testWithNewForgeUsage (line 40) | @Order(2)
method testWithVeryOldForgeUsage (line 53) | @Order(3)
method testWithOldForgeUsage (line 66) | @Order(4)
method testWithFabric (line 79) | @Order(5)
method testWithQuilt (line 90) | @Order(6)
method testWithFabric119 (line 107) | @Order(6)
method testWithNeoForgeUsage (line 117) | @Order(8)
method basicAssertions (line 129) | private void basicAssertions(Path updateDir, boolean error, String ver...
method basicAssertions (line 134) | private void basicAssertions(Path updateDir, boolean error, String ver...
FILE: src/test/java/fr/flowarg/flowupdater/Updates.java
class Updates (line 16) | public class Updates
method vanillaUsage (line 20) | public static Result vanillaUsage()
method testWithNewForgeUsage (line 48) | public static Result testWithNewForgeUsage()
method testWithVeryOldForgeUsage (line 82) | public static Result testWithVeryOldForgeUsage()
method testWithOldForgeUsage (line 116) | public static Result testWithOldForgeUsage()
method testWithFabric (line 149) | public static Result testWithFabric()
method testWithQuilt (line 184) | public static Result testWithQuilt(UpdaterOptions opts)
method testWithFabric119 (line 220) | public static Result testWithFabric119()
method testWithNeoForgeUsage (line 255) | public static Result testWithNeoForgeUsage()
method testWithNeoForgeUsage2 (line 288) | public static Result testWithNeoForgeUsage2()
class Result (line 321) | public static class Result
method Result (line 328) | public Result(Path updateDir, boolean error, String version, String ...
method Result (line 336) | public Result(Path updateDir, boolean error, String version)
method testWithLast1122Forge (line 345) | public static Result testWithLast1122Forge()
method testWith121Forge (line 379) | public static Result testWith121Forge()
FILE: src/test/java/fr/flowarg/flowupdater/utils/VersionTest.java
class VersionTest (line 9) | public class VersionTest
method testVersionCompareWithSameSize (line 11) | @Test
method testVersionCompareWithDifferentSize (line 30) | @Test
method testVersionBetween (line 49) | @Test
method testVersionEmpty (line 62) | @Test
FILE: src/test/java/fr/flowarg/flowupdater/utils/builderapi/BuilderAPITest.java
class BuilderAPITest (line 9) | public class BuilderAPITest
method shouldFailBecauseMissingRequiredArgument (line 11) | @Test
method shouldWorkBecauseRequiredArgumentIsFilled (line 17) | @Test
method shouldFailBecauseOfBadObject (line 24) | @Test
method shouldFailBecauseUndefinedParentArgument (line 30) | @Test
method shouldWorkBecauseDefinedParentArgument (line 36) | @Test
class TestObject (line 44) | private static class TestObject
method TestObject (line 49) | public TestObject(String str, int anInt)
class AnotherTestObject (line 56) | private static class AnotherTestObject
method AnotherTestObject (line 61) | public AnotherTestObject(boolean aBoolean, boolean anotherBoolean)
class TestBuilder (line 68) | private static class TestBuilder implements IBuilder<TestObject>
method withAnArgument (line 73) | public TestBuilder withAnArgument(String anArgument)
method withAnInt (line 79) | public TestBuilder withAnInt(int anInt)
method build (line 85) | @Contract(" -> new")
class AnotherTestBuilder (line 96) | private static class AnotherTestBuilder implements IBuilder<AnotherTes...
method withABoolean (line 101) | public AnotherTestBuilder withABoolean(boolean aBoolean)
method withAnotherBoolean (line 107) | public AnotherTestBuilder withAnotherBoolean(boolean anotherBoolean)
method build (line 113) | @Contract(" -> new")
Condensed preview — 89 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (329K chars).
[
{
"path": ".gitattributes",
"chars": 160,
"preview": "#\r\n# https://help.github.com/articles/dealing-with-line-endings/\r\n#\r\n# These are explicitly windows files and should use"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 647,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: 'BUG :'\nlabels: bug\nassignees: FlowArg\n\n---\n\n**Des"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 630,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: 'Feature Request :'\nlabels: feature request\nass"
},
{
"path": ".github/workflows/docs.yml",
"chars": 599,
"preview": "name: CI Documentation\n\non:\n push:\n branches: [ master ]\n\njobs:\n docs:\n runs-on: ubuntu-latest\n permissions:\n"
},
{
"path": ".github/workflows/gradle-ci.yml",
"chars": 522,
"preview": "name: Gradle CI\n\non:\n push:\n branches: [ master ]\n\njobs:\n testjava:\n strategy:\n matrix:\n jdk: [17, 2"
},
{
"path": ".github/workflows/gradle-publish.yml",
"chars": 867,
"preview": "name: Gradle Package\n\non:\n release:\n types: [published]\n\njobs:\n build:\n runs-on: ubuntu-latest\n permissions:\n"
},
{
"path": ".gitignore",
"chars": 165,
"preview": ".gradle\nbuild\nupdater\nrun\nsrc/test/java/fr/flowarg/flowupdatertest\n.idea\nout\nforge-installer.jar.log\nforge-installer-pat"
},
{
"path": "LICENSE",
"chars": 35821,
"preview": " GNU GENERAL PUBLIC LICENSE\r\n Version 3, 29 June 2007\r\n\r\n Copyright (C) 2007 Fr"
},
{
"path": "README.MD",
"chars": 7800,
"preview": "[version]: https://img.shields.io/maven-central/v/fr.flowarg/flowupdater.svg?label=Download\r\n[download]: https://search."
},
{
"path": "build.gradle",
"chars": 2708,
"preview": "plugins {\r\n id 'java-library'\r\n id 'idea'\r\n id 'maven-publish'\r\n id 'signing'\r\n}\r\n\r\ngroup = 'fr.flowarg'\r\nve"
},
{
"path": "flowupdater-schema.json",
"chars": 5339,
"preview": "{\n \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n \"description\": \"\",\n \"type\": \"object\",\n \"properties"
},
{
"path": "gradle/libs.versions.toml",
"chars": 520,
"preview": "[versions]\ngson = \"2.13.2\"\nflowmultitools = \"1.4.5\"\n\nannotations = \"26.1.0\"\n\noll = \"3.2.11\"\njunit = \"6.0.3\"\n\n[libraries]"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 202,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
},
{
"path": "gradle.properties",
"chars": 36,
"preview": "org.gradle.configuration-cache=true\n"
},
{
"path": "gradlew",
"chars": 5766,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2763,
"preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
},
{
"path": "settings.gradle",
"chars": 32,
"preview": "rootProject.name = 'FlowUpdater'"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/FlowUpdater.java",
"chars": 18763,
"preview": "package fr.flowarg.flowupdater;\r\n\r\nimport fr.flowarg.flowio.FileUtils;\r\nimport fr.flowarg.flowlogger.ILogger;\r\nimport fr"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/DownloadList.java",
"chars": 6593,
"preview": "package fr.flowarg.flowupdater.download;\n\nimport fr.flowarg.flowupdater.download.json.AssetDownloadable;\nimport fr.flowa"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/IProgressCallback.java",
"chars": 1076,
"preview": "package fr.flowarg.flowupdater.download;\n\nimport fr.flowarg.flowlogger.ILogger;\nimport fr.flowarg.flowupdater.FlowUpdate"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/Step.java",
"chars": 832,
"preview": "package fr.flowarg.flowupdater.download;\n\nimport fr.flowarg.flowupdater.FlowUpdater;\n\n/**\n * Represent each step of a Mi"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/VanillaDownloader.java",
"chars": 8346,
"preview": "package fr.flowarg.flowupdater.download;\r\n\r\nimport fr.flowarg.flowio.FileUtils;\r\nimport fr.flowarg.flowlogger.ILogger;\r\n"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/VanillaReader.java",
"chars": 9277,
"preview": "package fr.flowarg.flowupdater.download;\r\n\r\nimport com.google.gson.GsonBuilder;\r\nimport com.google.gson.JsonElement;\r\nim"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/AssetDownloadable.java",
"chars": 2017,
"preview": "package fr.flowarg.flowupdater.download.json;\r\n\r\n/**\r\n * This class represents an asset.\r\n */\r\npublic class AssetDownloa"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/AssetIndex.java",
"chars": 748,
"preview": "package fr.flowarg.flowupdater.download.json;\r\n\r\nimport java.util.Collections;\r\nimport java.util.LinkedHashMap;\r\nimport "
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/CurseFileInfo.java",
"chars": 2827,
"preview": "package fr.flowarg.flowupdater.download.json;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonObject;\nimpo"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/CurseModPackInfo.java",
"chars": 2274,
"preview": "package fr.flowarg.flowupdater.download.json;\n\n/**\n * This class represents a mod pack file in the CurseForge API.\n */\np"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/Downloadable.java",
"chars": 1890,
"preview": "package fr.flowarg.flowupdater.download.json;\r\n\r\nimport java.util.Objects;\r\n\r\n/**\r\n * This class represents a classic do"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/ExternalFile.java",
"chars": 4731,
"preview": "package fr.flowarg.flowupdater.download.json;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonObject;\nimpo"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/MCP.java",
"chars": 2372,
"preview": "package fr.flowarg.flowupdater.download.json;\n\nimport com.google.gson.JsonObject;\nimport fr.flowarg.flowupdater.utils.Fl"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/Mod.java",
"chars": 3401,
"preview": "package fr.flowarg.flowupdater.download.json;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimp"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/ModrinthModPackInfo.java",
"chars": 1035,
"preview": "package fr.flowarg.flowupdater.download.json;\n\npublic class ModrinthModPackInfo extends ModrinthVersionInfo\n{\n privat"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/ModrinthVersionInfo.java",
"chars": 2733,
"preview": "package fr.flowarg.flowupdater.download.json;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimp"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/OptiFineInfo.java",
"chars": 1101,
"preview": "package fr.flowarg.flowupdater.download.json;\n\n/**\n * This class represents an OptiFineInfo object.\n */\npublic class Opt"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/json/package-info.java",
"chars": 125,
"preview": "/**\n * This package contains some objects that can be/are parsed as a JSON.\n */\npackage fr.flowarg.flowupdater.download."
},
{
"path": "src/main/java/fr/flowarg/flowupdater/download/package-info.java",
"chars": 107,
"preview": "/**\n * This package contains some things about download stuff.\n */\npackage fr.flowarg.flowupdater.download;"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/Integration.java",
"chars": 983,
"preview": "package fr.flowarg.flowupdater.integrations;\n\nimport fr.flowarg.flowlogger.ILogger;\n\nimport java.nio.file.Files;\nimport "
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/IntegrationManager.java",
"chars": 7971,
"preview": "package fr.flowarg.flowupdater.integrations;\n\nimport fr.flowarg.flowio.FileUtils;\nimport fr.flowarg.flowlogger.ILogger;\n"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/CurseForgeIntegration.java",
"chars": 13855,
"preview": "package fr.flowarg.flowupdater.integrations.curseforgeintegration;\n\nimport com.google.gson.*;\nimport fr.flowarg.flowio.F"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/CurseModPack.java",
"chars": 2033,
"preview": "package fr.flowarg.flowupdater.integrations.curseforgeintegration;\n\nimport fr.flowarg.flowupdater.download.json.Mod;\nimp"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/ICurseForgeCompatible.java",
"chars": 808,
"preview": "package fr.flowarg.flowupdater.integrations.curseforgeintegration;\n\nimport fr.flowarg.flowupdater.download.json.CurseFil"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/curseforgeintegration/package-info.java",
"chars": 109,
"preview": "/**\n * CurseForge Integration package.\n */\npackage fr.flowarg.flowupdater.integrations.curseforgeintegration;"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/IModrinthCompatible.java",
"chars": 1074,
"preview": "package fr.flowarg.flowupdater.integrations.modrinthintegration;\n\nimport fr.flowarg.flowupdater.download.json.Mod;\nimpor"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/ModrinthIntegration.java",
"chars": 8614,
"preview": "package fr.flowarg.flowupdater.integrations.modrinthintegration;\n\nimport com.google.gson.JsonArray;\nimport com.google.gs"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/ModrinthModPack.java",
"chars": 1731,
"preview": "package fr.flowarg.flowupdater.integrations.modrinthintegration;\n\nimport fr.flowarg.flowupdater.download.json.Mod;\n\nimpo"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/modrinthintegration/package-info.java",
"chars": 105,
"preview": "/**\n * Modrinth Integration package.\n */\npackage fr.flowarg.flowupdater.integrations.modrinthintegration;"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/IOptiFineCompatible.java",
"chars": 306,
"preview": "package fr.flowarg.flowupdater.integrations.optifineintegration;\n\nimport fr.flowarg.flowupdater.download.json.OptiFineIn"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/OptiFine.java",
"chars": 614,
"preview": "package fr.flowarg.flowupdater.integrations.optifineintegration;\n\n/**\n * This class represents a basic OptiFine object.\n"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/OptiFineIntegration.java",
"chars": 4421,
"preview": "package fr.flowarg.flowupdater.integrations.optifineintegration;\n\nimport fr.flowarg.flowlogger.ILogger;\nimport fr.flowar"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/optifineintegration/package-info.java",
"chars": 105,
"preview": "/**\n * OptiFine Integration package.\n */\npackage fr.flowarg.flowupdater.integrations.optifineintegration;"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/integrations/package-info.java",
"chars": 80,
"preview": "/**\n * Integration API package.\n */\npackage fr.flowarg.flowupdater.integrations;"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/package-info.java",
"chars": 159,
"preview": "/**\n * Main package of FlowUpdater. The only class contained in this package is {@link fr.flowarg.flowupdater.FlowUpdate"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/ExternalFileDeleter.java",
"chars": 1309,
"preview": "package fr.flowarg.flowupdater.utils;\n\nimport fr.flowarg.flowio.FileUtils;\nimport fr.flowarg.flowupdater.download.Downlo"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/FlowUpdaterException.java",
"chars": 923,
"preview": "package fr.flowarg.flowupdater.utils;\n\n/**\n * A simple runtime exception class that represents a fatal FlowUpdater error"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/IFileDeleter.java",
"chars": 149,
"preview": "package fr.flowarg.flowupdater.utils;\n\n/**\n * Just a marker class that is extended by all file deleter classes.\n */\npubl"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/IOUtils.java",
"chars": 9184,
"preview": "package fr.flowarg.flowupdater.utils;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonNull;\nimport com.g"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/ModFileDeleter.java",
"chars": 4988,
"preview": "package fr.flowarg.flowupdater.utils;\n\nimport fr.flowarg.flowio.FileUtils;\nimport fr.flowarg.flowlogger.ILogger;\nimport "
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/UpdaterOptions.java",
"chars": 5938,
"preview": "package fr.flowarg.flowupdater.utils;\n\nimport fr.flowarg.flowupdater.utils.builderapi.BuilderArgument;\nimport fr.flowarg"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/Version.java",
"chars": 2308,
"preview": "package fr.flowarg.flowupdater.utils;\n\nimport org.jetbrains.annotations.Contract;\nimport org.jetbrains.annotations.NotNu"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/VersionChecker.java",
"chars": 1117,
"preview": "package fr.flowarg.flowupdater.utils;\n\nimport fr.flowarg.flowlogger.ILogger;\nimport fr.flowarg.flowupdater.FlowUpdater;\n"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/builderapi/BuilderArgument.java",
"chars": 3815,
"preview": "package fr.flowarg.flowupdater.utils.builderapi;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.function.S"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/builderapi/BuilderException.java",
"chars": 606,
"preview": "package fr.flowarg.flowupdater.utils.builderapi;\n\n/**\n * Builder API; This exception is thrown when an error occurred wi"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/builderapi/IBuilder.java",
"chars": 416,
"preview": "package fr.flowarg.flowupdater.utils.builderapi;\n\n/**\n * Builder API ; Builder interface.\n * @version 1.6\n * @author flo"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/builderapi/package-info.java",
"chars": 80,
"preview": "/**\n * Builder API package.\n */\npackage fr.flowarg.flowupdater.utils.builderapi;"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/utils/package-info.java",
"chars": 65,
"preview": "/**\n * Utility package.\n */\npackage fr.flowarg.flowupdater.utils;"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/AbstractModLoaderVersion.java",
"chars": 5900,
"preview": "package fr.flowarg.flowupdater.versions;\n\nimport fr.flowarg.flowlogger.ILogger;\nimport fr.flowarg.flowupdater.FlowUpdate"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/IModLoaderVersion.java",
"chars": 3396,
"preview": "package fr.flowarg.flowupdater.versions;\n\nimport fr.flowarg.flowlogger.ILogger;\nimport fr.flowarg.flowupdater.FlowUpdate"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/ModLoaderUtils.java",
"chars": 3812,
"preview": "package fr.flowarg.flowupdater.versions;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport c"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/ModLoaderVersionBuilder.java",
"chars": 5914,
"preview": "package fr.flowarg.flowupdater.versions;\n\nimport fr.flowarg.flowupdater.download.json.*;\nimport fr.flowarg.flowupdater.u"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/ParsedLibrary.java",
"chars": 1027,
"preview": "package fr.flowarg.flowupdater.versions;\n\nimport fr.flowarg.flowlogger.ILogger;\nimport fr.flowarg.flowupdater.utils.IOUt"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/VanillaVersion.java",
"chars": 12821,
"preview": "package fr.flowarg.flowupdater.versions;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport c"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/fabric/FabricBasedVersion.java",
"chars": 4629,
"preview": "package fr.flowarg.flowupdater.versions.fabric;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\ni"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/fabric/FabricVersion.java",
"chars": 1652,
"preview": "package fr.flowarg.flowupdater.versions.fabric;\n\nimport fr.flowarg.flowupdater.FlowUpdater;\nimport fr.flowarg.flowupdate"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/fabric/FabricVersionBuilder.java",
"chars": 2300,
"preview": "package fr.flowarg.flowupdater.versions.fabric;\n\nimport fr.flowarg.flowupdater.download.json.OptiFineInfo;\nimport fr.flo"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/fabric/QuiltVersion.java",
"chars": 1640,
"preview": "package fr.flowarg.flowupdater.versions.fabric;\n\nimport fr.flowarg.flowupdater.FlowUpdater;\nimport fr.flowarg.flowupdate"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/fabric/QuiltVersionBuilder.java",
"chars": 2254,
"preview": "package fr.flowarg.flowupdater.versions.fabric;\n\nimport fr.flowarg.flowupdater.download.json.OptiFineInfo;\nimport fr.flo"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/fabric/package-info.java",
"chars": 180,
"preview": "/**\n * This package contains all the classes that are used to install Fabric-based mod loaders (Fabric and Quilt at the "
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/forge/ForgeVersion.java",
"chars": 18686,
"preview": "package fr.flowarg.flowupdater.versions.forge;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nim"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/forge/ForgeVersionBuilder.java",
"chars": 2102,
"preview": "package fr.flowarg.flowupdater.versions.forge;\n\nimport fr.flowarg.flowupdater.download.json.OptiFineInfo;\nimport fr.flow"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/forge/package-info.java",
"chars": 127,
"preview": "/**\n * This package contains all the classes that are used to install Forge.\n */\npackage fr.flowarg.flowupdater.versions"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/neoforge/NeoForgeVersion.java",
"chars": 5686,
"preview": "package fr.flowarg.flowupdater.versions.neoforge;\n\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParser;"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/neoforge/NeoForgeVersionBuilder.java",
"chars": 1988,
"preview": "package fr.flowarg.flowupdater.versions.neoforge;\n\nimport fr.flowarg.flowupdater.download.json.OptiFineInfo;\nimport fr.f"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/neoforge/package-info.java",
"chars": 133,
"preview": "/**\n * This package contains all the classes that are used to install NeoForge.\n */\npackage fr.flowarg.flowupdater.versi"
},
{
"path": "src/main/java/fr/flowarg/flowupdater/versions/package-info.java",
"chars": 116,
"preview": "/**\n * This package contains all common classes to the versions system.\n */\npackage fr.flowarg.flowupdater.versions;"
},
{
"path": "src/test/java/fr/flowarg/flowupdater/IntegrationTests.java",
"chars": 6114,
"preview": "package fr.flowarg.flowupdater;\n\nimport fr.flowarg.flowio.FileUtils;\nimport fr.flowarg.flowupdater.utils.UpdaterOptions;"
},
{
"path": "src/test/java/fr/flowarg/flowupdater/Updates.java",
"chars": 12769,
"preview": "package fr.flowarg.flowupdater;\n\nimport fr.flowarg.flowupdater.utils.UpdaterOptions;\nimport fr.flowarg.flowupdater.versi"
},
{
"path": "src/test/java/fr/flowarg/flowupdater/utils/VersionTest.java",
"chars": 2374,
"preview": "package fr.flowarg.flowupdater.utils;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\n\nimport static org.j"
},
{
"path": "src/test/java/fr/flowarg/flowupdater/utils/builderapi/BuilderAPITest.java",
"chars": 3831,
"preview": "package fr.flowarg.flowupdater.utils.builderapi;\n\nimport org.jetbrains.annotations.Contract;\nimport org.jetbrains.annota"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the FlowArg/FlowUpdater GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 89 files (300.9 KB), approximately 69.6k tokens, and a symbol index with 492 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.