forge support

This commit is contained in:
Timerix 2024-12-27 22:12:20 +05:00
parent cb85b132c3
commit 631f5c9126
8 changed files with 76 additions and 31 deletions

View File

@ -17,16 +17,16 @@ public record Config
public int max_memory { get; set; } = 4096; public int max_memory { get; set; } = 4096;
public string minecraft_dir { get; set; } = "."; public string minecraft_dir { get; set; } = ".";
public bool download_java { get; set; } = true; public bool download_java { get; set; } = true;
public bool redirect_game_output { get; set; } = false;
public string? last_launched_version { get; set; } public string? last_launched_version { get; set; }
public int max_parallel_downloads { get; set; } = 16; public int max_parallel_downloads { get; set; } = 16;
public VersionCatalogProps[] version_catalogs { get; set; } = public VersionCatalogProps[] version_catalogs { get; set; } =
[ [
new() { Name = "Mojang", Url = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json" } new() { Name = "Mojang", Url = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json" }
]; ];
[JsonIgnore] static IOPath _filePath = "config.json"; [JsonIgnore] private static IOPath _filePath = "config.json";
public static Config LoadFromFile() public static Config LoadFromFile()
{ {

View File

@ -7,6 +7,7 @@ using Mlaumcherb.Client.Avalonia.классы;
using Mlaumcherb.Client.Avalonia.сеть; using Mlaumcherb.Client.Avalonia.сеть;
using Mlaumcherb.Client.Avalonia.сеть.TaskFactories; using Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
using Mlaumcherb.Client.Avalonia.холопы; using Mlaumcherb.Client.Avalonia.холопы;
using Newtonsoft.Json.Linq;
using static Mlaumcherb.Client.Avalonia.холопы.PathHelper; using static Mlaumcherb.Client.Avalonia.холопы.PathHelper;
namespace Mlaumcherb.Client.Avalonia; namespace Mlaumcherb.Client.Avalonia;
@ -62,9 +63,32 @@ public class GameVersion
private GameVersion(GameVersionProps props) private GameVersion(GameVersionProps props)
{ {
_props = props; _props = props;
string descriptorText = File.ReadAllText(props.LocalDescriptorPath); string descriptorText = File.ReadAllText(props.LocalDescriptorPath);
_descriptor = JsonConvert.DeserializeObject<GameVersionDescriptor>(descriptorText) JObject descriptorRaw = JObject.Parse(descriptorText);
?? throw new Exception($"can't parse descriptor file '{props.LocalDescriptorPath}'");
// 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); _javaArgs = new JavaArguments(_descriptor);
_gameArgs = new GameArguments(_descriptor); _gameArgs = new GameArguments(_descriptor);
_libraries = new Libraries(_descriptor); _libraries = new Libraries(_descriptor);
@ -82,7 +106,7 @@ public class GameVersion
new LibrariesDownloadTaskFactory(_descriptor, _libraries), new LibrariesDownloadTaskFactory(_descriptor, _libraries),
new VersionFileDownloadTaskFactory(_descriptor), new VersionFileDownloadTaskFactory(_descriptor),
]; ];
if(LauncherApp.Config.download_java) if (LauncherApp.Config.download_java)
{ {
taskFactories.Add(new JavaDownloadTaskFactory(_descriptor)); taskFactories.Add(new JavaDownloadTaskFactory(_descriptor));
} }
@ -190,9 +214,13 @@ public class GameVersion
var command = Cli.Wrap(JavaExecutableFilePath.ToString()) var command = Cli.Wrap(JavaExecutableFilePath.ToString())
.WithWorkingDirectory(WorkingDirectory.ToString()) .WithWorkingDirectory(WorkingDirectory.ToString())
.WithArguments(argsList) .WithArguments(argsList)
.WithStandardOutputPipe(PipeTarget.ToDelegate(LogGameOut))
.WithStandardErrorPipe(PipeTarget.ToDelegate(LogGameError))
.WithValidation(CommandResultValidation.None); .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.LogInfo(Name, "launching the game");
LauncherApp.Logger.LogDebug(Name, "java: " + command.TargetFilePath); LauncherApp.Logger.LogDebug(Name, "java: " + command.TargetFilePath);
LauncherApp.Logger.LogDebug(Name, "working_dir: " + command.WorkingDirPath); LauncherApp.Logger.LogDebug(Name, "working_dir: " + command.WorkingDirPath);

View File

@ -15,6 +15,12 @@ public class LauncherApp : Application
Config = Config.LoadFromFile(); Config = Config.LoadFromFile();
Logger.DebugLogEnabled = Config.debug; Logger.DebugLogEnabled = Config.debug;
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
// some file required by forge installer
if (!File.Exists("launcher_profiles.json"))
{
File.WriteAllText("launcher_profiles.json", "{}");
}
} }
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()

View File

@ -90,6 +90,9 @@
<CheckBox IsChecked="{Binding #window.EnableJavaDownload}"> <CheckBox IsChecked="{Binding #window.EnableJavaDownload}">
Скачивать джаву Скачивать джаву
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding #window.RedirectGameOutput}">
Выводить лог игры в лаунчер
</CheckBox>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
<Button Name="LaunchButton" Grid.Row="1" <Button Name="LaunchButton" Grid.Row="1"

View File

@ -51,6 +51,15 @@ public partial class MainWindow : Window
set => SetValue(EnableJavaDownloadProperty, value); set => SetValue(EnableJavaDownloadProperty, value);
} }
public static readonly StyledProperty<bool> RedirectGameOutputProperty =
AvaloniaProperty.Register<MainWindow, bool>(nameof(RedirectGameOutput),
defaultBindingMode: BindingMode.TwoWay, defaultValue: false);
public bool RedirectGameOutput
{
get => GetValue(RedirectGameOutputProperty);
set => SetValue(RedirectGameOutputProperty, value);
}
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
@ -65,6 +74,7 @@ public partial class MainWindow : Window
PlayerName = LauncherApp.Config.player_name; PlayerName = LauncherApp.Config.player_name;
MemoryLimit = LauncherApp.Config.max_memory; MemoryLimit = LauncherApp.Config.max_memory;
EnableJavaDownload = LauncherApp.Config.download_java; EnableJavaDownload = LauncherApp.Config.download_java;
RedirectGameOutput = LauncherApp.Config.redirect_game_output;
InstalledVersionComboBox.SelectedIndex = 0; InstalledVersionComboBox.SelectedIndex = 0;
InstalledVersionComboBox.IsEnabled = false; InstalledVersionComboBox.IsEnabled = false;
@ -119,6 +129,7 @@ public partial class MainWindow : Window
LauncherApp.Config.player_name = PlayerName; LauncherApp.Config.player_name = PlayerName;
LauncherApp.Config.max_memory = MemoryLimit; LauncherApp.Config.max_memory = MemoryLimit;
LauncherApp.Config.download_java = EnableJavaDownload; LauncherApp.Config.download_java = EnableJavaDownload;
LauncherApp.Config.redirect_game_output = RedirectGameOutput;
LauncherApp.Config.SaveToFile(); LauncherApp.Config.SaveToFile();
if (selectedVersion == null) if (selectedVersion == null)
return; return;

View File

@ -21,6 +21,7 @@ public class GameVersionDescriptor
public class Artifact public class Artifact
{ {
public string? path = null;
[JsonRequired] public string url { get; set; } = ""; [JsonRequired] public string url { get; set; } = "";
[JsonRequired] public string sha1 { get; set; } = ""; [JsonRequired] public string sha1 { get; set; } = "";
[JsonRequired] public int size { get; set; } [JsonRequired] public int size { get; set; }

View File

@ -7,18 +7,22 @@ public class Libraries
{ {
private static readonly string[] enabled_features = []; private static readonly string[] enabled_features = [];
public record JarLib(string name, IOPath? jarFilePath, Artifact artifact); public record JarLib(string name, IOPath jarFilePath, Artifact artifact);
public record NativeLib(string name, IOPath? jarFilePath, Artifact artifact, Extract? extractionOptions) public record NativeLib(string name, IOPath jarFilePath, Artifact artifact, Extract? extractionOptions)
: JarLib(name, jarFilePath, artifact); : JarLib(name, jarFilePath, artifact);
public IReadOnlyCollection<JarLib> Libs { get; } public IReadOnlyCollection<JarLib> Libs { get; }
private IOPath? TryGetJarFilePath(Artifact artifact) private IOPath GetJarFilePath(Artifact artifact)
{ {
if(string.IsNullOrEmpty(artifact.url)) string relativePath;
return null; if (!string.IsNullOrEmpty(artifact.path))
string urlTail = artifact.url.AsSpan().After("://").After('/').ToString(); relativePath = artifact.path;
return Path.Concat(PathHelper.GetLibrariesDir(), urlTail); else if (!string.IsNullOrEmpty(artifact.url))
relativePath = artifact.url.AsSpan().After("://").After('/').ToString();
else throw new ArgumentException("Artifact must have a path or url");
return Path.Concat(PathHelper.GetLibrariesDir(), relativePath);
} }
public Libraries(GameVersionDescriptor descriptor) public Libraries(GameVersionDescriptor descriptor)
@ -63,7 +67,7 @@ public class Libraries
if(!libHashes.Add(artifact.sha1)) if(!libHashes.Add(artifact.sha1))
continue; continue;
libs.Add(new NativeLib(l.name, TryGetJarFilePath(artifact), artifact, l.extract)); libs.Add(new NativeLib(l.name, GetJarFilePath(artifact), artifact, l.extract));
} }
else else
{ {
@ -75,7 +79,7 @@ public class Libraries
if(!libHashes.Add(artifact.sha1)) if(!libHashes.Add(artifact.sha1))
continue; continue;
libs.Add(new JarLib(l.name, TryGetJarFilePath(artifact), artifact)); libs.Add(new JarLib(l.name, GetJarFilePath(artifact), artifact));
} }
} }

View File

@ -43,15 +43,7 @@ public class LibrariesDownloadTaskFactory : INetworkTaskFactory
foreach (var l in _libraries.Libs) foreach (var l in _libraries.Libs)
{ {
// Forge installer downloads some libraries manually. if (!File.Exists(l.jarFilePath))
// Such libraries have empty url in descriptor.
if(l.jarFilePath is null)
{
LauncherApp.Logger.LogDebug(nameof(NetworkHelper), $"library artifact has empty url: '{l.name}'");
continue;
}
if (!File.Exists(l.jarFilePath.Value))
{ {
_libsToDownload.Add(l); _libsToDownload.Add(l);
} }
@ -61,7 +53,7 @@ public class LibrariesDownloadTaskFactory : INetworkTaskFactory
} }
else if (checkHashes) else if (checkHashes)
{ {
using var fs = File.OpenRead(l.jarFilePath.Value); using var fs = File.OpenRead(l.jarFilePath);
string hash = _hasher.ComputeHash(fs).HashToString(); string hash = _hasher.ComputeHash(fs).HashToString();
if(hash != l.artifact.sha1) if(hash != l.artifact.sha1)
_libsToDownload.Add(l); _libsToDownload.Add(l);
@ -91,12 +83,12 @@ public class LibrariesDownloadTaskFactory : INetworkTaskFactory
await Parallel.ForEachAsync(_libsToDownload, opt, async (l, _ct) => await Parallel.ForEachAsync(_libsToDownload, opt, async (l, _ct) =>
{ {
LauncherApp.Logger.LogDebug(nameof(NetworkHelper), $"downloading library '{l.name}' to '{l.jarFilePath}'"); LauncherApp.Logger.LogDebug(nameof(NetworkHelper), $"downloading library '{l.name}' to '{l.jarFilePath}'");
if(l.jarFilePath is null) if(string.IsNullOrEmpty(l.artifact.url))
throw new Exception("jarFilePath is null"); throw new Exception($"library '{l.name}' doesn't have a url to download");
await DownloadFile(l.artifact.url, l.jarFilePath.Value, _ct, pr.AddBytesCount); await DownloadFile(l.artifact.url, l.jarFilePath, _ct, pr.AddBytesCount);
if (l is Libraries.NativeLib n) if (l is Libraries.NativeLib n)
{ {
var zipf = File.OpenRead(n.jarFilePath!.Value); var zipf = File.OpenRead(n.jarFilePath);
ZipFile.ExtractToDirectory(zipf, _nativesDir.ToString(), true); ZipFile.ExtractToDirectory(zipf, _nativesDir.ToString(), true);
if (n.extractionOptions?.exclude != null) if (n.extractionOptions?.exclude != null)
{ {