using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Reflection; using System.Text; using Newtonsoft.Json.Linq; namespace McMan { internal class Program { public static void Main(string[] args) { if (args.Length == 0) { args = new string[1]; Console.WriteLine("Username"); Console.Write("> "); args[0] = Console.ReadLine(); } if (args.Length == 1) { args = new [] {args[0], "choose"}; } if (args.Length == 2) { Console.WriteLine("Max memory (empty for 2G)"); Console.Write("> "); args = new [] {args[0], args[1], Console.ReadLine()}; } if (Directory.Exists("assets")) Directory.Delete("assets", true); if (Directory.Exists("Game")) Directory.Delete("Game", true); Directory.CreateDirectory("Game"); using WebClient client = new WebClient(); string maxMem = args[2]; maxMem = string.IsNullOrWhiteSpace(maxMem) ? "2G" : maxMem; Environment.CurrentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); JObject gameJson = GetMinecraftJson(client, args[1], out string ver); DownloadClient(client, gameJson["downloads"]["client"]); DownloadAssets(client, gameJson["assetIndex"], ver); string classpath = DownloadLibs(client, (JArray) gameJson["libraries"]); Console.WriteLine("Creating launch script"); classpath += "client.jar"; const string mainClass = "net.minecraft.client.main.Main"; const string java = "javaw"; const string lowMem = "768M"; string javaOptions = $"-server -splash:splash.png -d64 -da -dsa -Xrs -Xms{lowMem} -Xmx{maxMem} -XX:NewSize={lowMem} -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:+DisableExplicitGC -Djava.library.path=libraries -cp {classpath} {mainClass}"; string jvmBat = $"start /D %cd% /I /HIGH {java} {javaOptions} --username {args[0]} --version {ver} --gameDir %cd% --assetsDir assets --assetIndex {gameJson.Value("assets")} --uuid 2536abce90e8476a871679918164abc5 --accessToken 99abe417230342cb8e9e2168ab46297a --userType legacy --versionType release --nativeLauncherVersion 307"; File.WriteAllText(Path.Combine("Game", "start.bat"), jvmBat); Console.WriteLine("Done!"); } //Downloads a JRE, doesn't start /*private static void GetJRE(WebClient client, bool x64) { Console.WriteLine("Fetching JRE Versions..."); IEnumerable json = JArray.Parse(client.DownloadString("https://api.adoptopenjdk.net/v2/info/releases/openjdk11")) .SelectMany(s => (JArray)s["binaries"]); Console.WriteLine("Selecting version..."); string download = json.Last(s => s.Value("os") == "windows" && s.Value("architecture") == (x64 ? "x64" : "x32") && s.Value("binary_type") == "jre" && s.Value("heap_size") == "normal") .Value("binary_link"); Console.WriteLine($"Downloading from {download}..."); if (Directory.Exists("jdk")) Directory.Delete("jdk", true); Directory.CreateDirectory("jdk"); using MemoryStream ms = new MemoryStream(client.DownloadData(download)); Console.WriteLine("Extracting..."); using ZipArchive archive = new ZipArchive(ms); archive.ExtractToDirectory("jdk"); Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(Directory.GetDirectories("jdk")[0], "Game"); Directory.Delete("jdk", true); }*/ private static string DownloadLibs(WebClient client, JArray libraries) { string output = ""; string libdir = Path.Combine("Game", "libraries"); Console.WriteLine("Downloading libs..."); Directory.CreateDirectory(libdir); Console.CursorTop++; JToken[] tmp = libraries.ToArray(); for (int i = 0; i < tmp.Length; i++) { JToken lib = tmp[i]; if (lib["downloads"].Any(s => ((JProperty) s).Name == "classifiers")) output = lib["downloads"]["classifiers"].Where(lib2 => ((JProperty) lib2).Name == "natives-windows") .Aggregate( output, (current, lib2) => current + DownloadLib(client, lib2.First.Value("sha1"), lib2.First.Value("url"), lib2.First.Value("path"), libdir)); Console.CursorLeft = 0; Console.CursorTop--; Console.WriteLine($"[{i + 1}/{tmp.Length}] Getting {lib["downloads"]["artifact"].Value("path")}{new string(' ', 10)}"); output += DownloadLib(client, lib["downloads"]["artifact"].Value("sha1"), lib["downloads"]["artifact"].Value("url"), lib["downloads"]["artifact"].Value("path"), libdir); } return output; } private static string DownloadLib(WebClient client, string hash, string url, string path, string libdir) { string tmp = path.Split('/').Aggregate(libdir, Path.Combine); byte[] libB = client.DownloadData(url); if (Tools.Hash(libB) != hash) { Console.WriteLine("ERROR: HASH MISMATCH IN LIBRARY"); throw new Exception(); } Directory.CreateDirectory(Path.GetDirectoryName(tmp)); File.WriteAllBytes(tmp, libB); return $"{path.Split('/').Aggregate("libraries", Path.Combine)};"; } private static void DownloadAssets(WebClient client, JToken assetIndex, string version) { bool success = false; byte[] indexB = new byte[0]; while (!success) { Console.WriteLine("Downloading asset index..."); indexB = client.DownloadData(assetIndex.Value("url")); success = Tools.Hash(indexB) == assetIndex.Value("sha1"); } JObject index = JObject.Parse(Encoding.Default.GetString(indexB)); string assetPath = Path.Combine("Game", "assets"); Directory.CreateDirectory(Path.Combine(assetPath, "indexes")); File.WriteAllBytes(Path.Combine(assetPath, "indexes", Path.GetFileName(assetIndex.Value("url"))), indexB); Console.WriteLine("Processing..."); Console.CursorTop++; JToken[] tmp = Enumerable.ToArray(index["objects"]); for (int i = 0; i < tmp.Length; i++) { JProperty asset = (JProperty)tmp[i]; string name = asset.Name; Console.CursorLeft = 0; Console.CursorTop--; Console.WriteLine($"[{i + 1}/{tmp.Length}] Getting {name}{new string(' ', 10)}"); string hash = asset.First.Value("hash"); string url = $"http://resources.download.minecraft.net/{string.Join("", hash.Substring(0, 2))}/{hash}"; byte[] file = client.DownloadData(url); if (Tools.Hash(file) != hash) { Console.WriteLine("ERROR: HASH MISMATCH IN ASSET"); throw new Exception(); } string path = Path.Combine(assetPath, "objects", hash.Substring(0, 2)); if (!Directory.Exists(path)) Directory.CreateDirectory(path); File.WriteAllBytes(Path.Combine(path, hash), file); } } private static void DownloadClient(WebClient client, JToken clientJson) { bool success = false; byte[] mcClient = new byte[0]; while (!success) { Console.WriteLine("Downloading client..."); mcClient = client.DownloadData(clientJson.Value("url")); success = Tools.Hash(mcClient) == clientJson.Value("sha1"); } File.WriteAllBytes(Path.Combine("Game", "client.jar"), mcClient); } private static JObject GetMinecraftJson(WebClient client, string verdat, out string version) { Tuple tmp; Tuple[] versions = GetVersions(client); if (verdat == "choose") { Console.WriteLine("Select a Version (empty for latest)"); for (int i = 0; i < versions.Length; i++) Console.WriteLine($"{i + 1}\t- {versions[i].Item1}"); Console.Write("> "); string inp = Console.ReadLine(); tmp = string.IsNullOrWhiteSpace(inp) ? versions.Last() : versions[int.Parse(inp) - 1]; } else tmp = versions.First(s => s.Item1.ToLower().Equals(verdat.ToLower(), StringComparison.InvariantCultureIgnoreCase)); version = tmp.Item1; Console.WriteLine($"Using minecraft {version}"); return JObject.Parse(client.DownloadString(tmp.Item2)); } private static Tuple[] GetVersions(WebClient client) { JObject tmp = JObject.Parse(client.DownloadString("https://launchermeta.mojang.com/mc/game/version_manifest.json")); return tmp["versions"].Where(token => token.Value("type") == "release").Select(token => new Tuple(token.Value("id"), token.Value("url"))).OrderBy(s => Version.Parse(s.Item1)).ToArray(); } } }