game assets downloading
This commit is contained in:
parent
581c4b5498
commit
c2e2785a32
@ -2,9 +2,12 @@
|
||||
|
||||
public class LauncherLogger : FileLogger
|
||||
{
|
||||
public static readonly IOPath LogsDirectory = "launcher-logs";
|
||||
public static readonly IOPath LogsDirectory = "launcher_logs";
|
||||
|
||||
public LauncherLogger() : base(LogsDirectory, "млаумчерб")
|
||||
{
|
||||
#if DEBUG
|
||||
DebugLogEnabled = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -1,72 +1,191 @@
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Buffers;
|
||||
using System.Net.Http;
|
||||
using Млаумчерб.Клиент.классы;
|
||||
using Timer = DTLib.Timer;
|
||||
|
||||
namespace Млаумчерб.Клиент;
|
||||
|
||||
public record struct NetworkTransferResult(long BytesTotal, long BytesTransferred, long BytesPerSecond)
|
||||
{
|
||||
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
|
||||
public static class Network
|
||||
{
|
||||
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();
|
||||
NetworkTransferTask task = new(sourceStream, destinationStream);
|
||||
task.Start();
|
||||
return task;
|
||||
await using var src = await http.GetStreamAsync(url, ct);
|
||||
await using var dst = File.OpenWrite(outPath);
|
||||
|
||||
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.Collections;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Text;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
global using Newtonsoft.Json;
|
||||
global using DTLib.Logging;
|
||||
global using DTLib.Filesystem;
|
||||
global using File = DTLib.Filesystem.File;
|
||||
@ -17,9 +21,16 @@ public class Главне
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
try
|
||||
{
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Приложение.Логгер.LogError(nameof(Главне), ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
|
||||
@ -1,56 +1,85 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliWrap;
|
||||
using CliWrap;
|
||||
using DTLib.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using Млаумчерб.Клиент.классы;
|
||||
using static Млаумчерб.Клиент.классы.Пролетариат;
|
||||
|
||||
namespace Млаумчерб.Клиент;
|
||||
|
||||
public interface IGame
|
||||
public class GameVersionDescriptor
|
||||
{
|
||||
string Name { get; }
|
||||
IOPath InstallationDirectory { get; }
|
||||
Progress<NetworkTransferResult> BeginUpdate();
|
||||
void CancelUpdate();
|
||||
Task Launch();
|
||||
void Close();
|
||||
}
|
||||
|
||||
public class MinecraftVersion : IGame
|
||||
{
|
||||
public string Name { get; }
|
||||
public IOPath InstallationDirectory { get; }
|
||||
private readonly GameVersionProps _props;
|
||||
public string Name => _props.Name;
|
||||
public IOPath WorkingDirectory { get; }
|
||||
|
||||
private IOPath JavaExecutableFilePath;
|
||||
|
||||
private MinecraftVersionDescriptor descriptor;
|
||||
private JavaArguments javaArgs;
|
||||
private GameArguments gameArgs;
|
||||
private CancellationTokenSource? cts;
|
||||
private CancellationTokenSource? gameCts;
|
||||
private CancellationTokenSource? downloadCts;
|
||||
private CommandTask<CommandResult>? commandTask;
|
||||
|
||||
public MinecraftVersion(IOPath descriptorFilePath)
|
||||
public static async Task<List<GameVersionProps>> GetAllVersionsAsync()
|
||||
{
|
||||
Name = descriptorFilePath.LastName().ToString().Replace(".json", "");
|
||||
InstallationDirectory = Path.Concat(Приложение.Настройки.путь_к_кубачу, Name);
|
||||
string descriptorText = File.ReadAllText(descriptorFilePath);
|
||||
var propsList = new List<GameVersionProps>();
|
||||
foreach (IOPath f in Directory.GetFiles(GetVersionDescriptorDir()))
|
||||
{
|
||||
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)
|
||||
?? throw new Exception($"can't parse descriptor file '{descriptorFilePath}'");
|
||||
?? throw new Exception($"can't parse descriptor file '{props.LocalDescriptorPath}'");
|
||||
javaArgs = new JavaArguments(descriptor);
|
||||
gameArgs = new GameArguments(descriptor);
|
||||
JavaExecutableFilePath = Path.Concat(Приложение.Настройки.путь_к_жабе, "bin",
|
||||
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()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
downloadCts?.Cancel();
|
||||
}
|
||||
|
||||
public async Task Launch()
|
||||
@ -58,24 +87,24 @@ public class MinecraftVersion : IGame
|
||||
var javaArgsList = javaArgs.FillPlaceholders([]);
|
||||
var gameArgsList = gameArgs.FillPlaceholders([]);
|
||||
var command = Cli.Wrap(JavaExecutableFilePath.ToString())
|
||||
.WithWorkingDirectory(InstallationDirectory.ToString())
|
||||
.WithWorkingDirectory(WorkingDirectory.ToString())
|
||||
.WithArguments(javaArgsList)
|
||||
.WithArguments(gameArgsList);
|
||||
Приложение.Логгер.LogInfo(nameof(MinecraftVersion),
|
||||
Приложение.Логгер.LogInfo(nameof(GameVersionDescriptor),
|
||||
$"launching the game" +
|
||||
"\njava: " + command.TargetFilePath +
|
||||
"\nworking_dir: " + command.WorkingDirPath +
|
||||
"\njava_arguments: \n\t" + javaArgsList.MergeToString("\n\t") +
|
||||
"\ngame_arguments: \n\t" + gameArgsList.MergeToString("\n\t"));
|
||||
cts = new();
|
||||
commandTask = command.ExecuteAsync(cts.Token);
|
||||
gameCts = new();
|
||||
commandTask = command.ExecuteAsync(gameCts.Token);
|
||||
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()
|
||||
{
|
||||
cts?.Cancel();
|
||||
gameCts?.Cancel();
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<ApplicationIcon>капитал\кубе.ico</ApplicationIcon>
|
||||
<AssemblyName>млаумчерб</AssemblyName>
|
||||
<Configurations>Release;Debug</Configurations>
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -19,7 +21,7 @@
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.*" />
|
||||
<PackageReference Include="Avalonia.Labs.Gif" Version="11.2.999-cibuild-00051673"/>
|
||||
<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="Newtonsoft.Json" Version="13.*" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Млаумчерб.Клиент;
|
||||
namespace Млаумчерб.Клиент;
|
||||
|
||||
public record Настройки
|
||||
{
|
||||
@ -10,6 +8,7 @@ public record Настройки
|
||||
public string путь_к_кубачу { get; set; } = ".";
|
||||
public string путь_к_жабе { get; set; } = "java";
|
||||
public bool скачать_жабу { get; set; } = true;
|
||||
public string? последняя_запущенная_версия;
|
||||
|
||||
public static Настройки ЗагрузитьИзФайла(string имя_файла = "млаумчерб.настройки")
|
||||
{
|
||||
@ -27,7 +26,7 @@ public record Настройки
|
||||
File.Move(имя_файла, имя_файла + ".старые", true);
|
||||
н = new Настройки();
|
||||
н.СохранитьВФайл();
|
||||
Ошибки.ПоказатьСообщение($"Не удалось прочитать настройки.\n" +
|
||||
Ошибки.ПоказатьСообщение("Настройки", $"Не удалось прочитать настройки.\n" +
|
||||
$"Сломанный файл настроек переименован в '{имя_файла}.старые'.\n" +
|
||||
$"Создан новый файл '{имя_файла}'.");
|
||||
}
|
||||
|
||||
@ -61,8 +61,8 @@
|
||||
Fullscreen
|
||||
</CheckBox>
|
||||
|
||||
<CheckBox IsChecked="{Binding #window.UpdateGameFiles}">
|
||||
Update game files
|
||||
<CheckBox IsChecked="{Binding #window.ForceUpdateGameFiles}">
|
||||
Force update game files
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<Button Grid.Row="1" Margin="10" Padding="0 0 0 4"
|
||||
|
||||
@ -3,6 +3,8 @@ using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using Млаумчерб.Клиент.классы;
|
||||
|
||||
namespace Млаумчерб.Клиент;
|
||||
|
||||
@ -35,13 +37,13 @@ public partial class Окне : Window
|
||||
set => SetValue(FullscreenProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> UpdateGameFilesProperty =
|
||||
AvaloniaProperty.Register<Окне, bool>(nameof(UpdateGameFiles),
|
||||
defaultBindingMode: BindingMode.TwoWay, defaultValue: true);
|
||||
public bool UpdateGameFiles
|
||||
public static readonly StyledProperty<bool> ForceUpdateGameFilesProperty =
|
||||
AvaloniaProperty.Register<Окне, bool>(nameof(ForceUpdateGameFiles),
|
||||
defaultBindingMode: BindingMode.TwoWay, defaultValue: false);
|
||||
public bool ForceUpdateGameFiles
|
||||
{
|
||||
get => GetValue(UpdateGameFilesProperty);
|
||||
set => SetValue(UpdateGameFilesProperty, value);
|
||||
get => GetValue(ForceUpdateGameFilesProperty);
|
||||
set => SetValue(ForceUpdateGameFilesProperty, value);
|
||||
}
|
||||
|
||||
public Окне()
|
||||
@ -49,7 +51,7 @@ public partial class Окне : Window
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
protected override async void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -57,59 +59,90 @@ public partial class Окне : Window
|
||||
Username = Приложение.Настройки.имя_пользователя;
|
||||
MemoryLimit = Приложение.Настройки.выделенная_память_мб;
|
||||
Fullscreen = Приложение.Настройки.открывать_на_весь_экран;
|
||||
|
||||
IOPath localDescriptorsDir = Path.Concat(Приложение.Настройки.путь_к_кубачу, "version_descriptors");
|
||||
Directory.Create(localDescriptorsDir);
|
||||
var descriptorFiles = Directory.GetFiles(localDescriptorsDir);
|
||||
foreach(var descriptorFile in descriptorFiles)
|
||||
|
||||
Directory.Create(Пролетариат.GetVersionDescriptorDir());
|
||||
VersionComboBox.SelectedIndex = 0;
|
||||
VersionComboBox.IsEnabled = false;
|
||||
var versions = await GameVersionDescriptor.GetAllVersionsAsync();
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
MinecraftVersion mc = new(descriptorFile);
|
||||
VersionComboBox.Items.Add(mc);
|
||||
}
|
||||
foreach (var p in versions)
|
||||
{
|
||||
VersionComboBox.Items.Add(new VersionItemView(p));
|
||||
if (Приложение.Настройки.последняя_запущенная_версия != null &&
|
||||
p.Name == Приложение.Настройки.последняя_запущенная_версия)
|
||||
VersionComboBox.SelectedIndex = VersionComboBox.Items.Count - 1;
|
||||
}
|
||||
VersionComboBox.IsEnabled = true;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Ошибки.ПоказатьСообщение(ex);
|
||||
Ошибки.ПоказатьСообщение(nameof(Окне), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void LaunchButtonHandler(object? sender, RoutedEventArgs e)
|
||||
private async void LaunchButtonHandler(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var selectedVersionView = (VersionItemView?)VersionComboBox.SelectedItem;
|
||||
var selectedVersion = selectedVersionView?.Props;
|
||||
Приложение.Настройки.последняя_запущенная_версия = selectedVersion?.Name;
|
||||
Приложение.Настройки.имя_пользователя = Username;
|
||||
Приложение.Настройки.выделенная_память_мб = MemoryLimit;
|
||||
Приложение.Настройки.открывать_на_весь_экран = Fullscreen;
|
||||
Приложение.Настройки.СохранитьВФайл();
|
||||
|
||||
IGame? game = (IGame?)VersionComboBox.SelectionBoxItem;
|
||||
if (game != null)
|
||||
{
|
||||
if (UpdateGameFiles)
|
||||
{
|
||||
var progress = game.BeginUpdate();
|
||||
progress.ProgressChanged += (o, result) => Приложение.Логгер.LogDebug("Downloads", result);
|
||||
}
|
||||
}
|
||||
if (selectedVersion == null)
|
||||
return;
|
||||
|
||||
var v = await GameVersionDescriptor.CreateFromPropsAsync(selectedVersion);
|
||||
v.BeginUpdate(ForceUpdateGameFiles);
|
||||
Dispatcher.UIThread.Invoke(() => ForceUpdateGameFiles = false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Ошибки.ПоказатьСообщение(ex);
|
||||
Ошибки.ПоказатьСообщение(nameof(Окне), ex);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 DTLib.Ben.Demystifier;
|
||||
using DTLib.Demystifier;
|
||||
using MsBox.Avalonia;
|
||||
using MsBox.Avalonia.Dto;
|
||||
using MsBox.Avalonia.Enums;
|
||||
@ -9,10 +9,10 @@ namespace Млаумчерб.Клиент;
|
||||
|
||||
public static class Ошибки
|
||||
{
|
||||
internal static void ПоказатьСообщение(Exception err)
|
||||
=> ПоказатьСообщение(err.ToStringDemystified());
|
||||
internal static void ПоказатьСообщение(string context, Exception err)
|
||||
=> ПоказатьСообщение(context, err.ToStringDemystified());
|
||||
|
||||
internal static async void ПоказатьСообщение(string err)
|
||||
internal static async void ПоказатьСообщение(string context, string err)
|
||||
{
|
||||
Приложение.Логгер.LogError(nameof(Ошибки), err);
|
||||
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 Млаумчерб.Клиент.классы;
|
||||
|
||||
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 bool CheckOs(Os os)
|
||||
{
|
||||
return os.name switch
|
||||
public static bool CheckOs(Os os) =>
|
||||
os.name switch
|
||||
{
|
||||
null => true,
|
||||
"osx" => OperatingSystem.IsWindows(),
|
||||
"linux" => OperatingSystem.IsLinux(),
|
||||
"windows" => OperatingSystem.IsWindows(),
|
||||
_ => 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)
|
||||
{
|
||||
bool allowed = false;
|
||||
@ -139,9 +38,8 @@ public static class Буржуазия
|
||||
{
|
||||
if (is_enabled)
|
||||
{
|
||||
if (r.action == "allow")
|
||||
allowed = true;
|
||||
else return false;
|
||||
if (r.action != "allow")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,4 +53,4 @@ public static class Буржуазия
|
||||
|
||||
return allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,89 +1,25 @@
|
||||
using DTLib.Extensions;
|
||||
namespace Млаумчерб.Клиент.классы;
|
||||
|
||||
namespace Млаумчерб.Клиент.классы;
|
||||
|
||||
public class Пролетариат
|
||||
public static class Пролетариат
|
||||
{
|
||||
public static IOPath GetAssetIndexFilePath(string id) =>
|
||||
Path.Concat(Приложение.Настройки.путь_к_кубачу, $"assets/indexes/{id}.json");
|
||||
|
||||
}
|
||||
|
||||
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 static IOPath GetVersionDescriptorDir() =>
|
||||
Path.Concat(Приложение.Настройки.путь_к_кубачу, "version_descriptors");
|
||||
|
||||
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.Add(av.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static string GetVersionDescriptorName(IOPath path) =>
|
||||
path.LastName().RemoveExtension().ToString();
|
||||
|
||||
public static IOPath GetVersionDescriptorPath(string name) =>
|
||||
Path.Concat(GetVersionDescriptorDir(), Path.ReplaceRestrictedChars(name) + ".json");
|
||||
|
||||
public static IOPath GetVersionDir() =>
|
||||
Path.Concat(Приложение.Настройки.путь_к_кубачу, "versions");
|
||||
|
||||
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
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Debug|x64 = Debug|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Release|x64.ActiveCfg = Release|x64
|
||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Release|x64.Build.0 = Release|x64
|
||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9B9D8B05-255F-49C3-89EC-3F43A66491D3}.Debug|x64.Build.0 = Debug|x64
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Loading…
Reference in New Issue
Block a user