/* Copyright (c) 2013-2014 Jesper Öqvist <jesper@llbit.se>
*
* This file is part of Chunky.
*
* Chunky is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Chunky is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Chunky. If not, see <http://www.gnu.org/licenses/>.
*/
package se.llbit.chunky.resources;
import se.llbit.chunky.PersistentSettings;
import se.llbit.json.JsonObject;
import se.llbit.json.JsonParser;
import se.llbit.json.JsonParser.SyntaxError;
import se.llbit.log.Log;
import se.llbit.util.OSDetector;
import se.llbit.util.Util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Locates the Minecraft installation.
*
* @author Jesper Öqvist <jesper@llbit.se>
*/
public class MinecraftFinder {
private static File minecraftJar = null;
private static boolean foundJar = false;
/**
* @return The configured Minecraft directory.
*/
public static File getMinecraftDirectory() {
File mcDir = new File(PersistentSettings.getMinecraftDirectory());
if (getMinecraftJar(mcDir, false) == null) {
mcDir = getDefaultMinecraftDirectory();
}
Log.infof("Found Minecraft directory %s", mcDir.getAbsolutePath());
return mcDir;
}
/**
* @return The platform-dependent default Minecraft directory.
*/
public static File getDefaultMinecraftDirectory() {
String home = System.getProperty("user.home", ".");
switch (OSDetector.getOS()) {
case WIN:
String appdata = System.getenv("APPDATA");
if (appdata != null) {
return new File(appdata, ".minecraft");
} else {
return new File(home, ".minecraft");
}
case MAC:
return new File(home, "Library/Application Support/minecraft");
default:
return new File(home, ".minecraft");
}
}
/**
* @return The saves directory of the local Minecraft installation
*/
public static File getSavesDirectory() {
return new File(getMinecraftDirectory(), "saves");
}
/**
* @return The texture pack directory of the local Minecraft installation
*/
public static File getTexturePacksDirectory() {
return new File(getMinecraftDirectory(), "texturepacks");
}
/**
* @return The resource pack directory of the local Minecraft installation
*/
public static File getResourcePacksDirectory() {
return new File(getMinecraftDirectory(), "resourcepacks");
}
/**
* NB: This method caches its result
*
* @return File reference to the latest Minecraft jar of the local
* Minecraft installation, or <code>null</code> if the Minecraft jar
* could not be found.
*/
public static File getMinecraftJar() {
synchronized (MinecraftFinder.class) {
if (!foundJar) {
minecraftJar = getMinecraftJar(getMinecraftDirectory(), true);
if (minecraftJar == null) {
// Fall back on downloaded Jar if available.
File resourceDir = new File(SettingsDirectory.getSettingsDirectory(), "resources");
if (resourceDir.isDirectory()) {
File jar = new File(resourceDir, "minecraft.jar");
if (jar.isFile() && jar.canRead()) {
minecraftJar = jar;
}
}
}
foundJar = true;
}
return minecraftJar;
}
}
/**
* NB: This method caches its result
*
* @return File reference to the latest Minecraft jar of the local
* Minecraft installation, or throws a FileNotFoundException if it
* could not be found.
*/
public static File getMinecraftJarNonNull() throws FileNotFoundException {
File jarFile = getMinecraftJar();
if (jarFile == null) {
throw new FileNotFoundException("Could not locate Minecraft Jar!");
}
return jarFile;
}
/**
* @param mcDir directory to search for Minecraft jar
* @param debug print debug messages if {@code true}
* @return File reference to the latest Minecraft jar of the local
* Minecraft installation, or <code>null</code> if the Minecraft jar
* could not be found.
*/
public static File getMinecraftJar(File mcDir, boolean debug) {
// MC 1.6.1 and above jars located in subdirectories under versions/
File versions = new File(mcDir, "versions");
if (versions.isDirectory()) {
File[] dirs = versions.listFiles(pathname -> {
return pathname.isDirectory();
});
List<MCVersion> versionDirs = new ArrayList<>();
for (File directory : dirs) {
File jarPath = new File(directory, directory.getName() + ".jar");
if (!jarPath.isFile()) {
continue;
}
String releaseTime = "";
try {
File jsonFile = new File(directory, directory.getName() + ".json");
FileInputStream in = new FileInputStream(jsonFile);
JsonParser parser = new JsonParser(in);
JsonObject obj = parser.parse().object();
releaseTime = obj.get("releaseTime").stringValue("");
} catch (IOException | SyntaxError e) {
// Json parsing failed.
} finally {
versionDirs.add(new MCVersion(jarPath, releaseTime));
}
}
// Select latest available minecraft version.
if (!versionDirs.isEmpty()) {
MCVersion latest = versionDirs.get(0);
for (int i = 1; i < versionDirs.size(); ++i) {
if (versionDirs.get(i).compareTo(latest) > 0) {
latest = versionDirs.get(i);
}
}
if (debug) {
Log.infof("Found latest Minecraft version: %s", latest.jar.getAbsolutePath());
}
return latest.jar;
}
}
// Backwards compatibility for pre-1.6.1:
File bin = new File(mcDir, "bin");
if (bin.isDirectory()) {
for (File file : bin.listFiles()) {
if (file.getName().equalsIgnoreCase("minecraft.jar")) {
return new File(bin, file.getName());
}
}
}
// Failed to locate Minecraft Jar.
return null;
}
private static class MCVersion implements Comparable<MCVersion> {
private final File jar;
private final Date timestamp;
/**
* If time not ISO 8601, the timestamp is set to {@code new Date(0)}.
*/
public MCVersion(File jarFile, String time) {
jar = jarFile;
timestamp = Util.dateFromISO8601(time);
}
@Override public int compareTo(MCVersion other) {
return timestamp.compareTo(other.timestamp);
}
@Override public String toString() {
return jar.getName();
}
}
}