now it works

This commit is contained in:
2024-11-06 00:04:12 +05:00
parent 612976dfe6
commit 1f663902e2
54 changed files with 763 additions and 586 deletions

View File

@@ -0,0 +1,27 @@
namespace Mlaumcherb.Client.Avalonia.классы;
public class GameVersionCatalog
{
[JsonRequired] public List<RemoteVersionDescriptorProps> versions { get; set; } = null!;
}
public class AssetProperties
{
[JsonRequired] public string hash { get; set; } = "";
[JsonRequired] public int size { get; set; }
}
public class AssetIndex
{
[JsonRequired] public Dictionary<string, AssetProperties> objects { get; set; } = new();
}
public class RemoteVersionDescriptorProps
{
[JsonRequired] public string id { get; set; } = "";
[JsonRequired] public string type { get; set; } = "";
[JsonRequired] public string url { get; set; } = "";
[JsonRequired] public string sha1 { get; set; } = "";
[JsonRequired] public DateTime time { get; set; }
[JsonRequired] public DateTime releaseTime { get; set; }
}

View File

@@ -0,0 +1,114 @@
using System.Linq;
// ReSharper disable CollectionNeverUpdated.Global
namespace Mlaumcherb.Client.Avalonia.классы;
public class GameVersionDescriptor
{
[JsonRequired] public string id { get; set; } = "";
[JsonRequired] public DateTime time { get; set; }
[JsonRequired] public DateTime releaseTime { get; set; }
[JsonRequired] public string type { get; set; } = "";
[JsonRequired] public string mainClass { get; set; } = "";
[JsonRequired] public Downloads downloads { get; set; } = null!;
[JsonRequired] public List<Library> libraries { get; set; } = null!;
[JsonRequired] public AssetIndexProperties assetIndex { get; set; } = null!;
[JsonRequired] public string assets { get; set; } = "";
public JavaVersion javaVersion { get; set; } = new() { component = "jre-legacy", majorVersion = 8 };
public string? minecraftArguments { get; set; }
public ArgumentsNew? arguments { get; set; }
}
public class Artifact
{
[JsonRequired] public string url { get; set; } = "";
[JsonRequired] public string sha1 { get; set; } = "";
[JsonRequired] public int size { get; set; }
}
public class Os
{
public string? name { get; set; }
public string? arch { get; set; }
}
public class Rule
{
[JsonRequired] public string action { get; set; } = "";
public Os? os { get; set; }
public Dictionary<string, bool>? features { get; set; }
}
public class LibraryDownloads
{
public Artifact? artifact { get; set; }
public Dictionary<string, Artifact>? classifiers { get; set; }
}
public class Extract
{
public List<string>? exclude { get; set; }
}
public class Natives
{
public string? linux { get; set; }
public string? osx { get; set; }
public string? windows { get; set; }
}
public class Library
{
[JsonRequired] public string name { get; set; } = "";
public List<Rule>? rules { get; set; }
public Natives? natives { get; set; }
public Extract? extract { get; set; }
[JsonRequired] public LibraryDownloads downloads { get; set; } = null!;
}
public class AssetIndexProperties
{
[JsonRequired] public string id { get; set; } = "";
[JsonRequired] public string url { get; set; } = "";
[JsonRequired] public string sha1 { get; set; } = "";
[JsonRequired] public int size { get; set; }
[JsonRequired] public int totalSize { get; set; }
}
public class Downloads
{
[JsonRequired] public Artifact client { get; set; } = null!;
}
public class JavaVersion
{
[JsonRequired] public string component { get; set; } = "";
[JsonRequired] public int majorVersion { get; set; }
}
public class ArgValue
{
public struct StringOrArray : IEnumerable<string>
{
private string[] ar;
public StringOrArray(ICollection<string> v) => ar = v.ToArray();
public StringOrArray(string v) => ar = [v];
public static implicit operator StringOrArray(string[] v) => new(v);
public static implicit operator StringOrArray(string v) => new(v);
public static implicit operator string[](StringOrArray sar) => sar.ar;
public IEnumerator<string> GetEnumerator() => ar.AsEnumerable().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ar.GetEnumerator();
}
public ArgValue() { }
public ArgValue(string arg) => value = arg;
public static implicit operator ArgValue(string arg) => new(arg);
[JsonRequired] public StringOrArray value { get; set; } = [];
public List<Rule> rules { get; set; } = new();
}
public class ArgumentsNew
{
[JsonRequired] public List<ArgValue> jvm { get; set; } = new();
[JsonRequired] public List<ArgValue> game { get; set; } = new();
}

View File

@@ -0,0 +1,85 @@
using System.Runtime.InteropServices;
using Mlaumcherb.Client.Avalonia.холопы;
namespace Mlaumcherb.Client.Avalonia.классы;
public class JavaVersionCatalog
{
[JsonProperty("linux")]
public Dictionary<string, JavaVersionProps[]>? linux_x86 { get; set; }
[JsonProperty("linux-i386")]
public Dictionary<string, JavaVersionProps[]>? linux_x64 { get; set; }
[JsonProperty("mac-os")]
public Dictionary<string, JavaVersionProps[]>? osx_x64 { get; set; }
[JsonProperty("mac-os-arm64")]
public Dictionary<string, JavaVersionProps[]>? osx_arm64 { get; set; }
[JsonProperty("windows-arm64")]
public Dictionary<string, JavaVersionProps[]>? windows_arm64 { get; set; }
[JsonProperty("windows-x64")]
public Dictionary<string, JavaVersionProps[]>? windows_x64 { get; set; }
[JsonProperty("windows-x86")]
public Dictionary<string, JavaVersionProps[]>? windows_x86 { get; set; }
public JavaVersionProps GetVersionProps(JavaVersion version)
{
var arch = RuntimeInformation.OSArchitecture;
Dictionary<string, JavaVersionProps[]>? propsDict = null;
switch (arch)
{
case Architecture.X86:
if (OperatingSystem.IsWindows())
propsDict = windows_x86;
else if (OperatingSystem.IsLinux())
propsDict = linux_x86;
break;
case Architecture.X64:
if (OperatingSystem.IsWindows())
propsDict = windows_x64;
else if (OperatingSystem.IsLinux())
propsDict = linux_x64;
else if (OperatingSystem.IsMacOS())
propsDict = osx_x64;
break;
case Architecture.Arm64:
if (OperatingSystem.IsWindows())
propsDict = windows_arm64;
else if (OperatingSystem.IsMacOS())
propsDict = osx_arm64;
break;
}
if (propsDict != null && propsDict.TryGetValue(version.component, out var props_array))
{
if (props_array.Length != 0)
return props_array[0];
}
throw new PlatformNotSupportedException($"Can't download java {version.majorVersion} for your operating system. " +
$"Download it manually to directory {PathHelper.GetJavaRuntimeDir(version.component)}");
}
}
public class JavaVersionProps
{
/// url of JavaDistributiveManifest
[JsonRequired] public Artifact manifest { get; set; } = null!;
}
public class JavaDistributiveManifest
{
[JsonRequired] public Dictionary<string, JavaDistributiveElementProps> files { get; set; } = null!;
}
public class JavaDistributiveElementProps
{
/// "directory" / "file"
[JsonRequired] public string type { get; set; } = "";
public bool? executable { get; set; }
public JavaCompressedArtifact? downloads { get; set; }
}
public class JavaCompressedArtifact
{
public Artifact? lzma { get; set; }
[JsonRequired] public Artifact raw { get; set; } = null!;
}

View File

@@ -0,0 +1,40 @@
using Mlaumcherb.Client.Avalonia.холопы;
namespace Mlaumcherb.Client.Avalonia.классы;
public static class Rules
{
public static bool Check(ICollection<Rule>? rules, ICollection<string> features)
{
if(rules is null || rules.Count == 0)
return true;
bool allowed = false;
foreach (var r in rules)
{
if (r.os != null && !PlatformHelper.CheckOs(r.os))
continue;
if (r.features == null)
allowed = r.action == "allow";
else
{
foreach (var feature in features)
{
if (r.features.TryGetValue(feature, out bool is_enabled))
{
if (is_enabled)
{
allowed = r.action == "allow";
}
}
}
}
if(allowed)
break;
}
return allowed;
}
}

View File

@@ -0,0 +1,31 @@
namespace Mlaumcherb.Client.Avalonia.классы;
public class ArgumentsWithPlaceholders
{
protected List<string> _raw_args = new();
public IEnumerable<string> FillPlaceholders(Dictionary<string, string> values)
{
foreach (var _s in _raw_args)
{
string arg = _s;
int begin = arg.IndexOf("${", StringComparison.Ordinal);
while(begin != -1)
{
int keyBegin = begin + 2;
int end = arg.IndexOf('}', keyBegin);
if (end != -1)
{
var key = arg.Substring(keyBegin, end - keyBegin);
if (!values.TryGetValue(key, out var value))
throw new Exception($"can't find value for placeholder '{key}'");
arg = arg.Replace("${"+ key + "}", value);
}
if(end + 1 < arg.Length)
begin = arg.IndexOf("${", end + 1, StringComparison.Ordinal);
else break;
}
yield return arg;
}
}
}

View File

@@ -0,0 +1,41 @@
using DTLib.Extensions;
namespace Mlaumcherb.Client.Avalonia.классы;
public class GameArguments : ArgumentsWithPlaceholders
{
private static readonly string[] _initial_arguments =
[
"--width", "${resolution_width}",
"--height", "${resolution_height}",
];
private static readonly string[] _enabled_features =
[
];
public GameArguments(GameVersionDescriptor d)
{
_raw_args.AddRange(_initial_arguments);
if (d.minecraftArguments is not null)
{
_raw_args.AddRange(d.minecraftArguments.SplitToList(' ', quot: '"'));
}
else if (d.arguments is not null)
{
foreach (var av in d.arguments.game)
{
if(Rules.Check(av.rules, _enabled_features))
{
foreach (var arg in av.value)
{
if(!_raw_args.Contains(arg))
_raw_args.Add(arg);
}
}
}
}
else throw new Exception("no game arguments specified in descriptor");
}
}

View File

@@ -0,0 +1,62 @@
using Mlaumcherb.Client.Avalonia.холопы;
namespace Mlaumcherb.Client.Avalonia.классы;
public class GameVersionProps : IComparable<GameVersionProps>, IEquatable<GameVersionProps>
{
public string Name { get; }
public IOPath LocalDescriptorPath { get; }
public string? RemoteDescriptorUrl { get; }
private bool _isDownloaded;
public bool IsDownloaded
{
get => _isDownloaded;
set
{
bool downloadCompleted = value && !_isDownloaded;
_isDownloaded = value;
if(downloadCompleted)
OnDownloadCompleted?.Invoke();
}
}
public event Action? OnDownloadCompleted;
public GameVersionProps(string name, string? url)
{
Name = name;
LocalDescriptorPath = PathHelper.GetVersionDescriptorPath(name);
RemoteDescriptorUrl = url;
IsDownloaded = File.Exists(PathHelper.GetVersionJarFilePath(name));
}
public override string ToString() => Name;
public override int GetHashCode() => Name.GetHashCode();
public int CompareTo(GameVersionProps? other)
{
if (ReferenceEquals(this, other)) return 0;
if (other is null) return 1;
if (Version.TryParse(Name, out var version1) && Version.TryParse(other.Name, out var version2))
{
return version1.CompareTo(version2);
}
return String.Compare(Name, other.Name, StringComparison.InvariantCulture);
}
public bool Equals(GameVersionProps? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return Name == other.Name;
}
public override bool Equals(object? obj)
{
if (obj is GameVersionProps other) return Equals(other);
if (ReferenceEquals(this, obj)) return true;
return false;
}
}

View File

@@ -0,0 +1,49 @@
namespace Mlaumcherb.Client.Avalonia.классы;
public class JavaArguments : ArgumentsWithPlaceholders
{
private static readonly string[] _initial_arguments =
[
"-XX:+UnlockExperimentalVMOptions",
"-XX:+UseG1GC",
"-XX:G1NewSizePercent=20",
"-XX:G1ReservePercent=20",
"-XX:MaxGCPauseMillis=50",
"-XX:G1HeapRegionSize=32M",
"-XX:+DisableExplicitGC",
"-XX:+AlwaysPreTouch",
"-XX:+ParallelRefProcEnabled",
"-Xms${xms}M",
"-Xmx${xmx}M",
"-Dfile.encoding=UTF-8",
"-Dlog4j.configurationFile=${path}",
"-Djava.library.path=${natives_directory}",
"-Dminecraft.client.jar=${client_jar}",
"-Dminecraft.launcher.brand=${launcher_name}",
"-Dminecraft.launcher.version=${launcher_version}",
"-cp", "${classpath}"
];
private static readonly string[] _enabled_features =
[
];
public JavaArguments(GameVersionDescriptor d)
{
_raw_args.AddRange(_initial_arguments);
if (d.arguments is not null)
{
foreach (var av in d.arguments.jvm)
{
if (Rules.Check(av.rules, _enabled_features))
{
foreach (var arg in av.value)
{
if(!_raw_args.Contains(arg))
_raw_args.Add(arg);
}
}
}
}
}
}

View File

@@ -0,0 +1,80 @@
using DTLib.Extensions;
using Mlaumcherb.Client.Avalonia.холопы;
namespace Mlaumcherb.Client.Avalonia.классы;
public class Libraries
{
private static readonly string[] enabled_features = [];
public record JarLib(string name, IOPath jarFilePath, Artifact artifact);
public record NativeLib(string name, IOPath jarFilePath, Artifact artifact, Extract? extractionOptions)
: JarLib(name, jarFilePath, artifact);
public IReadOnlyCollection<JarLib> Libs { get; }
public Libraries(GameVersionDescriptor descriptor)
{
List<JarLib> libs = new();
HashSet<string> libHashes = new();
foreach (var l in descriptor.libraries)
{
if (l.rules != null && !Rules.Check(l.rules, enabled_features))
continue;
if (l.natives != null)
{
string? nativesKey;
if (OperatingSystem.IsWindows())
nativesKey = l.natives.windows;
else if (OperatingSystem.IsLinux())
nativesKey = l.natives.linux;
else if (OperatingSystem.IsMacOS())
nativesKey = l.natives.osx;
else throw new PlatformNotSupportedException();
if(nativesKey is null)
throw new Exception($"nativesKey for '{l.name}' is null");
// example: "natives-windows-${arch}"
if (nativesKey.Contains('$'))
{
var span = nativesKey.AsSpan();
nativesKey = span.After("${").Before('}') switch
{
"arch" => span.Before("${").ToString() + PlatformHelper.GetArchOld(),
_ => throw new Exception($"unknown placeholder in {nativesKey}")
};
}
Artifact artifact = null!;
if(l.downloads.classifiers != null && !l.downloads.classifiers.TryGetValue(nativesKey, out artifact!))
throw new Exception($"can't find artifact for '{l.name}' with nativesKey '{nativesKey}'");
// skipping duplicates (WHO THE HELL CREATES THIS DISCRIPTORS AAAAAAAAA)
if(!libHashes.Add(artifact.sha1))
continue;
string urlTail = artifact.url.AsSpan().After("://").After('/').ToString();
IOPath jarFilePath = Path.Concat(PathHelper.GetLibrariesDir(), urlTail);
libs.Add(new NativeLib(l.name, jarFilePath, artifact, l.extract));
}
else
{
Artifact? artifact = l.downloads.artifact;
if (artifact == null)
throw new NullReferenceException($"artifact for '{l.name}' is null");
// skipping duplicates
if(!libHashes.Add(artifact.sha1))
continue;
string urlTail = artifact.url.AsSpan().After("://").After('/').ToString();
IOPath jarFilePath = Path.Concat(PathHelper.GetLibrariesDir(), urlTail);
libs.Add(new JarLib(l.name, jarFilePath, artifact));
}
}
Libs = libs;
}
}