package eu.thog92.launcher.launch; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import eu.thog92.launcher.download.Downloadable; import eu.thog92.launcher.model.DownloadModel; import eu.thog92.launcher.model.LaunchModel; import eu.thog92.launcher.util.*; import eu.thog92.launcher.version.*; import java.io.*; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class GameLaunch implements JavaProcessRunnable { private Gson gson; private LaunchModel launchModel; private DownloadModel downloadModel; private File nativeDir; private LogAgent log; private File gameDir; private File commonDir; public GameLaunch(LaunchModel m, DownloadModel m2, LogAgent log) { this.log = log; this.launchModel = m; this.downloadModel = m2; this.gameDir = downloadModel.getModPackDir(); this.commonDir = Util.getMinecraftDir(); } public void startGame() throws IOException { gson = new GsonBuilder().create(); OperatingSystem os = OperatingSystem.getCurrentPlatform(); nativeDir = new File(gameDir, "bin/natives"); if (nativeDir.exists()) { if (!IOUtils.delete(nativeDir)) { throw new IOException("Native cannot be remove!"); } } if (!nativeDir.isDirectory()) nativeDir.mkdirs(); try { unpackNatives(nativeDir); } catch (IOException e) { // launcher.getLogAgent().logSevereException( // "Couldn't unpack natives!", e); e.printStackTrace(); return; } JavaProcessLauncher processLauncher = new JavaProcessLauncher( os.getJavaDir(), new String[0]); processLauncher.directory(gameDir); if (os.equals(OperatingSystem.OSX)) processLauncher.addCommands(new String[] { "-Xdock:icon=" + getAssetObject("icons/minecraft.icns") .getAbsolutePath(), "-Xdock:name=FTF"}); else if (os.equals(OperatingSystem.WINDOWS)) { processLauncher .addCommands(new String[] {"-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"}); } String defaultArgument = "-Xmx" + launchModel.getRAM() + "m"; processLauncher.addSplitCommands(defaultArgument); processLauncher.addSplitCommands("-XX:MaxPermSize=128m"); processLauncher.addSplitCommands("-Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true"); processLauncher.addCommands(new String[] {"-Djava.library.path=" + nativeDir.getAbsolutePath()}); processLauncher.addCommands(new String[] {"-cp", constructClassPath(launchModel.getVersion())}); processLauncher.addCommands(new String[] {launchModel.getVersion().getMainClass()}); File virtualRoot = reconstructAssets(); String[] args = getMinecraftArguments(launchModel.getVersion(), gameDir, virtualRoot); if (args == null) return; processLauncher.addCommands(args); try { JavaProcess process = processLauncher.start(); log.logInfo("Running " + StringUtils.join(processLauncher.getFullCommands(), " ")); System.gc(); //FIXME: WTF process.safeSetExitRunnable(this); } catch (IOException e) { e.printStackTrace(); } } private File reconstructAssets() throws IOException { File assetsDir = new File(commonDir, "assets"); File indexDir = new File(assetsDir, "indexes"); File objectDir = new File(assetsDir, "objects"); String assetVersion = launchModel.getVersion().getAssets() == null ? "legacy" : launchModel.getVersion().getAssets(); File indexFile = new File(indexDir, assetVersion + ".json"); File virtualRoot = new File(new File(assetsDir, "virtual"), assetVersion); if (!indexFile.isFile()) { log.logWarning("No assets index file " + virtualRoot + "; can't reconstruct assets"); return virtualRoot; } AssetIndex index = this.gson.fromJson(StringUtils.readFileToString(indexFile, Charset.defaultCharset()), AssetIndex.class); if (index.isVirtual()) { log.logInfo("Reconstructing virtual assets folder at " + virtualRoot); for (Map.Entry<String, AssetObject> entry : index.getObjectMap().entrySet()) { File target = new File(virtualRoot, entry.getKey()); File original = new File(new File(objectDir, entry.getValue().getHash().substring(0, 2)), entry.getValue().getHash()); if (!target.isFile()) Files.copy(original.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); } } return virtualRoot; } private String constructClassPath(final Version version) { StringBuilder result = new StringBuilder(); Collection<File> classPath = version.getClassPath( OperatingSystem.getCurrentPlatform(), commonDir); String separator = System.getProperty("path.separator"); System.out.println(classPath); for (File file : classPath) { if (result.length() > 0) result.append(separator); result.append(file.getAbsolutePath()); } result.append(separator); result.append(downloadModel.getMinecraftJarPath().getAbsolutePath()); return result.toString(); } private String[] getMinecraftArguments(Version version, File gameDirectory, File assetsDirectory) { if (version.getMinecraftArguments() == null) { return null; } Map<String, String> map = new HashMap<String, String>(); String[] split = version.getMinecraftArguments().split(" "); map.put("auth_access_token", launchModel.getToken()); map.put("auth_session", String.format("token:%s:%s", launchModel.getToken(), launchModel.getProfileID())); map.put("auth_player_name", launchModel.getUsername()); map.put("auth_uuid", launchModel.getProfileID()); map.put("user_type", launchModel.getUsername()); map.put("profile_name", "FTF"); map.put("version_name", version.getId()); map.put("game_directory", gameDirectory.getAbsolutePath()); map.put("game_assets", assetsDirectory.getAbsolutePath()); map.put("assets_root", new File(commonDir, "assets").getAbsolutePath()); map.put("assets_index_name", launchModel.getVersion().getAssets()); map.put("user_properties", "{}"); for (int i = 0; i < split.length; i++) { split[i] = replaceData(map, split[i]); } return split; } private String replaceData(Map<String, String> map, String arg) { for (Map.Entry<String, String> entry : map.entrySet()) { arg = arg.replace("${" + entry.getKey() + "}", entry.getValue()); } return arg; } private File getAssetObject(String name) throws IOException { File assetsDir = new File(commonDir, "assets"); File indexDir = new File(assetsDir, "indexes"); File objectsDir = new File(assetsDir, "objects"); String assetVersion = launchModel.getVersion().getAssets(); File indexFile = new File(indexDir, assetVersion + ".json"); AssetIndex index = gson.fromJson( StringUtils.readFileToString(indexFile, "UTF-8"), AssetIndex.class); String hash = index.getObjectMap().get(name).getHash(); return new File(objectsDir, hash.substring(0, 2) + "/" + hash); } private void unpackNatives(File targetDir) throws IOException { OperatingSystem os = OperatingSystem.getCurrentPlatform(); Collection<Library> libraries = launchModel.getVersion() .getRelevantLibraries(); for (Library library : libraries) { Map<OperatingSystem, String> nativesPerOs = library.getNatives(); if ((nativesPerOs != null) && (nativesPerOs.get(os) != null)) { File file = new File(commonDir, "libraries/" + library.getArtifactPath(nativesPerOs .get(os))); ZipFile zip = new ZipFile(file); ExtractRules extractRules = library.getExtractRules(); try { Enumeration<? extends ZipEntry> entries = zip.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); if ((extractRules != null) && (!extractRules .shouldExtract(entry.getName()))) { continue; } File targetFile = new File(targetDir, entry.getName()); if (targetFile.getParentFile() != null) targetFile.getParentFile().mkdirs(); if (!entry.isDirectory()) { BufferedInputStream inputStream = new BufferedInputStream( zip.getInputStream(entry)); byte[] buffer = new byte[2048]; FileOutputStream outputStream = new FileOutputStream( targetFile); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( outputStream); try { int length; while ((length = inputStream.read(buffer, 0, buffer.length)) != -1) bufferedOutputStream.write(buffer, 0, length); } finally { Downloadable .closeSilently(bufferedOutputStream); Downloadable.closeSilently(outputStream); Downloadable.closeSilently(inputStream); } } } } finally { zip.close(); } } } } @Override public void onJavaProcessEnded(final JavaProcess paramJavaProcess) { // TODO Auto-generated method stub final int exitCode = paramJavaProcess.getExitCode(); log.logInfo("Game exit with code " + exitCode); System.exit(0); } }