mlaumcherb/Mlaumcherb.Client.Avalonia/GameVersion.cs

253 lines
11 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 Newtonsoft.Json.Linq;
using static Mlaumcherb.Client.Avalonia.холопы.PathHelper;
namespace Mlaumcherb.Client.Avalonia;
public class GameVersion
{
private readonly GameVersionProps _props;
public string Name => _props.Name;
public IOPath WorkingDirectory { get; }
private IOPath JavaExecutableFilePath;
private GameVersionDescriptor _descriptor;
private JavaArguments _javaArgs;
private GameArguments _gameArgs;
private Libraries _libraries;
private CancellationTokenSource? _gameCts;
private CommandTask<CommandResult>? _commandTask;
public static Task<List<GameVersionProps>> GetAllVersionsAsync()
{
var propsList = new List<GameVersionProps>();
// local descriptors
Directory.Create(GetVersionsDir());
foreach (IOPath subdir in Directory.GetDirectories(GetVersionsDir()))
{
string name = subdir.LastName().ToString();
var d = new GameVersionProps(name);
if(!File.Exists(d.LocalDescriptorPath))
throw new Exception("Can't find version descriptor file in directory '{subdir}'. Rename it as directory name.");
propsList.Add(d);
}
// reverse sort
propsList.Sort((a, b) => b.CompareTo(a));
return Task.FromResult(propsList);
}
public static async Task<GameVersion> CreateFromPropsAsync(GameVersionProps props, bool checkHash)
{
//TODO: refresh version descriptor from server
if (!File.Exists(props.LocalDescriptorPath))
{
if (props.RemoteProps is null)
throw new NullReferenceException("can't download game version descriptor '"
+ props.Name + "', because RemoteDescriptorUrl is null");
await NetworkHelper.DownloadFile(props.RemoteProps.url, props.LocalDescriptorPath);
}
return new GameVersion(props);
}
private GameVersion(GameVersionProps props)
{
_props = props;
string descriptorText = File.ReadAllText(props.LocalDescriptorPath);
JObject descriptorRaw = JObject.Parse(descriptorText);
// Descriptors can inherit from other descriptors.
// For example, 1.12.2-forge-14.23.5.2860 inherits from 1.12.2
if (descriptorRaw.TryGetValue("inheritsFrom", out var v))
{
string parentDescriptorId = v.Value<string>()
?? throw new Exception("inheritsFrom is null");
LauncherApp.Logger.LogInfo(Name, $"merging descriptor '{parentDescriptorId}' with '{Name}'");
IOPath parentDescriptorPath = GetVersionDescriptorPath(parentDescriptorId);
if (!File.Exists(parentDescriptorPath))
throw new Exception($"Версия '{Name} требует установить версию '{parentDescriptorId}'");
string parentDescriptorText = File.ReadAllText(parentDescriptorPath);
JObject parentDescriptorRaw = JObject.Parse(parentDescriptorText);
parentDescriptorRaw.Merge(descriptorRaw,
new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Concat });
descriptorRaw = parentDescriptorRaw;
// removing dependency
descriptorRaw.Remove("inheritsFrom");
File.WriteAllText(props.LocalDescriptorPath, descriptorRaw.ToString());
}
_descriptor = descriptorRaw.ToObject<GameVersionDescriptor>()
?? throw new Exception($"can't parse descriptor file '{props.LocalDescriptorPath}'");
_javaArgs = new JavaArguments(_descriptor);
_gameArgs = new GameArguments(_descriptor);
_libraries = new Libraries(_descriptor);
WorkingDirectory = GetVersionDir(_descriptor.id);
JavaExecutableFilePath = GetJavaExecutablePath(_descriptor.javaVersion.component, LauncherApp.Config.debug);
}
public async Task UpdateFiles(bool checkHashes, Action<NetworkTask> networkTaskCreatedCallback)
{
LauncherApp.Logger.LogInfo(Name, $"started updating version {Name}");
List<INetworkTaskFactory> taskFactories =
[
new AssetsDownloadTaskFactory(_descriptor),
new LibrariesDownloadTaskFactory(_descriptor, _libraries),
];
if (LauncherApp.Config.download_java)
{
taskFactories.Add(new JavaDownloadTaskFactory(_descriptor));
}
if (_descriptor.modpack != null)
{
taskFactories.Add(new ModpackDownloadTaskFactory(_descriptor));
}
// has to be downloaded last because it is used by GameVersionProps to check if version is installed
taskFactories.Add(new VersionJarDownloadTaskFactory(_descriptor));
var networkTasks = new List<NetworkTask>();
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.IsDownloaded = true;
LauncherApp.Logger.LogInfo(Name, $"finished updating version {Name}");
}
//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");
Directory.Create(WorkingDirectory);
string uuid = GetPlayerUUID(LauncherApp.Config.player_name);
Dictionary<string, string> 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(';') },
{ "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", ";"},
{ "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<string> 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(Name, "launching the game");
LauncherApp.Logger.LogDebug(Name, "java: " + command.TargetFilePath);
LauncherApp.Logger.LogDebug(Name, "working_dir: " + command.WorkingDirPath);
LauncherApp.Logger.LogDebug(Name, "arguments: \n\t" + argsList.MergeToString("\n\t"));
_gameCts = new();
_commandTask = command.ExecuteAsync(_gameCts.Token);
var result = await _commandTask;
LauncherApp.Logger.LogInfo(Name, $"game exited with code {result.ExitCode}");
}
private void LogGameOut(string line)
{
LauncherApp.Logger.LogInfo(Name, line);
}
private void LogGameError(string line)
{
LauncherApp.Logger.LogWarn(Name, line);
}
public void Close()
{
_gameCts?.Cancel();
}
public override string ToString() => Name;
}