using System.Linq; using System.Security.Cryptography; using CliWrap; using DTLib.Extensions; using Mlaumcherb.Client.Avalonia.зримое; using Mlaumcherb.Client.Avalonia.классы; using Mlaumcherb.Client.Avalonia.сеть; using Mlaumcherb.Client.Avalonia.сеть.TaskFactories; using Mlaumcherb.Client.Avalonia.холопы; using static Mlaumcherb.Client.Avalonia.холопы.PathHelper; namespace Mlaumcherb.Client.Avalonia; public class GameVersion { private readonly InstalledGameVersionProps _props; public string Id => _props.Id; public IOPath WorkingDirectory { get; } private IOPath JavaExecutableFilePath; private GameVersionDescriptor _descriptor; private JavaArguments _javaArgs; private GameArguments _gameArgs; private Libraries _libraries; private CancellationTokenSource? _gameCts; private CommandTask? _commandTask; internal GameVersion(InstalledGameVersionProps props, GameVersionDescriptor descriptor) { _props = props; _descriptor = descriptor; _javaArgs = new JavaArguments(_descriptor); _gameArgs = new GameArguments(_descriptor); _libraries = new Libraries(_descriptor); WorkingDirectory = GetVersionDir(_descriptor.id); JavaExecutableFilePath = GetJavaExecutablePath( _descriptor.javaVersion.component, LauncherApp.Config.redirect_game_output); } public async Task Download(bool checkHashes, Action networkTaskCreatedCallback) { LauncherApp.Logger.LogInfo(Id, $"started updating version {Id}"); List taskFactories = [ new AssetsDownloadTaskFactory(_descriptor), new LibrariesDownloadTaskFactory(_descriptor, _libraries), ]; if (LauncherApp.Config.download_java && (!File.Exists(JavaExecutableFilePath) || checkHashes)) taskFactories.Add(new JavaDownloadTaskFactory(_descriptor)); if (_descriptor.modpack != null) taskFactories.Add(new ModpackDownloadTaskFactory(_descriptor)); // has to be downloaded last because it is used to check if version is installed taskFactories.Add(new VersionJarDownloadTaskFactory(_descriptor)); var networkTasks = new List(); for (int i = 0; i < taskFactories.Count; i++) { var nt = await taskFactories[i].CreateAsync(checkHashes); if (nt != null) { networkTasks.Add(nt); networkTaskCreatedCallback.Invoke(nt); } } foreach (var nt in networkTasks) { await nt.StartAsync(); } // create log4j config file if (!File.Exists(GetLog4jConfigFilePath())) { await using var res = EmbeddedResources.GetResourceStream( $"Mlaumcherb.Client.Avalonia.встроенное.{GetLog4jConfigFileName()}"); await using var file = File.OpenWrite(GetLog4jConfigFilePath()); await res.CopyToAsync(file); } _props.IsInstalled = true; LauncherApp.Logger.LogInfo(Id, $"finished updating version {Id}"); } //minecraft player uuid explanation //https://gist.github.com/CatDany/0e71ca7cd9b42a254e49/ //java uuid generation in c# //https://stackoverflow.com/questions/18021808/uuid-interop-with-c-sharp-code public static string GetPlayerUUID(string name) { byte[] name_bytes = Encoding.UTF8.GetBytes("OfflinePlayer:" + name); var md5 = MD5.Create(); byte[] hash = md5.ComputeHash(name_bytes); hash[6] &= 0x0f; hash[6] |= 0x30; hash[8] &= 0x3f; hash[8] |= 0x80; StringBuilder sb = new StringBuilder(); for (int i = 0; i < hash.Length; i++) sb.Append(hash[i].ToString("x2")); sb.Insert(8, '-').Insert(13, '-').Insert(18, '-').Insert(23, '-'); return sb.ToString(); } public async Task Launch() { if (string.IsNullOrWhiteSpace(LauncherApp.Config.player_name)) throw new Exception("invalid player name"); string classSeparator = PlatformHelper.GetOs() == "windows" ? ";" : ":"; Directory.Create(WorkingDirectory); string uuid = GetPlayerUUID(LauncherApp.Config.player_name); Dictionary placeholder_values = new() { { "resolution_width", "1600" }, { "resolution_height", "900" }, { "xms", LauncherApp.Config.max_memory.ToString() }, { "xmx", LauncherApp.Config.max_memory.ToString() }, { "auth_player_name", LauncherApp.Config.player_name }, { "auth_access_token", uuid }, { "auth_session", $"token:{uuid}:{uuid}" }, { "auth_xuid", "" }, { "auth_uuid", uuid }, { "clientid", "" }, { "user_properties", "{}" }, { "user_type", "userType" }, { "launcher_name", "java-minecraft-launcher" }, { "launcher_version", "1.6.84-j" }, { "classpath", _libraries.Libs.Select(l => l.jarFilePath) .Append(GetVersionJarFilePath(_descriptor.id)) .MergeToString(classSeparator) }, { "assets_index_name", _descriptor.assets }, { "assets_root", GetAssetsDir().ToString() }, { "game_assets", GetAssetsDir().ToString() }, { "game_directory", WorkingDirectory.ToString() }, { "natives_directory", GetNativeLibrariesDir(_descriptor.id).ToString() }, { "library_directory", GetLibrariesDir().ToString() }, { "classpath_separator", classSeparator}, { "path", GetLog4jConfigFilePath().ToString() }, { "version_name", _descriptor.id }, { "version_type", _descriptor.type }, { "arch", PlatformHelper.GetArchOld() }, // { "quickPlayMultiplayer", "" }, // { "quickPlayPath", "" }, // { "quickPlayRealms", "" }, // { "quickPlaySingleplayer", "" }, { "client_jar", GetVersionJarFilePath(_descriptor.id).ToString() }, }; List argsList = new(); argsList.AddRange(_javaArgs.FillPlaceholders(placeholder_values)); argsList.Add(_descriptor.mainClass); argsList.AddRange(_gameArgs.FillPlaceholders(placeholder_values)); var command = Cli.Wrap(JavaExecutableFilePath.ToString()) .WithWorkingDirectory(WorkingDirectory.ToString()) .WithArguments(argsList) .WithValidation(CommandResultValidation.None); if (LauncherApp.Config.redirect_game_output) { command = command .WithStandardOutputPipe(PipeTarget.ToDelegate(LogGameOut)) .WithStandardErrorPipe(PipeTarget.ToDelegate(LogGameError)); } LauncherApp.Logger.LogInfo(Id, "launching the game"); LauncherApp.Logger.LogDebug(Id, "java: " + command.TargetFilePath); LauncherApp.Logger.LogDebug(Id, "working_dir: " + command.WorkingDirPath); LauncherApp.Logger.LogDebug(Id, "arguments: \n\t" + argsList.MergeToString("\n\t")); _gameCts = new(); _commandTask = command.ExecuteAsync(_gameCts.Token); var result = await _commandTask; LauncherApp.Logger.LogInfo(Id, $"game exited with code {result.ExitCode}"); } private void LogGameOut(string line) { LauncherApp.Logger.LogInfo(Id, line); } private void LogGameError(string line) { LauncherApp.Logger.LogWarn(Id, line); } public void Close() { _gameCts?.Cancel(); } public override string ToString() => Id; }