Add the base files to the Launcher Git.
198
LICENSE.txt
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser 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
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
The following license also applies to contributions by third parties:
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* The names of its contributors may not be used to endorse or promote
|
||||||
|
products derived from this software without specific prior
|
||||||
|
written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||||
|
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
92
README.md
|
|
@ -1,3 +1,91 @@
|
||||||
# StardustLauncher-Files
|
SKCraft Launcher
|
||||||
|
================
|
||||||
|
|
||||||
Files for the StardustLauncher
|

|
||||||
|
|
||||||
|
[](https://liberapay.com/offbeatwitch/donate)
|
||||||
|
|
||||||
|
*If you rely on this software, please consider [donating](https://liberapay.com/offbeatwitch/donate) to help keep it maintained!*
|
||||||
|
|
||||||
|
Need to make it easy for people to install and play your modpack? The SKCraft Launcher platform may be for you if:
|
||||||
|
|
||||||
|
:heavy_check_mark: You want your own logo and branding on the launcher, with your own news feed
|
||||||
|
:heavy_check_mark: You want to be able to update the modpack whenever you want
|
||||||
|
:heavy_check_mark: You don't want to depend on anyone else
|
||||||
|
:heavy_check_mark: There's several modpacks you want to provide as a set
|
||||||
|
:heavy_check_mark: You want to provide 'hidden' modpacks that are only accessible by a secret code
|
||||||
|
|
||||||
|
It may not be for you if:
|
||||||
|
|
||||||
|
:small_orange_diamond: You made a cool modpack and you just want it out there
|
||||||
|
:small_orange_diamond: You don't have a website or place for people to download files from
|
||||||
|
:small_orange_diamond: You don't want anything to do with distributing the launcher
|
||||||
|
|
||||||
|
## What We Do Right
|
||||||
|
|
||||||
|
Creating modpacks with this launcher is super easy: :one: only drag & drop of configs and mods into a folder, :two: ZERO! scary XML/JSON files to edit, and :three: there's a nice interface for creating modpacks (command-line is also available).
|
||||||
|
|
||||||
|
The launcher has all standard features that you'd expect like :one: resume of incomplete downloads, :two: incremental updates, and :three: file deduplication (saves disk space when you have files duplicated between updates or modpacks).
|
||||||
|
|
||||||
|
LiteLoader, Forge, Fabric, and custom .jar mods are supported. You can put resource packs in, or really even random mod files that don't go in the configs or mods folder.
|
||||||
|
|
||||||
|
You do need some sort of website, but it does **not** need anything complicated like PHP.
|
||||||
|
|
||||||
|
For those technically inclined, you can generate modpacks from Git (or any VCS) using your favorite continuous integration server, and then dynamically create modpacks even down to one per branch or tag.
|
||||||
|
|
||||||
|
Best of all, our launcher is Minecraft's oldest surviving modpack launcher platform. :thumbsup:
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
You'll probably need a few hours to get everything working. Fortunately, we have a [detailed wiki](https://github.com/SKCraft/Launcher/wiki) with screenshots for every step, intended for someone with minimal technical experience.
|
||||||
|
|
||||||
|
For your evaluation, there are sample files provided that you will be able to upload to your site to see if it even works for you.
|
||||||
|
|
||||||
|
## Pretty Pictures
|
||||||
|
|
||||||
|
If you are going to create modpacks with our GUI tool, you'll be seeing this beaut:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Making a modpack is just drag and drop into a folder like this:
|
||||||
|
|
||||||
|
* src/**config**/
|
||||||
|
* src/**mods**/
|
||||||
|
* src/**resourcepacks**/
|
||||||
|
* loaders/
|
||||||
|
|
||||||
|
LiteLoader and Forge installers, or Fabric Loader, go into the *loaders* folder. :ok_hand:
|
||||||
|
|
||||||
|
### More Pictures
|
||||||
|
|
||||||
|
There's a dark version of the launcher (personally not the creator's cup of tea) that you can use:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Users can select optional mods/features:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The launcher can update itself and you can use it in portable mode too.
|
||||||
|
|
||||||
|
### The Light Theme
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Pull requests can be submitted on GitHub, but we will accept them at our discretion. Please note that your code must follow Oracle's Java Code Conventions.
|
||||||
|
|
||||||
|
Contributions by third parties must be dual licensed under the two licenses described within LICENSE.txt (GNU Lesser General Public License, version 3, and the 3-clause BSD license).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The launcher is licensed under the GNU Lesser General Public License, version 3.
|
||||||
|
|
|
||||||
3
build.bat
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
@echo off
|
||||||
|
call gradlew clean build
|
||||||
|
pause
|
||||||
75
build.gradle
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
plugins {
|
||||||
|
id "com.github.johnrengelman.shadow" version "7.1.2"
|
||||||
|
id 'io.freefair.lombok' version '5.3.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
println """
|
||||||
|
*******************************************
|
||||||
|
You are building SKCraft Launcher!
|
||||||
|
|
||||||
|
Output files will be in [subproject]/build/libs
|
||||||
|
*******************************************
|
||||||
|
"""
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
apply plugin: 'java'
|
||||||
|
|
||||||
|
group = 'com.skcraft'
|
||||||
|
version = '4.6-SNAPSHOT'
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion.set(JavaLanguageVersion.of(8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JavaVersion.current().isJava8Compatible()) {
|
||||||
|
// Java 8 turns on doclint which we fail
|
||||||
|
tasks.withType(Javadoc) {
|
||||||
|
options.addStringOption('Xdoclint:none', '-quiet')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaExec) {
|
||||||
|
workingDir = new File(rootDir, "run/")
|
||||||
|
workingDir.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work around gradle shadow bug
|
||||||
|
// see https://github.com/johnrengelman/shadow/issues/713
|
||||||
|
afterEvaluate {
|
||||||
|
startScripts {
|
||||||
|
dependsOn(shadowJar)
|
||||||
|
}
|
||||||
|
|
||||||
|
distTar {
|
||||||
|
dependsOn(shadowJar)
|
||||||
|
}
|
||||||
|
|
||||||
|
distZip {
|
||||||
|
dependsOn(shadowJar)
|
||||||
|
}
|
||||||
|
|
||||||
|
startShadowScripts {
|
||||||
|
dependsOn(jar)
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowDistTar {
|
||||||
|
dependsOn(jar)
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowDistZip {
|
||||||
|
dependsOn(jar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean {
|
||||||
|
subprojects {
|
||||||
|
rootProject.clean.dependsOn tasks.matching { it.name == "clean" }
|
||||||
|
}
|
||||||
|
}
|
||||||
3
build.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
./gradlew clean build
|
||||||
|
read -p "Press any key to continue..."
|
||||||
33
creator-tools/build.gradle
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
plugins {
|
||||||
|
id 'application'
|
||||||
|
id "com.github.johnrengelman.shadow"
|
||||||
|
id 'io.freefair.lombok'
|
||||||
|
}
|
||||||
|
|
||||||
|
version = "2.1.0-SNAPSHOT"
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClassName = "com.skcraft.launcher.creator.Creator"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(path: ':launcher-builder')
|
||||||
|
implementation 'org.eclipse.jetty:jetty-server:9.3.1.v20150714'
|
||||||
|
implementation 'com.jidesoft:jide-oss:3.6.18'
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
filesMatching('**/*.properties') {
|
||||||
|
filter {
|
||||||
|
it.replace('${project.version}', project.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
archiveClassifier.set("")
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
dependsOn(shadowJar)
|
||||||
|
}
|
||||||
2
creator-tools/lombok.config
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# This file is generated by the 'io.freefair.lombok' Gradle plugin
|
||||||
|
config.stopBubbling = true
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import com.skcraft.launcher.Launcher;
|
||||||
|
import com.skcraft.launcher.creator.controller.WelcomeController;
|
||||||
|
import com.skcraft.launcher.creator.dialog.WelcomeDialog;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.CreatorConfig;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.RecentEntry;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Workspace;
|
||||||
|
import com.skcraft.launcher.persistence.Persistence;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.filechooser.FileSystemView;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public class Creator {
|
||||||
|
|
||||||
|
@Getter private final File dataDir;
|
||||||
|
@Getter private final CreatorConfig config;
|
||||||
|
@Getter private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
|
||||||
|
|
||||||
|
public Creator() {
|
||||||
|
this.dataDir = getAppDataDir();
|
||||||
|
this.config = Persistence.load(new File(dataDir, "config.json"), CreatorConfig.class);
|
||||||
|
|
||||||
|
// Remove deleted workspaces
|
||||||
|
List<RecentEntry> recentEntries = config.getRecentEntries();
|
||||||
|
Iterator<RecentEntry> it = recentEntries.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
RecentEntry workspace = it.next();
|
||||||
|
if (!Workspace.getWorkspaceFile(workspace.getPath()).exists()) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showWelcome() {
|
||||||
|
WelcomeDialog dialog = new WelcomeDialog();
|
||||||
|
WelcomeController controller = new WelcomeController(dialog, this);
|
||||||
|
controller.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getFileChooseDefaultDir() {
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
FileSystemView fsv = chooser.getFileSystemView();
|
||||||
|
return fsv.getDefaultDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getAppDataDir() {
|
||||||
|
String osName = System.getProperty("os.name").toLowerCase();
|
||||||
|
if (osName.contains("win")) {
|
||||||
|
return new File(getFileChooseDefaultDir(), "SKCraft Modpack Creator");
|
||||||
|
} else {
|
||||||
|
return new File(System.getProperty("user.home"), ".skcraftcreator");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
Launcher.setupLogger();
|
||||||
|
System.setProperty("skcraftLauncher.killWithoutConfirm", "true");
|
||||||
|
|
||||||
|
final Creator creator = new Creator();
|
||||||
|
|
||||||
|
SwingUtilities.invokeAndWait(() -> {
|
||||||
|
SwingHelper.setSwingProperties("Modpack Creator");
|
||||||
|
SwingHelper.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
creator.showWelcome();
|
||||||
|
} catch (Exception e) {
|
||||||
|
SwingHelper.showErrorDialog(null, "Failed to start the modpack creator program.", "Start Error", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
import com.skcraft.concurrency.Deferred;
|
||||||
|
import com.skcraft.concurrency.Deferreds;
|
||||||
|
import com.skcraft.concurrency.SettableProgress;
|
||||||
|
import com.skcraft.launcher.creator.dialog.GenerateListingDialog;
|
||||||
|
import com.skcraft.launcher.creator.dialog.ManifestEntryDialog;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.ManifestEntry;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Workspace;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.ListingType;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.ManifestEntryTableModel;
|
||||||
|
import com.skcraft.launcher.dialog.ProgressDialog;
|
||||||
|
import com.skcraft.launcher.persistence.Persistence;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.util.SwingExecutor;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class GenerateListingController {
|
||||||
|
|
||||||
|
private final GenerateListingDialog dialog;
|
||||||
|
private final Workspace workspace;
|
||||||
|
private final List<ManifestEntry> manifestEntries;
|
||||||
|
private final ListeningExecutorService executor;
|
||||||
|
private final ManifestEntryTableModel manifestTableModel;
|
||||||
|
|
||||||
|
public GenerateListingController(GenerateListingDialog dialog, Workspace workspace, List<ManifestEntry> manifestEntries, ListeningExecutorService executor) {
|
||||||
|
this.dialog = dialog;
|
||||||
|
this.workspace = workspace;
|
||||||
|
this.manifestEntries = manifestEntries;
|
||||||
|
this.executor = executor;
|
||||||
|
|
||||||
|
this.manifestTableModel = new ManifestEntryTableModel(manifestEntries);
|
||||||
|
dialog.getManifestsTable().setModel(manifestTableModel);
|
||||||
|
dialog.getManifestsTableAdjuster().adjustColumns();
|
||||||
|
|
||||||
|
initListeners();
|
||||||
|
|
||||||
|
setListingType(workspace.getPackageListingType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputDir(File dir) {
|
||||||
|
dialog.getDestDirField().setPath(dir.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListingType(ListingType type) {
|
||||||
|
dialog.getListingTypeCombo().setSelectedItem(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ManifestEntry> getManifestFromIndex(int selectedIndex) {
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
ManifestEntry manifest = manifestEntries.get(selectedIndex);
|
||||||
|
if (manifest != null) {
|
||||||
|
return Optional.fromNullable(manifest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ManifestEntry> getSelectedManifest() {
|
||||||
|
JTable table = dialog.getManifestsTable();
|
||||||
|
int selectedIndex = table.getSelectedRow();
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
selectedIndex = table.convertRowIndexToModel(selectedIndex);
|
||||||
|
ManifestEntry manifest = manifestEntries.get(selectedIndex);
|
||||||
|
if (manifest != null) {
|
||||||
|
return Optional.fromNullable(manifest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingHelper.showErrorDialog(dialog, "Please select a modpack from the list.", "Error");
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateManifestEntryInTable(ManifestEntry manifestEntry) {
|
||||||
|
int index = manifestEntries.indexOf(manifestEntry);
|
||||||
|
if (index >= 0) {
|
||||||
|
manifestTableModel.fireTableRowsUpdated(index, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initListeners() {
|
||||||
|
dialog.getManifestsTable().addMouseListener(new MouseAdapter() {
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
JTable table = (JTable) e.getSource();
|
||||||
|
Point point = e.getPoint();
|
||||||
|
int selectedIndex = table.rowAtPoint(point);
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
selectedIndex = table.convertRowIndexToModel(selectedIndex);
|
||||||
|
Optional<ManifestEntry> optional = getManifestFromIndex(selectedIndex);
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
if (showModifyDialog(optional.get())) {
|
||||||
|
updateManifestEntryInTable(optional.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.getListingTypeCombo().addItemListener(e -> {
|
||||||
|
ListingType type = (ListingType) e.getItem();
|
||||||
|
dialog.getGameKeyWarning().setVisible(!type.isGameKeyCompatible());
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.getEditManifestButton().addActionListener(e -> {
|
||||||
|
Optional<ManifestEntry> optional = getSelectedManifest();
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
if (showModifyDialog(optional.get())) {
|
||||||
|
updateManifestEntryInTable(optional.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.getGenerateButton().addActionListener(e -> tryGenerate());
|
||||||
|
|
||||||
|
dialog.getCancelButton().addActionListener(e -> dialog.dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean showModifyDialog(ManifestEntry manifestEntry) {
|
||||||
|
ManifestEntryDialog modifyDialog = new ManifestEntryDialog(dialog);
|
||||||
|
modifyDialog.setTitle("Modify " + manifestEntry.getManifestInfo().getLocation());
|
||||||
|
ManifestEntryController controller = new ManifestEntryController(modifyDialog, manifestEntry);
|
||||||
|
return controller.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryGenerate() {
|
||||||
|
String path = dialog.getDestDirField().getPath().trim();
|
||||||
|
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(dialog, "A directory must be entered.", "Error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ManifestEntry> selected = manifestEntries.stream()
|
||||||
|
.filter(ManifestEntry::isSelected)
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toCollection(Lists::newArrayList));
|
||||||
|
|
||||||
|
if (selected.isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(dialog, "At least one modpack must be selected to appear in the package list.", "Error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListingType listingType = (ListingType) dialog.getListingTypeCombo().getSelectedItem();
|
||||||
|
File destDir = new File(path);
|
||||||
|
destDir.mkdirs();
|
||||||
|
File file = new File(destDir, listingType.getFilename());
|
||||||
|
|
||||||
|
workspace.setPackageListingEntries(selected);
|
||||||
|
workspace.setPackageListingType(listingType);
|
||||||
|
Persistence.commitAndForget(workspace);
|
||||||
|
|
||||||
|
SettableProgress progress = new SettableProgress("Generating package listing...", -1);
|
||||||
|
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(() -> listingType.generate(selected)))
|
||||||
|
.thenTap(() -> progress.set("Deleting older package listing files...", -1))
|
||||||
|
.thenApply(input -> {
|
||||||
|
for (ListingType otherListingType : ListingType.values()) {
|
||||||
|
File f = new File(destDir, otherListingType.getFilename());
|
||||||
|
if (f.exists()) {
|
||||||
|
f.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
})
|
||||||
|
.thenTap(() -> progress.set("Writing package listing to disk...", -1))
|
||||||
|
.thenApply(input -> {
|
||||||
|
try {
|
||||||
|
Files.write(input, file, Charset.forName("UTF-8"));
|
||||||
|
return file;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to write package listing file to disk", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.handleAsync(v -> {
|
||||||
|
if (listingType.isGameKeyCompatible()) {
|
||||||
|
SwingHelper.showMessageDialog(dialog, "Successfully generated package listing.", "Success", null, JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} else {
|
||||||
|
SwingHelper.showMessageDialog(dialog, "Successfully generated package listing.\n\n" +
|
||||||
|
"Note that any modpacks with game keys set were not added.",
|
||||||
|
"Success", null, JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
dialog.dispose();
|
||||||
|
SwingHelper.browseDir(destDir, dialog);
|
||||||
|
}, ex -> {}, SwingExecutor.INSTANCE);
|
||||||
|
|
||||||
|
ProgressDialog.showProgress(dialog, deferred, progress, "Writing package listing...", "Writing package listing...");
|
||||||
|
SwingHelper.addErrorDialogCallback(dialog, deferred);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.creator.dialog.ManifestEntryDialog;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.ManifestEntry;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
|
||||||
|
public class ManifestEntryController {
|
||||||
|
|
||||||
|
private final ManifestEntryDialog dialog;
|
||||||
|
private final ManifestEntry manifestEntry;
|
||||||
|
|
||||||
|
private boolean save;
|
||||||
|
|
||||||
|
public ManifestEntryController(ManifestEntryDialog dialog, ManifestEntry manifestEntry) {
|
||||||
|
this.dialog = dialog;
|
||||||
|
this.manifestEntry = manifestEntry;
|
||||||
|
|
||||||
|
initListeners();
|
||||||
|
copyFrom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyFrom() {
|
||||||
|
dialog.getIncludeCheck().setSelected(manifestEntry.isSelected());
|
||||||
|
dialog.getPrioritySpinner().setValue(manifestEntry.getManifestInfo().getPriority());
|
||||||
|
SwingHelper.setTextAndResetCaret(dialog.getGameKeysText(), SwingHelper.listToLines(manifestEntry.getGameKeys()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyTo() {
|
||||||
|
manifestEntry.setSelected(dialog.getIncludeCheck().isSelected());
|
||||||
|
manifestEntry.getManifestInfo().setPriority((Integer) dialog.getPrioritySpinner().getValue());
|
||||||
|
manifestEntry.setGameKeys(SwingHelper.linesToList(dialog.getGameKeysText().getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean show() {
|
||||||
|
dialog.setVisible(true);
|
||||||
|
return save;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initListeners() {
|
||||||
|
dialog.getOkButton().addActionListener(e -> {
|
||||||
|
copyTo();
|
||||||
|
save = true;
|
||||||
|
dialog.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.getCancelButton().addActionListener(e -> dialog.dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,862 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
import com.skcraft.concurrency.Deferred;
|
||||||
|
import com.skcraft.concurrency.Deferreds;
|
||||||
|
import com.skcraft.concurrency.SettableProgress;
|
||||||
|
import com.skcraft.launcher.Instance;
|
||||||
|
import com.skcraft.launcher.InstanceList;
|
||||||
|
import com.skcraft.launcher.Launcher;
|
||||||
|
import com.skcraft.launcher.auth.OfflineSession;
|
||||||
|
import com.skcraft.launcher.auth.Session;
|
||||||
|
import com.skcraft.launcher.builder.BuilderConfig;
|
||||||
|
import com.skcraft.launcher.builder.FnPatternList;
|
||||||
|
import com.skcraft.launcher.creator.Creator;
|
||||||
|
import com.skcraft.launcher.creator.controller.task.*;
|
||||||
|
import com.skcraft.launcher.creator.dialog.AboutDialog;
|
||||||
|
import com.skcraft.launcher.creator.dialog.*;
|
||||||
|
import com.skcraft.launcher.creator.dialog.BuildDialog.BuildOptions;
|
||||||
|
import com.skcraft.launcher.creator.dialog.DeployServerDialog.DeployOptions;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.*;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.PackTableModel;
|
||||||
|
import com.skcraft.launcher.creator.server.TestServer;
|
||||||
|
import com.skcraft.launcher.creator.server.TestServerBuilder;
|
||||||
|
import com.skcraft.launcher.creator.swing.PackDirectoryFilter;
|
||||||
|
import com.skcraft.launcher.dialog.*;
|
||||||
|
import com.skcraft.launcher.model.modpack.LaunchModifier;
|
||||||
|
import com.skcraft.launcher.persistence.Persistence;
|
||||||
|
import com.skcraft.launcher.swing.PopupMouseAdapter;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.util.MorePaths;
|
||||||
|
import com.skcraft.launcher.util.SwingExecutor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class PackManagerController {
|
||||||
|
|
||||||
|
private static final DateFormat VERSION_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
|
||||||
|
private static final Pattern FILENAME_SANITIZE = Pattern.compile("[^a-z0-9_\\-\\.]+");
|
||||||
|
|
||||||
|
@Getter private final File workspaceDir;
|
||||||
|
@Getter private final Creator creator;
|
||||||
|
@Getter private final File workspaceFile;
|
||||||
|
@Getter private final File dataDir;
|
||||||
|
@Getter private final File distDir;
|
||||||
|
@Getter private final File launcherDir;
|
||||||
|
@Getter private File webRoot;
|
||||||
|
@Getter private Workspace workspace;
|
||||||
|
@Getter private final Launcher launcher;
|
||||||
|
@Getter private final ListeningExecutorService executor;
|
||||||
|
@Getter private final TestServer testServer;
|
||||||
|
|
||||||
|
private File lastServerDestDir;
|
||||||
|
|
||||||
|
private final PackManagerFrame frame;
|
||||||
|
private PackTableModel packTableModel;
|
||||||
|
|
||||||
|
public PackManagerController(PackManagerFrame frame, File workspaceDir, Creator creator) throws IOException {
|
||||||
|
this.workspaceDir = workspaceDir;
|
||||||
|
this.creator = creator;
|
||||||
|
this.dataDir = Workspace.getDataDir(workspaceDir);
|
||||||
|
workspaceFile = Workspace.getWorkspaceFile(workspaceDir);
|
||||||
|
|
||||||
|
this.distDir = new File(workspaceDir, "_upload");
|
||||||
|
launcherDir = new File(dataDir, "staging/launcher");
|
||||||
|
File launcherConfigDir = new File(creator.getDataDir(), "launcher");
|
||||||
|
this.webRoot = new File(dataDir, "staging/www");
|
||||||
|
|
||||||
|
launcherDir.mkdirs();
|
||||||
|
launcherConfigDir.mkdirs();
|
||||||
|
webRoot.mkdirs();
|
||||||
|
|
||||||
|
this.launcher = new Launcher(launcherDir, launcherConfigDir);
|
||||||
|
this.executor = launcher.getExecutor();
|
||||||
|
this.frame = frame;
|
||||||
|
|
||||||
|
TestServerBuilder builder = new TestServerBuilder();
|
||||||
|
builder.setBaseDir(webRoot);
|
||||||
|
builder.setPort(0);
|
||||||
|
testServer = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.setTitle("Modpack Creator - [" + workspaceDir.getAbsolutePath() + "]");
|
||||||
|
|
||||||
|
initListeners();
|
||||||
|
loadWorkspace();
|
||||||
|
|
||||||
|
Deferreds.makeDeferred(executor.submit(() -> {
|
||||||
|
startServer();
|
||||||
|
return null;
|
||||||
|
}))
|
||||||
|
.handle(
|
||||||
|
result -> {
|
||||||
|
},
|
||||||
|
(ex) -> SwingHelper.showErrorDialog(frame, "Failed to start a local web server. You will be unable to test modpacks.", "Error", ex)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startServer() throws Exception {
|
||||||
|
testServer.start();
|
||||||
|
|
||||||
|
launcher.getProperties().setProperty("newsUrl", "http://localhost:" + testServer.getLocalPort() + "/news.html");
|
||||||
|
launcher.getProperties().setProperty("packageListUrl", "http://localhost:" + testServer.getLocalPort() + "/packages.json");
|
||||||
|
launcher.getProperties().setProperty("selfUpdateUrl", "http://localhost:" + testServer.getLocalPort() + "/latest.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadWorkspace() {
|
||||||
|
PackLoader loader = new PackLoader();
|
||||||
|
|
||||||
|
SettableProgress progress = new SettableProgress("Loading workspace...", -1);
|
||||||
|
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(() -> {
|
||||||
|
Workspace workspace = Persistence.load(workspaceFile, Workspace.class);
|
||||||
|
workspace.setDirectory(workspaceDir);
|
||||||
|
workspace.load();
|
||||||
|
if (!workspaceFile.exists()) {
|
||||||
|
Persistence.commitAndForget(workspace);
|
||||||
|
}
|
||||||
|
this.workspace = workspace;
|
||||||
|
return workspace;
|
||||||
|
}))
|
||||||
|
.thenTap(() -> progress.observe(loader))
|
||||||
|
.thenApply(loader)
|
||||||
|
.thenApplyAsync(packs -> {
|
||||||
|
JTable table = frame.getPackTable();
|
||||||
|
packTableModel = new PackTableModel(packs);
|
||||||
|
table.setModel(packTableModel);
|
||||||
|
packTableModel.fireTableDataChanged();
|
||||||
|
table.getRowSorter().toggleSortOrder(1);
|
||||||
|
if (packTableModel.getRowCount() > 0) {
|
||||||
|
table.addRowSelectionInterval(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return packs;
|
||||||
|
}, SwingExecutor.INSTANCE);
|
||||||
|
|
||||||
|
ProgressDialog.showProgress(frame, deferred, progress, "Loading workspace...", "Loading workspace...");
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, deferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkPackLoaded(Pack pack) {
|
||||||
|
if (pack.isLoaded()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
SwingHelper.showErrorDialog(frame, "The selected pack could not be loaded. You will have to remove it from the workspace or change its location.", "Error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Pack> getPackFromIndex(int selectedIndex, boolean requireLoaded) {
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
Pack pack = workspace.getPacks().get(selectedIndex);
|
||||||
|
if (pack != null && (!requireLoaded || checkPackLoaded(pack))) {
|
||||||
|
return Optional.of(pack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Pack> getSelectedPack(boolean requireLoaded) {
|
||||||
|
JTable table = frame.getPackTable();
|
||||||
|
int selectedIndex = table.getSelectedRow();
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
selectedIndex = table.convertRowIndexToModel(selectedIndex);
|
||||||
|
Pack pack = workspace.getPacks().get(selectedIndex);
|
||||||
|
if (pack != null && (!requireLoaded || checkPackLoaded(pack))) {
|
||||||
|
return Optional.of(pack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingHelper.showErrorDialog(frame, "Please select a modpack from the list.", "Error");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean writeWorkspace() {
|
||||||
|
try {
|
||||||
|
Persistence.commit(workspace);
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
SwingHelper.showErrorDialog(frame, "Failed to write the current state of the workspace to disk. " +
|
||||||
|
"Any recent changes to the list of modpacks may not appear in the workspace on the next load.", "Error", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean writeBuilderConfig(Pack pack, BuilderConfig config) {
|
||||||
|
try {
|
||||||
|
Persistence.write(pack.getConfigFile(), config, Persistence.L2F_LIST_PRETTY_PRINTER);
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
SwingHelper.showErrorDialog(frame, "Failed to write modpack.json to disk. Aborting.", "Error", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOfflineEnabled() {
|
||||||
|
CreatorConfig config = creator.getConfig();
|
||||||
|
|
||||||
|
if (config.isOfflineEnabled()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Session session = AccountSelectDialog.showAccountRequest(frame, launcher);
|
||||||
|
if (session != null) {
|
||||||
|
config.setOfflineEnabled(true);
|
||||||
|
Persistence.commitAndForget(config);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canAddPackDir(File dir) {
|
||||||
|
try {
|
||||||
|
if (dir.exists() && !dir.isDirectory()) {
|
||||||
|
SwingHelper.showErrorDialog(frame, "The selected path is a file that already exists. It must be a directory.", "Error");
|
||||||
|
return false;
|
||||||
|
} else if (dir.getCanonicalPath().equals(workspaceDir.getCanonicalPath())) {
|
||||||
|
SwingHelper.showErrorDialog(frame, "You cannot choose the workspace directory.", "Error");
|
||||||
|
return false;
|
||||||
|
} else if (workspace.hasPack(dir)) {
|
||||||
|
SwingHelper.showErrorDialog(frame, "There is already a modpack in this workspace that uses that directory.", "Error");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
SwingHelper.showErrorDialog(frame, "An unexpected error occurred while checking if the modpack being added can be added.", "Error", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addPackToWorkspace(Pack pack) {
|
||||||
|
pack.load();
|
||||||
|
|
||||||
|
try {
|
||||||
|
File base = workspaceDir;
|
||||||
|
File child = pack.getDirectory();
|
||||||
|
if (MorePaths.isSubDirectory(base, child)) {
|
||||||
|
pack.setLocation(MorePaths.relativize(base, child));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
SwingHelper.showErrorDialog(frame, "An unexpected error occurred that could have been caused by the removal " +
|
||||||
|
"of the workspace or modpack directory.", "Error", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Pack> packs = workspace.getPacks();
|
||||||
|
pack.setWorkspace(workspace);
|
||||||
|
packs.add(pack);
|
||||||
|
packTableModel.fireTableRowsInserted(packs.size() - 1, packs.size() - 1);
|
||||||
|
writeWorkspace();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePackInWorkspace(Pack pack) {
|
||||||
|
List<Pack> packs = workspace.getPacks();
|
||||||
|
pack.load();
|
||||||
|
int index = packs.indexOf(pack);
|
||||||
|
if (index >= 0) {
|
||||||
|
packTableModel.fireTableRowsUpdated(index, index);
|
||||||
|
}
|
||||||
|
writeWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removePackFromWorkspace(Pack pack) {
|
||||||
|
if (workspace.getPacks().remove(pack)) {
|
||||||
|
packTableModel.fireTableDataChanged();
|
||||||
|
writeWorkspace();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDefaultConfig(BuilderConfig config) {
|
||||||
|
LaunchModifier launchModifier = new LaunchModifier();
|
||||||
|
launchModifier.setFlags(ImmutableList.of("-Dfml.ignoreInvalidMinecraftCertificates=true"));
|
||||||
|
config.setLaunchModifier(launchModifier);
|
||||||
|
|
||||||
|
FnPatternList userFiles = new FnPatternList();
|
||||||
|
userFiles.setInclude(Lists.newArrayList("options.txt", "optionsshaders.txt"));
|
||||||
|
userFiles.setExclude(Lists.<String>newArrayList());
|
||||||
|
config.setUserFiles(userFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initListeners() {
|
||||||
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
try {
|
||||||
|
testServer.stop();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
System.exit(0); // TODO: Proper shutdown
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getPackTable().addMouseListener(new MouseAdapter() {
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
JTable table = (JTable) e.getSource();
|
||||||
|
Point point = e.getPoint();
|
||||||
|
int selectedIndex = table.rowAtPoint(point);
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
selectedIndex = table.convertRowIndexToModel(selectedIndex);
|
||||||
|
Optional<Pack> optional = getPackFromIndex(selectedIndex, true);
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
if (e.isControlDown()) {
|
||||||
|
SwingHelper.browseDir(optional.get().getDirectory(), frame);
|
||||||
|
} else {
|
||||||
|
startTest(optional.get(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getPackTable().addMouseListener(new PopupMouseAdapter() {
|
||||||
|
@Override
|
||||||
|
protected void showPopup(MouseEvent e) {
|
||||||
|
JTable table = (JTable) e.getSource();
|
||||||
|
Point point = e.getPoint();
|
||||||
|
int selectedIndex = table.rowAtPoint(point);
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
table.setRowSelectionInterval(selectedIndex, selectedIndex);
|
||||||
|
Optional<Pack> optional = getSelectedPack(false);
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
popupPackMenu(e.getComponent(), e.getX(), e.getY(), optional.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getNewPackMenuItem().addActionListener(event -> tryAddPackViaDialog());
|
||||||
|
|
||||||
|
frame.getNewPackAtLocationMenuItem().addActionListener(e -> tryAddPackViaDirectory(true));
|
||||||
|
|
||||||
|
frame.getImportPackMenuItem().addActionListener(event -> tryAddPackViaDirectory(false));
|
||||||
|
|
||||||
|
frame.getRemovePackItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> optional = getSelectedPack(false);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
Pack pack = optional.get();
|
||||||
|
|
||||||
|
if (!pack.isLoaded() || SwingHelper.confirmDialog(frame, "Are you sure that you want to remove this modpack? No files will be deleted " +
|
||||||
|
"and you can later re-import the modpack using 'Add Existing'.", "Confirm")) {
|
||||||
|
removePackFromWorkspace(pack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getDeletePackItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> optional = getSelectedPack(false);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
Pack pack = optional.get();
|
||||||
|
|
||||||
|
String input = JOptionPane.showInputDialog(
|
||||||
|
frame,
|
||||||
|
"Are you sure that you want to delete '" + pack.getDirectory().getAbsolutePath() + "'? " +
|
||||||
|
"If yes, type 'delete' below.",
|
||||||
|
"Confirm",
|
||||||
|
JOptionPane.WARNING_MESSAGE);
|
||||||
|
|
||||||
|
if (input != null && input.replaceAll("'", "").equalsIgnoreCase("delete")) {
|
||||||
|
removePackFromWorkspace(pack);
|
||||||
|
|
||||||
|
DirectoryDeleter deleter = new DirectoryDeleter(pack.getDirectory());
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(deleter), executor);
|
||||||
|
ProgressDialog.showProgress(frame, deferred, deleter, "Deleting modpack...", "Deleting modpack...");
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, deferred);
|
||||||
|
} else if (input != null) {
|
||||||
|
SwingHelper.showMessageDialog(frame, "You did not enter the correct word. Nothing was deleted.", "Failure", null, JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getChangePackLocationMenuItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> optional = getSelectedPack(false);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
Pack pack = optional.get();
|
||||||
|
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
chooser.setDialogTitle("Choose New Folder for Pack");
|
||||||
|
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||||
|
chooser.setFileFilter(new PackDirectoryFilter());
|
||||||
|
|
||||||
|
File dir = workspaceDir;
|
||||||
|
|
||||||
|
do {
|
||||||
|
chooser.setCurrentDirectory(dir);
|
||||||
|
int returnVal = chooser.showOpenDialog(frame);
|
||||||
|
|
||||||
|
if (returnVal == JFileChooser.APPROVE_OPTION) {
|
||||||
|
dir = chooser.getSelectedFile();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} while (!canAddPackDir(dir));
|
||||||
|
|
||||||
|
pack.setLocation(dir.getAbsolutePath());
|
||||||
|
updatePackInWorkspace(pack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getRefreshMenuItem().addActionListener(e -> loadWorkspace());
|
||||||
|
|
||||||
|
frame.getQuitMenuItem().addActionListener(e -> {
|
||||||
|
frame.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getEditConfigMenuItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> optional = getSelectedPack(true);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
Pack pack = optional.get();
|
||||||
|
File file = pack.getConfigFile();
|
||||||
|
BuilderConfig config = Persistence.read(file, BuilderConfig.class);
|
||||||
|
|
||||||
|
if (BuilderConfigDialog.showEditor(frame, config)) {
|
||||||
|
writeBuilderConfig(pack, config);
|
||||||
|
updatePackInWorkspace(pack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getOpenFolderMenuItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> selectedPack = getSelectedPack(true);
|
||||||
|
|
||||||
|
selectedPack.ifPresent(pack -> SwingHelper.browseDir(pack.getDirectory(), frame));
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getCheckProblemsMenuItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> optional = getSelectedPack(true);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
ProblemChecker checker = new ProblemChecker(optional.get());
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(checker), executor)
|
||||||
|
.handleAsync(this::showProblems, (ex) -> {
|
||||||
|
}, SwingExecutor.INSTANCE);
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, deferred);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getTestMenuItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> optional = getSelectedPack(true);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
Pack pack = optional.get();
|
||||||
|
startTest(pack, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getTestOnlineMenuItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> optional = getSelectedPack(true);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
Pack pack = optional.get();
|
||||||
|
startTest(pack, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getOptionsMenuItem().addActionListener(e -> {
|
||||||
|
ConfigurationDialog configDialog = new ConfigurationDialog(frame, launcher);
|
||||||
|
configDialog.setVisible(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getInstanceOptionsMenuItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> selectedPack = getSelectedPack(true);
|
||||||
|
|
||||||
|
selectedPack.ifPresent(pack -> {
|
||||||
|
InstanceList.Enumerator instanceList = launcher.getInstances().createEnumerator();
|
||||||
|
|
||||||
|
ListenableFuture<InstanceList> future = executor.submit(instanceList);
|
||||||
|
Futures.addCallback(future, new FutureCallback<InstanceList>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(InstanceList result) {
|
||||||
|
Instance found = null;
|
||||||
|
|
||||||
|
for (Instance instance : result.getInstances()) {
|
||||||
|
if (instance.getName().equals(pack.getCachedConfig().getName())) {
|
||||||
|
found = instance;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found == null) {
|
||||||
|
SwingHelper.showErrorDialog(frame, "No instance found for that pack - you need " +
|
||||||
|
"to test the pack first.", "Not Found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceSettingsDialog.open(frame, found);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable ignored) {
|
||||||
|
}
|
||||||
|
}, SwingExecutor.INSTANCE);
|
||||||
|
|
||||||
|
ProgressDialog.showProgress(frame, future, instanceList, "Enumerating instances...",
|
||||||
|
"Enumerating instances...");
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, future);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getClearInstanceMenuItem().addActionListener(e -> {
|
||||||
|
DirectoryDeleter deleter = new DirectoryDeleter(launcher.getInstancesDir());
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(deleter), executor);
|
||||||
|
ProgressDialog.showProgress(frame, deferred, deleter, "Deleting test instances...", "Deleting test instances...");
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, deferred);
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getClearWebRootMenuItem().addActionListener(e -> {
|
||||||
|
DirectoryDeleter deleter = new DirectoryDeleter(webRoot);
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(deleter), executor);
|
||||||
|
ProgressDialog.showProgress(frame, deferred, deleter, "Deleting web server files...", "Deleting web server files...");
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, deferred);
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getBuildMenuItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> optional = getSelectedPack(true);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
Pack pack = optional.get();
|
||||||
|
buildPack(pack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getDeployServerMenuItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> optional = getSelectedPack(true);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
Pack pack = optional.get();
|
||||||
|
DeployOptions options = DeployServerDialog.showDeployDialog(frame, lastServerDestDir);
|
||||||
|
|
||||||
|
if (options != null) {
|
||||||
|
ConsoleFrame.showMessages();
|
||||||
|
|
||||||
|
File destDir = options.getDestDir();
|
||||||
|
destDir.mkdirs();
|
||||||
|
lastServerDestDir = destDir;
|
||||||
|
|
||||||
|
ServerDeploy deploy = new ServerDeploy(pack.getSourceDir(), options);
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(deploy), executor)
|
||||||
|
.handleAsync(r -> SwingHelper.showMessageDialog(frame, "Server deployment complete!", "Success", null, JOptionPane.INFORMATION_MESSAGE),
|
||||||
|
ex -> {
|
||||||
|
},
|
||||||
|
SwingExecutor.INSTANCE);
|
||||||
|
ProgressDialog.showProgress(frame, deferred, deploy, "Deploying files...", "Deploying server files...");
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, deferred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getGeneratePackagesMenuItem().addActionListener(e -> {
|
||||||
|
List<ManifestEntry> entries = workspace.getPackageListingEntries();
|
||||||
|
ManifestInfoEnumerator enumerator = new ManifestInfoEnumerator(distDir);
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(() -> enumerator.apply(entries)))
|
||||||
|
.handleAsync(loaded -> {
|
||||||
|
GenerateListingDialog dialog = new GenerateListingDialog(frame);
|
||||||
|
GenerateListingController controller = new GenerateListingController(dialog, workspace, loaded, executor);
|
||||||
|
controller.setOutputDir(distDir);
|
||||||
|
controller.show();
|
||||||
|
}, ex -> {
|
||||||
|
}, SwingExecutor.INSTANCE);
|
||||||
|
ProgressDialog.showProgress(frame, deferred, new SettableProgress("Searching...", -1), "Searching for manifests...", "Searching for manifests...");
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, deferred);
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getVersionCheckMenuItem().addActionListener(e -> {
|
||||||
|
Optional<Pack> optional = getSelectedPack(true);
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
Pack pack = optional.get();
|
||||||
|
|
||||||
|
VersionCheckDialog dialog = new VersionCheckDialog(frame);
|
||||||
|
VersionCheckController controller = new VersionCheckController(dialog, executor);
|
||||||
|
controller.showUpdates(pack.getModsDir(), pack.getCachedConfig().getGameVersion(), frame);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getOpenOutputFolderMenuItem().addActionListener(e -> SwingHelper.browseDir(distDir, frame));
|
||||||
|
|
||||||
|
frame.getOpenWorkspaceFolderMenuItem().addActionListener(e1 -> SwingHelper.browseDir(workspaceDir, frame));
|
||||||
|
|
||||||
|
frame.getOpenLauncherFolderMenuItem().addActionListener(e1 -> SwingHelper.browseDir(launcherDir, frame));
|
||||||
|
|
||||||
|
frame.getOpenWebRootMenuItem().addActionListener(e1 -> SwingHelper.browseDir(webRoot, frame));
|
||||||
|
|
||||||
|
frame.getOpenConsoleMenuItem().addActionListener(e -> {
|
||||||
|
ConsoleFrame.showMessages();
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getDocsMenuItem().addActionListener(e -> {
|
||||||
|
SwingHelper.openURL("https://github.com/SKCraft/Launcher/wiki", frame);
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.getAboutMenuItem().addActionListener(e -> {
|
||||||
|
AboutDialog.showAboutDialog(frame);
|
||||||
|
});
|
||||||
|
|
||||||
|
SwingHelper.addActionListeners(frame.getNewPackButton(), frame.getNewPackMenuItem().getActionListeners());
|
||||||
|
SwingHelper.addActionListeners(frame.getImportButton(), frame.getImportPackMenuItem().getActionListeners());
|
||||||
|
SwingHelper.addActionListeners(frame.getEditConfigButton(), frame.getEditConfigMenuItem().getActionListeners());
|
||||||
|
SwingHelper.addActionListeners(frame.getOpenFolderButton(), frame.getOpenFolderMenuItem().getActionListeners());
|
||||||
|
SwingHelper.addActionListeners(frame.getCheckProblemsButton(), frame.getCheckProblemsMenuItem().getActionListeners());
|
||||||
|
SwingHelper.addActionListeners(frame.getTestButton(), frame.getTestMenuItem().getActionListeners());
|
||||||
|
SwingHelper.addActionListeners(frame.getBuildButton(), frame.getBuildMenuItem().getActionListeners());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void popupPackMenu(Component component, int x, int y, Pack pack) {
|
||||||
|
JPopupMenu popup = new JPopupMenu();
|
||||||
|
JMenuItem menuItem;
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Edit modpack.json...");
|
||||||
|
menuItem.addActionListener(e -> frame.getEditConfigMenuItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Open Directory");
|
||||||
|
menuItem.addActionListener(e -> frame.getOpenFolderMenuItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Check for Problems");
|
||||||
|
menuItem.addActionListener(e -> frame.getCheckProblemsMenuItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
popup.addSeparator();
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Test");
|
||||||
|
menuItem.addActionListener(e -> frame.getTestMenuItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Test Online");
|
||||||
|
menuItem.addActionListener(e -> frame.getTestOnlineMenuItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Instance settings...");
|
||||||
|
menuItem.addActionListener(e -> frame.getInstanceOptionsMenuItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Build...");
|
||||||
|
menuItem.addActionListener(e -> frame.getBuildMenuItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Deploy Server...");
|
||||||
|
menuItem.addActionListener(e -> frame.getDeployServerMenuItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
popup.addSeparator();
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Change Location...");
|
||||||
|
menuItem.addActionListener(e -> frame.getChangePackLocationMenuItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Remove...");
|
||||||
|
menuItem.addActionListener(e -> frame.getRemovePackItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Delete Forever...");
|
||||||
|
menuItem.addActionListener(e -> frame.getDeletePackItem().doClick());
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
popup.show(component, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryAddPackViaDialog() {
|
||||||
|
BuilderConfig config = new BuilderConfig();
|
||||||
|
addDefaultConfig(config);
|
||||||
|
Pack pack = new Pack();
|
||||||
|
File dir;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (BuilderConfigDialog.showEditor(frame, config)) {
|
||||||
|
dir = new File(workspaceDir, config.getName());
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} while (!canAddPackDir(dir));
|
||||||
|
|
||||||
|
pack.setLocation(dir.getAbsolutePath());
|
||||||
|
|
||||||
|
if (pack.getConfigFile().exists()) {
|
||||||
|
if (SwingHelper.confirmDialog(frame, "There's already an existing modpack with that name, though " +
|
||||||
|
"it is not imported into this workspace. Would you like to import it and ignore " +
|
||||||
|
"the new modpack that you just entered?", "Conflict")) {
|
||||||
|
addPackToWorkspace(pack);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (writeBuilderConfig(pack, config)) {
|
||||||
|
pack.createGuideFolders();
|
||||||
|
addPackToWorkspace(pack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryAddPackViaDirectory(boolean createNew) {
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
chooser.setDialogTitle("Choose Folder for Pack");
|
||||||
|
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||||
|
chooser.setFileFilter(new PackDirectoryFilter());
|
||||||
|
|
||||||
|
File dir = workspaceDir;
|
||||||
|
|
||||||
|
do {
|
||||||
|
chooser.setCurrentDirectory(dir);
|
||||||
|
int returnVal = chooser.showOpenDialog(frame);
|
||||||
|
|
||||||
|
if (returnVal == JFileChooser.APPROVE_OPTION) {
|
||||||
|
dir = chooser.getSelectedFile();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} while (!canAddPackDir(dir));
|
||||||
|
|
||||||
|
Pack pack = new Pack();
|
||||||
|
pack.setLocation(dir.getAbsolutePath());
|
||||||
|
|
||||||
|
if (pack.getConfigFile().exists()) {
|
||||||
|
if (createNew) {
|
||||||
|
if (SwingHelper.confirmDialog(frame, "There's already a modpack in that directory. Do you want to import it instead?", "Exists Already")) {
|
||||||
|
addPackToWorkspace(pack);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addPackToWorkspace(pack);
|
||||||
|
}
|
||||||
|
} else if (createNew || SwingHelper.confirmDialog(frame, "You've selected a directory that doesn't seem to be a modpack " +
|
||||||
|
"(at least with the directory structure this program expects). Would you like to create the files necessary to turn " +
|
||||||
|
"that folder into a modpack?", "Import Error")) {
|
||||||
|
|
||||||
|
BuilderConfig config = new BuilderConfig();
|
||||||
|
addDefaultConfig(config);
|
||||||
|
|
||||||
|
if (BuilderConfigDialog.showEditor(frame, config)) {
|
||||||
|
if (writeBuilderConfig(pack, config)) {
|
||||||
|
pack.createGuideFolders();
|
||||||
|
addPackToWorkspace(pack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startTest(Pack pack, boolean online) {
|
||||||
|
Session session;
|
||||||
|
|
||||||
|
if (online) {
|
||||||
|
session = AccountSelectDialog.showAccountRequest(frame, launcher);
|
||||||
|
if (session == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!isOfflineEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session = new OfflineSession("Player");
|
||||||
|
}
|
||||||
|
|
||||||
|
String version = generateVersionFromDate();
|
||||||
|
|
||||||
|
PackBuilder builder = new PackBuilder(pack, webRoot, version, "staging.json", false, false);
|
||||||
|
InstanceList.Enumerator enumerator = launcher.getInstances().createEnumerator();
|
||||||
|
TestLauncher instanceLauncher = new TestLauncher(launcher, frame, pack.getCachedConfig().getName(), session);
|
||||||
|
|
||||||
|
SettableProgress progress = new SettableProgress(builder);
|
||||||
|
|
||||||
|
ConsoleFrame.showMessages();
|
||||||
|
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(builder), executor)
|
||||||
|
.thenTap(() -> progress.set("Loading instance in test launcher...", -1))
|
||||||
|
.thenRun(enumerator)
|
||||||
|
.thenTap(() -> progress.set("Launching", -1))
|
||||||
|
.thenApply(instanceLauncher)
|
||||||
|
.handleAsync(result -> ConsoleFrame.hideMessages(), ex -> {}, SwingExecutor.INSTANCE);
|
||||||
|
|
||||||
|
ProgressDialog.showProgress(frame, deferred, progress, "Setting up test instance...", "Preparing files for launch...");
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, deferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildPack(Pack pack) {
|
||||||
|
String initialVersion = generateVersionFromDate();
|
||||||
|
BuildOptions options = BuildDialog.showBuildDialog(frame, initialVersion, generateManifestName(pack), distDir);
|
||||||
|
|
||||||
|
if (options != null) {
|
||||||
|
ConsoleFrame.showMessages();
|
||||||
|
PackBuilder builder = new PackBuilder(pack, options.getDestDir(), options.getVersion(), options.getManifestFilename(), false, true);
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(builder), executor)
|
||||||
|
.handleAsync(result -> {
|
||||||
|
ConsoleFrame.hideMessages();
|
||||||
|
SwingHelper.showMessageDialog(frame, "Successfully generated the package files.", "Success", null, JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}, ex -> {}, SwingExecutor.INSTANCE);
|
||||||
|
ProgressDialog.showProgress(frame, deferred, builder, "Building modpack...", "Building modpack...");
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, deferred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showProblems(List<Problem> problems) {
|
||||||
|
if (problems.isEmpty()) {
|
||||||
|
SwingHelper.showMessageDialog(frame, "No potential problems found!", "Success", null, JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} else {
|
||||||
|
ProblemViewer viewer = new ProblemViewer(frame, problems);
|
||||||
|
viewer.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateManifestName(Pack pack) {
|
||||||
|
File file = pack.getConfigFile();
|
||||||
|
if (file.exists()) {
|
||||||
|
BuilderConfig config = Persistence.read(file, BuilderConfig.class, true);
|
||||||
|
if (config != null) {
|
||||||
|
String name = Strings.nullToEmpty(config.getName());
|
||||||
|
name = name.toLowerCase();
|
||||||
|
name = FILENAME_SANITIZE.matcher(name).replaceAll("-");
|
||||||
|
name = name.trim();
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
return name + ".json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "my_modpack.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generateVersionFromDate() {
|
||||||
|
Date today = Calendar.getInstance().getTime();
|
||||||
|
return VERSION_DATE_FORMAT.format(today);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
import com.skcraft.concurrency.Deferred;
|
||||||
|
import com.skcraft.concurrency.Deferreds;
|
||||||
|
import com.skcraft.concurrency.SettableProgress;
|
||||||
|
import com.skcraft.launcher.creator.controller.task.DirectoryWalker;
|
||||||
|
import com.skcraft.launcher.creator.dialog.VersionCheckDialog;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.ModFile;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.ModFileTableModel;
|
||||||
|
import com.skcraft.launcher.creator.util.ModInfoReader;
|
||||||
|
import com.skcraft.launcher.creator.util.ModInfoReader.ModInfo;
|
||||||
|
import com.skcraft.launcher.creator.util.NemModList;
|
||||||
|
import com.skcraft.launcher.creator.util.NemModList.ModEntry;
|
||||||
|
import com.skcraft.launcher.dialog.ProgressDialog;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.util.SwingExecutor;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class VersionCheckController {
|
||||||
|
|
||||||
|
private final VersionCheckDialog dialog;
|
||||||
|
private final ListeningExecutorService executor;
|
||||||
|
|
||||||
|
public VersionCheckController(VersionCheckDialog dialog, ListeningExecutorService executor) {
|
||||||
|
this.dialog = dialog;
|
||||||
|
this.executor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showUpdates(File dir, String gameVersion, Window parentWindow) {
|
||||||
|
initListeners();
|
||||||
|
|
||||||
|
DirectoryWalker walker = new DirectoryWalker(dir);
|
||||||
|
walker.setRecursive(false);
|
||||||
|
walker.setFileFilter(pathname -> pathname.getName().endsWith(".jar"));
|
||||||
|
|
||||||
|
ModInfoReader binaryInspector = new ModInfoReader();
|
||||||
|
NemModList nemModList = new NemModList();
|
||||||
|
|
||||||
|
SettableProgress progress = new SettableProgress("Retrieving mod information...", -1);
|
||||||
|
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(walker), executor)
|
||||||
|
.thenTap(() -> progress.set("Querying NotEnoughMods for version data...", -1))
|
||||||
|
.thenTap(() -> {
|
||||||
|
try {
|
||||||
|
nemModList.load(gameVersion);
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new RuntimeException("Failed to retrieve mod information from NotEnoughMods. Perhaps NEM doesn't support your pack's MC version.", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.thenTap(() -> progress.set("Scanning mod files for manifests...", -1))
|
||||||
|
.thenApply(files -> {
|
||||||
|
List<ModFile> mods = Lists.newArrayList();
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
ModFile mod = new ModFile();
|
||||||
|
mod.setGameVersion(gameVersion);
|
||||||
|
mod.setFile(file);
|
||||||
|
|
||||||
|
List<? extends ModInfo> infoList = binaryInspector.detectMods(file);
|
||||||
|
if (!infoList.isEmpty()) {
|
||||||
|
ModInfo info = infoList.get(0);
|
||||||
|
mod.setModId(info.getModId());
|
||||||
|
mod.setName(info.getName());
|
||||||
|
mod.setVersion(info.getVersion());
|
||||||
|
|
||||||
|
if (info.getUrl() != null) {
|
||||||
|
mod.setUrl(getFirstUrl(info.getUrl(), "http://" + info.getUrl()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mod.getModId() != null) {
|
||||||
|
ModEntry entry = nemModList.get(mod.getModId());
|
||||||
|
|
||||||
|
if (entry != null) {
|
||||||
|
mod.setLatestVersion(entry.getLatestVersion());
|
||||||
|
mod.setLatestDevVersion(entry.getLatestDevVersion());
|
||||||
|
if (entry.getUrl() != null) {
|
||||||
|
mod.setUrl(entry.getUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mods.add(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mods;
|
||||||
|
})
|
||||||
|
.handleAsync(mods -> {
|
||||||
|
List<ModFile> known = Lists.newArrayList();
|
||||||
|
List<ModFile> unknown = Lists.newArrayList();
|
||||||
|
|
||||||
|
for (ModFile mod : mods) {
|
||||||
|
if (mod.getVersion() != null && mod.getLatestVersion() != null) {
|
||||||
|
if (!mod.getCleanVersion().equals(mod.getLatestVersion()) && !mod.getCleanVersion().equals(mod.getLatestDevVersion())) {
|
||||||
|
known.add(mod);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unknown.add(mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.getKnownModsTable().setModel(new ModFileTableModel(known));
|
||||||
|
dialog.getUnknownModsTable().setModel(new ModFileTableModel(unknown));
|
||||||
|
dialog.getKnownModsTable().getRowSorter().toggleSortOrder(1);
|
||||||
|
dialog.getUnknownModsTable().getRowSorter().toggleSortOrder(1);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}, ex -> {
|
||||||
|
}, SwingExecutor.INSTANCE);
|
||||||
|
|
||||||
|
ProgressDialog.showProgress(parentWindow, deferred, progress, "Checking for mod updates...", "Checking for mod updates...");
|
||||||
|
SwingHelper.addErrorDialogCallback(parentWindow, deferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initListeners() {
|
||||||
|
dialog.getCloseButton().addActionListener(e -> dialog.dispose());
|
||||||
|
|
||||||
|
ModTableMouseListener mouseListener = new ModTableMouseListener();
|
||||||
|
dialog.getKnownModsTable().addMouseListener(mouseListener);
|
||||||
|
dialog.getUnknownModsTable().addMouseListener(mouseListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static URL getFirstUrl(String... options) {
|
||||||
|
for (String option : options) {
|
||||||
|
try {
|
||||||
|
return new URL(option);
|
||||||
|
} catch (MalformedURLException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModTableMouseListener extends MouseAdapter {
|
||||||
|
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
JTable table = (JTable) e.getSource();
|
||||||
|
Point point = e.getPoint();
|
||||||
|
int selectedIndex = table.rowAtPoint(point);
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
selectedIndex = table.convertRowIndexToModel(selectedIndex);
|
||||||
|
ModFile mod = ((ModFileTableModel) table.getModel()).getMod(selectedIndex);
|
||||||
|
if (mod != null && mod.getUrl() != null) {
|
||||||
|
SwingHelper.openURL(mod.getUrl(), dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
import com.skcraft.concurrency.Deferred;
|
||||||
|
import com.skcraft.concurrency.Deferreds;
|
||||||
|
import com.skcraft.concurrency.SettableProgress;
|
||||||
|
import com.skcraft.launcher.creator.Creator;
|
||||||
|
import com.skcraft.launcher.creator.dialog.AboutDialog;
|
||||||
|
import com.skcraft.launcher.creator.dialog.PackManagerFrame;
|
||||||
|
import com.skcraft.launcher.creator.dialog.WelcomeDialog;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.RecentEntry;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Workspace;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.RecentListModel;
|
||||||
|
import com.skcraft.launcher.creator.swing.WorkspaceDirectoryFilter;
|
||||||
|
import com.skcraft.launcher.dialog.ProgressDialog;
|
||||||
|
import com.skcraft.launcher.persistence.Persistence;
|
||||||
|
import com.skcraft.launcher.swing.PopupMouseAdapter;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.util.MorePaths;
|
||||||
|
import com.skcraft.launcher.util.SwingExecutor;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class WelcomeController {
|
||||||
|
|
||||||
|
private final WelcomeDialog dialog;
|
||||||
|
private final Creator creator;
|
||||||
|
private final RecentListModel recentListModel;
|
||||||
|
|
||||||
|
public WelcomeController(WelcomeDialog dialog, Creator creator) {
|
||||||
|
this.dialog = dialog;
|
||||||
|
this.creator = creator;
|
||||||
|
|
||||||
|
initListeners();
|
||||||
|
|
||||||
|
recentListModel = new RecentListModel(creator.getConfig().getRecentEntries());
|
||||||
|
dialog.getRecentList().setModel(recentListModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean openWorkspace(File dir) {
|
||||||
|
ListeningExecutorService executor = creator.getExecutor();
|
||||||
|
PackManagerFrame frame = new PackManagerFrame();
|
||||||
|
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(() -> {
|
||||||
|
PackManagerController controller = new PackManagerController(frame, dir, creator);
|
||||||
|
addRecentEntry(dir);
|
||||||
|
return controller;
|
||||||
|
}), executor)
|
||||||
|
.handleAsync(PackManagerController::show, ex -> {}, SwingExecutor.INSTANCE);
|
||||||
|
ProgressDialog.showProgress(frame, deferred, new SettableProgress("Loading...", -1), "Loading workspace...", "Loading workspace...");
|
||||||
|
SwingHelper.addErrorDialogCallback(frame, deferred);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRecentEntry(File dir) {
|
||||||
|
List<RecentEntry> newEntries = creator.getConfig().getRecentEntries()
|
||||||
|
.stream()
|
||||||
|
.filter(entry -> {
|
||||||
|
try {
|
||||||
|
return !MorePaths.isSamePath(entry.getPath(), dir);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toCollection(Lists::newArrayList));
|
||||||
|
|
||||||
|
RecentEntry recent = new RecentEntry();
|
||||||
|
recent.setPath(dir);
|
||||||
|
newEntries.add(0, recent);
|
||||||
|
|
||||||
|
creator.getConfig().setRecentEntries(newEntries);
|
||||||
|
|
||||||
|
Persistence.commitAndForget(creator.getConfig());
|
||||||
|
|
||||||
|
recentListModel.fireUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeRecentEntry(RecentEntry entry) {
|
||||||
|
creator.getConfig().getRecentEntries().remove(entry);
|
||||||
|
Persistence.commitAndForget(creator.getConfig());
|
||||||
|
|
||||||
|
recentListModel.fireUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<RecentEntry> getSelectedRecentEntry() {
|
||||||
|
int selectedIndex = dialog.getRecentList().getSelectedIndex();
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
return Optional.fromNullable(creator.getConfig().getRecentEntries().get(selectedIndex));
|
||||||
|
} else {
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<File> getWorkspaceDir() {
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
chooser.setDialogTitle("Select Workspace Directory");
|
||||||
|
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||||
|
chooser.setFileFilter(new WorkspaceDirectoryFilter());
|
||||||
|
|
||||||
|
int returnVal = chooser.showOpenDialog(dialog);
|
||||||
|
|
||||||
|
if (returnVal == JFileChooser.APPROVE_OPTION) {
|
||||||
|
return Optional.fromNullable(chooser.getSelectedFile());
|
||||||
|
} else {
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initListeners() {
|
||||||
|
dialog.getRecentList().addMouseListener(new MouseAdapter() {
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
JList<RecentEntry> table = (JList<RecentEntry>) e.getSource();
|
||||||
|
Point point = e.getPoint();
|
||||||
|
int selectedIndex = table.locationToIndex(point);
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
table.setSelectedIndex(selectedIndex);
|
||||||
|
Optional<RecentEntry> optional = getSelectedRecentEntry();
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
if (openWorkspace(optional.get().getPath())) {
|
||||||
|
dialog.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.getRecentList().addMouseListener(new PopupMouseAdapter() {
|
||||||
|
@Override
|
||||||
|
protected void showPopup(MouseEvent e) {
|
||||||
|
JList<RecentEntry> table = (JList<RecentEntry>) e.getSource();
|
||||||
|
Point point = e.getPoint();
|
||||||
|
int selectedIndex = table.locationToIndex(point);
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
table.setSelectedIndex(selectedIndex);
|
||||||
|
Optional<RecentEntry> optional = getSelectedRecentEntry();
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
popupRecentWorkspaceMenu(e.getComponent(), e.getX(), e.getY(), optional.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.getNewButton().addActionListener(e -> {
|
||||||
|
Optional<File> optional = getWorkspaceDir();
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
File workspaceFile = Workspace.getWorkspaceFile(optional.get());
|
||||||
|
if (!workspaceFile.exists() || SwingHelper.confirmDialog(dialog, "There is already a workspace there. Do you want to load it?", "Existing")) {
|
||||||
|
if (openWorkspace(optional.get())) {
|
||||||
|
dialog.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.getOpenButton().addActionListener(e -> {
|
||||||
|
Optional<File> optional = getWorkspaceDir();
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
File workspaceFile = Workspace.getWorkspaceFile(optional.get());
|
||||||
|
if (workspaceFile.exists() || SwingHelper.confirmDialog(dialog, "Do you want to create a new workspace there?", "Create New")) {
|
||||||
|
if (openWorkspace(optional.get())) {
|
||||||
|
dialog.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.getHelpButton().addActionListener(e -> {
|
||||||
|
SwingHelper.openURL("https://github.com/SKCraft/Launcher/wiki", dialog);
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.getAboutButton().addActionListener(e -> {
|
||||||
|
AboutDialog.showAboutDialog(dialog);
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.getQuitButton().addActionListener(e -> {
|
||||||
|
dialog.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void popupRecentWorkspaceMenu(Component component, int x, int y, RecentEntry entry) {
|
||||||
|
JPopupMenu popup = new JPopupMenu();
|
||||||
|
JMenuItem menuItem;
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Remove");
|
||||||
|
menuItem.addActionListener(e -> removeRecentEntry(entry));
|
||||||
|
popup.add(menuItem);
|
||||||
|
|
||||||
|
popup.show(component, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller.task;
|
||||||
|
|
||||||
|
import com.skcraft.concurrency.ProgressObservable;
|
||||||
|
import com.skcraft.launcher.LauncherException;
|
||||||
|
import com.skcraft.launcher.LauncherUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public class DirectoryDeleter implements Callable<File>, ProgressObservable {
|
||||||
|
|
||||||
|
private final File dir;
|
||||||
|
|
||||||
|
public DirectoryDeleter(File dir) {
|
||||||
|
this.dir = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File call() throws Exception {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
|
||||||
|
List<File> failures = new ArrayList<File>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
LauncherUtils.interruptibleDelete(dir, failures);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
LauncherUtils.interruptibleDelete(dir, failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures.size() > 0) {
|
||||||
|
throw new LauncherException(failures.size() + " failed to delete", failures.size() + " file(s) could not be deleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProgress() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatus() {
|
||||||
|
return "Deleting files...";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller.task;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.skcraft.launcher.util.MorePaths;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
public class DirectoryWalker implements Callable<List<File>> {
|
||||||
|
|
||||||
|
@Getter private final File dir;
|
||||||
|
@Getter @Setter private FileFilter fileFilter = pathname -> true;
|
||||||
|
@Getter @Setter private boolean recursive;
|
||||||
|
|
||||||
|
public DirectoryWalker(File dir) {
|
||||||
|
checkNotNull(dir, "dir");
|
||||||
|
this.dir = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<File> call() throws IOException {
|
||||||
|
if (!dir.isDirectory()) {
|
||||||
|
throw new IllegalArgumentException(dir.getAbsolutePath() + " is not a directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<File> matched = Lists.newArrayList();
|
||||||
|
Set<String> seen = Sets.newHashSet();
|
||||||
|
|
||||||
|
Queue<File> queue = new LinkedList<>();
|
||||||
|
queue.add(dir);
|
||||||
|
|
||||||
|
File cur;
|
||||||
|
while ((cur = queue.poll()) != null) {
|
||||||
|
String canonical = cur.getCanonicalPath();
|
||||||
|
if (!seen.contains(canonical) && MorePaths.isSubDirectory(dir, cur)) {
|
||||||
|
File[] files = cur.listFiles();
|
||||||
|
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
if (recursive && file.isDirectory()) {
|
||||||
|
queue.add(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileFilter.accept(file)) {
|
||||||
|
matched.add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller.task;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.creator.model.creator.ManifestEntry;
|
||||||
|
import com.skcraft.launcher.model.modpack.Manifest;
|
||||||
|
import com.skcraft.launcher.model.modpack.ManifestInfo;
|
||||||
|
import com.skcraft.launcher.persistence.Persistence;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class ManifestInfoEnumerator implements Function<List<ManifestEntry>, List<ManifestEntry>> {
|
||||||
|
|
||||||
|
private final File searchDir;
|
||||||
|
|
||||||
|
public ManifestInfoEnumerator(File searchDir) {
|
||||||
|
this.searchDir = searchDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ManifestEntry> apply(List<ManifestEntry> entries) {
|
||||||
|
File[] files = searchDir.listFiles(f -> f.isFile() && f.getName().toLowerCase().endsWith(".json") && !f.getName().startsWith("packages."));
|
||||||
|
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
String location = file.getName();
|
||||||
|
Manifest manifest = Persistence.read(file, Manifest.class, true);
|
||||||
|
|
||||||
|
if (manifest != null) {
|
||||||
|
ManifestInfo info = new ManifestInfo();
|
||||||
|
info.setName(manifest.getName());
|
||||||
|
info.setTitle(manifest.getTitle());
|
||||||
|
info.setVersion(manifest.getVersion());
|
||||||
|
info.setPriority(0);
|
||||||
|
info.setLocation(location);
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
for (ManifestEntry entry : entries) {
|
||||||
|
if (entry.getManifestInfo().getLocation().equals(location)) {
|
||||||
|
info.setPriority(entry.getManifestInfo().getPriority());
|
||||||
|
entry.setManifestInfo(info);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
ManifestEntry entry = new ManifestEntry();
|
||||||
|
entry.setManifestInfo(info);
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller.task;
|
||||||
|
|
||||||
|
import com.skcraft.concurrency.ProgressObservable;
|
||||||
|
import com.skcraft.launcher.LauncherException;
|
||||||
|
import com.skcraft.launcher.LauncherUtils;
|
||||||
|
import com.skcraft.launcher.builder.PackageBuilder;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Pack;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public class PackBuilder implements Callable<PackBuilder>, ProgressObservable {
|
||||||
|
|
||||||
|
private final Pack pack;
|
||||||
|
private final File outputDir;
|
||||||
|
private final String version;
|
||||||
|
private final String manifestFilename;
|
||||||
|
private final boolean clean;
|
||||||
|
private final boolean downloadUrls;
|
||||||
|
|
||||||
|
public PackBuilder(Pack pack, File outputDir, String version, String manifestFilename, boolean clean, boolean downloadUrls) {
|
||||||
|
this.pack = pack;
|
||||||
|
this.outputDir = outputDir;
|
||||||
|
this.version = version;
|
||||||
|
this.manifestFilename = manifestFilename;
|
||||||
|
this.clean = clean;
|
||||||
|
this.downloadUrls = downloadUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PackBuilder call() throws Exception {
|
||||||
|
if (clean) {
|
||||||
|
List<File> failures = new ArrayList<File>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
LauncherUtils.interruptibleDelete(outputDir, failures);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
LauncherUtils.interruptibleDelete(outputDir, failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures.size() > 0) {
|
||||||
|
throw new LauncherException(failures.size() + " failed to delete", "There were " + failures.size() + " failures during cleaning.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
outputDir.mkdirs();
|
||||||
|
|
||||||
|
System.setProperty("com.skcraft.builder.ignoreURLOverrides", downloadUrls ? "false" : "true");
|
||||||
|
String[] args = {
|
||||||
|
"--version", version,
|
||||||
|
"--manifest-dest", new File(outputDir, manifestFilename).getAbsolutePath(),
|
||||||
|
"-i", pack.getDirectory().getAbsolutePath(),
|
||||||
|
"-o", outputDir.getAbsolutePath()
|
||||||
|
};
|
||||||
|
PackageBuilder.main(args);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProgress() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatus() {
|
||||||
|
return "Building modpack...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller.task;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.skcraft.concurrency.ProgressObservable;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Pack;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Workspace;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PackLoader implements ProgressObservable, Function<Workspace, List<Pack>> {
|
||||||
|
|
||||||
|
private int index;
|
||||||
|
private int size = 0;
|
||||||
|
private Pack lastPack;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Pack> apply(Workspace workspace) {
|
||||||
|
List<Pack> packs = workspace.getPacks();
|
||||||
|
size = packs.size();
|
||||||
|
|
||||||
|
for (Pack pack : packs) {
|
||||||
|
lastPack = pack;
|
||||||
|
pack.load();
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPack = null;
|
||||||
|
|
||||||
|
return packs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProgress() {
|
||||||
|
if (size == 0) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return index / (double) size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatus() {
|
||||||
|
Pack pack = lastPack;
|
||||||
|
if (pack != null) {
|
||||||
|
return "Loading " + pack.getDirectory().getName() + "...";
|
||||||
|
} else {
|
||||||
|
return "Enumerating packs...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller.task;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.skcraft.concurrency.ProgressObservable;
|
||||||
|
import com.skcraft.launcher.builder.BuilderOptions;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Pack;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Problem;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public class ProblemChecker implements Callable<List<Problem>>, ProgressObservable {
|
||||||
|
|
||||||
|
private final Pack pack;
|
||||||
|
|
||||||
|
public ProblemChecker(Pack pack) {
|
||||||
|
this.pack = pack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Problem> call() throws Exception {
|
||||||
|
List<Problem> problems = Lists.newArrayList();
|
||||||
|
|
||||||
|
File packDir = pack.getDirectory();
|
||||||
|
File srcDir = pack.getSourceDir();
|
||||||
|
|
||||||
|
File loadersDir = new File(packDir, BuilderOptions.DEFAULT_LOADERS_DIRNAME);
|
||||||
|
File modsDir = new File(srcDir, "mods");
|
||||||
|
boolean hasLoaders = hasFiles(loadersDir);
|
||||||
|
boolean hasMods = hasFiles(modsDir);
|
||||||
|
|
||||||
|
String[] files;
|
||||||
|
|
||||||
|
if (new File(packDir, "_CLIENT").exists()) {
|
||||||
|
problems.add(new Problem("Root _CLIENT", "There's a _CLIENT folder that's not in " +
|
||||||
|
"the src/ folder. Only files that are in src/ will actually appear in the " +
|
||||||
|
"modpack, so you probably intended to put _CLIENT in src/."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new File(packDir, "_SERVER").exists()) {
|
||||||
|
problems.add(new Problem("Root _SERVER", "There's a _SERVER folder that's not in " +
|
||||||
|
"the src/ folder. Only files that are in src/ will actually appear in the " +
|
||||||
|
"modpack, so you probably intended to put _SERVER in src/."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new File(packDir, "mods").exists()) {
|
||||||
|
problems.add(new Problem("Root mods", "There's a mods folder that's not in " +
|
||||||
|
"the src/ folder. Only files that are in src/ will actually appear in the " +
|
||||||
|
"modpack."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new File(packDir, "config").exists()) {
|
||||||
|
problems.add(new Problem("Root mods", "There's a config folder that's not in " +
|
||||||
|
"the src/ folder. Only files that are in src/ will actually appear in the " +
|
||||||
|
"modpack."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new File(packDir, "version.json").exists()) {
|
||||||
|
problems.add(new Problem("Legacy version.json", "There's a version.json file in the " +
|
||||||
|
"project directory. If you are upgrading your modpack from an old version " +
|
||||||
|
"of the launcher, then you should be able to delete version.json as it is " +
|
||||||
|
"no longer needed to create a modpack. If you are intentionally overriding the " +
|
||||||
|
"Minecraft version manifest, then ignore this warning."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMods && !hasLoaders) {
|
||||||
|
problems.add(new Problem("No Loaders", "There appears to be a mods/ folder but there's no mod loaders in loaders/."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return problems;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProgress() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatus() {
|
||||||
|
return "Checking for problems...";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasFiles(File dir) {
|
||||||
|
String[] contents = dir.list();
|
||||||
|
return contents != null && contents.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller.task;
|
||||||
|
|
||||||
|
import com.skcraft.concurrency.ProgressObservable;
|
||||||
|
import com.skcraft.launcher.LauncherException;
|
||||||
|
import com.skcraft.launcher.LauncherUtils;
|
||||||
|
import com.skcraft.launcher.builder.ServerCopyExport;
|
||||||
|
import com.skcraft.launcher.creator.dialog.DeployServerDialog.DeployOptions;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public class ServerDeploy implements Callable<ServerDeploy>, ProgressObservable {
|
||||||
|
|
||||||
|
private final File srcDir;
|
||||||
|
private final DeployOptions options;
|
||||||
|
|
||||||
|
public ServerDeploy(File srcDir, DeployOptions options) {
|
||||||
|
this.srcDir = srcDir;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServerDeploy call() throws Exception {
|
||||||
|
File modsDir = new File(options.getDestDir(), "mods");
|
||||||
|
|
||||||
|
if (options.isCleanMods() && modsDir.isDirectory()) {
|
||||||
|
List<File> failures = new ArrayList<File>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
LauncherUtils.interruptibleDelete(modsDir, failures);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
LauncherUtils.interruptibleDelete(modsDir, failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures.size() > 0) {
|
||||||
|
throw new LauncherException(failures.size() + " failed to delete", "There were " + failures.size() + " failures during cleaning.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] args = {
|
||||||
|
"--source", srcDir.getAbsolutePath(),
|
||||||
|
"--dest", options.getDestDir().getAbsolutePath()
|
||||||
|
};
|
||||||
|
ServerCopyExport.main(args);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProgress() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatus() {
|
||||||
|
return "Deploying server files...";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.controller.task;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.skcraft.concurrency.ProgressObservable;
|
||||||
|
import com.skcraft.launcher.Instance;
|
||||||
|
import com.skcraft.launcher.InstanceList;
|
||||||
|
import com.skcraft.launcher.Launcher;
|
||||||
|
import com.skcraft.launcher.auth.Session;
|
||||||
|
import com.skcraft.launcher.launch.LaunchOptions;
|
||||||
|
import com.skcraft.launcher.launch.LaunchOptions.UpdatePolicy;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TestLauncher implements Function<InstanceList, Instance>, ProgressObservable {
|
||||||
|
|
||||||
|
private final Launcher launcher;
|
||||||
|
private final Window window;
|
||||||
|
private final String id;
|
||||||
|
private final Session session;
|
||||||
|
|
||||||
|
public TestLauncher(Launcher launcher, Window window, String id, Session session) {
|
||||||
|
this.launcher = launcher;
|
||||||
|
this.window = window;
|
||||||
|
this.id = id;
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Instance> findInstance(List<Instance> instances) {
|
||||||
|
for (Instance instance : instances) {
|
||||||
|
if (instance.getName().equals(id)) {
|
||||||
|
return Optional.fromNullable(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instance apply(InstanceList instanceList) {
|
||||||
|
Optional<Instance> optional = findInstance(instanceList.getInstances());
|
||||||
|
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
LaunchOptions options = new LaunchOptions.Builder()
|
||||||
|
.setInstance(optional.get())
|
||||||
|
.setUpdatePolicy(UpdatePolicy.ALWAYS_UPDATE)
|
||||||
|
.setWindow(window)
|
||||||
|
.setSession(session)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
launcher.getLaunchSupervisor().launch(options);
|
||||||
|
|
||||||
|
return optional.get();
|
||||||
|
} else {
|
||||||
|
SwingHelper.showErrorDialog(window,
|
||||||
|
"After generating the necessary files, it appears the modpack can't be found in the " +
|
||||||
|
"launcher. Did you change modpack.json while the launcher was launching?", "Launch Error");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProgress() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatus() {
|
||||||
|
return "Launching the game...";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.LauncherUtils;
|
||||||
|
import com.skcraft.launcher.creator.Creator;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
@Log
|
||||||
|
public class AboutDialog extends JDialog {
|
||||||
|
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
public AboutDialog(Window parent) {
|
||||||
|
super(parent, "About", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Properties properties = LauncherUtils.loadProperties(Creator.class, "creator.properties", "com.skcraft.creator.propertiesFile");
|
||||||
|
version = properties.getProperty("version", "????");
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.log(Level.WARNING, "Failed to get version", e);
|
||||||
|
version = "????";
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
setResizable(false);
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("insets dialog"));
|
||||||
|
|
||||||
|
container.add(new JLabel(SwingHelper.createIcon(Creator.class, "about_header.png")), "dock north");
|
||||||
|
container.add(new JLabel("<html>Version " + version), "wrap");
|
||||||
|
container.add(new JLabel("<html>Licensed under GNU Lesser General Public License, version 3."), "wrap, gapbottom unrel");
|
||||||
|
container.add(new JLabel("<html>Created by the SKCraft team. Visit our website!"), "wrap, gapbottom unrel");
|
||||||
|
|
||||||
|
JButton okButton = new JButton("OK");
|
||||||
|
JButton sourceCodeButton = new JButton("Source Code");
|
||||||
|
JButton skCraftButton = new JButton("Website");
|
||||||
|
|
||||||
|
container.add(sourceCodeButton, "span, split 3, sizegroup bttn");
|
||||||
|
container.add(skCraftButton, "sizegroup bttn");
|
||||||
|
container.add(okButton, "tag ok, sizegroup bttn");
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
getRootPane().setDefaultButton(okButton);
|
||||||
|
getRootPane().registerKeyboardAction(e -> okButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
|
||||||
|
okButton.addActionListener(e -> dispose());
|
||||||
|
sourceCodeButton.addActionListener(e -> SwingHelper.openURL("https://github.com/SKCraft/Launcher", this));
|
||||||
|
skCraftButton.addActionListener(e -> SwingHelper.openURL("http://www.skcraft.com", this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showAboutDialog(Window parent) {
|
||||||
|
AboutDialog dialog = new AboutDialog(parent);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.swing.DirectoryField;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.swing.TextFieldPopupMenu;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class BuildDialog extends JDialog {
|
||||||
|
|
||||||
|
private final DirectoryField destDirField = new DirectoryField();
|
||||||
|
private final JTextField versionText = new JTextField(20);
|
||||||
|
private final JTextField manifestFilenameText = new JTextField(30);
|
||||||
|
@Getter
|
||||||
|
private BuildOptions options;
|
||||||
|
|
||||||
|
public BuildDialog(Window parent) {
|
||||||
|
super(parent, "Build Release", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
setResizable(false);
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
versionText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
manifestFilenameText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("insets dialog"));
|
||||||
|
|
||||||
|
container.add(new JLabel("Version:"));
|
||||||
|
container.add(versionText, "span");
|
||||||
|
|
||||||
|
container.add(new JLabel("Manifest Filename:"));
|
||||||
|
container.add(manifestFilenameText, "span");
|
||||||
|
|
||||||
|
container.add(new JLabel("Output Directory:"));
|
||||||
|
container.add(destDirField, "span");
|
||||||
|
|
||||||
|
JButton buildButton = new JButton("Build");
|
||||||
|
JButton cancelButton = new JButton("Cancel");
|
||||||
|
|
||||||
|
container.add(buildButton, "tag ok, span, split 2, sizegroup bttn");
|
||||||
|
container.add(cancelButton, "tag cancel, sizegroup bttn");
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
getRootPane().setDefaultButton(buildButton);
|
||||||
|
getRootPane().registerKeyboardAction(e -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
|
||||||
|
buildButton.addActionListener(e -> returnValue());
|
||||||
|
cancelButton.addActionListener(e -> dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void returnValue() {
|
||||||
|
String version = versionText.getText().trim();
|
||||||
|
String manifestFilename = manifestFilenameText.getText().trim();
|
||||||
|
|
||||||
|
if (version.isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(this, "A version string must be entered.", "Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifestFilename.isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(this, "A manifest filename must be entered.", "Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destDirField.getPath().isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(this, "A destination directory must be entered.", "Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
options = new BuildOptions(version, manifestFilename, new File(destDirField.getPath()));
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BuildOptions showBuildDialog(Window parent, String version, String manifestName, File destDir) {
|
||||||
|
BuildDialog dialog = new BuildDialog(parent);
|
||||||
|
dialog.versionText.setText(version);
|
||||||
|
dialog.manifestFilenameText.setText(manifestName);
|
||||||
|
dialog.destDirField.setPath(destDir.getAbsolutePath());
|
||||||
|
dialog.setVisible(true);
|
||||||
|
return dialog.getOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class BuildOptions {
|
||||||
|
private final String version;
|
||||||
|
private final String manifestFilename;
|
||||||
|
private final File destDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.jidesoft.swing.SearchableUtils;
|
||||||
|
import com.jidesoft.swing.TableSearchable;
|
||||||
|
import com.skcraft.launcher.builder.BuilderConfig;
|
||||||
|
import com.skcraft.launcher.builder.FeaturePattern;
|
||||||
|
import com.skcraft.launcher.builder.FnPatternList;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.FeaturePatternTableModel;
|
||||||
|
import com.skcraft.launcher.model.modpack.LaunchModifier;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.swing.TextFieldPopupMenu;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
public class BuilderConfigDialog extends JDialog {
|
||||||
|
|
||||||
|
private final JTextField nameText = new JTextField(20);
|
||||||
|
private final JTextField titleText = new JTextField(30);
|
||||||
|
private final JTextField gameVersionText = new JTextField(10);
|
||||||
|
private final JTextArea launchFlagsArea = new JTextArea(10, 40);
|
||||||
|
private final JTextArea userFilesIncludeArea = new JTextArea(15, 40);
|
||||||
|
private final JTextArea userFilesExcludeArea = new JTextArea(8, 40);
|
||||||
|
private final FeaturePatternTable featuresTable = new FeaturePatternTable();
|
||||||
|
private FeaturePatternTableModel featuresModel;
|
||||||
|
|
||||||
|
private final BuilderConfig config;
|
||||||
|
private boolean saved = false;
|
||||||
|
|
||||||
|
public BuilderConfigDialog(Window parent, BuilderConfig config) {
|
||||||
|
super(parent, "Modpack Properties", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
setResizable(false);
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
|
||||||
|
copyFrom();
|
||||||
|
|
||||||
|
nameText.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
nameText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
titleText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
gameVersionText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
launchFlagsArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
userFilesIncludeArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
|
||||||
|
launchFlagsArea.setFont(nameText.getFont());
|
||||||
|
userFilesIncludeArea.setFont(nameText.getFont());
|
||||||
|
userFilesExcludeArea.setFont(nameText.getFont());
|
||||||
|
|
||||||
|
JTabbedPane tabbedPane = new JTabbedPane();
|
||||||
|
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("fill, insets dialog"));
|
||||||
|
|
||||||
|
tabbedPane.addTab("Modpack", null, createMainPanel());
|
||||||
|
tabbedPane.addTab("Launch", null, createLaunchPanel());
|
||||||
|
tabbedPane.addTab("User Files", null, createUserFilesPanel());
|
||||||
|
tabbedPane.addTab("Optional Features", null, createFeaturesPanel());
|
||||||
|
|
||||||
|
container.add(tabbedPane, "span, grow, gapbottom unrel");
|
||||||
|
|
||||||
|
JButton saveButton = new JButton("Save");
|
||||||
|
JButton cancelButton = new JButton("Cancel");
|
||||||
|
|
||||||
|
container.add(saveButton, "tag ok, span, split 2, sizegroup bttn");
|
||||||
|
container.add(cancelButton, "tag cancel, sizegroup bttn");
|
||||||
|
|
||||||
|
getRootPane().setDefaultButton(saveButton);
|
||||||
|
getRootPane().registerKeyboardAction(event -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
saveButton.addActionListener(e -> {
|
||||||
|
if (nameText.getText().trim().isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(BuilderConfigDialog.this, "The 'Name' field cannot be empty.", "Input Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameVersionText.getText().trim().isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(BuilderConfigDialog.this, "The 'Game Version' field must be a Minecraft version.", "Input Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyTo();
|
||||||
|
saved = true;
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
cancelButton.addActionListener(e -> dispose());
|
||||||
|
|
||||||
|
TableSearchable tableSearchable = SearchableUtils.installSearchable(featuresTable);
|
||||||
|
tableSearchable.setMainIndex(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createMainPanel() {
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
SwingHelper.removeOpaqueness(container);
|
||||||
|
container.setLayout(new MigLayout("insets dialog"));
|
||||||
|
|
||||||
|
container.add(new JLabel("Name:"));
|
||||||
|
container.add(nameText, "span");
|
||||||
|
|
||||||
|
container.add(new JLabel("Title:"));
|
||||||
|
container.add(titleText, "span");
|
||||||
|
|
||||||
|
container.add(new JLabel("Game Version:"));
|
||||||
|
container.add(gameVersionText, "span");
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createLaunchPanel() {
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
SwingHelper.removeOpaqueness(container);
|
||||||
|
container.setLayout(new MigLayout("insets dialog"));
|
||||||
|
|
||||||
|
container.add(new JLabel("Launch Flags:"), "wrap");
|
||||||
|
container.add(SwingHelper.wrapScrollPane(launchFlagsArea), "span");
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createUserFilesPanel() {
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
SwingHelper.removeOpaqueness(container);
|
||||||
|
container.setLayout(new MigLayout("insets dialog"));
|
||||||
|
|
||||||
|
container.add(new JLabel("Include Patterns:"), "wrap");
|
||||||
|
container.add(SwingHelper.wrapScrollPane(userFilesIncludeArea), "span, gapbottom unrel");
|
||||||
|
|
||||||
|
container.add(new JLabel("Exclude Patterns:"), "wrap");
|
||||||
|
container.add(SwingHelper.wrapScrollPane(userFilesExcludeArea), "span");
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createFeaturesPanel() {
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
SwingHelper.removeOpaqueness(container);
|
||||||
|
container.setLayout(new MigLayout("fill, insets dialog"));
|
||||||
|
|
||||||
|
JButton newButton = new JButton("New...");
|
||||||
|
JButton editButton = new JButton("Edit...");
|
||||||
|
JButton deleteButton = new JButton("Delete...");
|
||||||
|
|
||||||
|
container.add(newButton, "span, split 3, sizegroup bttn");
|
||||||
|
container.add(editButton, "sizegroup bttn");
|
||||||
|
container.add(deleteButton, "sizegroup bttn");
|
||||||
|
|
||||||
|
container.add(SwingHelper.wrapScrollPane(featuresTable), "grow, w 10:100:null, gaptop 10");
|
||||||
|
|
||||||
|
newButton.addActionListener(e -> {
|
||||||
|
FeaturePattern pattern = new FeaturePattern();
|
||||||
|
if (FeaturePatternDialog.showEditor(BuilderConfigDialog.this, pattern)) {
|
||||||
|
featuresModel.addFeature(pattern);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
editButton.addActionListener(e -> {
|
||||||
|
int index = featuresTable.getSelectedRow();
|
||||||
|
if (index > -1) {
|
||||||
|
FeaturePattern pattern = featuresModel.getFeature(index);
|
||||||
|
FeaturePatternDialog.showEditor(BuilderConfigDialog.this, pattern);
|
||||||
|
featuresModel.fireTableDataChanged();
|
||||||
|
} else {
|
||||||
|
SwingHelper.showErrorDialog(BuilderConfigDialog.this, "Select a feature first.", "No Selection");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteButton.addActionListener(e -> {
|
||||||
|
int index = featuresTable.getSelectedRow();
|
||||||
|
if (index > -1) {
|
||||||
|
FeaturePattern pattern = featuresModel.getFeature(index);
|
||||||
|
if (SwingHelper.confirmDialog(BuilderConfigDialog.this, "Are you sure that you want to delete '" + pattern.getFeature().getName() + "'?", "Delete")) {
|
||||||
|
featuresModel.removeFeature(index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SwingHelper.showErrorDialog(BuilderConfigDialog.this, "Select a feature first.", "No Selection");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyFrom() {
|
||||||
|
SwingHelper.setTextAndResetCaret(nameText, config.getName());
|
||||||
|
SwingHelper.setTextAndResetCaret(titleText, config.getTitle());
|
||||||
|
SwingHelper.setTextAndResetCaret(gameVersionText, config.getGameVersion());
|
||||||
|
SwingHelper.setTextAndResetCaret(launchFlagsArea, SwingHelper.listToLines(config.getLaunchModifier().getFlags()));
|
||||||
|
SwingHelper.setTextAndResetCaret(userFilesIncludeArea, SwingHelper.listToLines(config.getUserFiles().getInclude()));
|
||||||
|
SwingHelper.setTextAndResetCaret(userFilesExcludeArea, SwingHelper.listToLines(config.getUserFiles().getExclude()));
|
||||||
|
featuresModel = new FeaturePatternTableModel(config.getFeatures());
|
||||||
|
featuresTable.setModel(featuresModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyTo() {
|
||||||
|
config.setName(nameText.getText().trim());
|
||||||
|
config.setTitle(Strings.emptyToNull(titleText.getText().trim()));
|
||||||
|
config.setGameVersion(gameVersionText.getText().trim());
|
||||||
|
|
||||||
|
LaunchModifier launchModifier = config.getLaunchModifier();
|
||||||
|
FnPatternList userFiles = config.getUserFiles();
|
||||||
|
|
||||||
|
launchModifier.setFlags(SwingHelper.linesToList(launchFlagsArea.getText()));
|
||||||
|
userFiles.setInclude(SwingHelper.linesToList(userFilesIncludeArea.getText()));
|
||||||
|
userFiles.setExclude(SwingHelper.linesToList(userFilesExcludeArea.getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean showEditor(Window window, BuilderConfig config) {
|
||||||
|
BuilderConfigDialog dialog = new BuilderConfigDialog(window, config);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
return dialog.saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.swing.DirectoryField;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class DeployServerDialog extends JDialog {
|
||||||
|
|
||||||
|
private final DirectoryField destDirField = new DirectoryField();
|
||||||
|
private final JCheckBox cleanModsCheck = new JCheckBox("Delete \"mods/\" folder before deploying");
|
||||||
|
@Getter
|
||||||
|
private DeployOptions options;
|
||||||
|
|
||||||
|
public DeployServerDialog(Window parent) {
|
||||||
|
super(parent, "Deploy Server Files", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
setResizable(false);
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
|
||||||
|
cleanModsCheck.setSelected(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("insets dialog"));
|
||||||
|
|
||||||
|
container.add(new JLabel("Output Directory:"));
|
||||||
|
container.add(destDirField, "span");
|
||||||
|
|
||||||
|
container.add(cleanModsCheck, "span, gapbottom unrel");
|
||||||
|
|
||||||
|
JButton buildButton = new JButton("Deploy");
|
||||||
|
JButton cancelButton = new JButton("Cancel");
|
||||||
|
|
||||||
|
container.add(buildButton, "tag ok, span, split 2, sizegroup bttn");
|
||||||
|
container.add(cancelButton, "tag cancel, sizegroup bttn");
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
getRootPane().setDefaultButton(buildButton);
|
||||||
|
getRootPane().registerKeyboardAction(e -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
|
||||||
|
buildButton.addActionListener(e -> returnValue());
|
||||||
|
cancelButton.addActionListener(e -> dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void returnValue() {
|
||||||
|
String dir = destDirField.getPath();
|
||||||
|
|
||||||
|
if (dir.isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(this, "A directory must be entered.", "Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File dirFile = new File(dir);
|
||||||
|
|
||||||
|
if (!dirFile.isDirectory()) {
|
||||||
|
SwingHelper.showErrorDialog(this, "The selected path is not a directory that exists.", "Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
options = new DeployOptions(dirFile,cleanModsCheck.isSelected());
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DeployOptions showDeployDialog(Window parent, File destDir) {
|
||||||
|
DeployServerDialog dialog = new DeployServerDialog(parent);
|
||||||
|
if (destDir != null) {
|
||||||
|
dialog.destDirField.setPath(destDir.getAbsolutePath());
|
||||||
|
}
|
||||||
|
dialog.setVisible(true);
|
||||||
|
return dialog.getOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DeployOptions {
|
||||||
|
private final File destDir;
|
||||||
|
private final boolean cleanMods;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.skcraft.launcher.builder.FeaturePattern;
|
||||||
|
import com.skcraft.launcher.builder.FnPatternList;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.RecommendationComboBoxModel;
|
||||||
|
import com.skcraft.launcher.model.modpack.Feature;
|
||||||
|
import com.skcraft.launcher.model.modpack.Feature.Recommendation;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.swing.TextFieldPopupMenu;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
public class FeaturePatternDialog extends JDialog {
|
||||||
|
|
||||||
|
private static final Joiner NEW_LINE_JOINER = Joiner.on("\n");
|
||||||
|
|
||||||
|
private final JTextField nameText = new JTextField(20);
|
||||||
|
private final JTextArea descArea = new JTextArea(3, 40);
|
||||||
|
private final JComboBox recommendationCombo = new JComboBox(new RecommendationComboBoxModel());
|
||||||
|
private final JCheckBox selectedCheck = new JCheckBox("Selected by default");
|
||||||
|
private final JTextArea includeArea = new JTextArea(8, 40);
|
||||||
|
private final JTextArea excludeArea = new JTextArea(3, 40);
|
||||||
|
|
||||||
|
private final FeaturePattern pattern;
|
||||||
|
private boolean saved = false;
|
||||||
|
|
||||||
|
public FeaturePatternDialog(Window parent, FeaturePattern pattern) {
|
||||||
|
super(parent, "Configure Feature", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
this.pattern = pattern;
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
setResizable(false);
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
|
||||||
|
copyFrom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
nameText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
descArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
includeArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
excludeArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
|
||||||
|
descArea.setFont(nameText.getFont());
|
||||||
|
includeArea.setFont(nameText.getFont());
|
||||||
|
excludeArea.setFont(nameText.getFont());
|
||||||
|
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("insets dialog"));
|
||||||
|
|
||||||
|
container.add(new JLabel("Feature Name:"));
|
||||||
|
container.add(nameText, "span");
|
||||||
|
|
||||||
|
container.add(new JLabel("Recommendation:"));
|
||||||
|
container.add(recommendationCombo, "span");
|
||||||
|
|
||||||
|
container.add(selectedCheck, "span");
|
||||||
|
|
||||||
|
container.add(new JLabel("Description:"), "wrap");
|
||||||
|
container.add(SwingHelper.wrapScrollPane(descArea), "span");
|
||||||
|
|
||||||
|
container.add(new JLabel("Include Patterns:"), "wrap");
|
||||||
|
container.add(SwingHelper.wrapScrollPane(includeArea), "span");
|
||||||
|
|
||||||
|
container.add(new JLabel("Exclude Patterns:"), "wrap");
|
||||||
|
container.add(SwingHelper.wrapScrollPane(excludeArea), "span, gapbottom unrel");
|
||||||
|
|
||||||
|
JButton okButton = new JButton("OK");
|
||||||
|
JButton cancelButton = new JButton("Cancel");
|
||||||
|
|
||||||
|
container.add(okButton, "tag ok, span, split 2, sizegroup bttn");
|
||||||
|
container.add(cancelButton, "tag cancel, sizegroup bttn");
|
||||||
|
|
||||||
|
getRootPane().setDefaultButton(okButton);
|
||||||
|
getRootPane().registerKeyboardAction(e -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
okButton.addActionListener(e -> {
|
||||||
|
if (nameText.getText().trim().isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(FeaturePatternDialog.this, "The 'Feature Name' field cannot be empty.", "Input Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (descArea.getText().trim().isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(FeaturePatternDialog.this, "The 'Description' field cannot be empty.", "Input Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeArea.getText().trim().isEmpty()) {
|
||||||
|
SwingHelper.showErrorDialog(FeaturePatternDialog.this, "The 'Include Patterns' field cannot be empty.", "Input Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyTo();
|
||||||
|
saved = true;
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
cancelButton.addActionListener(e -> dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyFrom() {
|
||||||
|
if (pattern.getFeature() == null) {
|
||||||
|
pattern.setFeature(new Feature());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern.getFilePatterns() == null) {
|
||||||
|
pattern.setFilePatterns(new FnPatternList());
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingHelper.setTextAndResetCaret(nameText, pattern.getFeature().getName());
|
||||||
|
SwingHelper.setTextAndResetCaret(descArea, pattern.getFeature().getDescription());
|
||||||
|
recommendationCombo.setSelectedItem(pattern.getFeature().getRecommendation());
|
||||||
|
selectedCheck.setSelected(pattern.getFeature().isSelected());
|
||||||
|
SwingHelper.setTextAndResetCaret(includeArea, NEW_LINE_JOINER.join(pattern.getFilePatterns().getInclude()));
|
||||||
|
SwingHelper.setTextAndResetCaret(excludeArea, NEW_LINE_JOINER.join(pattern.getFilePatterns().getExclude()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyTo() {
|
||||||
|
pattern.getFeature().setName(nameText.getText().trim());
|
||||||
|
pattern.getFeature().setDescription(descArea.getText().trim());
|
||||||
|
pattern.getFeature().setRecommendation((Recommendation) recommendationCombo.getSelectedItem());
|
||||||
|
pattern.getFeature().setSelected(selectedCheck.isSelected());
|
||||||
|
pattern.getFilePatterns().setInclude(SwingHelper.linesToList(includeArea.getText()));
|
||||||
|
pattern.getFilePatterns().setExclude(SwingHelper.linesToList(excludeArea.getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean showEditor(Window window, FeaturePattern pattern) {
|
||||||
|
FeaturePatternDialog dialog = new FeaturePatternDialog(window, pattern);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
return dialog.saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.swing.DefaultTable;
|
||||||
|
|
||||||
|
import javax.swing.table.TableModel;
|
||||||
|
|
||||||
|
class FeaturePatternTable extends DefaultTable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setModel(TableModel dataModel) {
|
||||||
|
super.setModel(dataModel);
|
||||||
|
try {
|
||||||
|
getColumnModel().getColumn(1).setMaxWidth(80);
|
||||||
|
getColumnModel().getColumn(2).setMaxWidth(80);
|
||||||
|
} catch (ArrayIndexOutOfBoundsException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.jidesoft.swing.SearchableUtils;
|
||||||
|
import com.jidesoft.swing.TableSearchable;
|
||||||
|
import com.skcraft.launcher.creator.Creator;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.ListingType;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.ListingTypeComboBoxModel;
|
||||||
|
import com.skcraft.launcher.swing.DefaultTable;
|
||||||
|
import com.skcraft.launcher.swing.DirectoryField;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.swing.TableColumnAdjuster;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
public class GenerateListingDialog extends JDialog {
|
||||||
|
|
||||||
|
@Getter private final DirectoryField destDirField = new DirectoryField();
|
||||||
|
@Getter private final JComboBox<ListingType> listingTypeCombo = new JComboBox<>(new ListingTypeComboBoxModel());
|
||||||
|
@Getter private final JTable manifestsTable = new DefaultTable();
|
||||||
|
@Getter private final JLabel gameKeyWarning = new JLabel("Selected listing type won't support adding modpacks using 'game keys'.", SwingHelper.createIcon(Creator.class, "warning_icon.png"), SwingConstants.LEFT);
|
||||||
|
|
||||||
|
@Getter private final JButton editManifestButton = new JButton("Modify...");
|
||||||
|
|
||||||
|
@Getter private final JButton generateButton = new JButton("Generate");
|
||||||
|
@Getter private final JButton cancelButton = new JButton("Cancel");
|
||||||
|
|
||||||
|
@Getter private final TableColumnAdjuster manifestsTableAdjuster = new TableColumnAdjuster(manifestsTable);
|
||||||
|
|
||||||
|
public GenerateListingDialog(Window parent) {
|
||||||
|
super(parent, "Generate Package Listing", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
manifestsTableAdjuster.adjustColumns();
|
||||||
|
manifestsTableAdjuster.setDynamicAdjustment(true);
|
||||||
|
|
||||||
|
manifestsTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
|
||||||
|
manifestsTable.setAutoCreateRowSorter(true);
|
||||||
|
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("insets dialog, fill", "[grow 0][grow 100]"));
|
||||||
|
|
||||||
|
container.add(new JLabel("Output Directory:"));
|
||||||
|
container.add(destDirField, "span");
|
||||||
|
|
||||||
|
container.add(new JLabel("Package Listing Type:"));
|
||||||
|
container.add(listingTypeCombo, "span");
|
||||||
|
container.add(gameKeyWarning, "span, skip 1, hidemode 3");
|
||||||
|
|
||||||
|
container.add(new JLabel("Modpacks to Include:"), "span, gaptop unrel");
|
||||||
|
container.add(SwingHelper.wrapScrollPane(manifestsTable), "grow, pushy, span, w 500:650, h 170");
|
||||||
|
container.add(editManifestButton, "gapbottom unrel, span, split 2");
|
||||||
|
container.add(new JLabel("<html>Previously-selected modpacks and those in the _upload directory are the available options."), "gapbottom unrel");
|
||||||
|
|
||||||
|
container.add(generateButton, "tag ok, span, split 2, sizegroup bttn");
|
||||||
|
container.add(cancelButton, "tag cancel, sizegroup bttn");
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
getRootPane().setDefaultButton(generateButton);
|
||||||
|
getRootPane().registerKeyboardAction(e -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
|
||||||
|
TableSearchable tableSearchable = SearchableUtils.installSearchable(manifestsTable);
|
||||||
|
tableSearchable.setMainIndex(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.swing.TextFieldPopupMenu;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
public class ManifestEntryDialog extends JDialog {
|
||||||
|
|
||||||
|
@Getter private final JSpinner prioritySpinner = new JSpinner();
|
||||||
|
@Getter private final JTextArea gameKeysText = new JTextArea(5, 30);
|
||||||
|
@Getter private final JCheckBox includeCheck = new JCheckBox("Include in package listing");
|
||||||
|
|
||||||
|
@Getter private final JButton okButton = new JButton("OK");
|
||||||
|
@Getter private final JButton cancelButton = new JButton("Cancel");
|
||||||
|
|
||||||
|
public ManifestEntryDialog(Window parent) {
|
||||||
|
super(parent, "Modpack Entry", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
setResizable(false);
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
gameKeysText.setFont(prioritySpinner.getFont());
|
||||||
|
|
||||||
|
prioritySpinner.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
gameKeysText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("insets dialog"));
|
||||||
|
|
||||||
|
container.add(includeCheck, "span, gapbottom unrel");
|
||||||
|
|
||||||
|
container.add(new JLabel("Priority:"));
|
||||||
|
container.add(prioritySpinner, "span, split 2, w 50");
|
||||||
|
container.add(new JLabel("(Greater is higher)"));
|
||||||
|
|
||||||
|
container.add(new JLabel("Game Keys:"));
|
||||||
|
container.add(SwingHelper.wrapScrollPane(gameKeysText), "span");
|
||||||
|
|
||||||
|
container.add(okButton, "tag ok, span, split 2, sizegroup bttn, gaptop unrel");
|
||||||
|
container.add(cancelButton, "tag cancel, sizegroup bttn");
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
getRootPane().setDefaultButton(okButton);
|
||||||
|
getRootPane().registerKeyboardAction(e -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.jidesoft.swing.SearchableUtils;
|
||||||
|
import com.jidesoft.swing.TableSearchable;
|
||||||
|
import com.skcraft.launcher.creator.Creator;
|
||||||
|
import com.skcraft.launcher.swing.DefaultTable;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.swing.TableColumnAdjuster;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
public class PackManagerFrame extends JFrame {
|
||||||
|
|
||||||
|
@Getter private final JButton newPackButton = new JButton("New Pack", SwingHelper.createIcon(Creator.class, "new.png"));
|
||||||
|
@Getter private final JButton importButton = new JButton("Add Existing", SwingHelper.createIcon(Creator.class, "import.png"));
|
||||||
|
@Getter private final JButton editConfigButton = new JButton("Modify", SwingHelper.createIcon(Creator.class, "edit.png"));
|
||||||
|
@Getter private final JButton openFolderButton = new JButton("Open", SwingHelper.createIcon(Creator.class, "open_folder.png"));
|
||||||
|
@Getter private final JButton checkProblemsButton = new JButton("Check", SwingHelper.createIcon(Creator.class, "check.png"));
|
||||||
|
@Getter private final JButton testButton = new JButton("Test", SwingHelper.createIcon(Creator.class, "test.png"));
|
||||||
|
@Getter private final JButton buildButton = new JButton("Build", SwingHelper.createIcon(Creator.class, "build.png"));
|
||||||
|
|
||||||
|
@Getter private final JMenuItem newPackMenuItem = new JMenuItem("New Pack...");
|
||||||
|
@Getter private final JMenuItem newPackAtLocationMenuItem = new JMenuItem("New Pack at Location...");
|
||||||
|
@Getter private final JMenuItem importPackMenuItem = new JMenuItem("Add Existing Pack...");
|
||||||
|
@Getter private final JMenuItem changePackLocationMenuItem = new JMenuItem("Change Pack Location...");
|
||||||
|
@Getter private final JMenuItem refreshMenuItem = new JMenuItem("Reload Workspace");
|
||||||
|
@Getter private final JMenuItem removePackItem = new JMenuItem("Remove Pack...");
|
||||||
|
@Getter private final JMenuItem deletePackItem = new JMenuItem("Delete Pack Forever...");
|
||||||
|
@Getter private final JMenuItem quitMenuItem = new JMenuItem("Exit");
|
||||||
|
@Getter private final JMenuItem editConfigMenuItem = new JMenuItem("Edit modpack.json...");
|
||||||
|
@Getter private final JMenuItem openFolderMenuItem = new JMenuItem("Open Directory");
|
||||||
|
@Getter private final JMenuItem checkProblemsMenuItem = new JMenuItem("Scan for Problems...");
|
||||||
|
@Getter private final JMenuItem testMenuItem = new JMenuItem("Test");
|
||||||
|
@Getter private final JMenuItem testOnlineMenuItem = new JMenuItem("Test Online");
|
||||||
|
@Getter private final JMenuItem optionsMenuItem = new JMenuItem("Test Launcher Options...");
|
||||||
|
@Getter private final JMenuItem instanceOptionsMenuItem = new JMenuItem("Test Instance Options...");
|
||||||
|
@Getter private final JMenuItem clearInstanceMenuItem = new JMenuItem("Delete Test Launcher Instances");
|
||||||
|
@Getter private final JMenuItem clearWebRootMenuItem = new JMenuItem("Empty Test Web Server");
|
||||||
|
@Getter private final JMenuItem buildMenuItem = new JMenuItem("Build Pack...");
|
||||||
|
@Getter private final JMenuItem deployServerMenuItem = new JMenuItem("Deploy Server...");
|
||||||
|
@Getter private final JMenuItem generatePackagesMenuItem = new JMenuItem("Generate packages.json...");
|
||||||
|
@Getter private final JMenuItem openOutputFolderMenuItem = new JMenuItem("Open Upload Folder");
|
||||||
|
@Getter private final JMenuItem versionCheckMenuItem = new JMenuItem("Check for Mod Updates");
|
||||||
|
@Getter private final JMenuItem openWorkspaceFolderMenuItem = new JMenuItem("Open Workspace Folder");
|
||||||
|
@Getter private final JMenuItem openLauncherFolderMenuItem = new JMenuItem("Open Test Launcher Folder");
|
||||||
|
@Getter private final JMenuItem openWebRootMenuItem = new JMenuItem("Open Test Web Server Folder");
|
||||||
|
@Getter private final JMenuItem openConsoleMenuItem = new JMenuItem("Open Console");
|
||||||
|
@Getter private final JMenuItem docsMenuItem = new JMenuItem("Documentation");
|
||||||
|
@Getter private final JMenuItem aboutMenuItem = new JMenuItem("About");
|
||||||
|
|
||||||
|
@Getter private final JTable packTable = new DefaultTable();
|
||||||
|
|
||||||
|
public PackManagerFrame() {
|
||||||
|
super("Modpack Creator");
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
initMenu();
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
|
||||||
|
SwingHelper.setFrameIcon(this, Creator.class, "icon.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
TableColumnAdjuster adjuster = new TableColumnAdjuster(packTable);
|
||||||
|
adjuster.adjustColumns();
|
||||||
|
adjuster.setDynamicAdjustment(true);
|
||||||
|
|
||||||
|
packTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
|
||||||
|
packTable.setAutoCreateRowSorter(true);
|
||||||
|
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("fill, wrap 1"));
|
||||||
|
|
||||||
|
container.add(createToolbar(), "dock north");
|
||||||
|
container.add(SwingHelper.wrapScrollPane(packTable), "grow, span, w null:800:null");
|
||||||
|
|
||||||
|
TableSearchable tableSearchable = SearchableUtils.installSearchable(packTable);
|
||||||
|
tableSearchable.setMainIndex(-1);
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JToolBar createToolbar() {
|
||||||
|
JToolBar toolBar = new JToolBar("Toolbar");
|
||||||
|
|
||||||
|
toolBar.setFloatable(false);
|
||||||
|
|
||||||
|
toolBar.add(newPackButton);
|
||||||
|
toolBar.add(importButton);
|
||||||
|
toolBar.addSeparator();
|
||||||
|
toolBar.add(editConfigButton);
|
||||||
|
toolBar.add(openFolderButton);
|
||||||
|
toolBar.add(checkProblemsButton);
|
||||||
|
toolBar.addSeparator();
|
||||||
|
toolBar.add(testButton);
|
||||||
|
toolBar.add(buildButton);
|
||||||
|
|
||||||
|
return toolBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initMenu() {
|
||||||
|
int ctrlKeyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
|
||||||
|
|
||||||
|
newPackMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ctrlKeyMask));
|
||||||
|
newPackAtLocationMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ctrlKeyMask | Event.SHIFT_MASK));
|
||||||
|
editConfigMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, ctrlKeyMask));
|
||||||
|
openFolderMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ctrlKeyMask | Event.SHIFT_MASK));
|
||||||
|
testMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0));
|
||||||
|
testOnlineMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0));
|
||||||
|
buildMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F10, Event.SHIFT_MASK));
|
||||||
|
deployServerMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F9, Event.SHIFT_MASK));
|
||||||
|
docsMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
|
||||||
|
|
||||||
|
JMenuBar menuBar;
|
||||||
|
JMenu menu;
|
||||||
|
|
||||||
|
menuBar = new JMenuBar();
|
||||||
|
menuBar.setBorder(BorderFactory.createEmptyBorder());
|
||||||
|
|
||||||
|
Insets menuInset = new Insets(2, 2, 2, 2);
|
||||||
|
|
||||||
|
menu = new JMenu("File");
|
||||||
|
menu.setMargin(menuInset);
|
||||||
|
menu.setMnemonic('f');
|
||||||
|
menuBar.add(menu);
|
||||||
|
menu.add(newPackMenuItem);
|
||||||
|
menu.add(newPackAtLocationMenuItem);
|
||||||
|
menu.add(importPackMenuItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(changePackLocationMenuItem);
|
||||||
|
menu.add(removePackItem);
|
||||||
|
menu.add(deletePackItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(refreshMenuItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(quitMenuItem);
|
||||||
|
|
||||||
|
menu = new JMenu("Edit");
|
||||||
|
menu.setMargin(menuInset);
|
||||||
|
menu.setMnemonic('e');
|
||||||
|
menuBar.add(menu);
|
||||||
|
menu.add(editConfigMenuItem);
|
||||||
|
menu.add(openFolderMenuItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(checkProblemsMenuItem);
|
||||||
|
|
||||||
|
menu = new JMenu("Test");
|
||||||
|
menu.setMargin(menuInset);
|
||||||
|
menu.setMnemonic('t');
|
||||||
|
menuBar.add(menu);
|
||||||
|
menu.add(testMenuItem);
|
||||||
|
menu.add(testOnlineMenuItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(optionsMenuItem);
|
||||||
|
menu.add(instanceOptionsMenuItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(clearInstanceMenuItem);
|
||||||
|
menu.add(clearWebRootMenuItem);
|
||||||
|
|
||||||
|
menu = new JMenu("Build");
|
||||||
|
menu.setMargin(menuInset);
|
||||||
|
menu.setMnemonic('b');
|
||||||
|
menuBar.add(menu);
|
||||||
|
menu.add(buildMenuItem);
|
||||||
|
menu.add(deployServerMenuItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(generatePackagesMenuItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(openOutputFolderMenuItem);
|
||||||
|
|
||||||
|
menu = new JMenu("Tools");
|
||||||
|
menu.setMargin(menuInset);
|
||||||
|
menu.setMnemonic('t');
|
||||||
|
menuBar.add(menu);
|
||||||
|
menu.add(versionCheckMenuItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(openWorkspaceFolderMenuItem);
|
||||||
|
menu.add(openLauncherFolderMenuItem);
|
||||||
|
menu.add(openWebRootMenuItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(openConsoleMenuItem);
|
||||||
|
|
||||||
|
menu = new JMenu("Help");
|
||||||
|
menu.setMargin(menuInset);
|
||||||
|
menu.setMnemonic('h');
|
||||||
|
menuBar.add(menu);
|
||||||
|
menu.add(docsMenuItem);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(aboutMenuItem);
|
||||||
|
|
||||||
|
setJMenuBar(menuBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.swing.DefaultTable;
|
||||||
|
|
||||||
|
import javax.swing.table.TableModel;
|
||||||
|
|
||||||
|
class ProblemTable extends DefaultTable {
|
||||||
|
|
||||||
|
public ProblemTable() {
|
||||||
|
super();
|
||||||
|
setTableHeader(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setModel(TableModel dataModel) {
|
||||||
|
super.setModel(dataModel);
|
||||||
|
try {
|
||||||
|
getColumnModel().getColumn(0).setMaxWidth(20);
|
||||||
|
} catch (ArrayIndexOutOfBoundsException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Problem;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.ProblemTableModel;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.swing.TextFieldPopupMenu;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ProblemViewer extends JDialog {
|
||||||
|
|
||||||
|
private static final String DEFAULT_EXPLANATION = "Select a problem on the left to see the explanation here.";
|
||||||
|
private final ProblemTable problemTable = new ProblemTable();
|
||||||
|
private final ProblemTableModel problemTableModel;
|
||||||
|
private final JTextArea explanationText = new JTextArea(DEFAULT_EXPLANATION);
|
||||||
|
|
||||||
|
public ProblemViewer(Window parent, List<Problem> problems) {
|
||||||
|
super(parent, "Potential Problems", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
|
||||||
|
problemTableModel = new ProblemTableModel(problems);
|
||||||
|
problemTable.setModel(problemTableModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
explanationText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||||
|
|
||||||
|
explanationText.setFont(new JTextField().getFont());
|
||||||
|
explanationText.setEditable(false);
|
||||||
|
explanationText.setLineWrap(true);
|
||||||
|
explanationText.setWrapStyleWord(true);
|
||||||
|
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("fill, insets dialog"));
|
||||||
|
|
||||||
|
JSplitPane splitPane = new JSplitPane(
|
||||||
|
JSplitPane.HORIZONTAL_SPLIT,
|
||||||
|
SwingHelper.wrapScrollPane(problemTable), SwingHelper.
|
||||||
|
wrapScrollPane(explanationText));
|
||||||
|
splitPane.setDividerLocation(180);
|
||||||
|
SwingHelper.flattenJSplitPane(splitPane);
|
||||||
|
|
||||||
|
container.add(splitPane, "grow, w 220:500, h 240, wrap");
|
||||||
|
|
||||||
|
JButton closeButton = new JButton("Close");
|
||||||
|
container.add(closeButton, "tag cancel, gaptop unrel");
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
getRootPane().setDefaultButton(closeButton);
|
||||||
|
getRootPane().registerKeyboardAction(e -> closeButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
|
||||||
|
problemTable.getSelectionModel().addListSelectionListener(e -> {
|
||||||
|
Problem selected = problemTableModel.getProblem(problemTable.getSelectedRow());
|
||||||
|
if (selected != null) {
|
||||||
|
SwingHelper.setTextAndResetCaret(explanationText, selected.getExplanation());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
closeButton.addActionListener(e -> dispose());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.jidesoft.swing.SearchableUtils;
|
||||||
|
import com.jidesoft.swing.TableSearchable;
|
||||||
|
import com.skcraft.launcher.swing.DefaultTable;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import com.skcraft.launcher.swing.TableColumnAdjuster;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
public class VersionCheckDialog extends JDialog {
|
||||||
|
|
||||||
|
@Getter private final JTable knownModsTable = new DefaultTable();
|
||||||
|
@Getter private final JTable unknownModsTable = new DefaultTable();
|
||||||
|
@Getter private final JButton closeButton = new JButton("Close");
|
||||||
|
private final TableColumnAdjuster updateTableAdjuster = new TableColumnAdjuster(knownModsTable);
|
||||||
|
private final TableColumnAdjuster unknownTableAdjuster = new TableColumnAdjuster(unknownModsTable);
|
||||||
|
|
||||||
|
public VersionCheckDialog(Window parent) {
|
||||||
|
super(parent, "Update Check", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
updateTableAdjuster.adjustColumns();
|
||||||
|
updateTableAdjuster.setDynamicAdjustment(true);
|
||||||
|
|
||||||
|
unknownTableAdjuster.adjustColumns();
|
||||||
|
unknownTableAdjuster.setDynamicAdjustment(true);
|
||||||
|
|
||||||
|
knownModsTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
|
||||||
|
knownModsTable.setAutoCreateRowSorter(true);
|
||||||
|
|
||||||
|
unknownModsTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
|
||||||
|
unknownModsTable.setAutoCreateRowSorter(true);
|
||||||
|
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("insets dialog, fill"));
|
||||||
|
|
||||||
|
container.add(new JLabel("With Potential Updates:"), "span");
|
||||||
|
container.add(SwingHelper.wrapScrollPane(knownModsTable), "grow, pushy, span, w 500:900, h 230");
|
||||||
|
|
||||||
|
container.add(new JLabel("Unknown Status:"), "span");
|
||||||
|
container.add(SwingHelper.wrapScrollPane(unknownModsTable), "grow, pushy, span, w 500:900, h 150, gapbottom unrel, wrap");
|
||||||
|
|
||||||
|
container.add(new JLabel("Version data is sourced from NotEnoughMods.com."), "");
|
||||||
|
container.add(closeButton, "tag cancel, sizegroup bttn");
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
getRootPane().registerKeyboardAction(e -> closeButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
|
||||||
|
TableSearchable tableSearchable;
|
||||||
|
|
||||||
|
tableSearchable = SearchableUtils.installSearchable(knownModsTable);
|
||||||
|
tableSearchable.setMainIndex(-1);
|
||||||
|
|
||||||
|
tableSearchable = SearchableUtils.installSearchable(unknownModsTable);
|
||||||
|
tableSearchable.setMainIndex(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.dialog;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.creator.Creator;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.RecentEntry;
|
||||||
|
import com.skcraft.launcher.creator.swing.BorderCellRenderer;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
public class WelcomeDialog extends JFrame {
|
||||||
|
|
||||||
|
@Getter private final JButton newButton = new JButton("New Workspace...", SwingHelper.createIcon(Creator.class, "new.png"));
|
||||||
|
@Getter private final JButton openButton = new JButton("Open Workspace...", SwingHelper.createIcon(Creator.class, "open_folder.png"));
|
||||||
|
@Getter private final JButton helpButton = new JButton("Help");
|
||||||
|
@Getter private final JButton aboutButton = new JButton("About");
|
||||||
|
@Getter private final JButton quitButton = new JButton("Quit");
|
||||||
|
@Getter private final JList<RecentEntry> recentList = new JList<>();
|
||||||
|
|
||||||
|
public WelcomeDialog() {
|
||||||
|
super("Modpack Creator");
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
setResizable(false);
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
|
||||||
|
SwingHelper.setFrameIcon(this, Creator.class, "icon.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
recentList.setCellRenderer(new BorderCellRenderer(BorderFactory.createEmptyBorder(5, 5, 5, 5)));
|
||||||
|
|
||||||
|
JPanel container = new JPanel();
|
||||||
|
container.setLayout(new MigLayout("insets 50 20"));
|
||||||
|
|
||||||
|
container.add(new JLabel(SwingHelper.createIcon(Creator.class, "welcome_logo.png")), "wrap, gap 20 20, gapbottom 30");
|
||||||
|
|
||||||
|
container.add(newButton, "grow, gap 50 50, wrap");
|
||||||
|
container.add(openButton, "grow, gap 50 50, wrap");
|
||||||
|
|
||||||
|
JScrollPane recentScrollPane = new JScrollPane(recentList);
|
||||||
|
recentScrollPane.setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, Color.LIGHT_GRAY));
|
||||||
|
recentScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
|
||||||
|
container.add(recentScrollPane, "dock east, w 280, h 390");
|
||||||
|
|
||||||
|
JPanel buttons = new JPanel();
|
||||||
|
buttons.setLayout(new MigLayout("insets 20", "[][]push[]"));
|
||||||
|
buttons.add(helpButton);
|
||||||
|
buttons.add(aboutButton);
|
||||||
|
buttons.add(quitButton);
|
||||||
|
container.add(buttons, "dock south");
|
||||||
|
|
||||||
|
add(container, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
getRootPane().registerKeyboardAction(e -> quitButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.creator;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CreatorConfig {
|
||||||
|
|
||||||
|
private List<RecentEntry> recentEntries = Lists.newArrayList();
|
||||||
|
private boolean offlineEnabled;
|
||||||
|
|
||||||
|
public void setRecentEntries(List<RecentEntry> recentEntries) {
|
||||||
|
this.recentEntries = recentEntries != null ? recentEntries : Lists.newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return super.equals(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return super.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.creator;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.skcraft.launcher.model.modpack.ManifestInfo;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ManifestEntry implements Comparable<ManifestEntry> {
|
||||||
|
|
||||||
|
private boolean selected = false;
|
||||||
|
private ManifestInfo manifestInfo;
|
||||||
|
private List<String> gameKeys = Lists.newArrayList();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(ManifestEntry o) {
|
||||||
|
return manifestInfo.compareTo(o.getManifestInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.creator;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ModFile {
|
||||||
|
|
||||||
|
private File file;
|
||||||
|
private String modId;
|
||||||
|
private String name;
|
||||||
|
private String gameVersion;
|
||||||
|
private String version;
|
||||||
|
private String latestVersion;
|
||||||
|
private String latestDevVersion;
|
||||||
|
private URL url;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public String getCleanVersion() {
|
||||||
|
String version = getVersion();
|
||||||
|
return version != null ?
|
||||||
|
version
|
||||||
|
.replaceAll("^" + Pattern.quote(gameVersion) + "\\-", "")
|
||||||
|
.replaceAll("\\-" + Pattern.quote(gameVersion) + "$", "")
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.creator;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.skcraft.launcher.builder.BuilderConfig;
|
||||||
|
import com.skcraft.launcher.builder.BuilderOptions;
|
||||||
|
import com.skcraft.launcher.persistence.Persistence;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Pack {
|
||||||
|
|
||||||
|
private String location;
|
||||||
|
@JsonIgnore private Workspace workspace;
|
||||||
|
@JsonIgnore private BuilderConfig cachedConfig;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public File getDirectory() {
|
||||||
|
File path = new File(location);
|
||||||
|
if (path.isAbsolute()) {
|
||||||
|
return path;
|
||||||
|
} else {
|
||||||
|
return new File(workspace.getDirectory(), location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public File getLoadersDir() {
|
||||||
|
return new File(getDirectory(), "loaders");
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public File getSourceDir() {
|
||||||
|
return new File(getDirectory(), "src");
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public File getModsDir() {
|
||||||
|
return new File(getSourceDir(), "mods");
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public File getConfigFile() {
|
||||||
|
return new File(getDirectory(), BuilderOptions.DEFAULT_CONFIG_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
setCachedConfig(Persistence.read(getConfigFile(), BuilderConfig.class, true));
|
||||||
|
getLoadersDir().mkdirs();
|
||||||
|
getSourceDir().mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createGuideFolders() {
|
||||||
|
new File(getSourceDir(), "config").mkdirs();
|
||||||
|
new File(getSourceDir(), "mods").mkdirs();
|
||||||
|
new File(getSourceDir(), "resourcepacks").mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isLoaded() {
|
||||||
|
return cachedConfig != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.creator;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Problem {
|
||||||
|
|
||||||
|
private final String title;
|
||||||
|
private final String explanation;
|
||||||
|
|
||||||
|
public Problem(String title, String explanation) {
|
||||||
|
this.title = title;
|
||||||
|
this.explanation = explanation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.creator;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class RecentEntry {
|
||||||
|
|
||||||
|
private File path;
|
||||||
|
|
||||||
|
public void setPath(File path) {
|
||||||
|
this.path = path != null ? path : new File(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return path.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.creator;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.skcraft.launcher.creator.model.swing.ListingType;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Workspace {
|
||||||
|
|
||||||
|
public static final String DIR_NAME = ".modpacks";
|
||||||
|
public static final String FILENAME = "workspace.json";
|
||||||
|
|
||||||
|
@JsonIgnore private File directory;
|
||||||
|
private List<Pack> packs = Lists.newArrayList();
|
||||||
|
private List<ManifestEntry> packageListingEntries = Lists.newArrayList();
|
||||||
|
private ListingType packageListingType = ListingType.STATIC;
|
||||||
|
|
||||||
|
public void setPacks(List<Pack> packs) {
|
||||||
|
this.packs = packs != null ? packs : Lists.<Pack>newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageListingEntries(List<ManifestEntry> entries) {
|
||||||
|
this.packageListingEntries = entries != null ? entries : Lists.newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageListingType(ListingType packageListingType) {
|
||||||
|
this.packageListingType = packageListingType != null ? packageListingType : ListingType.STATIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPack(File dir) {
|
||||||
|
for (Pack pack : packs) {
|
||||||
|
try {
|
||||||
|
if (pack.getDirectory().getCanonicalPath().equals(dir.getCanonicalPath())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
for (Pack pack : getPacks()) {
|
||||||
|
pack.setWorkspace(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return super.equals(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return super.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getDataDir(File workspaceDir) {
|
||||||
|
return new File(workspaceDir, DIR_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getWorkspaceFile(File workspaceDir) {
|
||||||
|
return new File(getDataDir(workspaceDir), FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.swing;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.builder.FeaturePattern;
|
||||||
|
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FeaturePatternTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
|
private final List<FeaturePattern> features;
|
||||||
|
|
||||||
|
public FeaturePatternTableModel(List<FeaturePattern> features) {
|
||||||
|
this.features = features;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return "Feature";
|
||||||
|
case 1:
|
||||||
|
return "Recommendation";
|
||||||
|
case 2:
|
||||||
|
return "Default?";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getColumnClass(int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return String.class;
|
||||||
|
case 1:
|
||||||
|
return String.class;
|
||||||
|
case 2:
|
||||||
|
return String.class;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValueAt(Object value, int rowIndex, int columnIndex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
return features.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return features.get(rowIndex).getFeature().getName();
|
||||||
|
case 1:
|
||||||
|
return features.get(rowIndex).getFeature().getRecommendation();
|
||||||
|
case 2:
|
||||||
|
return features.get(rowIndex).getFeature().isSelected() ? "Yes" : "";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FeaturePattern getFeature(int index) {
|
||||||
|
return features.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFeature(FeaturePattern pattern) {
|
||||||
|
features.add(pattern);
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeFeature(int index) {
|
||||||
|
features.remove(index);
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.swing;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.ManifestEntry;
|
||||||
|
import com.skcraft.launcher.model.modpack.ManifestInfo;
|
||||||
|
import com.skcraft.launcher.model.modpack.PackageList;
|
||||||
|
import com.skcraft.launcher.persistence.Persistence;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public enum ListingType {
|
||||||
|
|
||||||
|
STATIC("packages.json (static)", false, "packages.json") {
|
||||||
|
@Override
|
||||||
|
public String generate(List<ManifestEntry> entries) throws IOException {
|
||||||
|
PackageList list = new PackageList();
|
||||||
|
list.setPackages(Lists.newArrayList());
|
||||||
|
list.setMinimumVersion(PackageList.MIN_VERSION);
|
||||||
|
for (ManifestEntry entry : entries) {
|
||||||
|
if (entry.getGameKeys().isEmpty()) {
|
||||||
|
list.getPackages().add(entry.getManifestInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Persistence.writeValueAsString(list, Persistence.L2F_LIST_PRETTY_PRINTER);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PHP("packages.php (requires PHP on web server) ", true, "packages.php") {
|
||||||
|
@Override
|
||||||
|
public String generate(List<ManifestEntry> entries) throws IOException {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("<?php\r\n");
|
||||||
|
builder.append("$keys = isset($_GET['key']) ? array_map('trim', explode(',', strtolower($_GET['key']))) : array();\r\n");
|
||||||
|
builder.append("$packages = array();\r\n\r\n");
|
||||||
|
|
||||||
|
for (ManifestEntry entry : entries) {
|
||||||
|
ManifestInfo info = entry.getManifestInfo();
|
||||||
|
List<String> keys = entry.getGameKeys();
|
||||||
|
|
||||||
|
if (!keys.isEmpty()) {
|
||||||
|
builder.append("if (count(array_intersect(array(").append(escapeKeys(keys)).append("), $keys)) > 0)\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("$packages[] = array(\r\n");
|
||||||
|
builder.append(" 'name' => '").append(escape(info.getName())).append("',\r\n");
|
||||||
|
if (info.getTitle() != null) {
|
||||||
|
builder.append(" 'title' => '").append(escape(info.getTitle())).append("',\r\n");
|
||||||
|
}
|
||||||
|
builder.append(" 'version' => '").append(escape(info.getVersion())).append("',\r\n");
|
||||||
|
builder.append(" 'priority' => ").append(info.getPriority()).append(",\r\n");
|
||||||
|
builder.append(" 'location' => '").append(escape(info.getLocation())).append("',\r\n");
|
||||||
|
builder.append(");\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("$out = array('minimumVersion' => ").append(PackageList.MIN_VERSION).append(", 'packages' => $packages);\r\n");
|
||||||
|
builder.append("header('Content-Type: text/plain; charset=utf-8');\r\n");
|
||||||
|
builder.append("echo json_encode($out);\r\n");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escape(String t) {
|
||||||
|
return t.replaceAll("[\\\\\"']", "\\$0").replace("\r", "\\r").replace("\n", "\\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escapeKeys(List<String> list) {
|
||||||
|
return Joiner.on(", ").join(list.stream().map(s -> "'" + escape(s.toLowerCase()) + "'").collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Getter private final String name;
|
||||||
|
@Getter private final boolean gameKeyCompatible;
|
||||||
|
@Getter private final String filename;
|
||||||
|
|
||||||
|
ListingType(String name, boolean gameKeyCompatible, String filename) {
|
||||||
|
this.name = name;
|
||||||
|
this.gameKeyCompatible = gameKeyCompatible;
|
||||||
|
this.filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String generate(List<ManifestEntry> entries) throws IOException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.swing;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
public class ListingTypeComboBoxModel extends AbstractListModel<ListingType> implements ComboBoxModel<ListingType> {
|
||||||
|
|
||||||
|
private ListingType selection = ListingType.STATIC;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelectedItem(Object anItem) {
|
||||||
|
selection = (ListingType) anItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListingType getSelectedItem() {
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return ListingType.values().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListingType getElementAt(int index) {
|
||||||
|
return ListingType.values()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.swing;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.ManifestEntry;
|
||||||
|
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ManifestEntryTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
|
private final Joiner GAME_KEY_JOINER = Joiner.on(", ");
|
||||||
|
private final List<ManifestEntry> entries;
|
||||||
|
|
||||||
|
public ManifestEntryTableModel(List<ManifestEntry> entries) {
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return "";
|
||||||
|
case 1:
|
||||||
|
return "Modpack";
|
||||||
|
case 2:
|
||||||
|
return "Version";
|
||||||
|
case 3:
|
||||||
|
return "Priority";
|
||||||
|
case 4:
|
||||||
|
return "Location";
|
||||||
|
case 5:
|
||||||
|
return "Game Keys";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getColumnClass(int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return Boolean.class;
|
||||||
|
case 1:
|
||||||
|
return String.class;
|
||||||
|
case 2:
|
||||||
|
return String.class;
|
||||||
|
case 3:
|
||||||
|
return Integer.class;
|
||||||
|
case 4:
|
||||||
|
return String.class;
|
||||||
|
case 5:
|
||||||
|
return String.class;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void setValueAt(Object value, int rowIndex, int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
entries.get(rowIndex).setSelected((Boolean) value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
return entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||||
|
ManifestEntry entry = entries.get(rowIndex);
|
||||||
|
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return entry.isSelected();
|
||||||
|
case 1:
|
||||||
|
String title = entry.getManifestInfo().getTitle();
|
||||||
|
if (title != null) {
|
||||||
|
return title;
|
||||||
|
} else {
|
||||||
|
return entry.getManifestInfo().getName();
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
return entry.getManifestInfo().getVersion();
|
||||||
|
case 3:
|
||||||
|
return entry.getManifestInfo().getPriority();
|
||||||
|
case 4:
|
||||||
|
return entry.getManifestInfo().getLocation();
|
||||||
|
case 5:
|
||||||
|
List<String> gameKeys = entry.getGameKeys();
|
||||||
|
return gameKeys != null ? GAME_KEY_JOINER.join(gameKeys) : "";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.swing;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.creator.Creator;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.ModFile;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
public class ModFileTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
|
private static final Icon WWW_ICON;
|
||||||
|
private final List<ModFile> mods;
|
||||||
|
|
||||||
|
static {
|
||||||
|
WWW_ICON = SwingHelper.createIcon(Creator.class, "www_icon.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModFileTableModel(List<ModFile> mods) {
|
||||||
|
checkNotNull(mods, "mods");
|
||||||
|
this.mods = mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0: return "";
|
||||||
|
case 1: return "Mod";
|
||||||
|
case 2: return "Version";
|
||||||
|
case 3: return "Latest Release";
|
||||||
|
case 4: return "Latest Dev";
|
||||||
|
case 5: return "Mod ID";
|
||||||
|
case 6: return "Filename";
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getColumnClass(int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return Icon.class;
|
||||||
|
default:
|
||||||
|
return String.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValueAt(Object value, int rowIndex, int columnIndex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
return mods.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||||
|
@Nullable ModFile mod = mods.get(rowIndex);
|
||||||
|
|
||||||
|
if (mod == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0: return mod.getUrl() != null ? WWW_ICON : null;
|
||||||
|
case 1: return mod.getName() != null ? mod.getName() : mod.getFile().getName();
|
||||||
|
case 2: return mod.getCleanVersion();
|
||||||
|
case 3: return mod.getLatestVersion();
|
||||||
|
case 4: return mod.getLatestDevVersion();
|
||||||
|
case 5: return mod.getModId();
|
||||||
|
case 6: return mod.getFile().getName();
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModFile getMod(int index) {
|
||||||
|
return mods.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.swing;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.builder.BuilderConfig;
|
||||||
|
import com.skcraft.launcher.creator.Creator;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Pack;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PackTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
|
private final Icon instanceIcon;
|
||||||
|
private final Icon warningIcon;
|
||||||
|
private final List<Pack> packs;
|
||||||
|
|
||||||
|
public PackTableModel(List<Pack> packs) {
|
||||||
|
this.packs = packs;
|
||||||
|
|
||||||
|
instanceIcon = SwingHelper.createIcon(Creator.class, "pack_icon.png");
|
||||||
|
warningIcon = SwingHelper.createIcon(Creator.class, "warning_icon.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return "";
|
||||||
|
case 1:
|
||||||
|
return "Name";
|
||||||
|
case 2:
|
||||||
|
return "Title";
|
||||||
|
case 3:
|
||||||
|
return "Game Version";
|
||||||
|
case 4:
|
||||||
|
return "Location";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getColumnClass(int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return Icon.class;
|
||||||
|
default:
|
||||||
|
return String.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValueAt(Object value, int rowIndex, int columnIndex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
return packs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||||
|
Pack pack = packs.get(rowIndex);
|
||||||
|
|
||||||
|
BuilderConfig config = pack.getCachedConfig();
|
||||||
|
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return config != null ? instanceIcon : warningIcon;
|
||||||
|
case 1:
|
||||||
|
return config != null ? config.getName() : "<Moved or Deleted>";
|
||||||
|
case 2:
|
||||||
|
return config != null ? config.getTitle() : "?";
|
||||||
|
case 3:
|
||||||
|
return config != null ? config.getGameVersion() : "?";
|
||||||
|
case 4:
|
||||||
|
return pack.getLocation();
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.swing;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.creator.Creator;
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Problem;
|
||||||
|
import com.skcraft.launcher.swing.SwingHelper;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ProblemTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
|
private static final Icon WARNING_ICON;
|
||||||
|
|
||||||
|
static {
|
||||||
|
WARNING_ICON = SwingHelper.createIcon(Creator.class, "warning_icon.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Problem> problems;
|
||||||
|
|
||||||
|
public ProblemTableModel(List<Problem> problems) {
|
||||||
|
this.problems = problems;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return "";
|
||||||
|
case 1:
|
||||||
|
return "Problem";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getColumnClass(int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return Icon.class;
|
||||||
|
case 1:
|
||||||
|
return Problem.class;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValueAt(Object value, int rowIndex, int columnIndex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
return problems.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||||
|
switch (columnIndex) {
|
||||||
|
case 0:
|
||||||
|
return WARNING_ICON;
|
||||||
|
case 1:
|
||||||
|
return problems.get(rowIndex);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Problem getProblem(int index) {
|
||||||
|
return problems.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.swing;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.creator.model.creator.RecentEntry;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RecentListModel extends AbstractListModel<RecentEntry> {
|
||||||
|
|
||||||
|
private final List<RecentEntry> recentEntries;
|
||||||
|
|
||||||
|
public RecentListModel(List<RecentEntry> recentEntries) {
|
||||||
|
this.recentEntries = recentEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return recentEntries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecentEntry getElementAt(int index) {
|
||||||
|
return recentEntries.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireUpdate() {
|
||||||
|
fireContentsChanged(this, 0, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.model.swing;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.model.modpack.Feature.Recommendation;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
public class RecommendationComboBoxModel extends AbstractListModel implements ComboBoxModel {
|
||||||
|
|
||||||
|
private Recommendation selection;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelectedItem(Object anItem) {
|
||||||
|
selection = (Recommendation) anItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSelectedItem() {
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return Recommendation.values().length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getElementAt(int index) {
|
||||||
|
if (index == 0) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return Recommendation.values()[index - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.server;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.skcraft.launcher.selfupdate.LatestVersionInfo;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
class LatestHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
private final ObjectMapper mapper;
|
||||||
|
|
||||||
|
public LatestHandler(ObjectMapper mapper) {
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
|
||||||
|
response.setContentType("text/plain; charset=utf-8");
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
|
||||||
|
LatestVersionInfo info = new LatestVersionInfo();
|
||||||
|
info.setVersion("0.0.0");
|
||||||
|
info.setUrl(new URL("http://localhost"));
|
||||||
|
mapper.writeValue(response.getWriter(), info);
|
||||||
|
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.server;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
class NewsHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
|
||||||
|
response.setContentType("text/html; charset=utf-8");
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
|
||||||
|
PrintWriter writer = response.getWriter();
|
||||||
|
writer.write("<!DOCTYPE html>");
|
||||||
|
writer.write("<html>");
|
||||||
|
writer.write("<head>");
|
||||||
|
writer.write("<title>Staging Tool</title>");
|
||||||
|
writer.write("</head>");
|
||||||
|
writer.write("<body>");
|
||||||
|
writer.write("<p>Welcome to the staging tool!</p>");
|
||||||
|
writer.write("</body>");
|
||||||
|
writer.write("</html>");
|
||||||
|
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.server;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.skcraft.launcher.model.modpack.Manifest;
|
||||||
|
import com.skcraft.launcher.model.modpack.ManifestInfo;
|
||||||
|
import com.skcraft.launcher.model.modpack.PackageList;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class PackagesHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
private final ObjectMapper mapper;
|
||||||
|
private final File baseDir;
|
||||||
|
|
||||||
|
public PackagesHandler(ObjectMapper mapper, File baseDir) {
|
||||||
|
this.mapper = mapper;
|
||||||
|
this.baseDir = baseDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
|
||||||
|
response.setContentType("text/plain; charset=utf-8");
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
|
||||||
|
List<ManifestInfo> packages = Lists.newArrayList();
|
||||||
|
PackageList packageList = new PackageList();
|
||||||
|
packageList.setPackages(packages);
|
||||||
|
|
||||||
|
File[] files = baseDir.listFiles(new PackageFileFilter());
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
Manifest manifest = mapper.readValue(file, Manifest.class);
|
||||||
|
ManifestInfo info = new ManifestInfo();
|
||||||
|
info.setName(manifest.getName());
|
||||||
|
info.setTitle(manifest.getTitle());
|
||||||
|
info.setVersion(manifest.getVersion());
|
||||||
|
info.setLocation(file.getName());
|
||||||
|
packages.add(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper.writeValue(response.getWriter(), packageList);
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PackageFileFilter implements FileFilter {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File pathname) {
|
||||||
|
return pathname.getName().toLowerCase().endsWith(".json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.server;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
|
||||||
|
public class TestServer {
|
||||||
|
|
||||||
|
@Getter private final Server server;
|
||||||
|
|
||||||
|
public TestServer(Server server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws Exception {
|
||||||
|
getServer().start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLocalPort() {
|
||||||
|
return ((ServerConnector) server.getConnectors()[0]).getLocalPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() throws Exception {
|
||||||
|
getServer().stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.server;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
|
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
||||||
|
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||||
|
import org.eclipse.jetty.server.handler.ResourceHandler;
|
||||||
|
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class TestServerBuilder {
|
||||||
|
|
||||||
|
private File baseDir = new File(".");
|
||||||
|
private int port = 28888;
|
||||||
|
|
||||||
|
public File getBaseDir() {
|
||||||
|
return baseDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestServerBuilder setBaseDir(File baseDir) {
|
||||||
|
this.baseDir = baseDir;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestServerBuilder setPort(int port) {
|
||||||
|
this.port = port;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestServer build() {
|
||||||
|
Server server = new Server(port);
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
ResourceHandler resourceHandler = new ResourceHandler();
|
||||||
|
resourceHandler.setDirectoriesListed(true);
|
||||||
|
resourceHandler.setResourceBase(baseDir.getAbsolutePath());
|
||||||
|
resourceHandler.setMinMemoryMappedContentLength(-1); // Causes file locking on Windows
|
||||||
|
|
||||||
|
ContextHandler rootContext = new ContextHandler();
|
||||||
|
rootContext.setContextPath("/");
|
||||||
|
rootContext.setHandler(resourceHandler);
|
||||||
|
|
||||||
|
ContextHandler packagesContext = new ContextHandler("/packages.json");
|
||||||
|
packagesContext.setAllowNullPathInfo(true);
|
||||||
|
packagesContext.setHandler(new PackagesHandler(mapper, baseDir));
|
||||||
|
|
||||||
|
ContextHandler latestContext = new ContextHandler("/latest.json");
|
||||||
|
latestContext.setAllowNullPathInfo(true);
|
||||||
|
latestContext.setHandler(new LatestHandler(mapper));
|
||||||
|
|
||||||
|
ContextHandler newsContext = new ContextHandler("/news.html");
|
||||||
|
newsContext.setAllowNullPathInfo(true);
|
||||||
|
newsContext.setHandler(new NewsHandler());
|
||||||
|
|
||||||
|
ContextHandlerCollection contexts = new ContextHandlerCollection();
|
||||||
|
contexts.setHandlers(new Handler[]{packagesContext, latestContext, newsContext, rootContext});
|
||||||
|
|
||||||
|
GzipHandler gzip = new GzipHandler();
|
||||||
|
server.setHandler(gzip);
|
||||||
|
gzip.setHandler(contexts);
|
||||||
|
|
||||||
|
server.addBean(new ErrorHandler());
|
||||||
|
|
||||||
|
return new TestServer(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.swing;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class BorderCellRenderer implements ListCellRenderer {
|
||||||
|
|
||||||
|
private final Border border;
|
||||||
|
private final DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
|
||||||
|
|
||||||
|
public BorderCellRenderer(Border border) {
|
||||||
|
this.border = border;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||||
|
JLabel renderer = (JLabel) defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||||
|
renderer.setBorder(border);
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.swing;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.creator.model.creator.Workspace;
|
||||||
|
|
||||||
|
import javax.swing.filechooser.FileFilter;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class PackDirectoryFilter extends FileFilter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(File f) {
|
||||||
|
return f.isDirectory() && !f.getName().equals(Workspace.DIR_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Directories";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.swing;
|
||||||
|
|
||||||
|
import javax.swing.filechooser.FileFilter;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class WorkspaceDirectoryFilter extends FileFilter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(File f) {
|
||||||
|
return f.isDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Directories";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.io.CharStreams;
|
||||||
|
import com.google.common.io.Closer;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the mod information file from a mod .jar, with support for both Forge
|
||||||
|
* and LiteLoader.
|
||||||
|
*/
|
||||||
|
@Log
|
||||||
|
public class ModInfoReader {
|
||||||
|
|
||||||
|
private static final String FORGE_INFO_FILENAME = "mcmod.info";
|
||||||
|
private static final String LITELOADER_INFO_FILENAME = "litemod.json";
|
||||||
|
private final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect the mods listed in the given .jar
|
||||||
|
*
|
||||||
|
* @param file The file
|
||||||
|
* @return A list of detected mods
|
||||||
|
*/
|
||||||
|
public List<? extends ModInfo> detectMods(File file) {
|
||||||
|
Closer closer = Closer.create();
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileInputStream fis = closer.register(new FileInputStream(file));
|
||||||
|
BufferedInputStream bis = closer.register(new BufferedInputStream(fis));
|
||||||
|
ZipInputStream zis = closer.register(new ZipInputStream(bis));
|
||||||
|
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
if (entry.getName().equalsIgnoreCase(FORGE_INFO_FILENAME)) {
|
||||||
|
List<ForgeModInfo> mods;
|
||||||
|
String content = CharStreams.toString(new InputStreamReader(zis, Charsets.UTF_8));
|
||||||
|
|
||||||
|
try {
|
||||||
|
mods = mapper.readValue(content, ForgeModManifest.class).getMods();
|
||||||
|
} catch (JsonMappingException | JsonParseException e) {
|
||||||
|
mods = mapper.readValue(content, new TypeReference<List<ForgeModInfo>>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mods != null) {
|
||||||
|
// Ignore "examplemod"
|
||||||
|
return Collections.unmodifiableList(
|
||||||
|
mods.stream()
|
||||||
|
.filter(info -> !info.getModId().equals("examplemod"))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (entry.getName().equalsIgnoreCase(LITELOADER_INFO_FILENAME)) {
|
||||||
|
String content = CharStreams.toString(new InputStreamReader(zis, Charsets.UTF_8));
|
||||||
|
return new ImmutableList.Builder<ModInfo>().add(mapper.readValue(content, LiteLoaderModInfo.class)).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
} catch (JsonMappingException e) {
|
||||||
|
log.log(Level.WARNING, "Unknown format " + FORGE_INFO_FILENAME + " file in " + file.getAbsolutePath(), e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
log.log(Level.WARNING, "Corrupt " + FORGE_INFO_FILENAME + " file in " + file.getAbsolutePath(), e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.log(Level.WARNING, "Failed to read " + file.getAbsolutePath(), e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
closer.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ModInfo {
|
||||||
|
|
||||||
|
String getModId();
|
||||||
|
String getName();
|
||||||
|
String getDescription();
|
||||||
|
String getVersion();
|
||||||
|
String getGameVersion();
|
||||||
|
String getUrl();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
private static class ForgeModInfo implements ModInfo {
|
||||||
|
|
||||||
|
@JsonProperty("modid")
|
||||||
|
private String modId;
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
private String version;
|
||||||
|
@JsonProperty("mcversion")
|
||||||
|
private String gameVersion;
|
||||||
|
private String url;
|
||||||
|
private String updateUrl;
|
||||||
|
private List<String> authorList;
|
||||||
|
private String credits;
|
||||||
|
private List<String> dependencies;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
private static class ForgeModManifest {
|
||||||
|
|
||||||
|
@JsonProperty("modListVersion")
|
||||||
|
private int version;
|
||||||
|
@JsonProperty("modList")
|
||||||
|
private List<ForgeModInfo> mods;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
private static class LiteLoaderModInfo implements ModInfo {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String version;
|
||||||
|
@JsonProperty("mcversion")
|
||||||
|
private String gameVersion;
|
||||||
|
private String revision;
|
||||||
|
private String author;
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public String getModId() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.creator.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.skcraft.launcher.util.HttpRequest;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
public class NemModList {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private Map<String, ModEntry> mods = ImmutableMap.of();
|
||||||
|
|
||||||
|
public void load(String version) throws IOException, InterruptedException {
|
||||||
|
checkNotNull(version, "version");
|
||||||
|
|
||||||
|
List<ModEntry> mods = HttpRequest.get(HttpRequest.url("https://bot.notenoughmods.com/" + version + ".json"))
|
||||||
|
.execute()
|
||||||
|
.expectResponseCode(200)
|
||||||
|
.returnContent()
|
||||||
|
.asJson(new TypeReference<List<ModEntry>>() {});
|
||||||
|
|
||||||
|
Map<String, ModEntry> index = Maps.newHashMap();
|
||||||
|
|
||||||
|
for (ModEntry entry : mods) {
|
||||||
|
index.put(entry.getModId(), entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mods = Collections.unmodifiableMap(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ModEntry get(String modId) {
|
||||||
|
checkNotNull(modId, "modId");
|
||||||
|
return mods.get(modId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class ModEntry {
|
||||||
|
|
||||||
|
@JsonProperty("modid")
|
||||||
|
private String modId;
|
||||||
|
private String name;
|
||||||
|
private List<String> aliases;
|
||||||
|
|
||||||
|
@JsonProperty("version")
|
||||||
|
private String latestVersion;
|
||||||
|
@JsonProperty("dev")
|
||||||
|
private String latestDevVersion;
|
||||||
|
@JsonProperty("prevversion")
|
||||||
|
private String previousVersion;
|
||||||
|
|
||||||
|
private List<String> dependencies;
|
||||||
|
|
||||||
|
@JsonProperty("longurl")
|
||||||
|
private URL url;
|
||||||
|
private List<String> tags;
|
||||||
|
private String comment;
|
||||||
|
private String author;
|
||||||
|
|
||||||
|
private String license;
|
||||||
|
private URL repo;
|
||||||
|
|
||||||
|
@JsonProperty("added_at")
|
||||||
|
private Date addedAt;
|
||||||
|
@JsonProperty("lastupdated")
|
||||||
|
private Date lastUpdated;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 601 B |
|
After Width: | Height: | Size: 861 B |
|
After Width: | Height: | Size: 899 B |
|
|
@ -0,0 +1,7 @@
|
||||||
|
#
|
||||||
|
# SK's Minecraft Launcher
|
||||||
|
# Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
# Please see LICENSE.txt for license information.
|
||||||
|
#
|
||||||
|
|
||||||
|
version=${project.version}
|
||||||
|
After Width: | Height: | Size: 620 B |
|
After Width: | Height: | Size: 792 B |
|
After Width: | Height: | Size: 647 B |
|
After Width: | Height: | Size: 540 B |
|
After Width: | Height: | Size: 418 B |
|
After Width: | Height: | Size: 685 B |
|
After Width: | Height: | Size: 431 B |
|
After Width: | Height: | Size: 1 KiB |
|
After Width: | Height: | Size: 577 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 541 B |
|
After Width: | Height: | Size: 752 B |
|
After Width: | Height: | Size: 534 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
185
gradlew
vendored
Executable file
|
|
@ -0,0 +1,185 @@
|
||||||
|
#!/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" "$@"
|
||||||
89
gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
30
launcher-bootstrap/build.gradle
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
plugins {
|
||||||
|
id 'application'
|
||||||
|
id "com.github.johnrengelman.shadow"
|
||||||
|
id 'io.freefair.lombok'
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClassName = "com.skcraft.launcher.Bootstrap"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'com.googlecode.json-simple:json-simple:1.1.1'
|
||||||
|
implementation 'javax.xml.bind:jaxb-api:2.3.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
filesMatching('**/*.properties') {
|
||||||
|
filter {
|
||||||
|
it.replace('${project.version}', project.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
archiveClassifier.set("")
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
dependsOn(shadowJar)
|
||||||
|
}
|
||||||
2
launcher-bootstrap/lombok.config
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# This file is generated by the 'io.freefair.lombok' Gradle plugin
|
||||||
|
config.stopBubbling = true
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.bootstrap.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.filechooser.FileSystemView;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import static com.skcraft.launcher.bootstrap.SharedLocale.tr;
|
||||||
|
|
||||||
|
@Log
|
||||||
|
public class Bootstrap {
|
||||||
|
|
||||||
|
private static final int BOOTSTRAP_VERSION = 1;
|
||||||
|
|
||||||
|
@Getter private final File baseDir;
|
||||||
|
@Getter private final boolean portable;
|
||||||
|
@Getter private final File binariesDir;
|
||||||
|
@Getter private final Properties properties;
|
||||||
|
private final String[] originalArgs;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Throwable {
|
||||||
|
SimpleLogFormatter.configureGlobalLogger();
|
||||||
|
SharedLocale.loadBundle("com.skcraft.launcher.lang.Bootstrap", Locale.getDefault());
|
||||||
|
|
||||||
|
boolean portable = isPortableMode();
|
||||||
|
|
||||||
|
Bootstrap bootstrap = new Bootstrap(portable, args);
|
||||||
|
try {
|
||||||
|
bootstrap.cleanup();
|
||||||
|
bootstrap.launch();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Bootstrap.log.log(Level.WARNING, "Error", t);
|
||||||
|
Bootstrap.setSwingLookAndFeel();
|
||||||
|
SwingHelper.showErrorDialog(null, tr("errors.bootstrapError"), tr("errorTitle"), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bootstrap(boolean portable, String[] args) throws IOException {
|
||||||
|
this.properties = BootstrapUtils.loadProperties(Bootstrap.class, "bootstrap.properties");
|
||||||
|
|
||||||
|
File baseDir = portable ? new File(".") : getUserLauncherDir();
|
||||||
|
|
||||||
|
this.baseDir = baseDir;
|
||||||
|
this.portable = portable;
|
||||||
|
this.binariesDir = new File(baseDir, "launcher");
|
||||||
|
this.originalArgs = args;
|
||||||
|
|
||||||
|
binariesDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cleanup() {
|
||||||
|
File[] files = binariesDir.listFiles(new FileFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File pathname) {
|
||||||
|
return pathname.getName().endsWith(".tmp");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void launch() throws Throwable {
|
||||||
|
File[] files = binariesDir.listFiles(new LauncherBinary.Filter());
|
||||||
|
List<LauncherBinary> binaries = new ArrayList<LauncherBinary>();
|
||||||
|
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
Bootstrap.log.info("Found " + file.getAbsolutePath() + "...");
|
||||||
|
binaries.add(new LauncherBinary(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!binaries.isEmpty()) {
|
||||||
|
launchExisting(binaries, true);
|
||||||
|
} else {
|
||||||
|
launchInitial();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void launchInitial() throws Exception {
|
||||||
|
Bootstrap.log.info("Downloading the launcher...");
|
||||||
|
Thread thread = new Thread(new Downloader(this));
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void launchExisting(List<LauncherBinary> binaries, boolean redownload) throws Exception {
|
||||||
|
Collections.sort(binaries);
|
||||||
|
LauncherBinary working = null;
|
||||||
|
Class<?> clazz = null;
|
||||||
|
|
||||||
|
for (LauncherBinary binary : binaries) {
|
||||||
|
File testFile = binary.getPath();
|
||||||
|
try {
|
||||||
|
testFile = binary.getExecutableJar();
|
||||||
|
Bootstrap.log.info("Trying " + testFile.getAbsolutePath() + "...");
|
||||||
|
clazz = load(testFile);
|
||||||
|
Bootstrap.log.info("Launcher loaded successfully.");
|
||||||
|
working = binary;
|
||||||
|
break;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Bootstrap.log.log(Level.WARNING, "Failed to load " + testFile.getAbsoluteFile(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (working != null) {
|
||||||
|
for (LauncherBinary binary : binaries) {
|
||||||
|
if (working != binary) {
|
||||||
|
log.info("Removing " + binary.getPath() + "...");
|
||||||
|
binary.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(clazz);
|
||||||
|
} else {
|
||||||
|
if (redownload) {
|
||||||
|
launchInitial();
|
||||||
|
} else {
|
||||||
|
throw new IOException("Failed to find launchable .jar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute(Class<?> clazz) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
|
||||||
|
Method method = clazz.getDeclaredMethod("main", String[].class);
|
||||||
|
String[] launcherArgs;
|
||||||
|
|
||||||
|
if (portable) {
|
||||||
|
launcherArgs = new String[] {
|
||||||
|
"--portable",
|
||||||
|
"--dir",
|
||||||
|
baseDir.getAbsolutePath(),
|
||||||
|
"--bootstrap-version",
|
||||||
|
String.valueOf(BOOTSTRAP_VERSION) };
|
||||||
|
} else {
|
||||||
|
launcherArgs = new String[] {
|
||||||
|
"--dir",
|
||||||
|
baseDir.getAbsolutePath(),
|
||||||
|
"--bootstrap-version",
|
||||||
|
String.valueOf(BOOTSTRAP_VERSION) };
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] args = new String[originalArgs.length + launcherArgs.length];
|
||||||
|
System.arraycopy(launcherArgs, 0, args, 0, launcherArgs.length);
|
||||||
|
System.arraycopy(originalArgs, 0, args, launcherArgs.length, originalArgs.length);
|
||||||
|
|
||||||
|
log.info("Launching with arguments " + Arrays.toString(args));
|
||||||
|
|
||||||
|
method.invoke(null, new Object[] { args });
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> load(File jarFile) throws MalformedURLException, ClassNotFoundException {
|
||||||
|
URL[] urls = new URL[] { jarFile.toURI().toURL() };
|
||||||
|
URLClassLoader child = new URLClassLoader(urls, this.getClass().getClassLoader());
|
||||||
|
Class<?> clazz = Class.forName(getProperties().getProperty("launcherClass"), true, child);
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setSwingLookAndFeel() {
|
||||||
|
try {
|
||||||
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||||
|
} catch (Throwable e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getFileChooseDefaultDir() {
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
FileSystemView fsv = chooser.getFileSystemView();
|
||||||
|
return fsv.getDefaultDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getUserLauncherDir() {
|
||||||
|
String osName = System.getProperty("os.name").toLowerCase();
|
||||||
|
if (osName.contains("win")) {
|
||||||
|
return new File(getFileChooseDefaultDir(), getProperties().getProperty("homeFolderWindows"));
|
||||||
|
}
|
||||||
|
|
||||||
|
File dotFolder = new File(System.getProperty("user.home"), getProperties().getProperty("homeFolder"));
|
||||||
|
String xdgFolderName = getProperties().getProperty("homeFolderLinux");
|
||||||
|
|
||||||
|
if (osName.contains("linux") && !dotFolder.exists() && xdgFolderName != null && !xdgFolderName.isEmpty()) {
|
||||||
|
String xdgDataHome = System.getenv("XDG_DATA_HOME");
|
||||||
|
if (xdgDataHome.isEmpty()) {
|
||||||
|
xdgDataHome = System.getProperty("user.home") + "/.local/share";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new File(xdgDataHome, xdgFolderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPortableMode() {
|
||||||
|
return new File("portable.txt").exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.bootstrap;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class BootstrapUtils {
|
||||||
|
|
||||||
|
private static final Pattern absoluteUrlPattern = Pattern.compile("^[A-Za-z0-9\\-]+://.*$");
|
||||||
|
|
||||||
|
private BootstrapUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkInterrupted() throws InterruptedException {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeQuietly(Closeable closeable) {
|
||||||
|
try {
|
||||||
|
if (closeable != null) {
|
||||||
|
closeable.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Properties loadProperties(Class<?> clazz, String name) throws IOException {
|
||||||
|
Properties prop = new Properties();
|
||||||
|
InputStream in = null;
|
||||||
|
try {
|
||||||
|
in = clazz.getResourceAsStream(name);
|
||||||
|
prop.load(in);
|
||||||
|
} finally {
|
||||||
|
closeQuietly(in);
|
||||||
|
}
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.bootstrap;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.Bootstrap;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
import static com.skcraft.launcher.bootstrap.SharedLocale.tr;
|
||||||
|
|
||||||
|
@Log
|
||||||
|
public class DownloadFrame extends JFrame {
|
||||||
|
|
||||||
|
private Downloader downloader;
|
||||||
|
private Timer timer;
|
||||||
|
|
||||||
|
private final JLabel label = new JLabel();
|
||||||
|
private final JPanel progressPanel = new JPanel(new BorderLayout(0, 5));
|
||||||
|
private final JPanel textAreaPanel = new JPanel(new BorderLayout());
|
||||||
|
private final JProgressBar progressBar = new JProgressBar();
|
||||||
|
private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true);
|
||||||
|
private final JButton cancelButton = new JButton(tr("button.cancal"));
|
||||||
|
|
||||||
|
public DownloadFrame(ProgressObservable observable) {
|
||||||
|
super(tr("downloader.title"));
|
||||||
|
setResizable(false);
|
||||||
|
initComponents();
|
||||||
|
label.setText(tr("downloader.pleaseWait"));
|
||||||
|
setMinimumSize(new Dimension(400, 100));
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
SwingHelper.setIconImage(this, Bootstrap.class, "bootstrapper_icon.png");
|
||||||
|
|
||||||
|
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||||
|
addWindowListener(new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent event) {
|
||||||
|
if (confirmCancel()) {
|
||||||
|
cancel();
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
buttonsPanel.addGlue();
|
||||||
|
buttonsPanel.addElement(cancelButton);
|
||||||
|
buttonsPanel.setBorder(BorderFactory.createEmptyBorder(30, 13, 13, 13));;
|
||||||
|
|
||||||
|
progressBar.setIndeterminate(true);
|
||||||
|
progressBar.setMinimum(0);
|
||||||
|
progressBar.setMaximum(1000);
|
||||||
|
progressBar.setPreferredSize(new Dimension(0, 16));
|
||||||
|
|
||||||
|
progressPanel.add(label, BorderLayout.NORTH);
|
||||||
|
progressPanel.setBorder(BorderFactory.createEmptyBorder(13, 13, 0, 13));
|
||||||
|
progressPanel.add(progressBar, BorderLayout.CENTER);
|
||||||
|
textAreaPanel.setBorder(BorderFactory.createEmptyBorder(10, 13, 0, 13));
|
||||||
|
|
||||||
|
add(progressPanel, BorderLayout.NORTH);
|
||||||
|
add(textAreaPanel, BorderLayout.CENTER);
|
||||||
|
add(buttonsPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
textAreaPanel.setVisible(false);
|
||||||
|
cancelButton.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (confirmCancel()) {
|
||||||
|
cancel();
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean confirmCancel() {
|
||||||
|
return SwingHelper.confirmDialog(this, tr("progress.confirmCancel"), tr("progress.confirmCancelTitle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancel() {
|
||||||
|
Downloader downloader = this.downloader;
|
||||||
|
if (downloader != null) {
|
||||||
|
downloader.cancel();
|
||||||
|
} else {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setDownloader(Downloader downloader) {
|
||||||
|
this.downloader = downloader;
|
||||||
|
|
||||||
|
if (downloader == null) {
|
||||||
|
if (timer != null) {
|
||||||
|
timer.cancel();
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (timer == null) {
|
||||||
|
timer = new Timer();
|
||||||
|
timer.scheduleAtFixedRate(new UpdateProgress( downloader), 500, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UpdateProgress extends TimerTask {
|
||||||
|
private final Downloader downloader;
|
||||||
|
|
||||||
|
public UpdateProgress(Downloader downloader) {
|
||||||
|
this.downloader = downloader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Downloader downloader = DownloadFrame.this.downloader;
|
||||||
|
if (downloader != null) {
|
||||||
|
label.setText(downloader.getStatus());
|
||||||
|
double progress = downloader.getProgress();
|
||||||
|
if (progress < 0) {
|
||||||
|
progressBar.setIndeterminate(true);
|
||||||
|
} else {
|
||||||
|
progressBar.setIndeterminate(false);
|
||||||
|
progressBar.setValue((int) (1000 * progress));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
label.setText(tr("downloader.pleaseWait"));
|
||||||
|
progressBar.setIndeterminate(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.bootstrap;
|
||||||
|
|
||||||
|
import com.skcraft.launcher.Bootstrap;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
import org.json.simple.JSONValue;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import static com.skcraft.launcher.bootstrap.BootstrapUtils.checkInterrupted;
|
||||||
|
import static com.skcraft.launcher.bootstrap.SharedLocale.tr;
|
||||||
|
|
||||||
|
@Log
|
||||||
|
public class Downloader implements Runnable, ProgressObservable {
|
||||||
|
|
||||||
|
private final Bootstrap bootstrap;
|
||||||
|
private DownloadFrame dialog;
|
||||||
|
private HttpRequest httpRequest;
|
||||||
|
private Thread thread;
|
||||||
|
|
||||||
|
public Downloader(Bootstrap bootstrap) {
|
||||||
|
this.bootstrap = bootstrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
this.thread = Thread.currentThread();
|
||||||
|
|
||||||
|
try {
|
||||||
|
execute();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.log(Level.WARNING, "Interrupted");
|
||||||
|
System.exit(0);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.log(Level.WARNING, "Failed to download launcher", t);
|
||||||
|
SwingHelper.showErrorDialog(null, tr("errors.failedDownloadError"), tr("errorTitle"), t);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void execute() throws Exception {
|
||||||
|
SwingUtilities.invokeAndWait(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Bootstrap.setSwingLookAndFeel();
|
||||||
|
dialog = new DownloadFrame(Downloader.this);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
dialog.setDownloader(Downloader.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
URL updateUrl = HttpRequest.url(bootstrap.getProperties().getProperty("latestUrl"));
|
||||||
|
|
||||||
|
log.info("Reading update URL " + updateUrl + "...");
|
||||||
|
List<LauncherBinary> binaries = new ArrayList<LauncherBinary>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
String data = HttpRequest
|
||||||
|
.get(updateUrl)
|
||||||
|
.execute()
|
||||||
|
.expectResponseCode(200)
|
||||||
|
.returnContent()
|
||||||
|
.asString("UTF-8");
|
||||||
|
|
||||||
|
Object object = JSONValue.parse(data);
|
||||||
|
URL url;
|
||||||
|
|
||||||
|
if (object instanceof JSONObject) {
|
||||||
|
String rawUrl = String.valueOf(((JSONObject) object).get("url"));
|
||||||
|
if (rawUrl != null) {
|
||||||
|
url = HttpRequest.url(rawUrl.trim());
|
||||||
|
} else {
|
||||||
|
log.warning("Did not get valid update document - got:\n\n" + data);
|
||||||
|
throw new IOException("Update URL did not return a valid result");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warning("Did not get valid update document - got:\n\n" + data);
|
||||||
|
throw new IOException("Update URL did not return a valid result");
|
||||||
|
}
|
||||||
|
|
||||||
|
checkInterrupted();
|
||||||
|
|
||||||
|
File finalFile = new File(bootstrap.getBinariesDir(), System.currentTimeMillis() + ".jar");
|
||||||
|
File tempFile = new File(finalFile.getParentFile(), finalFile.getName() + ".tmp");
|
||||||
|
|
||||||
|
log.info("Downloading " + url + " to " + tempFile.getAbsolutePath());
|
||||||
|
|
||||||
|
httpRequest = HttpRequest.get(url);
|
||||||
|
httpRequest
|
||||||
|
.execute()
|
||||||
|
.expectResponseCode(200)
|
||||||
|
.saveContent(tempFile);
|
||||||
|
|
||||||
|
finalFile.delete();
|
||||||
|
tempFile.renameTo(finalFile);
|
||||||
|
|
||||||
|
LauncherBinary binary = new LauncherBinary(finalFile);
|
||||||
|
binaries.add(binary);
|
||||||
|
} finally {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
dialog.setDownloader(null);
|
||||||
|
dialog.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap.launchExisting(binaries, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
thread.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
HttpRequest httpRequest = this.httpRequest;
|
||||||
|
if (httpRequest != null) {
|
||||||
|
double progress = httpRequest.getProgress();
|
||||||
|
if (progress >= 0) {
|
||||||
|
return String.format(tr("downloader.progressStatus"), progress * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tr("downloader.status");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProgress() {
|
||||||
|
HttpRequest httpRequest = this.httpRequest;
|
||||||
|
return httpRequest != null ? httpRequest.getProgress() : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,504 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.bootstrap;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.bind.Unmarshaller;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.skcraft.launcher.bootstrap.BootstrapUtils.checkInterrupted;
|
||||||
|
import static com.skcraft.launcher.bootstrap.BootstrapUtils.closeQuietly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple fluent interface for performing HTTP requests that uses
|
||||||
|
* {@link java.net.HttpURLConnection} or {@link javax.net.ssl.HttpsURLConnection}.
|
||||||
|
*/
|
||||||
|
@Log
|
||||||
|
public class HttpRequest implements Closeable, ProgressObservable {
|
||||||
|
|
||||||
|
private static final int READ_TIMEOUT = 1000 * 60 * 10;
|
||||||
|
private static final int READ_BUFFER_SIZE = 1024 * 8;
|
||||||
|
|
||||||
|
private final Map<String, String> headers = new HashMap<String, String>();
|
||||||
|
private String method;
|
||||||
|
@Getter
|
||||||
|
private final URL url;
|
||||||
|
private String contentType;
|
||||||
|
private byte[] body;
|
||||||
|
private HttpURLConnection conn;
|
||||||
|
private InputStream inputStream;
|
||||||
|
private int redirectCount;
|
||||||
|
|
||||||
|
private long contentLength = -1;
|
||||||
|
private long readBytes = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new HTTP request.
|
||||||
|
*
|
||||||
|
* @param method the method
|
||||||
|
* @param url the URL
|
||||||
|
*/
|
||||||
|
private HttpRequest(String method, URL url) {
|
||||||
|
this.method = method;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit form data.
|
||||||
|
*
|
||||||
|
* @param form the form
|
||||||
|
* @return this object
|
||||||
|
*/
|
||||||
|
public HttpRequest bodyForm(Form form) {
|
||||||
|
contentType = "application/x-www-form-urlencoded";
|
||||||
|
body = form.toString().getBytes();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a header.
|
||||||
|
*
|
||||||
|
* @param key the header key
|
||||||
|
* @param value the header value
|
||||||
|
* @return this object
|
||||||
|
*/
|
||||||
|
public HttpRequest header(String key, String value) {
|
||||||
|
headers.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the request.
|
||||||
|
* <p/>
|
||||||
|
* After execution, {@link #close()} should be called.
|
||||||
|
*
|
||||||
|
* @return this object
|
||||||
|
* @throws java.io.IOException on I/O error
|
||||||
|
*/
|
||||||
|
public HttpRequest execute() throws IOException {
|
||||||
|
boolean successful = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (conn != null) {
|
||||||
|
throw new IllegalArgumentException("Connection already executed");
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = this.runRequest(url);
|
||||||
|
|
||||||
|
inputStream = conn.getResponseCode() == HttpURLConnection.HTTP_OK ?
|
||||||
|
conn.getInputStream() : conn.getErrorStream();
|
||||||
|
|
||||||
|
successful = true;
|
||||||
|
} finally {
|
||||||
|
if (!successful) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpURLConnection runRequest(URL url) throws IOException {
|
||||||
|
if (redirectCount > 20) {
|
||||||
|
throw new IOException("Too many redirects!");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Java) SKMCLauncher");
|
||||||
|
conn.setInstanceFollowRedirects(false);
|
||||||
|
|
||||||
|
if (body != null) {
|
||||||
|
conn.setRequestProperty("Content-Type", contentType);
|
||||||
|
conn.setRequestProperty("Content-Length", Integer.toString(body.length));
|
||||||
|
conn.setDoInput(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||||
|
conn.setRequestProperty(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.setRequestMethod(method);
|
||||||
|
conn.setUseCaches(false);
|
||||||
|
conn.setDoOutput(true);
|
||||||
|
conn.setReadTimeout(READ_TIMEOUT);
|
||||||
|
|
||||||
|
conn.connect();
|
||||||
|
|
||||||
|
if (body != null) {
|
||||||
|
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
|
||||||
|
out.write(body);
|
||||||
|
out.flush();
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (conn.getResponseCode()) {
|
||||||
|
case HttpURLConnection.HTTP_SEE_OTHER:
|
||||||
|
method = "GET";
|
||||||
|
body = null;
|
||||||
|
case HttpURLConnection.HTTP_MOVED_PERM:
|
||||||
|
case HttpURLConnection.HTTP_MOVED_TEMP:
|
||||||
|
case HttpURLConnection.HTTP_ACCEPTED:
|
||||||
|
case 307:
|
||||||
|
case 308:
|
||||||
|
String location = conn.getHeaderField("Location");
|
||||||
|
redirectCount++;
|
||||||
|
|
||||||
|
return runRequest(new URL(this.url, location));
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Require that the response code is one of the given response codes.
|
||||||
|
*
|
||||||
|
* @param codes a list of codes
|
||||||
|
* @return this object
|
||||||
|
* @throws java.io.IOException if there is an I/O error or the response code is not expected
|
||||||
|
*/
|
||||||
|
public HttpRequest expectResponseCode(int... codes) throws IOException {
|
||||||
|
int responseCode = getResponseCode();
|
||||||
|
|
||||||
|
for (int code : codes) {
|
||||||
|
if (code == responseCode) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
throw new IOException("Did not get expected response code, got " + responseCode + " for " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the response code.
|
||||||
|
*
|
||||||
|
* @return the response code
|
||||||
|
* @throws java.io.IOException on I/O error
|
||||||
|
*/
|
||||||
|
public int getResponseCode() throws IOException {
|
||||||
|
if (conn == null) {
|
||||||
|
throw new IllegalArgumentException("No connection has been made");
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn.getResponseCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the input stream.
|
||||||
|
*
|
||||||
|
* @return the input stream
|
||||||
|
*/
|
||||||
|
public InputStream getInputStream() {
|
||||||
|
return inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer the returned response.
|
||||||
|
*
|
||||||
|
* @return the buffered response
|
||||||
|
* @throws java.io.IOException on I/O error
|
||||||
|
* @throws InterruptedException on interruption
|
||||||
|
*/
|
||||||
|
public BufferedResponse returnContent() throws IOException, InterruptedException {
|
||||||
|
if (inputStream == null) {
|
||||||
|
throw new IllegalArgumentException("No input stream available");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
int b = 0;
|
||||||
|
while ((b = inputStream.read()) != -1) {
|
||||||
|
checkInterrupted();
|
||||||
|
bos.write(b);
|
||||||
|
}
|
||||||
|
return new BufferedResponse(bos.toByteArray());
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the result to a file.
|
||||||
|
*
|
||||||
|
* @param file the file
|
||||||
|
* @return this object
|
||||||
|
* @throws java.io.IOException on I/O error
|
||||||
|
* @throws InterruptedException on interruption
|
||||||
|
*/
|
||||||
|
public HttpRequest saveContent(File file) throws IOException, InterruptedException {
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
BufferedOutputStream bos = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fos = new FileOutputStream(file);
|
||||||
|
bos = new BufferedOutputStream(fos);
|
||||||
|
|
||||||
|
saveContent(bos);
|
||||||
|
} finally {
|
||||||
|
closeQuietly(bos);
|
||||||
|
closeQuietly(fos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the result to an output stream.
|
||||||
|
*
|
||||||
|
* @param out the output stream
|
||||||
|
* @return this object
|
||||||
|
* @throws java.io.IOException on I/O error
|
||||||
|
* @throws InterruptedException on interruption
|
||||||
|
*/
|
||||||
|
public HttpRequest saveContent(OutputStream out) throws IOException, InterruptedException {
|
||||||
|
BufferedInputStream bis;
|
||||||
|
|
||||||
|
try {
|
||||||
|
String field = conn.getHeaderField("Content-Length");
|
||||||
|
if (field != null) {
|
||||||
|
long len = Long.parseLong(field);
|
||||||
|
if (len >= 0) { // Let's just not deal with really big numbers
|
||||||
|
contentLength = len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
bis = new BufferedInputStream(inputStream);
|
||||||
|
|
||||||
|
byte[] data = new byte[READ_BUFFER_SIZE];
|
||||||
|
int len = 0;
|
||||||
|
while ((len = bis.read(data, 0, READ_BUFFER_SIZE)) >= 0) {
|
||||||
|
out.write(data, 0, len);
|
||||||
|
readBytes += len;
|
||||||
|
checkInterrupted();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProgress() {
|
||||||
|
if (contentLength >= 0) {
|
||||||
|
return readBytes / (double) contentLength;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (conn != null) conn.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a GET request.
|
||||||
|
*
|
||||||
|
* @param url the URL
|
||||||
|
* @return a new request object
|
||||||
|
*/
|
||||||
|
public static HttpRequest get(URL url) {
|
||||||
|
return request("GET", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a POST request.
|
||||||
|
*
|
||||||
|
* @param url the URL
|
||||||
|
* @return a new request object
|
||||||
|
*/
|
||||||
|
public static HttpRequest post(URL url) {
|
||||||
|
return request("POST", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a request.
|
||||||
|
*
|
||||||
|
* @param method the method
|
||||||
|
* @param url the URL
|
||||||
|
* @return a new request object
|
||||||
|
*/
|
||||||
|
public static HttpRequest request(String method, URL url) {
|
||||||
|
return new HttpRequest(method, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link java.net.URL} and throw a {@link RuntimeException} if the URL
|
||||||
|
* is not valid.
|
||||||
|
*
|
||||||
|
* @param url the url
|
||||||
|
* @return a URL object
|
||||||
|
* @throws RuntimeException if the URL is invalid
|
||||||
|
*/
|
||||||
|
public static URL url(String url) {
|
||||||
|
try {
|
||||||
|
return new URL(url);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used with {@link #bodyForm(Form)}.
|
||||||
|
*/
|
||||||
|
public final static class Form {
|
||||||
|
public final List<String> elements = new ArrayList<String>();
|
||||||
|
|
||||||
|
private Form() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a key/value to the form.
|
||||||
|
*
|
||||||
|
* @param key the key
|
||||||
|
* @param value the value
|
||||||
|
* @return this object
|
||||||
|
*/
|
||||||
|
public Form add(String key, String value) {
|
||||||
|
try {
|
||||||
|
elements.add(URLEncoder.encode(key, "UTF-8") +
|
||||||
|
"=" + URLEncoder.encode(value, "UTF-8"));
|
||||||
|
return this;
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
boolean first = true;
|
||||||
|
for (String element : elements) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
builder.append("&");
|
||||||
|
}
|
||||||
|
builder.append(element);
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new form.
|
||||||
|
*
|
||||||
|
* @return a new form
|
||||||
|
*/
|
||||||
|
public static Form form() {
|
||||||
|
return new Form();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to buffer the response in memory.
|
||||||
|
*/
|
||||||
|
public class BufferedResponse {
|
||||||
|
private final byte[] data;
|
||||||
|
|
||||||
|
private BufferedResponse(byte[] data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the result as bytes.
|
||||||
|
*
|
||||||
|
* @return the data
|
||||||
|
*/
|
||||||
|
public byte[] asBytes() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the result as a string.
|
||||||
|
*
|
||||||
|
* @param encoding the encoding
|
||||||
|
* @return the string
|
||||||
|
* @throws java.io.IOException on I/O error
|
||||||
|
*/
|
||||||
|
public String asString(String encoding) throws IOException {
|
||||||
|
return new String(data, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the result as an instance of the given class that has been
|
||||||
|
* deserialized from a XML payload.
|
||||||
|
*
|
||||||
|
* @return the object
|
||||||
|
* @throws java.io.IOException on I/O error
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T asXml(Class<T> cls) throws IOException {
|
||||||
|
try {
|
||||||
|
JAXBContext context = JAXBContext.newInstance(cls);
|
||||||
|
Unmarshaller um = context.createUnmarshaller();
|
||||||
|
return (T) um.unmarshal(new ByteArrayInputStream(data));
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the result to a file.
|
||||||
|
*
|
||||||
|
* @param file the file
|
||||||
|
* @return this object
|
||||||
|
* @throws java.io.IOException on I/O error
|
||||||
|
* @throws InterruptedException on interruption
|
||||||
|
*/
|
||||||
|
public BufferedResponse saveContent(File file) throws IOException, InterruptedException {
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
BufferedOutputStream bos = null;
|
||||||
|
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
|
||||||
|
try {
|
||||||
|
fos = new FileOutputStream(file);
|
||||||
|
bos = new BufferedOutputStream(fos);
|
||||||
|
|
||||||
|
saveContent(bos);
|
||||||
|
} finally {
|
||||||
|
closeQuietly(bos);
|
||||||
|
closeQuietly(fos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the result to an output stream.
|
||||||
|
*
|
||||||
|
* @param out the output stream
|
||||||
|
* @return this object
|
||||||
|
* @throws java.io.IOException on I/O error
|
||||||
|
* @throws InterruptedException on interruption
|
||||||
|
*/
|
||||||
|
public BufferedResponse saveContent(OutputStream out) throws IOException, InterruptedException {
|
||||||
|
out.write(data);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.bootstrap;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@Log
|
||||||
|
public class LauncherBinary implements Comparable<LauncherBinary> {
|
||||||
|
|
||||||
|
public static final Pattern PATTERN = Pattern.compile("^([0-9]+)\\.jar(\\.pack)?$");
|
||||||
|
@Getter
|
||||||
|
private final File path;
|
||||||
|
private final long time;
|
||||||
|
private final boolean packed;
|
||||||
|
|
||||||
|
public LauncherBinary(File path) {
|
||||||
|
this.path = path;
|
||||||
|
String name = path.getName();
|
||||||
|
Matcher m = PATTERN.matcher(name);
|
||||||
|
if (!m.matches()) {
|
||||||
|
throw new IllegalArgumentException("Invalid filename: " + path);
|
||||||
|
}
|
||||||
|
time = Long.parseLong(m.group(1));
|
||||||
|
packed = m.group(2) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getExecutableJar() throws PackedJarException {
|
||||||
|
if (packed) {
|
||||||
|
log.warning("Launcher binary " + path.getAbsolutePath() + " is a pack200 file, which is " +
|
||||||
|
"no longer supported.");
|
||||||
|
|
||||||
|
throw new PackedJarException("Cannot unpack .jar.pack files!");
|
||||||
|
} else {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(LauncherBinary o) {
|
||||||
|
if (time > o.time) {
|
||||||
|
return -1;
|
||||||
|
} else if (time < o.time) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
if (packed && !o.packed) {
|
||||||
|
return 1;
|
||||||
|
} else if (!packed && o.packed) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
path.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Filter implements FileFilter {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file) {
|
||||||
|
return file.isFile() && LauncherBinary.PATTERN.matcher(file.getName()).matches();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PackedJarException extends Exception {
|
||||||
|
public PackedJarException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.bootstrap;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class LinedBoxPanel extends JPanel {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final boolean horizontal;
|
||||||
|
@Getter @Setter
|
||||||
|
private int spacing = 6;
|
||||||
|
private boolean needsSpacer = false;
|
||||||
|
|
||||||
|
public LinedBoxPanel(boolean horizontal) {
|
||||||
|
this.horizontal = horizontal;
|
||||||
|
setLayout(new BoxLayout(this,
|
||||||
|
horizontal ? BoxLayout.X_AXIS : BoxLayout.Y_AXIS));
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinedBoxPanel fullyPadded() {
|
||||||
|
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addElement(Component component) {
|
||||||
|
if (needsSpacer) {
|
||||||
|
add(horizontal ?
|
||||||
|
Box.createHorizontalStrut(spacing) :
|
||||||
|
Box.createVerticalStrut(spacing));
|
||||||
|
}
|
||||||
|
add(component);
|
||||||
|
needsSpacer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addGlue() {
|
||||||
|
add(horizontal ?
|
||||||
|
Box.createHorizontalGlue() :
|
||||||
|
Box.createVerticalGlue());
|
||||||
|
needsSpacer = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.bootstrap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementations of this interface can provide information on the progress
|
||||||
|
* of a task.
|
||||||
|
*/
|
||||||
|
public interface ProgressObservable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the progress value as a number between 0 and 1 (inclusive), or -1
|
||||||
|
* if progress information is unavailable.
|
||||||
|
*
|
||||||
|
* @return the progress value
|
||||||
|
*/
|
||||||
|
double getProgress();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.bootstrap;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.MissingResourceException;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles loading a shared message {@link ResourceBundle}.
|
||||||
|
*/
|
||||||
|
@Log
|
||||||
|
public class SharedLocale {
|
||||||
|
|
||||||
|
private static Locale locale = Locale.getDefault();
|
||||||
|
private static ResourceBundle bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current locale.
|
||||||
|
*
|
||||||
|
* @return the current locale
|
||||||
|
*/
|
||||||
|
public static Locale getLocale() {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current resource bundle.
|
||||||
|
*
|
||||||
|
* @return the current resource bundle, or null if not available
|
||||||
|
*/
|
||||||
|
public static ResourceBundle getBundle() {
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a string.
|
||||||
|
*
|
||||||
|
* <p>If the string is not available, then ${key} will be returned.</p>
|
||||||
|
*
|
||||||
|
* @param key the key
|
||||||
|
* @return the translated string
|
||||||
|
*/
|
||||||
|
public static String tr(String key) {
|
||||||
|
if (bundle != null) {
|
||||||
|
try {
|
||||||
|
return bundle.getString(key);
|
||||||
|
} catch (MissingResourceException e) {
|
||||||
|
log.log(Level.WARNING, "Failed to find message", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "${" + key + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a translated string.
|
||||||
|
*
|
||||||
|
* <p>If the string is not available, then ${key}:args will be returned.</p>
|
||||||
|
*
|
||||||
|
* @param key the key
|
||||||
|
* @param args arguments
|
||||||
|
* @return a translated string
|
||||||
|
*/
|
||||||
|
public static String tr(String key, Object... args) {
|
||||||
|
if (bundle != null) {
|
||||||
|
try {
|
||||||
|
MessageFormat formatter = new MessageFormat(tr(key));
|
||||||
|
formatter.setLocale(getLocale());
|
||||||
|
return formatter.format(args);
|
||||||
|
} catch (MissingResourceException e) {
|
||||||
|
log.log(Level.WARNING, "Failed to find message", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "${" + key + "}:" + args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a shared resource bundle.
|
||||||
|
*
|
||||||
|
* @param baseName the bundle name
|
||||||
|
* @param locale the locale
|
||||||
|
* @return true if loaded successfully
|
||||||
|
*/
|
||||||
|
public static boolean loadBundle(@NonNull String baseName, @NonNull Locale locale) {
|
||||||
|
try {
|
||||||
|
SharedLocale.locale = locale;
|
||||||
|
bundle = ResourceBundle.getBundle(baseName, locale,
|
||||||
|
SharedLocale.class.getClassLoader());
|
||||||
|
return true;
|
||||||
|
} catch (MissingResourceException e) {
|
||||||
|
log.log(Level.SEVERE, "Failed to load resource bundle", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.bootstrap;
|
||||||
|
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.logging.*;
|
||||||
|
|
||||||
|
@Log
|
||||||
|
public final class SimpleLogFormatter extends Formatter {
|
||||||
|
|
||||||
|
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(LogRecord record) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append("[")
|
||||||
|
.append(record.getLevel().getLocalizedName().toLowerCase())
|
||||||
|
.append("] ")
|
||||||
|
.append(formatMessage(record))
|
||||||
|
.append(LINE_SEPARATOR);
|
||||||
|
|
||||||
|
if (record.getThrown() != null) {
|
||||||
|
try {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter pw = new PrintWriter(sw);
|
||||||
|
record.getThrown().printStackTrace(pw);
|
||||||
|
pw.close();
|
||||||
|
sb.append(sw.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void configureGlobalLogger() {
|
||||||
|
Logger globalLogger = Logger.getLogger("");
|
||||||
|
|
||||||
|
// Set formatter
|
||||||
|
for (Handler handler : globalLogger.getHandlers()) {
|
||||||
|
handler.setFormatter(new SimpleLogFormatter());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set level
|
||||||
|
String logLevel = System.getProperty(
|
||||||
|
SimpleLogFormatter.class.getCanonicalName() + ".logLevel", "INFO");
|
||||||
|
try {
|
||||||
|
Level level = Level.parse(logLevel);
|
||||||
|
globalLogger.setLevel(level);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.log(Level.WARNING, "Invalid log level of " + logLevel, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,227 @@
|
||||||
|
/*
|
||||||
|
* SK's Minecraft Launcher
|
||||||
|
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
* Please see LICENSE.txt for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.skcraft.launcher.bootstrap;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import static com.skcraft.launcher.bootstrap.BootstrapUtils.closeQuietly;
|
||||||
|
import static com.skcraft.launcher.bootstrap.SharedLocale.tr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swing utility methods.
|
||||||
|
*/
|
||||||
|
@Log
|
||||||
|
public final class SwingHelper {
|
||||||
|
|
||||||
|
private SwingHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String htmlEscape(String str) {
|
||||||
|
return str.replace(">", ">")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace("&", "&");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows an popup error dialog, with potential extra details shown either immediately
|
||||||
|
* or available on the dialog.
|
||||||
|
*
|
||||||
|
* @param parentComponent the frame from which the dialog is displayed, otherwise
|
||||||
|
* null to use the default frame
|
||||||
|
* @param message the message to display
|
||||||
|
* @param title the title string for the dialog
|
||||||
|
* @see #showMessageDialog(java.awt.Component, String, String, String, int) for details
|
||||||
|
*/
|
||||||
|
public static void showErrorDialog(Component parentComponent, @NonNull String message,
|
||||||
|
@NonNull String title) {
|
||||||
|
showErrorDialog(parentComponent, message, title, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows an popup error dialog, with potential extra details shown either immediately
|
||||||
|
* or available on the dialog.
|
||||||
|
*
|
||||||
|
* @param parentComponent the frame from which the dialog is displayed, otherwise
|
||||||
|
* null to use the default frame
|
||||||
|
* @param message the message to display
|
||||||
|
* @param title the title string for the dialog
|
||||||
|
* @param throwable the exception, or null if there is no exception to show
|
||||||
|
* @see #showMessageDialog(java.awt.Component, String, String, String, int) for details
|
||||||
|
*/
|
||||||
|
public static void showErrorDialog(Component parentComponent, @NonNull String message,
|
||||||
|
@NonNull String title, Throwable throwable) {
|
||||||
|
String detailsText = null;
|
||||||
|
|
||||||
|
// Get a string version of the exception and use that for
|
||||||
|
// the extra details text
|
||||||
|
if (throwable != null) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
throwable.printStackTrace(new PrintWriter(sw));
|
||||||
|
detailsText = sw.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
showMessageDialog(parentComponent,
|
||||||
|
message, title,
|
||||||
|
detailsText, JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a message dialog using
|
||||||
|
* {@link javax.swing.JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)}.
|
||||||
|
*
|
||||||
|
* <p>The dialog will be shown from the Event Dispatch Thread, regardless of the
|
||||||
|
* thread it is called from. In either case, the method will block until the
|
||||||
|
* user has closed the dialog (or dialog creation fails for whatever reason).</p>
|
||||||
|
*
|
||||||
|
* @param parentComponent the frame from which the dialog is displayed, otherwise
|
||||||
|
* null to use the default frame
|
||||||
|
* @param message the message to display
|
||||||
|
* @param title the title string for the dialog
|
||||||
|
* @param messageType see {@link javax.swing.JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)}
|
||||||
|
* for available message types
|
||||||
|
*/
|
||||||
|
public static void showMessageDialog(final Component parentComponent,
|
||||||
|
@NonNull final String message,
|
||||||
|
@NonNull final String title,
|
||||||
|
final String detailsText,
|
||||||
|
final int messageType) {
|
||||||
|
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
// To force the label to wrap, convert the message to broken HTML
|
||||||
|
String htmlMessage = "<html><div style=\"width: 250px\">" + htmlEscape(message);
|
||||||
|
|
||||||
|
JPanel panel = new JPanel(new BorderLayout(0, detailsText != null ? 20 : 0));
|
||||||
|
|
||||||
|
// Add the main message
|
||||||
|
panel.add(new JLabel(htmlMessage), BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// Add the extra details
|
||||||
|
if (detailsText != null) {
|
||||||
|
JTextArea textArea = new JTextArea(tr("errorDialog.reportError") + "\n\n" + detailsText);
|
||||||
|
JLabel tempLabel = new JLabel();
|
||||||
|
textArea.setFont(tempLabel.getFont());
|
||||||
|
textArea.setBackground(tempLabel.getBackground());
|
||||||
|
textArea.setTabSize(2);
|
||||||
|
textArea.setEditable(false);;
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane(textArea);
|
||||||
|
scrollPane.setPreferredSize(new Dimension(350, 120));
|
||||||
|
panel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
JOptionPane.showMessageDialog(
|
||||||
|
parentComponent, panel, title, messageType);
|
||||||
|
} else {
|
||||||
|
// Call method again from the Event Dispatch Thread
|
||||||
|
try {
|
||||||
|
SwingUtilities.invokeAndWait(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
showMessageDialog(
|
||||||
|
parentComponent, message, title,
|
||||||
|
detailsText, messageType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks the user a binary yes or no question.
|
||||||
|
*
|
||||||
|
* @param parentComponent the component
|
||||||
|
* @param message the message to display
|
||||||
|
* @param title the title string for the dialog
|
||||||
|
* @return whether 'yes' was selected
|
||||||
|
*/
|
||||||
|
public static boolean confirmDialog(final Component parentComponent,
|
||||||
|
@NonNull final String message,
|
||||||
|
@NonNull final String title) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
return JOptionPane.showConfirmDialog(
|
||||||
|
parentComponent, message, title, JOptionPane.YES_NO_OPTION) ==
|
||||||
|
JOptionPane.YES_OPTION;
|
||||||
|
} else {
|
||||||
|
// Use an AtomicBoolean to pass the result back from the
|
||||||
|
// Event Dispatcher Thread
|
||||||
|
final AtomicBoolean yesSelected = new AtomicBoolean();
|
||||||
|
|
||||||
|
try {
|
||||||
|
SwingUtilities.invokeAndWait(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
yesSelected.set(confirmDialog(parentComponent, title, message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return yesSelected.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufferedImage readIconImage(Class<?> clazz, String path) {
|
||||||
|
InputStream in = null;
|
||||||
|
try {
|
||||||
|
in = clazz.getResourceAsStream(path);
|
||||||
|
if (in != null) {
|
||||||
|
return ImageIO.read(in);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
} finally {
|
||||||
|
closeQuietly(in);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setIconImage(Window frame, Class<?> clazz, String path) {
|
||||||
|
BufferedImage image = readIconImage(clazz, path);
|
||||||
|
if (image != null) {
|
||||||
|
frame.setIconImage(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focus a component.
|
||||||
|
*
|
||||||
|
* <p>The focus call happens in {@link javax.swing.SwingUtilities#invokeLater(Runnable)}.</p>
|
||||||
|
*
|
||||||
|
* @param component the component
|
||||||
|
*/
|
||||||
|
public static void focusLater(@NonNull final Component component) {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (component instanceof JTextComponent) {
|
||||||
|
((JTextComponent) component).selectAll();
|
||||||
|
}
|
||||||
|
component.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
#
|
||||||
|
# SK's Minecraft Launcher
|
||||||
|
# Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
# Please see LICENSE.txt for license information.
|
||||||
|
#
|
||||||
|
|
||||||
|
homeFolderWindows=Example Launcher
|
||||||
|
homeFolderLinux=example_launcher
|
||||||
|
homeFolder=.examplelauncher
|
||||||
|
launcherClass=com.skcraft.launcher.Launcher
|
||||||
|
latestUrl=http://update.skcraft.com/quark/launcher/latest.json
|
||||||
|
After Width: | Height: | Size: 2 KiB |
|
|
@ -0,0 +1,21 @@
|
||||||
|
#
|
||||||
|
# SK's Minecraft Launcher
|
||||||
|
# Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||||
|
# Please see LICENSE.txt for license information.
|
||||||
|
#
|
||||||
|
|
||||||
|
button.cancal=Cancel
|
||||||
|
|
||||||
|
errorTitle=Error
|
||||||
|
errors.failedDownloadError=The SKCraft launcher could not be downloaded.
|
||||||
|
errors.bootstrapError=An error occurred while trying to bootstrap the launcher\!
|
||||||
|
|
||||||
|
errorDialog.reportError=To report this error, provide\:
|
||||||
|
|
||||||
|
downloader.title=Downloading SKCraft Launcher...
|
||||||
|
downloader.pleaseWait=Please wait...
|
||||||
|
downloader.status=Downloading latest SKCraft Launcher...
|
||||||
|
downloader.progressStatus=Downloading latest SKCraft Launcher (%.2f%%)...
|
||||||
|
|
||||||
|
progress.confirmCancel=Are you sure that you wish to cancel?
|
||||||
|
progress.confirmCancelTitle=Cancel
|
||||||