game assets downloading
This commit is contained in:
parent
581c4b5498
commit
c2e2785a32
@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
public class LauncherLogger : FileLogger
|
public class LauncherLogger : FileLogger
|
||||||
{
|
{
|
||||||
public static readonly IOPath LogsDirectory = "launcher-logs";
|
public static readonly IOPath LogsDirectory = "launcher_logs";
|
||||||
|
|
||||||
public LauncherLogger() : base(LogsDirectory, "млаумчерб")
|
public LauncherLogger() : base(LogsDirectory, "млаумчерб")
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
DebugLogEnabled = true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,72 +1,191 @@
|
|||||||
using System.Net.Http;
|
using System.Buffers;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using Млаумчерб.Клиент.классы;
|
||||||
using System.Threading.Tasks;
|
using Timer = DTLib.Timer;
|
||||||
|
|
||||||
namespace Млаумчерб.Клиент;
|
namespace Млаумчерб.Клиент;
|
||||||
|
|
||||||
public record struct NetworkTransferResult(long BytesTotal, long BytesTransferred, long BytesPerSecond)
|
public static class Network
|
||||||
{
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"transferred {BytesTransferred}/{BytesTotal} bytes ({BytesPerSecond}) bps";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NetworkTransferTask
|
|
||||||
{
|
|
||||||
public Task Task { get; private set; }
|
|
||||||
public Progress<NetworkTransferResult> Progress { get; private set; }
|
|
||||||
|
|
||||||
private Stream _src;
|
|
||||||
private Stream _dst;
|
|
||||||
private CancellationTokenSource _cts;
|
|
||||||
private DTLib.Timer _timer;
|
|
||||||
|
|
||||||
public NetworkTransferTask(Stream src, Stream dst)
|
|
||||||
{
|
|
||||||
_src = src;
|
|
||||||
_dst = dst;
|
|
||||||
_cts = new CancellationTokenSource();
|
|
||||||
Progress = new Progress<NetworkTransferResult>();
|
|
||||||
_timer = new(true, 1000, ReportProgress);
|
|
||||||
Task = Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
_timer.Start();
|
|
||||||
Task = _src.CopyToAsync(_dst);
|
|
||||||
_timer.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
_cts.Cancel();
|
|
||||||
_timer.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private long previousBytesTransferred;
|
|
||||||
private void ReportProgress()
|
|
||||||
{
|
|
||||||
long bytesTotal = _src.Length, bytesTransferred = _src.Position;
|
|
||||||
long bytesPerSecond = bytesTransferred - previousBytesTransferred;
|
|
||||||
previousBytesTransferred = bytesTransferred;
|
|
||||||
((IProgress<NetworkTransferResult>)Progress).Report(new NetworkTransferResult(bytesTotal, bytesTransferred, bytesPerSecond));
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskAwaiter GetAwaiter() => Task.GetAwaiter();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class NetworkHelper
|
|
||||||
{
|
{
|
||||||
private static HttpClient http = new();
|
private static HttpClient http = new();
|
||||||
|
private const string ASSET_SERVER_URL = "https://resources.download.minecraft.net/";
|
||||||
|
private static readonly string[] VERSION_MANIFEST_URLS = {
|
||||||
|
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
|
||||||
|
};
|
||||||
|
|
||||||
public static NetworkTransferTask DownloadHTTPFileAsync(string url, Stream destinationStream)
|
public static async Task DownloadFileHTTP(string url, IOPath outPath,
|
||||||
|
Action<ArraySegment<byte>>? transformFunc = null, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var sourceStream = http.GetStreamAsync(url).GetAwaiter().GetResult();
|
await using var src = await http.GetStreamAsync(url, ct);
|
||||||
NetworkTransferTask task = new(sourceStream, destinationStream);
|
await using var dst = File.OpenWrite(outPath);
|
||||||
task.Start();
|
|
||||||
return task;
|
await src.CopyTransformAsync(dst, transformFunc, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task CopyTransformAsync(this Stream src, Stream dst,
|
||||||
|
Action<ArraySegment<byte>>? transformFunc = null, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// default dotnet runtime buffer size
|
||||||
|
int bufferSize = 81920;
|
||||||
|
byte[] readBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||||
|
byte[] writeBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var readTask = src.ReadAsync(readBuffer, 0, bufferSize, ct).ConfigureAwait(false);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int readCount = await readTask;
|
||||||
|
if (readCount == 0)
|
||||||
|
break;
|
||||||
|
(readBuffer, writeBuffer) = (writeBuffer, readBuffer);
|
||||||
|
readTask = src.ReadAsync(readBuffer, 0, bufferSize, ct).ConfigureAwait(false);
|
||||||
|
transformFunc?.Invoke(new ArraySegment<byte>(writeBuffer, 0, readCount));
|
||||||
|
dst.Write(writeBuffer, 0, readCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{ }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(readBuffer);
|
||||||
|
ArrayPool<byte>.Shared.Return(writeBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task DownloadAssets(AssetIndexProperties assetIndexProperties, CancellationToken ct, bool force)
|
||||||
|
{
|
||||||
|
IOPath indexFilePath = Пролетариат.GetAssetIndexFilePath(assetIndexProperties.id);
|
||||||
|
if (File.Exists(indexFilePath) && !force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IOPath indexFilePathTmp = indexFilePath + ".tmp";
|
||||||
|
if(File.Exists(indexFilePathTmp))
|
||||||
|
File.Delete(indexFilePathTmp);
|
||||||
|
|
||||||
|
// TODO: add something to Downloads ScrollList
|
||||||
|
Приложение.Логгер.LogInfo(nameof(DownloadAssets), $"started downloading asset index to '{indexFilePathTmp}'");
|
||||||
|
await DownloadFileHTTP(assetIndexProperties.url, indexFilePathTmp, null, ct);
|
||||||
|
Приложение.Логгер.LogInfo(nameof(DownloadAssets), "finished downloading asset index");
|
||||||
|
|
||||||
|
string indexFileText = File.ReadAllText(indexFilePathTmp);
|
||||||
|
AssetIndex assetIndex = JsonConvert.DeserializeObject<AssetIndex>(indexFileText)
|
||||||
|
?? throw new Exception($"can't deserialize asset index file '{indexFilePathTmp}'");
|
||||||
|
|
||||||
|
var assets = new List<KeyValuePair<string, AssetProperties>>();
|
||||||
|
HashSet<string> assetHashes = new HashSet<string>();
|
||||||
|
long totalSize = 0;
|
||||||
|
long currentSize = 0;
|
||||||
|
foreach (var pair in assetIndex.objects)
|
||||||
|
{
|
||||||
|
if (!assetHashes.Contains(pair.Value.hash))
|
||||||
|
{
|
||||||
|
assets.Add(pair);
|
||||||
|
assetHashes.Add(pair.Value.hash);
|
||||||
|
totalSize += pair.Value.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddBytesCountAtomic(ArraySegment<byte> chunk)
|
||||||
|
{
|
||||||
|
long chunkSize = chunk.Count;
|
||||||
|
Interlocked.Add(ref currentSize, chunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
long prevSize = 0;
|
||||||
|
int timerDelay = 2000;
|
||||||
|
void ReportProgress()
|
||||||
|
{
|
||||||
|
// TODO: add something to Downloads ScrollList
|
||||||
|
long totalSizeM = totalSize/(1024*1024);
|
||||||
|
long currentSizeM = currentSize/(1024*1024);
|
||||||
|
long bytesPerSec = (currentSize - prevSize) / (timerDelay / 1000);
|
||||||
|
float KbytesPerSec = bytesPerSec / 1024f;
|
||||||
|
prevSize = currentSize;
|
||||||
|
Приложение.Логгер.LogDebug(nameof(DownloadAssets),
|
||||||
|
$"download progress {currentSizeM}Mb/{totalSizeM}Mb ({KbytesPerSec}Kb/s)");
|
||||||
|
}
|
||||||
|
using Timer timer = new Timer(true, timerDelay, ReportProgress);
|
||||||
|
timer.Start();
|
||||||
|
|
||||||
|
Приложение.Логгер.LogInfo(nameof(DownloadAssets), "started downloading assets");
|
||||||
|
int parallelDownloads = 32;
|
||||||
|
var tasks = new Task[parallelDownloads];
|
||||||
|
var currentlyDownloadingFileHashes = new string[parallelDownloads];
|
||||||
|
int i = 0;
|
||||||
|
foreach (var a in assets)
|
||||||
|
{
|
||||||
|
string hash = a.Value.hash;
|
||||||
|
string hashStart = hash.Substring(0, 2);
|
||||||
|
var assetUrl = $"{ASSET_SERVER_URL}/{hashStart}/{hash}";
|
||||||
|
IOPath assetFilePath = Path.Concat("assets", "objects", hashStart, hash);
|
||||||
|
Приложение.Логгер.LogDebug(nameof(DownloadAssets), $"downloading asset '{a.Key}' {hash}");
|
||||||
|
tasks[i] = DownloadFileHTTP(assetUrl, assetFilePath, AddBytesCountAtomic, ct);
|
||||||
|
currentlyDownloadingFileHashes[i] = hash;
|
||||||
|
if (++i == parallelDownloads)
|
||||||
|
{
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
timer.Stop();
|
||||||
|
timer.InvokeAction();
|
||||||
|
File.Move(indexFilePathTmp, indexFilePath, true);
|
||||||
|
Приложение.Логгер.LogInfo(nameof(DownloadAssets), "finished downloading assets");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<List<RemoteVersionDescriptorProps>> GetRemoteVersionDescriptorsAsync()
|
||||||
|
{
|
||||||
|
List<RemoteVersionDescriptorProps> descriptors = new();
|
||||||
|
foreach (var url in VERSION_MANIFEST_URLS)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var manifestText = await http.GetStringAsync(url);
|
||||||
|
var catalog = JsonConvert.DeserializeObject<VersionCatalog>(manifestText);
|
||||||
|
if (catalog != null)
|
||||||
|
descriptors.AddRange(catalog.versions);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Приложение.Логгер.LogWarn(nameof(Network), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<GameVersionProps>? _versionPropsList;
|
||||||
|
/// <returns>empty list if couldn't find any remote versions</returns>
|
||||||
|
public static async Task<IReadOnlyList<GameVersionProps>> GetDownloadableVersions()
|
||||||
|
{
|
||||||
|
if(_versionPropsList == null)
|
||||||
|
{
|
||||||
|
_versionPropsList = new();
|
||||||
|
var rvdlist = await GetRemoteVersionDescriptorsAsync();
|
||||||
|
foreach (var r in rvdlist)
|
||||||
|
{
|
||||||
|
if(r.type == "release")
|
||||||
|
_versionPropsList.Add(new GameVersionProps(r.id, r.url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _versionPropsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task DownloadVersionFile(string url, IOPath filePath, bool force)
|
||||||
|
{
|
||||||
|
if(File.Exists(filePath) && !force)
|
||||||
|
return;
|
||||||
|
await DownloadFileHTTP(url, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task DownloadJava(JavaVersion javaVersion, IOPath path, bool force)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task DownloadLibraries(List<Library> libraries, IOPath librariesDir, bool force)
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
9
Млаумчерб.Клиент/VersionItemView.axaml
Normal file
9
Млаумчерб.Клиент/VersionItemView.axaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:Млаумчерб.Клиент"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Млаумчерб.Клиент.VersionItemView">
|
||||||
|
<TextBlock Name="text" Background="Transparent"/>
|
||||||
|
</UserControl>
|
||||||
31
Млаумчерб.Клиент/VersionItemView.axaml.cs
Normal file
31
Млаумчерб.Клиент/VersionItemView.axaml.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Млаумчерб.Клиент.классы;
|
||||||
|
|
||||||
|
namespace Млаумчерб.Клиент;
|
||||||
|
|
||||||
|
public partial class VersionItemView : ListBoxItem
|
||||||
|
{
|
||||||
|
public GameVersionProps Props { get; }
|
||||||
|
private SolidColorBrush _avaliableColor = new(Color.FromRgb(30, 130, 40));
|
||||||
|
private SolidColorBrush _unavaliableColor = new(Color.FromRgb(170, 70, 70));
|
||||||
|
|
||||||
|
public VersionItemView()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VersionItemView(GameVersionProps props)
|
||||||
|
{
|
||||||
|
Props = props;
|
||||||
|
InitializeComponent();
|
||||||
|
text.Text = props.Name;
|
||||||
|
props.DownloadCompleted += UpdateBackground;
|
||||||
|
UpdateBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBackground()
|
||||||
|
{
|
||||||
|
Background = Props.IsDownloaded ? _avaliableColor : _unavaliableColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,11 @@
|
|||||||
global using System;
|
global using System;
|
||||||
|
global using System.Collections;
|
||||||
global using System.Collections.Generic;
|
global using System.Collections.Generic;
|
||||||
global using System.IO;
|
global using System.IO;
|
||||||
global using System.Text;
|
global using System.Text;
|
||||||
|
global using System.Threading;
|
||||||
|
global using System.Threading.Tasks;
|
||||||
|
global using Newtonsoft.Json;
|
||||||
global using DTLib.Logging;
|
global using DTLib.Logging;
|
||||||
global using DTLib.Filesystem;
|
global using DTLib.Filesystem;
|
||||||
global using File = DTLib.Filesystem.File;
|
global using File = DTLib.Filesystem.File;
|
||||||
@ -17,9 +21,16 @@ public class Главне
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
try
|
||||||
BuildAvaloniaApp()
|
{
|
||||||
.StartWithClassicDesktopLifetime(args);
|
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||||
|
BuildAvaloniaApp()
|
||||||
|
.StartWithClassicDesktopLifetime(args);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Приложение.Логгер.LogError(nameof(Главне), ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avalonia configuration, don't remove; also used by visual designer.
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
|
|||||||
@ -1,56 +1,85 @@
|
|||||||
using System.Threading;
|
using CliWrap;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliWrap;
|
|
||||||
using DTLib.Extensions;
|
using DTLib.Extensions;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Млаумчерб.Клиент.классы;
|
using Млаумчерб.Клиент.классы;
|
||||||
|
using static Млаумчерб.Клиент.классы.Пролетариат;
|
||||||
|
|
||||||
namespace Млаумчерб.Клиент;
|
namespace Млаумчерб.Клиент;
|
||||||
|
|
||||||
public interface IGame
|
public class GameVersionDescriptor
|
||||||
{
|
{
|
||||||
string Name { get; }
|
private readonly GameVersionProps _props;
|
||||||
IOPath InstallationDirectory { get; }
|
public string Name => _props.Name;
|
||||||
Progress<NetworkTransferResult> BeginUpdate();
|
public IOPath WorkingDirectory { get; }
|
||||||
void CancelUpdate();
|
|
||||||
Task Launch();
|
|
||||||
void Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MinecraftVersion : IGame
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public IOPath InstallationDirectory { get; }
|
|
||||||
|
|
||||||
private IOPath JavaExecutableFilePath;
|
private IOPath JavaExecutableFilePath;
|
||||||
|
|
||||||
private MinecraftVersionDescriptor descriptor;
|
private MinecraftVersionDescriptor descriptor;
|
||||||
private JavaArguments javaArgs;
|
private JavaArguments javaArgs;
|
||||||
private GameArguments gameArgs;
|
private GameArguments gameArgs;
|
||||||
private CancellationTokenSource? cts;
|
private CancellationTokenSource? gameCts;
|
||||||
|
private CancellationTokenSource? downloadCts;
|
||||||
private CommandTask<CommandResult>? commandTask;
|
private CommandTask<CommandResult>? commandTask;
|
||||||
|
|
||||||
public MinecraftVersion(IOPath descriptorFilePath)
|
public static async Task<List<GameVersionProps>> GetAllVersionsAsync()
|
||||||
{
|
{
|
||||||
Name = descriptorFilePath.LastName().ToString().Replace(".json", "");
|
var propsList = new List<GameVersionProps>();
|
||||||
InstallationDirectory = Path.Concat(Приложение.Настройки.путь_к_кубачу, Name);
|
foreach (IOPath f in Directory.GetFiles(GetVersionDescriptorDir()))
|
||||||
string descriptorText = File.ReadAllText(descriptorFilePath);
|
{
|
||||||
|
string name = GetVersionDescriptorName(f);
|
||||||
|
propsList.Add(new GameVersionProps(name, null, f));
|
||||||
|
}
|
||||||
|
var remoteVersions = await Network.GetDownloadableVersions();
|
||||||
|
propsList.AddRange(remoteVersions);
|
||||||
|
return propsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<GameVersionDescriptor> 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 Network.DownloadFileHTTP(props.RemoteDescriptorUrl, props.LocalDescriptorPath);
|
||||||
|
}
|
||||||
|
return new GameVersionDescriptor(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameVersionDescriptor(GameVersionProps props)
|
||||||
|
{
|
||||||
|
_props = props;
|
||||||
|
WorkingDirectory = Path.Concat(Приложение.Настройки.путь_к_кубачу, Name);
|
||||||
|
string descriptorText = File.ReadAllText(props.LocalDescriptorPath);
|
||||||
descriptor = JsonConvert.DeserializeObject<MinecraftVersionDescriptor>(descriptorText)
|
descriptor = JsonConvert.DeserializeObject<MinecraftVersionDescriptor>(descriptorText)
|
||||||
?? throw new Exception($"can't parse descriptor file '{descriptorFilePath}'");
|
?? 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);
|
||||||
JavaExecutableFilePath = Path.Concat(Приложение.Настройки.путь_к_жабе, "bin",
|
JavaExecutableFilePath = Path.Concat(Приложение.Настройки.путь_к_жабе, "bin",
|
||||||
OperatingSystem.IsWindows() ? "javaw.exe" : "javaw");
|
OperatingSystem.IsWindows() ? "javaw.exe" : "javaw");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Progress<NetworkTransferResult> BeginUpdate()
|
public async void BeginUpdate(bool force)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
try
|
||||||
|
{
|
||||||
|
downloadCts = new CancellationTokenSource();
|
||||||
|
if(Приложение.Настройки.скачать_жабу)
|
||||||
|
await Network.DownloadJava(descriptor.javaVersion, Приложение.Настройки.путь_к_жабе, force);
|
||||||
|
await Network.DownloadAssets(descriptor.assetIndex, downloadCts.Token, force);
|
||||||
|
await Network.DownloadVersionFile(descriptor.downloads.client.url, GetVersionJarFilePath(Name), force);
|
||||||
|
await Network.DownloadLibraries(descriptor.libraries, GetLibrariesDir(), force);
|
||||||
|
// await Network.DownloadModpack(modpack, WorkingDirectory, force);
|
||||||
|
_props.IsDownloaded = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Ошибки.ПоказатьСообщение("GameUpdate", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CancelUpdate()
|
public void CancelUpdate()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
downloadCts?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Launch()
|
public async Task Launch()
|
||||||
@ -58,24 +87,24 @@ public class MinecraftVersion : IGame
|
|||||||
var javaArgsList = javaArgs.FillPlaceholders([]);
|
var javaArgsList = javaArgs.FillPlaceholders([]);
|
||||||
var gameArgsList = gameArgs.FillPlaceholders([]);
|
var gameArgsList = gameArgs.FillPlaceholders([]);
|
||||||
var command = Cli.Wrap(JavaExecutableFilePath.ToString())
|
var command = Cli.Wrap(JavaExecutableFilePath.ToString())
|
||||||
.WithWorkingDirectory(InstallationDirectory.ToString())
|
.WithWorkingDirectory(WorkingDirectory.ToString())
|
||||||
.WithArguments(javaArgsList)
|
.WithArguments(javaArgsList)
|
||||||
.WithArguments(gameArgsList);
|
.WithArguments(gameArgsList);
|
||||||
Приложение.Логгер.LogInfo(nameof(MinecraftVersion),
|
Приложение.Логгер.LogInfo(nameof(GameVersionDescriptor),
|
||||||
$"launching the game" +
|
$"launching the game" +
|
||||||
"\njava: " + command.TargetFilePath +
|
"\njava: " + command.TargetFilePath +
|
||||||
"\nworking_dir: " + command.WorkingDirPath +
|
"\nworking_dir: " + command.WorkingDirPath +
|
||||||
"\njava_arguments: \n\t" + javaArgsList.MergeToString("\n\t") +
|
"\njava_arguments: \n\t" + javaArgsList.MergeToString("\n\t") +
|
||||||
"\ngame_arguments: \n\t" + gameArgsList.MergeToString("\n\t"));
|
"\ngame_arguments: \n\t" + gameArgsList.MergeToString("\n\t"));
|
||||||
cts = new();
|
gameCts = new();
|
||||||
commandTask = command.ExecuteAsync(cts.Token);
|
commandTask = command.ExecuteAsync(gameCts.Token);
|
||||||
var result = await commandTask;
|
var result = await commandTask;
|
||||||
Приложение.Логгер.LogInfo(nameof(MinecraftVersion), $"game exited with code {result.ExitCode}");
|
Приложение.Логгер.LogInfo(nameof(GameVersionDescriptor), $"game exited with code {result.ExitCode}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
public void Close()
|
||||||
{
|
{
|
||||||
cts?.Cancel();
|
gameCts?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
|
|||||||
@ -10,6 +10,8 @@
|
|||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
<ApplicationIcon>капитал\кубе.ico</ApplicationIcon>
|
<ApplicationIcon>капитал\кубе.ico</ApplicationIcon>
|
||||||
<AssemblyName>млаумчерб</AssemblyName>
|
<AssemblyName>млаумчерб</AssemblyName>
|
||||||
|
<Configurations>Release;Debug</Configurations>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -19,7 +21,7 @@
|
|||||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.*" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.*" />
|
||||||
<PackageReference Include="Avalonia.Labs.Gif" Version="11.2.999-cibuild-00051673"/>
|
<PackageReference Include="Avalonia.Labs.Gif" Version="11.2.999-cibuild-00051673"/>
|
||||||
<PackageReference Include="CliWrap" Version="3.6.*" />
|
<PackageReference Include="CliWrap" Version="3.6.*" />
|
||||||
<PackageReference Include="DTLib" Version="1.4.0" />
|
<PackageReference Include="DTLib" Version="1.4.1" />
|
||||||
<PackageReference Include="MessageBox.Avalonia" Version="3.1.*" />
|
<PackageReference Include="MessageBox.Avalonia" Version="3.1.*" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.*" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
using Newtonsoft.Json;
|
namespace Млаумчерб.Клиент;
|
||||||
|
|
||||||
namespace Млаумчерб.Клиент;
|
|
||||||
|
|
||||||
public record Настройки
|
public record Настройки
|
||||||
{
|
{
|
||||||
@ -10,6 +8,7 @@ public record Настройки
|
|||||||
public string путь_к_кубачу { get; set; } = ".";
|
public string путь_к_кубачу { get; set; } = ".";
|
||||||
public string путь_к_жабе { get; set; } = "java";
|
public string путь_к_жабе { get; set; } = "java";
|
||||||
public bool скачать_жабу { get; set; } = true;
|
public bool скачать_жабу { get; set; } = true;
|
||||||
|
public string? последняя_запущенная_версия;
|
||||||
|
|
||||||
public static Настройки ЗагрузитьИзФайла(string имя_файла = "млаумчерб.настройки")
|
public static Настройки ЗагрузитьИзФайла(string имя_файла = "млаумчерб.настройки")
|
||||||
{
|
{
|
||||||
@ -27,7 +26,7 @@ public record Настройки
|
|||||||
File.Move(имя_файла, имя_файла + ".старые", true);
|
File.Move(имя_файла, имя_файла + ".старые", true);
|
||||||
н = new Настройки();
|
н = new Настройки();
|
||||||
н.СохранитьВФайл();
|
н.СохранитьВФайл();
|
||||||
Ошибки.ПоказатьСообщение($"Не удалось прочитать настройки.\n" +
|
Ошибки.ПоказатьСообщение("Настройки", $"Не удалось прочитать настройки.\n" +
|
||||||
$"Сломанный файл настроек переименован в '{имя_файла}.старые'.\n" +
|
$"Сломанный файл настроек переименован в '{имя_файла}.старые'.\n" +
|
||||||
$"Создан новый файл '{имя_файла}'.");
|
$"Создан новый файл '{имя_файла}'.");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,8 +61,8 @@
|
|||||||
Fullscreen
|
Fullscreen
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
|
||||||
<CheckBox IsChecked="{Binding #window.UpdateGameFiles}">
|
<CheckBox IsChecked="{Binding #window.ForceUpdateGameFiles}">
|
||||||
Update game files
|
Force update game files
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button Grid.Row="1" Margin="10" Padding="0 0 0 4"
|
<Button Grid.Row="1" Margin="10" Padding="0 0 0 4"
|
||||||
|
|||||||
@ -3,6 +3,8 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Млаумчерб.Клиент.классы;
|
||||||
|
|
||||||
namespace Млаумчерб.Клиент;
|
namespace Млаумчерб.Клиент;
|
||||||
|
|
||||||
@ -35,13 +37,13 @@ public partial class Окне : Window
|
|||||||
set => SetValue(FullscreenProperty, value);
|
set => SetValue(FullscreenProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> UpdateGameFilesProperty =
|
public static readonly StyledProperty<bool> ForceUpdateGameFilesProperty =
|
||||||
AvaloniaProperty.Register<Окне, bool>(nameof(UpdateGameFiles),
|
AvaloniaProperty.Register<Окне, bool>(nameof(ForceUpdateGameFiles),
|
||||||
defaultBindingMode: BindingMode.TwoWay, defaultValue: true);
|
defaultBindingMode: BindingMode.TwoWay, defaultValue: false);
|
||||||
public bool UpdateGameFiles
|
public bool ForceUpdateGameFiles
|
||||||
{
|
{
|
||||||
get => GetValue(UpdateGameFilesProperty);
|
get => GetValue(ForceUpdateGameFilesProperty);
|
||||||
set => SetValue(UpdateGameFilesProperty, value);
|
set => SetValue(ForceUpdateGameFilesProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Окне()
|
public Окне()
|
||||||
@ -49,7 +51,7 @@ public partial class Окне : Window
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnLoaded(RoutedEventArgs e)
|
protected override async void OnLoaded(RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -57,59 +59,90 @@ public partial class Окне : Window
|
|||||||
Username = Приложение.Настройки.имя_пользователя;
|
Username = Приложение.Настройки.имя_пользователя;
|
||||||
MemoryLimit = Приложение.Настройки.выделенная_память_мб;
|
MemoryLimit = Приложение.Настройки.выделенная_память_мб;
|
||||||
Fullscreen = Приложение.Настройки.открывать_на_весь_экран;
|
Fullscreen = Приложение.Настройки.открывать_на_весь_экран;
|
||||||
|
|
||||||
IOPath localDescriptorsDir = Path.Concat(Приложение.Настройки.путь_к_кубачу, "version_descriptors");
|
Directory.Create(Пролетариат.GetVersionDescriptorDir());
|
||||||
Directory.Create(localDescriptorsDir);
|
VersionComboBox.SelectedIndex = 0;
|
||||||
var descriptorFiles = Directory.GetFiles(localDescriptorsDir);
|
VersionComboBox.IsEnabled = false;
|
||||||
foreach(var descriptorFile in descriptorFiles)
|
var versions = await GameVersionDescriptor.GetAllVersionsAsync();
|
||||||
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
{
|
{
|
||||||
MinecraftVersion mc = new(descriptorFile);
|
foreach (var p in versions)
|
||||||
VersionComboBox.Items.Add(mc);
|
{
|
||||||
}
|
VersionComboBox.Items.Add(new VersionItemView(p));
|
||||||
|
if (Приложение.Настройки.последняя_запущенная_версия != null &&
|
||||||
|
p.Name == Приложение.Настройки.последняя_запущенная_версия)
|
||||||
|
VersionComboBox.SelectedIndex = VersionComboBox.Items.Count - 1;
|
||||||
|
}
|
||||||
|
VersionComboBox.IsEnabled = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Ошибки.ПоказатьСообщение(ex);
|
Ошибки.ПоказатьСообщение(nameof(Окне), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchButtonHandler(object? sender, RoutedEventArgs e)
|
private async void LaunchButtonHandler(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
|
var selectedVersionView = (VersionItemView?)VersionComboBox.SelectedItem;
|
||||||
|
var selectedVersion = selectedVersionView?.Props;
|
||||||
|
Приложение.Настройки.последняя_запущенная_версия = selectedVersion?.Name;
|
||||||
Приложение.Настройки.имя_пользователя = Username;
|
Приложение.Настройки.имя_пользователя = Username;
|
||||||
Приложение.Настройки.выделенная_память_мб = MemoryLimit;
|
Приложение.Настройки.выделенная_память_мб = MemoryLimit;
|
||||||
Приложение.Настройки.открывать_на_весь_экран = Fullscreen;
|
Приложение.Настройки.открывать_на_весь_экран = Fullscreen;
|
||||||
Приложение.Настройки.СохранитьВФайл();
|
Приложение.Настройки.СохранитьВФайл();
|
||||||
|
if (selectedVersion == null)
|
||||||
IGame? game = (IGame?)VersionComboBox.SelectionBoxItem;
|
return;
|
||||||
if (game != null)
|
|
||||||
{
|
var v = await GameVersionDescriptor.CreateFromPropsAsync(selectedVersion);
|
||||||
if (UpdateGameFiles)
|
v.BeginUpdate(ForceUpdateGameFiles);
|
||||||
{
|
Dispatcher.UIThread.Invoke(() => ForceUpdateGameFiles = false);
|
||||||
var progress = game.BeginUpdate();
|
|
||||||
progress.ProgressChanged += (o, result) => Приложение.Логгер.LogDebug("Downloads", result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Ошибки.ПоказатьСообщение(ex);
|
Ошибки.ПоказатьСообщение(nameof(Окне), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenLogsDirectory(object? s, RoutedEventArgs e)
|
private void OpenLogsDirectory(object? s, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Launcher.LaunchDirectoryInfoAsync(new DirectoryInfo(LauncherLogger.LogsDirectory.ToString()));
|
try
|
||||||
|
{
|
||||||
|
Launcher.LaunchDirectoryInfoAsync(new DirectoryInfo(LauncherLogger.LogsDirectory.ToString()))
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Ошибки.ПоказатьСообщение(nameof(Окне), ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenLogFile(object? sender, RoutedEventArgs e)
|
private void OpenLogFile(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Launcher.LaunchFileInfoAsync(new FileInfo(Приложение.Логгер.LogfileName.ToString()));
|
try
|
||||||
|
{
|
||||||
|
Launcher.LaunchFileInfoAsync(new FileInfo(Приложение.Логгер.LogfileName.ToString()))
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Ошибки.ПоказатьСообщение(nameof(Окне), ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenSourceRepository(object? sender, RoutedEventArgs e)
|
private void OpenSourceRepository(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Launcher.LaunchUriAsync(new Uri("https://timerix.ddns.net:3322/Timerix/mlaumcherb"));
|
try
|
||||||
|
{
|
||||||
|
Launcher.LaunchUriAsync(new Uri("https://timerix.ddns.net:3322/Timerix/mlaumcherb"))
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Ошибки.ПоказатьСообщение(nameof(Окне), ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using DTLib.Ben.Demystifier;
|
using DTLib.Demystifier;
|
||||||
using MsBox.Avalonia;
|
using MsBox.Avalonia;
|
||||||
using MsBox.Avalonia.Dto;
|
using MsBox.Avalonia.Dto;
|
||||||
using MsBox.Avalonia.Enums;
|
using MsBox.Avalonia.Enums;
|
||||||
@ -9,10 +9,10 @@ namespace Млаумчерб.Клиент;
|
|||||||
|
|
||||||
public static class Ошибки
|
public static class Ошибки
|
||||||
{
|
{
|
||||||
internal static void ПоказатьСообщение(Exception err)
|
internal static void ПоказатьСообщение(string context, Exception err)
|
||||||
=> ПоказатьСообщение(err.ToStringDemystified());
|
=> ПоказатьСообщение(context, err.ToStringDemystified());
|
||||||
|
|
||||||
internal static async void ПоказатьСообщение(string err)
|
internal static async void ПоказатьСообщение(string context, string err)
|
||||||
{
|
{
|
||||||
Приложение.Логгер.LogError(nameof(Ошибки), err);
|
Приложение.Логгер.LogError(nameof(Ошибки), err);
|
||||||
var box = MessageBoxManager.GetMessageBoxCustom(new MessageBoxCustomParams
|
var box = MessageBoxManager.GetMessageBoxCustom(new MessageBoxCustomParams
|
||||||
|
|||||||
31
Млаумчерб.Клиент/классы/ArgumentsWithPlaceholders.cs
Normal file
31
Млаумчерб.Клиент/классы/ArgumentsWithPlaceholders.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
namespace Млаумчерб.Клиент.классы;
|
||||||
|
|
||||||
|
public class ArgumentsWithPlaceholders
|
||||||
|
{
|
||||||
|
protected List<string> raw_args = new();
|
||||||
|
|
||||||
|
public List<string> FillPlaceholders(Dictionary<string, string> values)
|
||||||
|
{
|
||||||
|
List<string> result = new();
|
||||||
|
foreach (var a in raw_args)
|
||||||
|
{
|
||||||
|
var f = a;
|
||||||
|
int begin = a.IndexOf('$');
|
||||||
|
if (begin != -1)
|
||||||
|
{
|
||||||
|
int keyBegin = begin + 2;
|
||||||
|
int end = a.IndexOf('}', keyBegin);
|
||||||
|
if (end != -1)
|
||||||
|
{
|
||||||
|
var key = a.Substring(keyBegin, end - keyBegin);
|
||||||
|
if (!values.TryGetValue(key, out var v))
|
||||||
|
throw new Exception($"can't find value for placeholder '{key}'");
|
||||||
|
f = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.Add(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Млаумчерб.Клиент/классы/GameArguments.cs
Normal file
27
Млаумчерб.Клиент/классы/GameArguments.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using DTLib.Extensions;
|
||||||
|
|
||||||
|
namespace Млаумчерб.Клиент.классы;
|
||||||
|
|
||||||
|
public class GameArguments : ArgumentsWithPlaceholders
|
||||||
|
{
|
||||||
|
private static readonly string[] _enabled_features =
|
||||||
|
[
|
||||||
|
"has_custom_resolution"
|
||||||
|
];
|
||||||
|
|
||||||
|
public GameArguments(MinecraftVersionDescriptor d)
|
||||||
|
{
|
||||||
|
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(Буржуазия.CheckRules(av.rules, _enabled_features))
|
||||||
|
raw_args.AddRange(av.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Млаумчерб.Клиент/классы/GameVersionProps.cs
Normal file
32
Млаумчерб.Клиент/классы/GameVersionProps.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
namespace Млаумчерб.Клиент.классы;
|
||||||
|
|
||||||
|
public class GameVersionProps
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public IOPath LocalDescriptorPath { get; }
|
||||||
|
public string? RemoteDescriptorUrl { get; }
|
||||||
|
private bool _isDownloaded;
|
||||||
|
public bool IsDownloaded
|
||||||
|
{
|
||||||
|
get => _isDownloaded;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isDownloaded = value;
|
||||||
|
DownloadCompleted?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public event Action? DownloadCompleted;
|
||||||
|
|
||||||
|
public GameVersionProps(string name, string? url, IOPath descriptorPath)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
LocalDescriptorPath = descriptorPath;
|
||||||
|
RemoteDescriptorUrl = url;
|
||||||
|
IsDownloaded = File.Exists(Пролетариат.GetVersionJarFilePath(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameVersionProps(string name, string? url) :
|
||||||
|
this(name, url, Пролетариат.GetVersionDescriptorPath(name)) { }
|
||||||
|
|
||||||
|
public override string ToString() => Name;
|
||||||
|
}
|
||||||
27
Млаумчерб.Клиент/классы/JavaArguments.cs
Normal file
27
Млаумчерб.Клиент/классы/JavaArguments.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
namespace Млаумчерб.Клиент.классы;
|
||||||
|
|
||||||
|
public class JavaArguments : ArgumentsWithPlaceholders
|
||||||
|
{
|
||||||
|
private static readonly string[] _initial_arguments =
|
||||||
|
[
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly string[] _enabled_features =
|
||||||
|
[
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
public JavaArguments(MinecraftVersionDescriptor d)
|
||||||
|
{
|
||||||
|
raw_args.AddRange(_initial_arguments);
|
||||||
|
if (d.arguments is not null)
|
||||||
|
{
|
||||||
|
foreach (var av in d.arguments.jvm)
|
||||||
|
{
|
||||||
|
if(Буржуазия.CheckRules(av.rules, _enabled_features))
|
||||||
|
raw_args.AddRange(av.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
126
Млаумчерб.Клиент/классы/MinecraftVersionDescriptor.cs
Normal file
126
Млаумчерб.Клиент/классы/MinecraftVersionDescriptor.cs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
using System.Linq;
|
||||||
|
// ReSharper disable CollectionNeverUpdated.Global
|
||||||
|
|
||||||
|
namespace Млаумчерб.Клиент.классы;
|
||||||
|
|
||||||
|
public class MinecraftVersionDescriptor
|
||||||
|
{
|
||||||
|
[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 JavaVersion javaVersion { 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 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 Classifiers
|
||||||
|
{
|
||||||
|
[JsonProperty("natives-linux")]
|
||||||
|
public Artifact? nativeslinux { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("natives-osx")]
|
||||||
|
public Artifact? nativesosx { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("natives-windows")]
|
||||||
|
public Artifact? nativeswindows { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LibraryDownloads
|
||||||
|
{
|
||||||
|
public Artifact? artifact { get; set; }
|
||||||
|
public Classifiers? 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();
|
||||||
|
}
|
||||||
27
Млаумчерб.Клиент/классы/VersionCatalog.cs
Normal file
27
Млаумчерб.Клиент/классы/VersionCatalog.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
namespace Млаумчерб.Клиент.классы;
|
||||||
|
|
||||||
|
public class VersionCatalog
|
||||||
|
{
|
||||||
|
[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; }
|
||||||
|
}
|
||||||
@ -1,129 +1,28 @@
|
|||||||
using Newtonsoft.Json;
|
using System.Runtime.InteropServices;
|
||||||
|
// ReSharper disable CollectionNeverUpdated.Global
|
||||||
|
|
||||||
namespace Млаумчерб.Клиент.классы;
|
namespace Млаумчерб.Клиент.классы;
|
||||||
|
|
||||||
public class Artifact
|
|
||||||
{
|
|
||||||
[JsonRequired] public string url { get; set; } = "";
|
|
||||||
[JsonRequired] public string sha1 { get; set; } = "";
|
|
||||||
[JsonRequired] public int size { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Os
|
|
||||||
{
|
|
||||||
[JsonRequired] public string name { 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 Classifiers
|
|
||||||
{
|
|
||||||
[JsonProperty("natives-linux")]
|
|
||||||
public Artifact? nativeslinux { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("natives-osx")]
|
|
||||||
public Artifact? nativesosx { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("natives-windows")]
|
|
||||||
public Artifact? nativeswindows { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LibraryDownloads
|
|
||||||
{
|
|
||||||
public Artifact? artifact { get; set; }
|
|
||||||
public Classifiers? 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 AssetIndex
|
|
||||||
{
|
|
||||||
[JsonRequired] public string id { get; set; } = "";
|
|
||||||
[JsonRequired] public int totalSize { get; set; }
|
|
||||||
[JsonRequired] public bool known { get; set; }
|
|
||||||
[JsonRequired] public string url { get; set; } = "";
|
|
||||||
[JsonRequired] public string sha1 { get; set; } = "";
|
|
||||||
[JsonRequired] public int size { 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
|
|
||||||
{
|
|
||||||
[JsonRequired] public string 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MinecraftVersionDescriptor
|
|
||||||
{
|
|
||||||
[JsonRequired] public string id { get; set; } = "";
|
|
||||||
[JsonRequired] public string jar { get; set; } = "";
|
|
||||||
[JsonRequired] public string family { 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 JavaVersion javaVersion { get; set; } = null!;
|
|
||||||
[JsonRequired] public List<Library> libraries { get; set; } = null!;
|
|
||||||
[JsonRequired] public AssetIndex assetIndex { get; set; } = null!;
|
|
||||||
[JsonRequired] public string assets { get; set; } = "";
|
|
||||||
public string? minecraftArguments { get; set; }
|
|
||||||
public ArgumentsNew? arguments { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Буржуазия
|
public static class Буржуазия
|
||||||
{
|
{
|
||||||
public static bool CheckOs(Os os)
|
public static bool CheckOs(Os os) =>
|
||||||
{
|
os.name switch
|
||||||
return os.name switch
|
|
||||||
{
|
{
|
||||||
|
null => true,
|
||||||
"osx" => OperatingSystem.IsWindows(),
|
"osx" => OperatingSystem.IsWindows(),
|
||||||
"linux" => OperatingSystem.IsLinux(),
|
"linux" => OperatingSystem.IsLinux(),
|
||||||
"windows" => OperatingSystem.IsWindows(),
|
"windows" => OperatingSystem.IsWindows(),
|
||||||
_ => throw new ArgumentOutOfRangeException(os.name)
|
_ => throw new ArgumentOutOfRangeException(os.name)
|
||||||
|
}
|
||||||
|
&& os.arch switch
|
||||||
|
{
|
||||||
|
null => true,
|
||||||
|
"x86" => RuntimeInformation.OSArchitecture == Architecture.X86,
|
||||||
|
"x64" => RuntimeInformation.OSArchitecture == Architecture.X64,
|
||||||
|
"arm64" => RuntimeInformation.OSArchitecture == Architecture.Arm64,
|
||||||
|
_ => false
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
public static bool CheckRules(ICollection<Rule> rules, ICollection<string> enabled_features)
|
public static bool CheckRules(ICollection<Rule> rules, ICollection<string> enabled_features)
|
||||||
{
|
{
|
||||||
bool allowed = false;
|
bool allowed = false;
|
||||||
@ -139,9 +38,8 @@ public static class Буржуазия
|
|||||||
{
|
{
|
||||||
if (is_enabled)
|
if (is_enabled)
|
||||||
{
|
{
|
||||||
if (r.action == "allow")
|
if (r.action != "allow")
|
||||||
allowed = true;
|
return false;
|
||||||
else return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,4 +53,4 @@ public static class Буржуазия
|
|||||||
|
|
||||||
return allowed;
|
return allowed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,89 +1,25 @@
|
|||||||
using DTLib.Extensions;
|
namespace Млаумчерб.Клиент.классы;
|
||||||
|
|
||||||
namespace Млаумчерб.Клиент.классы;
|
public static class Пролетариат
|
||||||
|
|
||||||
public class Пролетариат
|
|
||||||
{
|
{
|
||||||
|
public static IOPath GetAssetIndexFilePath(string id) =>
|
||||||
|
Path.Concat(Приложение.Настройки.путь_к_кубачу, $"assets/indexes/{id}.json");
|
||||||
|
|
||||||
}
|
public static IOPath GetVersionDescriptorDir() =>
|
||||||
|
Path.Concat(Приложение.Настройки.путь_к_кубачу, "version_descriptors");
|
||||||
public class ArgumentsWithPlaceholders
|
|
||||||
{
|
|
||||||
protected List<string> raw_args = new();
|
|
||||||
|
|
||||||
public List<string> FillPlaceholders(Dictionary<string, string> values)
|
|
||||||
{
|
|
||||||
List<string> result = new();
|
|
||||||
foreach (var a in raw_args)
|
|
||||||
{
|
|
||||||
var f = a;
|
|
||||||
int begin = a.IndexOf('$');
|
|
||||||
if (begin != -1)
|
|
||||||
{
|
|
||||||
int keyBegin = begin + 2;
|
|
||||||
int end = a.IndexOf('}', keyBegin);
|
|
||||||
if (end != -1)
|
|
||||||
{
|
|
||||||
var key = a.Substring(keyBegin, end - keyBegin);
|
|
||||||
if (!values.TryGetValue(key, out var v))
|
|
||||||
throw new Exception($"can't find value for placeholder '{key}'");
|
|
||||||
f = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.Add(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class JavaArguments : ArgumentsWithPlaceholders
|
|
||||||
{
|
|
||||||
private static readonly string[] _initial_arguments =
|
|
||||||
[
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
private static readonly string[] _enabled_features =
|
|
||||||
[
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
public JavaArguments(MinecraftVersionDescriptor d)
|
|
||||||
{
|
|
||||||
raw_args.AddRange(_initial_arguments);
|
|
||||||
if (d.arguments is not null)
|
|
||||||
{
|
|
||||||
foreach (var av in d.arguments.jvm)
|
|
||||||
{
|
|
||||||
if(Буржуазия.CheckRules(av.rules, _enabled_features))
|
|
||||||
raw_args.Add(av.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GameArguments : ArgumentsWithPlaceholders
|
|
||||||
{
|
|
||||||
private static readonly string[] _enabled_features =
|
|
||||||
[
|
|
||||||
"has_custom_resolution"
|
|
||||||
];
|
|
||||||
|
|
||||||
public GameArguments(MinecraftVersionDescriptor d)
|
public static string GetVersionDescriptorName(IOPath path) =>
|
||||||
{
|
path.LastName().RemoveExtension().ToString();
|
||||||
if (d.minecraftArguments is not null)
|
|
||||||
{
|
public static IOPath GetVersionDescriptorPath(string name) =>
|
||||||
raw_args.AddRange(d.minecraftArguments.SplitToList(' ', quot: '"'));
|
Path.Concat(GetVersionDescriptorDir(), Path.ReplaceRestrictedChars(name) + ".json");
|
||||||
}
|
|
||||||
else if (d.arguments is not null)
|
public static IOPath GetVersionDir() =>
|
||||||
{
|
Path.Concat(Приложение.Настройки.путь_к_кубачу, "versions");
|
||||||
foreach (var av in d.arguments.game)
|
|
||||||
{
|
|
||||||
if(Буржуазия.CheckRules(av.rules, _enabled_features))
|
|
||||||
raw_args.Add(av.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public static IOPath GetVersionJarFilePath(string name) =>
|
||||||
|
Path.Concat(GetVersionDir(), name + ".jar");
|
||||||
|
|
||||||
|
public static IOPath GetLibrariesDir() =>
|
||||||
|
Path.Concat(Приложение.Настройки.путь_к_кубачу, "libraries");
|
||||||
|
}
|
||||||
|
|||||||
@ -9,13 +9,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFolder", "SolutionF
|
|||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Release|x64 = Release|x64
|
||||||
Release|Any CPU = Release|Any CPU
|
Debug|x64 = Debug|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Release|x64.ActiveCfg = Release|x64
|
||||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Release|x64.Build.0 = Release|x64
|
||||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Release|Any CPU.Build.0 = Release|Any CPU
|
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Debug|x64.Build.0 = Debug|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user