229 lines
9.1 KiB
C#
229 lines
9.1 KiB
C#
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 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 async Task<List<GameVersionProps>> GetAllVersionsAsync()
|
||
{
|
||
var propsSet = new HashSet<GameVersionProps>();
|
||
|
||
// local descriptors
|
||
Directory.Create(GetVersionsDir());
|
||
foreach (IOPath subdir in Directory.GetDirectories(GetVersionsDir()))
|
||
{
|
||
string name = subdir.LastName().ToString();
|
||
var d = new GameVersionProps(name, null);
|
||
if(!File.Exists(d.LocalDescriptorPath))
|
||
throw new Exception("Can't find version descriptor file in directory '{subdir}'. Rename it as directory name.");
|
||
propsSet.Add(d);
|
||
}
|
||
|
||
// remote non-duplicating versions
|
||
foreach (var removeVersion in await Сеть.GetDownloadableVersions())
|
||
{
|
||
propsSet.Add(removeVersion);
|
||
}
|
||
|
||
// reverse sort
|
||
var propsList = propsSet.ToList();
|
||
propsList.Sort((a, b) => b.CompareTo(a));
|
||
return propsList;
|
||
}
|
||
|
||
public static async Task<GameVersion> CreateFromPropsAsync(GameVersionProps props)
|
||
{
|
||
if (!File.Exists(props.LocalDescriptorPath))
|
||
{
|
||
if (props.RemoteDescriptorUrl is null)
|
||
throw new NullReferenceException("can't download game version descriptor '"
|
||
+ props.Name + "', because RemoteDescriptorUrl is null");
|
||
await Сеть.DownloadFile(props.RemoteDescriptorUrl, props.LocalDescriptorPath);
|
||
}
|
||
|
||
return new GameVersion(props);
|
||
}
|
||
|
||
private GameVersion(GameVersionProps props)
|
||
{
|
||
_props = props;
|
||
string descriptorText = File.ReadAllText(props.LocalDescriptorPath);
|
||
_descriptor = JsonConvert.DeserializeObject<GameVersionDescriptor>(descriptorText)
|
||
?? 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),
|
||
new VersionFileDownloadTaskFactory(_descriptor),
|
||
];
|
||
if(LauncherApp.Config.download_java)
|
||
{
|
||
taskFactories.Add(new JavaDownloadTaskFactory(_descriptor));
|
||
}
|
||
//TODO: modpack
|
||
/*if (modpack != null)
|
||
{
|
||
taskFactories.Add(new ModpackDownloadTaskFactory(modpack));
|
||
}*/
|
||
|
||
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", "2048" },
|
||
{ "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() },
|
||
{ "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)
|
||
.WithStandardOutputPipe(PipeTarget.ToDelegate(LogGameOut))
|
||
.WithStandardErrorPipe(PipeTarget.ToDelegate(LogGameError))
|
||
.WithValidation(CommandResultValidation.None);
|
||
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;
|
||
} |