now it works

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

View File

@@ -0,0 +1,66 @@
using Mlaumcherb.Client.Avalonia.зримое;
using Mlaumcherb.Client.Avalonia.холопы;
namespace Mlaumcherb.Client.Avalonia;
public record Config
{
public bool debug { get; set; } =
#if DEBUG
true;
#else
false;
#endif
public string player_name { get; set; } = "";
public int max_memory { get; set; } = 4096;
public string minecraft_dir { get; set; } = ".";
public bool download_java { get; set; } = true;
public string? last_launched_version { get; set; }
public int max_parallel_downloads { get; set; } = 16;
[JsonIgnore] static IOPath _filePath = "config.json";
public static Config LoadFromFile()
{
LauncherApp.Logger.LogInfo(nameof(Config), $"loading config from file '{_filePath}'");
if(!File.Exists(_filePath))
{
LauncherApp.Logger.LogInfo(nameof(Config), "file doesn't exist");
return new Config();
}
string text = File.ReadAllText(_filePath);
string errorMessage = "config is empty";
Config? config = null;
try
{
config = JsonConvert.DeserializeObject<Config>(text);
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
if (config == null)
{
IOPath _brokenPath = _filePath + ".broken";
File.Move(_filePath, _brokenPath, true);
ErrorHelper.ShowMessageBox(nameof(Config),
$"Can't reed config file '{_filePath}'.\n" +
$"New config file has been created. Old is renamed to '{_brokenPath}'.\n\n" +
$"Config parser error: {errorMessage}");
config = new Config();
config.SaveToFile();
}
LauncherApp.Logger.LogDebug(nameof(Config), $"config has been loaded: {config}");
return config;
}
public void SaveToFile()
{
LauncherApp.Logger.LogInfo(nameof(Config), $"saving config to file '{_filePath}'");
var text = JsonConvert.SerializeObject(this, Formatting.Indented);
File.WriteAllText(_filePath, text);
LauncherApp.Logger.LogDebug(nameof(Config), $"config has been saved: {text}");
}
}

View File

@@ -0,0 +1,229 @@
using System.Linq;
using System.Security.Cryptography;
using CliWrap;
using DTLib.Extensions;
using Mlaumcherb.Client.Avalonia.зримое;
using Mlaumcherb.Client.Avalonia.классы;
using Mlaumcherb.Client.Avalonia.сеть;
using Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
using Mlaumcherb.Client.Avalonia.холопы;
using static Mlaumcherb.Client.Avalonia.холопы.PathHelper;
namespace Mlaumcherb.Client.Avalonia;
public class GameVersion
{
private readonly GameVersionProps _props;
public string Name => _props.Name;
public IOPath WorkingDirectory { get; }
private IOPath JavaExecutableFilePath;
private GameVersionDescriptor _descriptor;
private JavaArguments _javaArgs;
private GameArguments _gameArgs;
private Libraries _libraries;
private CancellationTokenSource? _gameCts;
private CommandTask<CommandResult>? _commandTask;
public static async Task<List<GameVersionProps>> GetAllVersionsAsync()
{
var propsSet = new HashSet<GameVersionProps>();
// local descriptors
Directory.Create(GetVersionsDir());
foreach (IOPath subdir in Directory.GetDirectories(GetVersionsDir()))
{
string name = subdir.LastName().ToString();
var d = new GameVersionProps(name, null);
if(!File.Exists(d.LocalDescriptorPath))
throw new Exception("Can't find version descriptor file in directory '{subdir}'. Rename it as directory name.");
propsSet.Add(d);
}
// remote non-duplicating versions
foreach (var removeVersion in await Сеть.GetDownloadableVersions())
{
propsSet.Add(removeVersion);
}
// reverse sort
var propsList = propsSet.ToList();
propsList.Sort((a, b) => b.CompareTo(a));
return propsList;
}
public static async Task<GameVersion> CreateFromPropsAsync(GameVersionProps props)
{
if (!File.Exists(props.LocalDescriptorPath))
{
if (props.RemoteDescriptorUrl is null)
throw new NullReferenceException("can't download game version descriptor '"
+ props.Name + "', because RemoteDescriptorUrl is null");
await Сеть.DownloadFile(props.RemoteDescriptorUrl, props.LocalDescriptorPath);
}
return new GameVersion(props);
}
private GameVersion(GameVersionProps props)
{
_props = props;
string descriptorText = File.ReadAllText(props.LocalDescriptorPath);
_descriptor = JsonConvert.DeserializeObject<GameVersionDescriptor>(descriptorText)
?? throw new Exception($"can't parse descriptor file '{props.LocalDescriptorPath}'");
_javaArgs = new JavaArguments(_descriptor);
_gameArgs = new GameArguments(_descriptor);
_libraries = new Libraries(_descriptor);
WorkingDirectory = GetVersionDir(_descriptor.id);
JavaExecutableFilePath = GetJavaExecutablePath(_descriptor.javaVersion.component, LauncherApp.Config.debug);
}
public async Task UpdateFiles(bool checkHashes, Action<NetworkTask> networkTaskCreatedCallback)
{
LauncherApp.Logger.LogInfo(Name, $"started updating version {Name}");
List<INetworkTaskFactory> taskFactories =
[
new AssetsDownloadTaskFactory(_descriptor),
new LibrariesDownloadTaskFactory(_descriptor, _libraries),
new VersionFileDownloadTaskFactory(_descriptor),
];
if(LauncherApp.Config.download_java)
{
taskFactories.Add(new JavaDownloadTaskFactory(_descriptor));
}
//TODO: modpack
/*if (modpack != null)
{
taskFactories.Add(new ModpackDownloadTaskFactory(modpack));
}*/
var networkTasks = new List<NetworkTask>();
for (int i = 0; i < taskFactories.Count; i++)
{
var nt = await taskFactories[i].CreateAsync(checkHashes);
if (nt != null)
{
networkTasks.Add(nt);
networkTaskCreatedCallback.Invoke(nt);
}
}
foreach (var nt in networkTasks)
{
await nt.StartAsync();
}
// create log4j config file
if (!File.Exists(GetLog4jConfigFilePath()))
{
await using var res = EmbeddedResources.GetResourceStream(
$"Mlaumcherb.Client.Avalonia.встроенное.{GetLog4jConfigFileName()}");
await using var file = File.OpenWrite(GetLog4jConfigFilePath());
await res.CopyToAsync(file);
}
_props.IsDownloaded = true;
LauncherApp.Logger.LogInfo(Name, $"finished updating version {Name}");
}
//minecraft player uuid explanation
//https://gist.github.com/CatDany/0e71ca7cd9b42a254e49/
//java uuid generation in c#
//https://stackoverflow.com/questions/18021808/uuid-interop-with-c-sharp-code
public static string GetPlayerUUID(string name)
{
byte[] name_bytes = Encoding.UTF8.GetBytes("OfflinePlayer:" + name);
var md5 = MD5.Create();
byte[] hash = md5.ComputeHash(name_bytes);
hash[6] &= 0x0f;
hash[6] |= 0x30;
hash[8] &= 0x3f;
hash[8] |= 0x80;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
sb.Append(hash[i].ToString("x2"));
sb.Insert(8, '-').Insert(13, '-').Insert(18, '-').Insert(23, '-');
return sb.ToString();
}
public async Task Launch()
{
if (string.IsNullOrWhiteSpace(LauncherApp.Config.player_name))
throw new Exception("invalid player name");
Directory.Create(WorkingDirectory);
string uuid = GetPlayerUUID(LauncherApp.Config.player_name);
Dictionary<string, string> placeholder_values = new() {
{ "resolution_width", "1600" },
{ "resolution_height", "900" },
{ "xms", "2048" },
{ "xmx", LauncherApp.Config.max_memory.ToString() },
{ "auth_player_name", LauncherApp.Config.player_name },
{ "auth_access_token", uuid },
{ "auth_session", $"token:{uuid}:{uuid}" },
{ "auth_xuid", "" },
{ "auth_uuid", uuid },
{ "clientid", "" },
{ "user_properties", "{}" },
{ "user_type", "userType" },
{ "launcher_name", "java-minecraft-launcher" },
{ "launcher_version", "1.6.84-j" },
{ "classpath", _libraries.Libs.Select(l => l.jarFilePath)
.Append(GetVersionJarFilePath(_descriptor.id))
.MergeToString(';') },
{ "assets_index_name", _descriptor.assets },
{ "assets_root", GetAssetsDir().ToString() },
{ "game_assets", GetAssetsDir().ToString() },
{ "game_directory", WorkingDirectory.ToString() },
{ "natives_directory", GetNativeLibrariesDir(_descriptor.id).ToString() },
{ "path", GetLog4jConfigFilePath().ToString() },
{ "version_name", _descriptor.id },
{ "version_type", _descriptor.type },
{ "arch", PlatformHelper.GetArchOld() },
// { "quickPlayMultiplayer", "" },
// { "quickPlayPath", "" },
// { "quickPlayRealms", "" },
// { "quickPlaySingleplayer", "" },
{ "client_jar", GetVersionJarFilePath(_descriptor.id).ToString() },
};
List<string> argsList = new();
argsList.AddRange(_javaArgs.FillPlaceholders(placeholder_values));
argsList.Add(_descriptor.mainClass);
argsList.AddRange(_gameArgs.FillPlaceholders(placeholder_values));
var command = Cli.Wrap(JavaExecutableFilePath.ToString())
.WithWorkingDirectory(WorkingDirectory.ToString())
.WithArguments(argsList)
.WithStandardOutputPipe(PipeTarget.ToDelegate(LogGameOut))
.WithStandardErrorPipe(PipeTarget.ToDelegate(LogGameError))
.WithValidation(CommandResultValidation.None);
LauncherApp.Logger.LogInfo(Name, "launching the game");
LauncherApp.Logger.LogDebug(Name, "java: " + command.TargetFilePath);
LauncherApp.Logger.LogDebug(Name, "working_dir: " + command.WorkingDirPath);
LauncherApp.Logger.LogDebug(Name, "arguments: \n\t" + argsList.MergeToString("\n\t"));
_gameCts = new();
_commandTask = command.ExecuteAsync(_gameCts.Token);
var result = await _commandTask;
LauncherApp.Logger.LogInfo(Name, $"game exited with code {result.ExitCode}");
}
private void LogGameOut(string line)
{
LauncherApp.Logger.LogInfo(Name, line);
}
private void LogGameError(string line)
{
LauncherApp.Logger.LogWarn(Name, line);
}
public void Close()
{
_gameCts?.Cancel();
}
public override string ToString() => Name;
}

View File

@@ -1,4 +1,6 @@
namespace Млаумчерб.Клиент; using Mlaumcherb.Client.Avalonia.зримое;
namespace Mlaumcherb.Client.Avalonia;
public class LauncherLogger : ILogger public class LauncherLogger : ILogger
{ {
@@ -18,10 +20,6 @@ public class LauncherLogger : ILogger
#endif #endif
]; ];
_compositeLogger = new CompositeLogger(loggers); _compositeLogger = new CompositeLogger(loggers);
#if DEBUG
DebugLogEnabled = true;
#endif
} }
public record LogMessage(string context, LogSeverity severity, object message, ILogFormat format); public record LogMessage(string context, LogSeverity severity, object message, ILogFormat format);

View File

@@ -30,21 +30,7 @@
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="капитал\**"/> <AvaloniaResource Include="капитал\**"/>
<EmbeddedResource Include="встроенное\**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="зримое\VersionItemView.axaml.cs">
<DependentUpon>VersionItemView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="зримое\Окне.axaml.cs">
<DependentUpon>Окне.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="зримое\Приложение.axaml.cs">
<DependentUpon>Приложение.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
</Project> </Project>

View File

@@ -14,11 +14,11 @@ global using Directory = DTLib.Filesystem.Directory;
global using Path = DTLib.Filesystem.Path; global using Path = DTLib.Filesystem.Path;
using System.Globalization; using System.Globalization;
using Avalonia; using Avalonia;
using Млаумчерб.Клиент.зримое; using Mlaumcherb.Client.Avalonia.зримое;
namespace Млаумчерб.Клиент; namespace Mlaumcherb.Client.Avalonia;
public class Главне public class Program
{ {
[STAThread] [STAThread]
public static void Main(string[] args) public static void Main(string[] args)
@@ -31,13 +31,13 @@ public class Главне
} }
catch (Exception ex) catch (Exception ex)
{ {
Приложение.Логгер.LogError(nameof(Главне), ex); LauncherApp.Logger.LogError(nameof(Program), ex);
} }
} }
// Avalonia configuration, don't remove; also used by visual designer. // Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp() public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<Приложение>() => AppBuilder.Configure<LauncherApp>()
.UsePlatformDetect() .UsePlatformDetect()
.LogToTrace(); .LogToTrace();
} }

View File

@@ -0,0 +1,28 @@
<Configuration status="WARN" packages="com.mojang.util">
<Appenders>
<Console name="SysOut" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n"/>
</Console>
<Queue name="ServerGuiConsole">
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n"/>
</Queue>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<filters>
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL"/>
<RegexFilter regex="(?s).*\$\{[^}]*\}.*" onMatch="DENY" onMismatch="NEUTRAL"/>
</filters>
<AppenderRef ref="SysOut"/>
<AppenderRef ref="File"/>
<AppenderRef ref="ServerGuiConsole"/>
</Root>
</Loggers>
</Configuration>

View File

@@ -1,6 +1,6 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Млаумчерб.Клиент.зримое.NetworkTaskView" x:Class="Mlaumcherb.Client.Avalonia.зримое.NetworkTaskView"
Padding="4" MinHeight="90" MinWidth="200" Padding="4" MinHeight="90" MinWidth="200"
VerticalAlignment="Top" VerticalAlignment="Top"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"

View File

@@ -1,9 +1,9 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Threading; using Avalonia.Threading;
using Млаумчерб.Клиент.сеть; using Mlaumcherb.Client.Avalonia.сеть;
namespace Млаумчерб.Клиент.зримое; namespace Mlaumcherb.Client.Avalonia.зримое;
public partial class NetworkTaskView : UserControl public partial class NetworkTaskView : UserControl
{ {

View File

@@ -1,6 +1,6 @@
<Application xmlns="https://github.com/avaloniaui" <Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Млаумчерб.Клиент.зримое.Приложение" x:Class="Mlaumcherb.Client.Avalonia.зримое.LauncherApp"
RequestedThemeVariant="Dark"> RequestedThemeVariant="Dark">
<Application.Styles> <Application.Styles>
<SimpleTheme /> <SimpleTheme />

View File

@@ -0,0 +1,29 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace Mlaumcherb.Client.Avalonia.зримое;
public class LauncherApp : Application
{
public static LauncherLogger Logger = new();
public static Config Config = new();
public override void Initialize()
{
Logger.LogInfo(nameof(LauncherApp), "приложение запущено");
Config = Config.LoadFromFile();
Logger.DebugLogEnabled = Config.debug;
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}

View File

@@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Млаумчерб.Клиент.зримое.LogMessageView" x:Class="Mlaumcherb.Client.Avalonia.зримое.LogMessageView"
FontSize="14"> FontSize="14">
<SelectableTextBlock Name="ContentTextBox" <SelectableTextBlock Name="ContentTextBox"
FontSize="{Binding $parent.FontSize}" FontSize="{Binding $parent.FontSize}"

View File

@@ -1,7 +1,6 @@
using Avalonia.Controls; using Avalonia.Controls;
using DTLib;
namespace Млаумчерб.Клиент.зримое; namespace Mlaumcherb.Client.Avalonia.зримое;
public partial class LogMessageView : UserControl public partial class LogMessageView : UserControl
{ {

View File

@@ -1,13 +1,13 @@
<Window xmlns="https://github.com/avaloniaui" <Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gif="clr-namespace:Avalonia.Labs.Gif;assembly=Avalonia.Labs.Gif" xmlns:gif="clr-namespace:Avalonia.Labs.Gif;assembly=Avalonia.Labs.Gif"
xmlns:local="clr-namespace:Млаумчерб" xmlns:local="clr-namespace:Mlaumcherb.Client.Avalonia"
x:Class="Млаумчерб.Клиент.зримое.Окне" x:Class="Mlaumcherb.Client.Avalonia.зримое.MainWindow"
Name="window" Name="window"
Title="млаумчерб" Title="млаумчерб"
Icon="avares://млаумчерб/капитал/кубе.ico" Icon="avares://млаумчерб/капитал/кубе.ico"
FontFamily="{StaticResource MonospaceFont}" FontSize="18" FontFamily="{StaticResource MonospaceFont}" FontSize="18"
MinWidth="800" MinHeight="500" MinWidth="1200" MinHeight="700"
Width="800" Height="500" Width="800" Height="500"
WindowStartupLocation="CenterScreen"> WindowStartupLocation="CenterScreen">
<Grid> <Grid>
@@ -30,7 +30,7 @@
<TextBlock>Ник:</TextBlock> <TextBlock>Ник:</TextBlock>
<TextBox Background="Transparent" <TextBox Background="Transparent"
Text="{Binding #window.Username}"/> Text="{Binding #window.PlayerName}"/>
<TextBlock> <TextBlock>
<Run>Выделенная память:</Run> <Run>Выделенная память:</Run>
@@ -45,9 +45,6 @@
Value="{Binding #window.MemoryLimit}"> Value="{Binding #window.MemoryLimit}">
</Slider> </Slider>
<CheckBox IsChecked="{Binding #window.Fullscreen}">
Запускать полноэкранное
</CheckBox>
<CheckBox IsChecked="{Binding #window.CheckGameFiles}"> <CheckBox IsChecked="{Binding #window.CheckGameFiles}">
Проверять файлы игры Проверять файлы игры
</CheckBox> </CheckBox>

View File

@@ -4,41 +4,33 @@ using Avalonia.Data;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Threading; using Avalonia.Threading;
using Млаумчерб.Клиент.классы; using Mlaumcherb.Client.Avalonia.классы;
using Mlaumcherb.Client.Avalonia.холопы;
namespace Млаумчерб.Клиент.зримое; namespace Mlaumcherb.Client.Avalonia.зримое;
public partial class Окне : Window public partial class MainWindow : Window
{ {
public static readonly StyledProperty<string> UsernameProperty = public static readonly StyledProperty<string> PlayerNameProperty =
AvaloniaProperty.Register<Окне, string>(nameof(Username), AvaloniaProperty.Register<MainWindow, string>(nameof(PlayerName),
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
public string Username public string PlayerName
{ {
get => GetValue(UsernameProperty); get => GetValue(PlayerNameProperty);
set => SetValue(UsernameProperty, value); set => SetValue(PlayerNameProperty, value);
} }
public static readonly StyledProperty<int> MemoryLimitProperty = public static readonly StyledProperty<int> MemoryLimitProperty =
AvaloniaProperty.Register<Окне, int>(nameof(MemoryLimit), AvaloniaProperty.Register<MainWindow, int>(nameof(MemoryLimit),
defaultBindingMode: BindingMode.TwoWay, defaultValue: 2048); defaultBindingMode: BindingMode.TwoWay, defaultValue: 2048);
public int MemoryLimit public int MemoryLimit
{ {
get => GetValue(MemoryLimitProperty); get => GetValue(MemoryLimitProperty);
set => SetValue(MemoryLimitProperty, value); set => SetValue(MemoryLimitProperty, value);
} }
public static readonly StyledProperty<bool> FullscreenProperty =
AvaloniaProperty.Register<Окне, bool>(nameof(Fullscreen),
defaultBindingMode: BindingMode.TwoWay, defaultValue: false);
public bool Fullscreen
{
get => GetValue(FullscreenProperty);
set => SetValue(FullscreenProperty, value);
}
public static readonly StyledProperty<bool> CheckGameFilesProperty = public static readonly StyledProperty<bool> CheckGameFilesProperty =
AvaloniaProperty.Register<Окне, bool>(nameof(CheckGameFiles), AvaloniaProperty.Register<MainWindow, bool>(nameof(CheckGameFiles),
defaultBindingMode: BindingMode.TwoWay, defaultValue: false); defaultBindingMode: BindingMode.TwoWay, defaultValue: false);
public bool CheckGameFiles public bool CheckGameFiles
{ {
@@ -48,7 +40,7 @@ public partial class Окне : Window
public static readonly StyledProperty<bool> EnableJavaDownloadProperty = public static readonly StyledProperty<bool> EnableJavaDownloadProperty =
AvaloniaProperty.Register<Окне, bool>(nameof(EnableJavaDownload), AvaloniaProperty.Register<MainWindow, bool>(nameof(EnableJavaDownload),
defaultBindingMode: BindingMode.TwoWay, defaultValue: true); defaultBindingMode: BindingMode.TwoWay, defaultValue: true);
public bool EnableJavaDownload public bool EnableJavaDownload
{ {
@@ -56,7 +48,7 @@ public partial class Окне : Window
set => SetValue(EnableJavaDownloadProperty, value); set => SetValue(EnableJavaDownloadProperty, value);
} }
public Окне() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
} }
@@ -65,14 +57,12 @@ public partial class Окне : Window
{ {
try try
{ {
Приложение.Логгер.OnLogMessage += GuiLogMessage; LauncherApp.Logger.OnLogMessage += GuiLogMessage;
Username = Приложение.Настройки.имя_пользователя; PlayerName = LauncherApp.Config.player_name;
MemoryLimit = Приложение.Настройки.выделенная_память_мб; MemoryLimit = LauncherApp.Config.max_memory;
Fullscreen = Приложение.Настройки.запускать_полноэкранное; EnableJavaDownload = LauncherApp.Config.download_java;
EnableJavaDownload = Приложение.Настройки.скачиватьабу;
Directory.Create(Пути.GetVersionDescriptorsDir());
VersionComboBox.SelectedIndex = 0; VersionComboBox.SelectedIndex = 0;
VersionComboBox.IsEnabled = false; VersionComboBox.IsEnabled = false;
var versions = await GameVersion.GetAllVersionsAsync(); var versions = await GameVersion.GetAllVersionsAsync();
@@ -81,8 +71,8 @@ public partial class Окне : Window
foreach (var p in versions) foreach (var p in versions)
{ {
VersionComboBox.Items.Add(new VersionItemView(p)); VersionComboBox.Items.Add(new VersionItemView(p));
if (Приложение.Настройки.последняяапущенная_версия != null && if (LauncherApp.Config.last_launched_version != null &&
p.Name == Приложение.Настройки.последняяапущенная_версия) p.Name == LauncherApp.Config.last_launched_version)
VersionComboBox.SelectedIndex = VersionComboBox.Items.Count - 1; VersionComboBox.SelectedIndex = VersionComboBox.Items.Count - 1;
} }
VersionComboBox.IsEnabled = true; VersionComboBox.IsEnabled = true;
@@ -90,7 +80,7 @@ public partial class Окне : Window
} }
catch (Exception ex) catch (Exception ex)
{ {
Ошибки.ПоказатьСообщение(nameof(Окне), ex); ErrorHelper.ShowMessageBox(nameof(MainWindow), ex);
} }
} }
@@ -115,12 +105,11 @@ public partial class Окне : Window
var selectedVersionView = (VersionItemView?)VersionComboBox.SelectedItem; var selectedVersionView = (VersionItemView?)VersionComboBox.SelectedItem;
var selectedVersion = selectedVersionView?.Props; var selectedVersion = selectedVersionView?.Props;
Приложение.Настройки.последняяапущенная_версия = selectedVersion?.Name; LauncherApp.Config.last_launched_version = selectedVersion?.Name;
Приложение.Настройки.имя_пользователя = Username; LauncherApp.Config.player_name = PlayerName;
Приложение.Настройки.выделенная_память_мб = MemoryLimit; LauncherApp.Config.max_memory = MemoryLimit;
Приложение.Настройки.запускать_полноэкранное = Fullscreen; LauncherApp.Config.download_java = EnableJavaDownload;
Приложение.Настройки.скачиватьабу = EnableJavaDownload; LauncherApp.Config.SaveToFile();
Приложение.Настройки.СохранитьВФайл();
if (selectedVersion == null) if (selectedVersion == null)
return; return;
@@ -138,10 +127,11 @@ public partial class Окне : Window
{ {
CheckGameFiles = false; CheckGameFiles = false;
}); });
await v.Launch();
} }
catch (Exception ex) catch (Exception ex)
{ {
Ошибки.ПоказатьСообщение(nameof(Окне), ex); ErrorHelper.ShowMessageBox(nameof(MainWindow), ex);
} }
finally finally
{ {
@@ -161,7 +151,7 @@ public partial class Окне : Window
} }
catch (Exception ex) catch (Exception ex)
{ {
Ошибки.ПоказатьСообщение(nameof(Окне), ex); ErrorHelper.ShowMessageBox(nameof(MainWindow), ex);
} }
} }
@@ -169,12 +159,12 @@ public partial class Окне : Window
{ {
try try
{ {
Launcher.LaunchFileInfoAsync(new FileInfo(Приложение.Логгер.LogfileName.ToString())) Launcher.LaunchFileInfoAsync(new FileInfo(LauncherApp.Logger.LogfileName.ToString()))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
Ошибки.ПоказатьСообщение(nameof(Окне), ex); ErrorHelper.ShowMessageBox(nameof(MainWindow), ex);
} }
} }
@@ -187,7 +177,7 @@ public partial class Окне : Window
} }
catch (Exception ex) catch (Exception ex)
{ {
Ошибки.ПоказатьСообщение(nameof(Окне), ex); ErrorHelper.ShowMessageBox(nameof(MainWindow), ex);
} }
} }

View File

@@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Млаумчерб.Клиент" xmlns:local="clr-namespace:Mlaumcherb.Client.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Млаумчерб.Клиент.зримое.VersionItemView"> x:Class="Mlaumcherb.Client.Avalonia.зримое.VersionItemView">
<TextBlock Name="text" Background="Transparent"/> <TextBlock Name="text" Background="Transparent"/>
</UserControl> </UserControl>

View File

@@ -1,8 +1,8 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Млаумчерб.Клиент.классы; using Mlaumcherb.Client.Avalonia.классы;
namespace Млаумчерб.Клиент.зримое; namespace Mlaumcherb.Client.Avalonia.зримое;
public partial class VersionItemView : ListBoxItem public partial class VersionItemView : ListBoxItem
{ {
@@ -12,7 +12,7 @@ public partial class VersionItemView : ListBoxItem
public VersionItemView() public VersionItemView()
{ {
throw new NotImplementedException(); throw new Exception();
} }
public VersionItemView(GameVersionProps props) public VersionItemView(GameVersionProps props)

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -1,4 +1,4 @@
namespace Млаумчерб.Клиент.классы; namespace Mlaumcherb.Client.Avalonia.классы;
public class GameVersionCatalog public class GameVersionCatalog
{ {

View File

@@ -1,7 +1,7 @@
using System.Linq; using System.Linq;
// ReSharper disable CollectionNeverUpdated.Global // ReSharper disable CollectionNeverUpdated.Global
namespace Млаумчерб.Клиент.классы; namespace Mlaumcherb.Client.Avalonia.классы;
public class GameVersionDescriptor public class GameVersionDescriptor
{ {

View File

@@ -1,6 +1,7 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Mlaumcherb.Client.Avalonia.холопы;
namespace Млаумчерб.Клиент.классы; namespace Mlaumcherb.Client.Avalonia.классы;
public class JavaVersionCatalog public class JavaVersionCatalog
{ {
@@ -54,7 +55,7 @@ public class JavaVersionCatalog
} }
throw new PlatformNotSupportedException($"Can't download java {version.majorVersion} for your operating system. " + throw new PlatformNotSupportedException($"Can't download java {version.majorVersion} for your operating system. " +
$"Download it manually to directory {Пути.GetJavaRuntimeDir(version.component)}"); $"Download it manually to directory {PathHelper.GetJavaRuntimeDir(version.component)}");
} }
} }

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
namespace Млаумчерб.Клиент.классы; using Mlaumcherb.Client.Avalonia.холопы;
namespace Mlaumcherb.Client.Avalonia.классы;
public class GameVersionProps : IComparable<GameVersionProps>, IEquatable<GameVersionProps> public class GameVersionProps : IComparable<GameVersionProps>, IEquatable<GameVersionProps>
{ {
@@ -19,16 +21,13 @@ public class GameVersionProps : IComparable<GameVersionProps>, IEquatable<GameVe
} }
public event Action? OnDownloadCompleted; public event Action? OnDownloadCompleted;
public GameVersionProps(string name, string? url, IOPath descriptorPath) public GameVersionProps(string name, string? url)
{ {
Name = name; Name = name;
LocalDescriptorPath = descriptorPath; LocalDescriptorPath = PathHelper.GetVersionDescriptorPath(name);
RemoteDescriptorUrl = url; RemoteDescriptorUrl = url;
IsDownloaded = File.Exists(Пути.GetVersionJarFilePath(name)); IsDownloaded = File.Exists(PathHelper.GetVersionJarFilePath(name));
} }
public GameVersionProps(string name, string? url) :
this(name, url, Пути.GetVersionDescriptorPath(name)) { }
public override string ToString() => Name; public override string ToString() => Name;

View File

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

View File

@@ -1,7 +1,7 @@
using System.Runtime.InteropServices; using DTLib.Extensions;
using DTLib.Extensions; using Mlaumcherb.Client.Avalonia.холопы;
namespace Млаумчерб.Клиент.классы; namespace Mlaumcherb.Client.Avalonia.классы;
public class Libraries public class Libraries
{ {
@@ -20,7 +20,7 @@ public class Libraries
foreach (var l in descriptor.libraries) foreach (var l in descriptor.libraries)
{ {
if (l.rules != null && !Буржуазия.CheckRules(l.rules, enabled_features)) if (l.rules != null && !Rules.Check(l.rules, enabled_features))
continue; continue;
if (l.natives != null) if (l.natives != null)
@@ -42,13 +42,7 @@ public class Libraries
var span = nativesKey.AsSpan(); var span = nativesKey.AsSpan();
nativesKey = span.After("${").Before('}') switch nativesKey = span.After("${").Before('}') switch
{ {
"arch" => RuntimeInformation.OSArchitecture switch "arch" => span.Before("${").ToString() + PlatformHelper.GetArchOld(),
{
Architecture.X64 => span.Before("${").ToString() + "64",
Architecture.X86 => span.Before("${").ToString() + "32",
_ => throw new PlatformNotSupportedException(
$"Unsupported architecture: {RuntimeInformation.OSArchitecture}")
},
_ => throw new Exception($"unknown placeholder in {nativesKey}") _ => throw new Exception($"unknown placeholder in {nativesKey}")
}; };
} }
@@ -62,7 +56,7 @@ public class Libraries
continue; continue;
string urlTail = artifact.url.AsSpan().After("://").After('/').ToString(); string urlTail = artifact.url.AsSpan().After("://").After('/').ToString();
IOPath jarFilePath = Path.Concat(Пути.GetLibrariesDir(), urlTail); IOPath jarFilePath = Path.Concat(PathHelper.GetLibrariesDir(), urlTail);
libs.Add(new NativeLib(l.name, jarFilePath, artifact, l.extract)); libs.Add(new NativeLib(l.name, jarFilePath, artifact, l.extract));
} }
else else
@@ -76,7 +70,7 @@ public class Libraries
continue; continue;
string urlTail = artifact.url.AsSpan().After("://").After('/').ToString(); string urlTail = artifact.url.AsSpan().After("://").After('/').ToString();
IOPath jarFilePath = Path.Concat(Пути.GetLibrariesDir(), urlTail); IOPath jarFilePath = Path.Concat(PathHelper.GetLibrariesDir(), urlTail);
libs.Add(new JarLib(l.name, jarFilePath, artifact)); libs.Add(new JarLib(l.name, jarFilePath, artifact));
} }
} }

View File

@@ -1,4 +1,4 @@
namespace Млаумчерб.Клиент.сеть; namespace Mlaumcherb.Client.Avalonia.сеть;
public record struct DataSize(long Bytes) public record struct DataSize(long Bytes)
{ {

View File

@@ -1,7 +1,7 @@
using Млаумчерб.Клиент.зримое; using Mlaumcherb.Client.Avalonia.зримое;
using Timer = DTLib.Timer; using Timer = DTLib.Timer;
namespace Млаумчерб.Клиент.сеть; namespace Mlaumcherb.Client.Avalonia.сеть;
public record struct DownloadProgress(DataSize Downloaded, DataSize Total, DataSize PerSecond) public record struct DownloadProgress(DataSize Downloaded, DataSize Total, DataSize PerSecond)
{ {
@@ -54,7 +54,7 @@ public class NetworkProgressReporter : IDisposable
long bytesPerSec = (_curSize - _prevSize) / (_timerDelay / 1000); long bytesPerSec = (_curSize - _prevSize) / (_timerDelay / 1000);
_prevSize = _curSize; _prevSize = _curSize;
var p = new DownloadProgress(_curSize, _totalSize, bytesPerSec); var p = new DownloadProgress(_curSize, _totalSize, bytesPerSec);
Приложение.Логгер.LogDebug(nameof(ReportProgress), LauncherApp.Logger.LogDebug(nameof(ReportProgress),
$"download progress {p}"); $"download progress {p}");
_reportProgressDelegate(p); _reportProgressDelegate(p);
} }

View File

@@ -1,4 +1,4 @@
namespace Млаумчерб.Клиент.сеть; namespace Mlaumcherb.Client.Avalonia.сеть;
public class NetworkTask : IDisposable public class NetworkTask : IDisposable
{ {

View File

@@ -2,11 +2,12 @@
using System.Net.Http; using System.Net.Http;
using System.Security.Cryptography; using System.Security.Cryptography;
using DTLib.Extensions; using DTLib.Extensions;
using Млаумчерб.Клиент.зримое; using Mlaumcherb.Client.Avalonia.зримое;
using Млаумчерб.Клиент.классы; using Mlaumcherb.Client.Avalonia.классы;
using static Млаумчерб.Клиент.сеть.Сеть; using Mlaumcherb.Client.Avalonia.холопы;
using static Mlaumcherb.Client.Avalonia.сеть.Сеть;
namespace Млаумчерб.Клиент.сеть.NetworkTaskFactories; namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
public class AssetsDownloadTaskFactory : INetworkTaskFactory public class AssetsDownloadTaskFactory : INetworkTaskFactory
{ {
@@ -20,7 +21,7 @@ public class AssetsDownloadTaskFactory : INetworkTaskFactory
{ {
_descriptor = descriptor; _descriptor = descriptor;
_hasher = SHA1.Create(); _hasher = SHA1.Create();
_indexFilePath = Пути.GetAssetIndexFilePath(_descriptor.assetIndex.id); _indexFilePath = PathHelper.GetAssetIndexFilePath(_descriptor.assetIndex.id);
} }
public async Task<NetworkTask?> CreateAsync(bool checkHashes) public async Task<NetworkTask?> CreateAsync(bool checkHashes)
@@ -38,9 +39,9 @@ public class AssetsDownloadTaskFactory : INetworkTaskFactory
{ {
if(!File.Exists(_indexFilePath)) if(!File.Exists(_indexFilePath))
{ {
Приложение.Логгер.LogInfo(nameof(Сеть), $"started downloading asset index to '{_indexFilePath}'"); LauncherApp.Logger.LogInfo(nameof(Сеть), $"started downloading asset index to '{_indexFilePath}'");
await DownloadFile(_descriptor.assetIndex.url, _indexFilePath); await DownloadFile(_descriptor.assetIndex.url, _indexFilePath);
Приложение.Логгер.LogInfo(nameof(Сеть), "finished downloading asset index"); LauncherApp.Logger.LogInfo(nameof(Сеть), "finished downloading asset index");
} }
string indexFileText = File.ReadAllText(_indexFilePath); string indexFileText = File.ReadAllText(_indexFilePath);
@@ -101,10 +102,10 @@ public class AssetsDownloadTaskFactory : INetworkTaskFactory
private async Task Download(NetworkProgressReporter pr, CancellationToken ct) private async Task Download(NetworkProgressReporter pr, CancellationToken ct)
{ {
Приложение.Логгер.LogInfo(nameof(Сеть), $"started downloading assets '{_descriptor.assetIndex.id}'"); LauncherApp.Logger.LogInfo(nameof(Сеть), $"started downloading assets '{_descriptor.assetIndex.id}'");
ParallelOptions opt = new() ParallelOptions opt = new()
{ {
MaxDegreeOfParallelism = Приложение.Настройки.максимум_параллельныхагрузок, MaxDegreeOfParallelism = LauncherApp.Config.max_parallel_downloads,
CancellationToken = ct CancellationToken = ct
}; };
await Parallel.ForEachAsync(_assetsToDownload, opt, await Parallel.ForEachAsync(_assetsToDownload, opt,
@@ -113,7 +114,7 @@ public class AssetsDownloadTaskFactory : INetworkTaskFactory
bool completed = false; bool completed = false;
while(!completed) while(!completed)
{ {
Приложение.Логгер.LogDebug(nameof(Сеть), $"downloading asset '{a.name}' {a.hash}"); LauncherApp.Logger.LogDebug(nameof(Сеть), $"downloading asset '{a.name}' {a.hash}");
try try
{ {
await DownloadFile(a.url, a.filePath, _ct, pr.AddBytesCount); await DownloadFile(a.url, a.filePath, _ct, pr.AddBytesCount);
@@ -124,13 +125,13 @@ public class AssetsDownloadTaskFactory : INetworkTaskFactory
// wait on rate limit // wait on rate limit
if(httpException.StatusCode == HttpStatusCode.TooManyRequests) if(httpException.StatusCode == HttpStatusCode.TooManyRequests)
{ {
Приложение.Логгер.LogDebug(nameof(Сеть), "rate limit hit"); LauncherApp.Logger.LogDebug(nameof(Сеть), "rate limit hit");
await Task.Delay(1000, _ct); await Task.Delay(1000, _ct);
} }
else throw; else throw;
} }
} }
}); });
Приложение.Логгер.LogInfo(nameof(Сеть), $"finished downloading assets '{_descriptor.assetIndex.id}'"); LauncherApp.Logger.LogInfo(nameof(Сеть), $"finished downloading assets '{_descriptor.assetIndex.id}'");
} }
} }

View File

@@ -1,4 +1,4 @@
namespace Млаумчерб.Клиент.сеть.NetworkTaskFactories; namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
public interface INetworkTaskFactory public interface INetworkTaskFactory
{ {

View File

@@ -1,10 +1,11 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using DTLib.Extensions; using DTLib.Extensions;
using Млаумчерб.Клиент.зримое; using Mlaumcherb.Client.Avalonia.зримое;
using Млаумчерб.Клиент.классы; using Mlaumcherb.Client.Avalonia.классы;
using static Млаумчерб.Клиент.сеть.Сеть; using Mlaumcherb.Client.Avalonia.холопы;
using static Mlaumcherb.Client.Avalonia.сеть.Сеть;
namespace Млаумчерб.Клиент.сеть.NetworkTaskFactories; namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
public class JavaDownloadTaskFactory : INetworkTaskFactory public class JavaDownloadTaskFactory : INetworkTaskFactory
{ {
@@ -19,7 +20,7 @@ public class JavaDownloadTaskFactory : INetworkTaskFactory
public JavaDownloadTaskFactory(GameVersionDescriptor descriptor) public JavaDownloadTaskFactory(GameVersionDescriptor descriptor)
{ {
_descriptor = descriptor; _descriptor = descriptor;
_javaVersionDir = Пути.GetJavaRuntimeDir(_descriptor.javaVersion.component); _javaVersionDir = PathHelper.GetJavaRuntimeDir(_descriptor.javaVersion.component);
_hasher = SHA1.Create(); _hasher = SHA1.Create();
} }
@@ -83,38 +84,38 @@ public class JavaDownloadTaskFactory : INetworkTaskFactory
private async Task Download(NetworkProgressReporter pr, CancellationToken ct) private async Task Download(NetworkProgressReporter pr, CancellationToken ct)
{ {
Приложение.Логгер.LogInfo(nameof(Сеть), "started downloading java runtime " + LauncherApp.Logger.LogInfo(nameof(Сеть), "started downloading java runtime " +
$"{_descriptor.javaVersion.majorVersion} '{_descriptor.javaVersion.component}'"); $"{_descriptor.javaVersion.majorVersion} '{_descriptor.javaVersion.component}'");
ParallelOptions opt = new() ParallelOptions opt = new()
{ {
MaxDegreeOfParallelism = Приложение.Настройки.максимум_параллельныхагрузок, MaxDegreeOfParallelism = LauncherApp.Config.max_parallel_downloads,
CancellationToken = ct CancellationToken = ct
}; };
await Parallel.ForEachAsync(_filesToDownload, opt, async (f, _ct) => await Parallel.ForEachAsync(_filesToDownload, opt, async (f, _ct) =>
{ {
if (f.props.downloads!.lzma != null) if (f.props.downloads!.lzma != null)
{ {
Приложение.Логгер.LogDebug(nameof(Сеть), $"downloading lzma-compressed file '{f.path}'"); LauncherApp.Logger.LogDebug(nameof(Сеть), $"downloading lzma-compressed file '{f.path}'");
await using var pipe = new TransformStream(await GetStream(f.props.downloads.lzma.url, _ct)); await using var pipe = new TransformStream(await GetStream(f.props.downloads.lzma.url, _ct));
pipe.AddTransform(pr.AddBytesCount); pipe.AddTransform(pr.AddBytesCount);
await using var fs = File.OpenWrite(f.path); await using var fs = File.OpenWrite(f.path);
LZMACompressor.Decompress(pipe, fs); LZMAHelper.Decompress(pipe, fs);
} }
else else
{ {
Приложение.Логгер.LogDebug(nameof(Сеть), $"downloading raw file '{f.path}'"); LauncherApp.Logger.LogDebug(nameof(Сеть), $"downloading raw file '{f.path}'");
await DownloadFile(f.props.downloads.raw.url, f.path, _ct, pr.AddBytesCount); await DownloadFile(f.props.downloads.raw.url, f.path, _ct, pr.AddBytesCount);
} }
if(!OperatingSystem.IsWindows() && f.props.executable is true) if(!OperatingSystem.IsWindows() && f.props.executable is true)
{ {
Приложение.Логгер.LogDebug(nameof(Сеть), $"adding execute rights to file '{f.path}'"); LauncherApp.Logger.LogDebug(nameof(Сеть), $"adding execute rights to file '{f.path}'");
System.IO.File.SetUnixFileMode(f.path.ToString(), UnixFileMode.UserExecute); System.IO.File.SetUnixFileMode(f.path.ToString(), UnixFileMode.UserExecute);
} }
}); });
Приложение.Логгер.LogInfo(nameof(Сеть), "finished downloading java runtime " + LauncherApp.Logger.LogInfo(nameof(Сеть), "finished downloading java runtime " +
$"{_descriptor.javaVersion.majorVersion} '{_descriptor.javaVersion.component}'"); $"{_descriptor.javaVersion.majorVersion} '{_descriptor.javaVersion.component}'");
} }

View File

@@ -1,11 +1,12 @@
using System.IO.Compression; using System.IO.Compression;
using System.Security.Cryptography; using System.Security.Cryptography;
using DTLib.Extensions; using DTLib.Extensions;
using Млаумчерб.Клиент.зримое; using Mlaumcherb.Client.Avalonia.зримое;
using Млаумчерб.Клиент.классы; using Mlaumcherb.Client.Avalonia.классы;
using static Млаумчерб.Клиент.сеть.Сеть; using Mlaumcherb.Client.Avalonia.холопы;
using static Mlaumcherb.Client.Avalonia.сеть.Сеть;
namespace Млаумчерб.Клиент.сеть.NetworkTaskFactories; namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
public class LibrariesDownloadTaskFactory : INetworkTaskFactory public class LibrariesDownloadTaskFactory : INetworkTaskFactory
{ {
@@ -20,7 +21,7 @@ public class LibrariesDownloadTaskFactory : INetworkTaskFactory
_descriptor = descriptor; _descriptor = descriptor;
_libraries = libraries; _libraries = libraries;
_hasher = SHA1.Create(); _hasher = SHA1.Create();
_nativesDir = Пути.GetNativeLibrariesDir(descriptor.id); _nativesDir = PathHelper.GetNativeLibrariesDir(descriptor.id);
} }
public Task<NetworkTask?> CreateAsync(bool checkHashes) public Task<NetworkTask?> CreateAsync(bool checkHashes)
@@ -72,16 +73,16 @@ public class LibrariesDownloadTaskFactory : INetworkTaskFactory
private async Task Download(NetworkProgressReporter pr, CancellationToken ct) private async Task Download(NetworkProgressReporter pr, CancellationToken ct)
{ {
Приложение.Логгер.LogInfo(nameof(Сеть), $"started downloading libraries '{_descriptor.id}'"); LauncherApp.Logger.LogInfo(nameof(Сеть), $"started downloading libraries '{_descriptor.id}'");
ParallelOptions opt = new() ParallelOptions opt = new()
{ {
MaxDegreeOfParallelism = Приложение.Настройки.максимум_параллельныхагрузок, MaxDegreeOfParallelism = LauncherApp.Config.max_parallel_downloads,
CancellationToken = ct CancellationToken = ct
}; };
await Parallel.ForEachAsync(_libsToDownload, opt, async (l, _ct) => await Parallel.ForEachAsync(_libsToDownload, opt, async (l, _ct) =>
{ {
Приложение.Логгер.LogDebug(nameof(Сеть), $"downloading library '{l.name}' to '{l.jarFilePath}'"); LauncherApp.Logger.LogDebug(nameof(Сеть), $"downloading library '{l.name}' to '{l.jarFilePath}'");
await DownloadFile(l.artifact.url, l.jarFilePath, _ct, pr.AddBytesCount); await DownloadFile(l.artifact.url, l.jarFilePath, _ct, pr.AddBytesCount);
if (l is Libraries.NativeLib n) if (l is Libraries.NativeLib n)
{ {
@@ -101,6 +102,6 @@ public class LibrariesDownloadTaskFactory : INetworkTaskFactory
} }
}); });
Приложение.Логгер.LogInfo(nameof(Сеть), $"finished downloading libraries '{_descriptor.id}'"); LauncherApp.Logger.LogInfo(nameof(Сеть), $"finished downloading libraries '{_descriptor.id}'");
} }
} }

View File

@@ -1,10 +1,11 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using DTLib.Extensions; using DTLib.Extensions;
using Млаумчерб.Клиент.зримое; using Mlaumcherb.Client.Avalonia.зримое;
using Млаумчерб.Клиент.классы; using Mlaumcherb.Client.Avalonia.классы;
using static Млаумчерб.Клиент.сеть.Сеть; using Mlaumcherb.Client.Avalonia.холопы;
using static Mlaumcherb.Client.Avalonia.сеть.Сеть;
namespace Млаумчерб.Клиент.сеть.NetworkTaskFactories; namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
public class VersionFileDownloadTaskFactory : INetworkTaskFactory public class VersionFileDownloadTaskFactory : INetworkTaskFactory
{ {
@@ -15,7 +16,7 @@ public class VersionFileDownloadTaskFactory : INetworkTaskFactory
public VersionFileDownloadTaskFactory(GameVersionDescriptor descriptor) public VersionFileDownloadTaskFactory(GameVersionDescriptor descriptor)
{ {
_descriptor = descriptor; _descriptor = descriptor;
_filePath = Пути.GetVersionJarFilePath(_descriptor.id); _filePath = PathHelper.GetVersionJarFilePath(_descriptor.id);
_hasher = SHA1.Create(); _hasher = SHA1.Create();
} }
@@ -49,8 +50,8 @@ public class VersionFileDownloadTaskFactory : INetworkTaskFactory
private async Task Download(NetworkProgressReporter pr, CancellationToken ct) private async Task Download(NetworkProgressReporter pr, CancellationToken ct)
{ {
Приложение.Логгер.LogInfo(nameof(Сеть), $"started downloading version file '{_descriptor.id}'"); LauncherApp.Logger.LogInfo(nameof(Сеть), $"started downloading version file '{_descriptor.id}'");
await DownloadFile(_descriptor.downloads.client.url, _filePath, ct, pr.AddBytesCount); await DownloadFile(_descriptor.downloads.client.url, _filePath, ct, pr.AddBytesCount);
Приложение.Логгер.LogInfo(nameof(Сеть), $"finished downloading version file '{_descriptor.id}'"); LauncherApp.Logger.LogInfo(nameof(Сеть), $"finished downloading version file '{_descriptor.id}'");
} }
} }

View File

@@ -1,9 +1,8 @@
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using Mlaumcherb.Client.Avalonia.зримое;
using Млаумчерб.Клиент.зримое; using Mlaumcherb.Client.Avalonia.классы;
using Млаумчерб.Клиент.классы;
namespace Млаумчерб.Клиент.сеть; namespace Mlaumcherb.Client.Avalonia.сеть;
public static class Сеть public static class Сеть
{ {
@@ -60,7 +59,7 @@ public static class Сеть
} }
catch (Exception ex) catch (Exception ex)
{ {
Приложение.Логгер.LogWarn(nameof(Сеть), ex); LauncherApp.Logger.LogWarn(nameof(Сеть), ex);
} }
} }

View File

@@ -1,21 +1,21 @@
using Avalonia.Controls; using Avalonia.Controls;
using DTLib.Demystifier; using DTLib.Demystifier;
using Mlaumcherb.Client.Avalonia.зримое;
using MsBox.Avalonia; using MsBox.Avalonia;
using MsBox.Avalonia.Dto; using MsBox.Avalonia.Dto;
using MsBox.Avalonia.Enums; using MsBox.Avalonia.Enums;
using MsBox.Avalonia.Models; using MsBox.Avalonia.Models;
using Млаумчерб.Клиент.зримое;
namespace Млаумчерб.Клиент; namespace Mlaumcherb.Client.Avalonia.холопы;
public static class Ошибки public static class ErrorHelper
{ {
internal static void ПоказатьСообщение(string context, Exception err) internal static void ShowMessageBox(string context, Exception err)
=> ПоказатьСообщение(context, err.ToStringDemystified()); => ShowMessageBox(context, err.ToStringDemystified());
internal static async void ПоказатьСообщение(string context, string err) internal static async void ShowMessageBox(string context, string err)
{ {
Приложение.Логгер.LogError(nameof(Ошибки), err); LauncherApp.Logger.LogError(context, err);
var box = MessageBoxManager.GetMessageBoxCustom(new MessageBoxCustomParams var box = MessageBoxManager.GetMessageBoxCustom(new MessageBoxCustomParams
{ {
ButtonDefinitions = new List<ButtonDefinition> { new() { Name = "пон" } }, ButtonDefinitions = new List<ButtonDefinition> { new() { Name = "пон" } },

View File

@@ -1,9 +1,9 @@
namespace Млаумчерб.Клиент; namespace Mlaumcherb.Client.Avalonia.холопы;
/// <summary> /// <summary>
/// https://gist.github.com/ststeiger/cb9750664952f775a341 /// https://gist.github.com/ststeiger/cb9750664952f775a341
/// </summary> /// </summary>
public static class LZMACompressor public static class LZMAHelper
{ {
public static void Decompress(Stream inputStream, Stream outputStream) public static void Decompress(Stream inputStream, Stream outputStream)
{ {

View File

@@ -0,0 +1,57 @@
using Mlaumcherb.Client.Avalonia.зримое;
namespace Mlaumcherb.Client.Avalonia.холопы;
public static class PathHelper
{
public static IOPath GetRootFullPath() =>
System.IO.Path.GetFullPath(new IOPath(LauncherApp.Config.minecraft_dir).ToString());
public static IOPath GetAssetsDir() =>
Path.Concat(GetRootFullPath(), "assets");
public static IOPath GetAssetIndexFilePath(string id) =>
Path.Concat(GetAssetsDir(), $"assets/indexes/{id}.json");
public static IOPath GetVersionDescriptorPath(string id) =>
Path.Concat(GetVersionDir(id), id + ".json");
public static IOPath GetVersionsDir() =>
Path.Concat(GetRootFullPath(), "versions");
public static IOPath GetVersionDir(string id) =>
Path.Concat(GetVersionsDir(), id);
public static IOPath GetVersionJarFilePath(string id) =>
Path.Concat(GetVersionDir(id), id + ".jar");
public static IOPath GetLibrariesDir() =>
Path.Concat(GetRootFullPath(), "libraries");
public static IOPath GetNativeLibrariesDir(string id) =>
Path.Concat(GetVersionDir(id), "natives", PlatformHelper.GetOsAndArch());
public static IOPath GetJavaRuntimesDir() =>
Path.Concat(GetRootFullPath(), "java");
public static IOPath GetJavaRuntimeDir(string id) =>
Path.Concat(GetJavaRuntimesDir(), PlatformHelper.GetOsAndArch(), id);
public static IOPath GetJavaBinDir(string id) =>
Path.Concat(GetJavaRuntimeDir(id), "bin");
public static IOPath GetJavaExecutablePath(string id, bool debug)
{
string executable_name = "java";
if (debug)
executable_name += "w";
if(OperatingSystem.IsWindows())
executable_name += ".exe";
return Path.Concat(GetJavaBinDir(id), executable_name);
}
public static string GetLog4jConfigFileName() => "log4j.xml";
public static IOPath GetLog4jConfigFilePath() =>
Path.Concat(GetAssetsDir(), GetLog4jConfigFileName());
}

View File

@@ -0,0 +1,55 @@
using System.Runtime.InteropServices;
using Mlaumcherb.Client.Avalonia.классы;
// ReSharper disable CollectionNeverUpdated.Global
namespace Mlaumcherb.Client.Avalonia.холопы;
public static class PlatformHelper
{
public static bool CheckOs(Os os) =>
os.name switch
{
null => true,
"osx" => OperatingSystem.IsMacOS(),
"linux" => OperatingSystem.IsLinux(),
"windows" => OperatingSystem.IsWindows(),
_ => throw new ArgumentOutOfRangeException(os.name)
}
&& os.arch switch
{
null => true,
"i386" or "x86" => RuntimeInformation.OSArchitecture == Architecture.X86,
"x64" => RuntimeInformation.OSArchitecture == Architecture.X64,
"arm64" => RuntimeInformation.OSArchitecture == Architecture.Arm64,
_ => false
};
public static string GetOs() =>
Environment.OSVersion.Platform switch
{
PlatformID.Win32NT => "windows",
PlatformID.Unix => "linux",
PlatformID.MacOSX => "osx",
_ => throw new PlatformNotSupportedException("OS not supported")
};
public static string GetArch() =>
RuntimeInformation.OSArchitecture switch
{
Architecture.X86 => "x86",
Architecture.X64 => "x64",
Architecture.Arm64 => "arm64",
_ => throw new PlatformNotSupportedException("OS not supported")
};
public static string GetArchOld() =>
RuntimeInformation.OSArchitecture switch
{
Architecture.X86 => "32",
Architecture.X64 => "64",
_ => throw new PlatformNotSupportedException("OS not supported")
};
public static string GetOsAndArch() => GetOs() + '-' + GetArch();
}

View File

@@ -1,6 +1,6 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Млаумчерб.Клиент", "Млаумчерб.Клиент\Млаумчерб.Клиент.csproj", "{9B9D8B05-255F-49C3-89EC-3F43A66491D3}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mlaumcherb.Client.Avalonia", "Mlaumcherb.Client.Avalonia\Mlaumcherb.Client.Avalonia.csproj", "{9B9D8B05-255F-49C3-89EC-3F43A66491D3}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFolder", "SolutionFolder", "{A3217C18-CC0D-4CE8-9C48-1BDEC1E1B333}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFolder", "SolutionFolder", "{A3217C18-CC0D-4CE8-9C48-1BDEC1E1B333}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject

View File

@@ -1,142 +0,0 @@
using System.Linq;
using CliWrap;
using DTLib.Extensions;
using Млаумчерб.Клиент.зримое;
using Млаумчерб.Клиент.классы;
using Млаумчерб.Клиент.сеть;
using Млаумчерб.Клиент.сеть.NetworkTaskFactories;
using static Млаумчерб.Клиент.классы.Пути;
namespace Млаумчерб.Клиент;
public class GameVersion
{
private readonly GameVersionProps _props;
public string Name => _props.Name;
public IOPath WorkingDirectory { get; }
private IOPath JavaExecutableFilePath;
private GameVersionDescriptor descriptor;
private JavaArguments javaArgs;
private GameArguments gameArgs;
private Libraries libraries;
private CancellationTokenSource? gameCts;
private CommandTask<CommandResult>? commandTask;
public static async Task<List<GameVersionProps>> GetAllVersionsAsync()
{
var propsSet = new HashSet<GameVersionProps>();
// local descriptors
foreach (IOPath f in Directory.GetFiles(GetVersionDescriptorsDir()))
{
string name = GetVersionDescriptorName(f);
propsSet.Add(new GameVersionProps(name, null, f));
}
// remote non-duplicating versions
foreach (var removeVersion in await Сеть.GetDownloadableVersions())
{
propsSet.Add(removeVersion);
}
// reverse sort
var propsList = propsSet.ToList();
propsList.Sort((a, b) => b.CompareTo(a));
return propsList;
}
public static async Task<GameVersion> CreateFromPropsAsync(GameVersionProps props)
{
if (!File.Exists(props.LocalDescriptorPath))
{
if (props.RemoteDescriptorUrl is null)
throw new NullReferenceException("can't download game version descriptor '"
+ props.Name + "', because RemoteDescriptorUrl is null");
await Сеть.DownloadFile(props.RemoteDescriptorUrl, props.LocalDescriptorPath);
}
return new GameVersion(props);
}
private GameVersion(GameVersionProps props)
{
_props = props;
string descriptorText = File.ReadAllText(props.LocalDescriptorPath);
descriptor = JsonConvert.DeserializeObject<GameVersionDescriptor>(descriptorText)
?? throw new Exception($"can't parse descriptor file '{props.LocalDescriptorPath}'");
javaArgs = new JavaArguments(descriptor);
gameArgs = new GameArguments(descriptor);
libraries = new Libraries(descriptor);
WorkingDirectory = GetVersionDir(descriptor.id);
JavaExecutableFilePath = GetJavaExecutablePath(descriptor.javaVersion.component);
}
public async Task UpdateFiles(bool checkHashes, Action<NetworkTask> networkTaskCreatedCallback)
{
Приложение.Логгер.LogInfo(nameof(GameVersion), $"started updating version {Name}");
List<INetworkTaskFactory> taskFactories =
[
new AssetsDownloadTaskFactory(descriptor),
new LibrariesDownloadTaskFactory(descriptor, libraries),
new VersionFileDownloadTaskFactory(descriptor),
];
if(Приложение.Настройки.скачиватьабу)
{
taskFactories.Add(new JavaDownloadTaskFactory(descriptor));
}
//TODO: modpack
/*if (modpack != null)
{
taskFactories.Add(new ModpackDownloadTaskFactory(modpack));
}*/
var networkTasks = new List<NetworkTask>();
for (int i = 0; i < taskFactories.Count; i++)
{
var nt = await taskFactories[i].CreateAsync(checkHashes);
if (nt != null)
{
networkTasks.Add(nt);
networkTaskCreatedCallback.Invoke(nt);
}
}
foreach (var nt in networkTasks)
{
await nt.StartAsync();
}
_props.IsDownloaded = true;
Приложение.Логгер.LogInfo(nameof(GameVersion), $"finished updating version {Name}");
}
public async Task Launch()
{
var javaArgsList = javaArgs.FillPlaceholders([]);
var gameArgsList = gameArgs.FillPlaceholders([]);
var command = Cli.Wrap(JavaExecutableFilePath.ToString())
.WithWorkingDirectory(WorkingDirectory.ToString())
.WithArguments(javaArgsList)
.WithArguments(gameArgsList);
Приложение.Логгер.LogInfo(nameof(GameVersion),
$"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"));
gameCts = new();
commandTask = command.ExecuteAsync(gameCts.Token);
var result = await commandTask;
Приложение.Логгер.LogInfo(nameof(GameVersion), $"game exited with code {result.ExitCode}");
}
public void Close()
{
gameCts?.Cancel();
}
public override string ToString() => Name;
}

View File

@@ -1,48 +0,0 @@
using Млаумчерб.Клиент.зримое;
namespace Млаумчерб.Клиент;
public record Настройки
{
public string имя_пользователя { get; set; } = "";
public int выделенная_память_мб { get; set; } = 4096;
public string путь_к_кубачу { get; set; } = ".";
public bool запускать_полноэкранное { get; set; } = false;
public bool скачиватьабу { get; set; } = true;
public string? последняяапущенная_версия { get; set; }
public int максимум_параллельныхагрузок { get; set; } = 16;
public static Настройки ЗагрузитьИзФайла(string имяайла = "млаумчерб.настройки")
{
Приложение.Логгер.LogInfo(nameof(Настройки), $"загружаются настройки из файла '{имя_файла}'");
if(!File.Exists(имяайла))
{
Приложение.Логгер.LogInfo(nameof(Настройки), "файл не существует");
return new Настройки();
}
string текст = File.ReadAllText(имяайла);
Настройки? н = JsonConvert.DeserializeObject<Настройки>(текст);
if (н == null)
{
File.Move(имяайла, имяайла + ".старые", true);
Ошибки.ПоказатьСообщение("Настройки", $"Не удалось прочитать настройки.\n" +
$"Сломанный файл настроек переименован в '{имя_файла}.старые'.\n" +
$"Создаётся новый файл '{имя_файла}'.");
н = new Настройки();
н.СохранитьВФайл();
}
Приложение.Логгер.LogInfo(nameof(Настройки), $"настройки загружены: {н}");
return н;
}
public void СохранитьВФайл(string имяайла = "млаумчерб.настройки")
{
//TODO: file backup and restore
Приложение.Логгер.LogDebug(nameof(Настройки), $"настройки сохраняются в файл '{имя_файла}'");
var текст = JsonConvert.SerializeObject(this, Formatting.Indented);
File.WriteAllText(имяайла, текст);
Приложение.Логгер.LogDebug(nameof(Настройки), $"настройки сохранены: {текст}");
}
}

View File

@@ -1,28 +0,0 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace Млаумчерб.Клиент.зримое;
public class Приложение : Application
{
public static LauncherLogger Логгер = new();
public static Настройки Настройки = new();
public override void Initialize()
{
Логгер.LogInfo(nameof(Приложение), "приложение запущено");
Настройки = Настройки.ЗагрузитьИзФайла();
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new Окне();
}
base.OnFrameworkInitializationCompleted();
}
}

View File

@@ -1,31 +0,0 @@
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;
}
}

View File

@@ -1,27 +0,0 @@
using DTLib.Extensions;
namespace Млаумчерб.Клиент.классы;
public class GameArguments : ArgumentsWithPlaceholders
{
private static readonly string[] _enabled_features =
[
"has_custom_resolution"
];
public GameArguments(GameVersionDescriptor 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);
}
}
}
}

View File

@@ -1,27 +0,0 @@
namespace Млаумчерб.Клиент.классы;
public class JavaArguments : ArgumentsWithPlaceholders
{
private static readonly string[] _initial_arguments =
[
];
private static readonly string[] _enabled_features =
[
];
public JavaArguments(GameVersionDescriptor d)
{
raw_args.AddRange(_initial_arguments);
if (d.arguments is not null)
{
foreach (var av in d.arguments.jvm)
{
if(Буржуазия.CheckRules(av.rules, _enabled_features))
raw_args.AddRange(av.value);
}
}
}
}

View File

@@ -1,65 +0,0 @@
using System.Runtime.InteropServices;
// ReSharper disable CollectionNeverUpdated.Global
namespace Млаумчерб.Клиент.классы;
public static class Буржуазия
{
public static bool CheckOs(Os os) =>
os.name switch
{
null => true,
"osx" => OperatingSystem.IsMacOS(),
"linux" => OperatingSystem.IsLinux(),
"windows" => OperatingSystem.IsWindows(),
_ => throw new ArgumentOutOfRangeException(os.name)
}
&& os.arch switch
{
null => true,
"i386" or "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;
foreach (var r in rules)
{
if (r.os == null || CheckOs(r.os))
{
if (r.features != null)
{
foreach (var feature in enabled_features)
{
if(r.features.TryGetValue(feature, out bool is_enabled))
{
if (is_enabled)
{
if (r.action != "allow")
return false;
}
}
}
}
if (r.action == "allow")
allowed = true;
else return false;
}
}
return allowed;
}
public static string GetOs() =>
Environment.OSVersion.Platform switch
{
PlatformID.Win32NT => "windows",
PlatformID.Unix => "linux",
PlatformID.MacOSX => "osx",
_ => throw new ArgumentOutOfRangeException(Environment.OSVersion.Platform.ToString())
};
}

View File

@@ -1,6 +0,0 @@
namespace Млаумчерб.Клиент.классы;
public static class Пролетариат
{
}

View File

@@ -1,41 +0,0 @@
using Млаумчерб.Клиент.зримое;
namespace Млаумчерб.Клиент.классы;
public static class Пути
{
public static IOPath GetAssetIndexFilePath(string id) =>
Path.Concat(Приложение.Настройки.путь_к_кубачу, $"assets/indexes/{id}.json");
public static IOPath GetVersionDescriptorsDir() =>
Path.Concat(Приложение.Настройки.путь_к_кубачу, "versions");
public static string GetVersionDescriptorName(IOPath path) =>
path.LastName().RemoveExtension().ToString();
public static IOPath GetVersionDescriptorPath(string name) =>
Path.Concat(GetVersionDescriptorsDir(), Path.ReplaceRestrictedChars(name) + ".json");
public static IOPath GetVersionDir(string id) =>
Path.Concat(Приложение.Настройки.путь_к_кубачу, "versions", id);
public static IOPath GetVersionJarFilePath(string id) =>
Path.Concat(GetVersionDir(id), id + ".jar");
public static IOPath GetLibrariesDir() =>
Path.Concat(Приложение.Настройки.путь_к_кубачу, "libraries");
public static IOPath GetNativeLibrariesDir(string id) =>
Path.Concat(GetVersionDir(id), "natives", Буржуазия.GetOs());
public static IOPath GetJavaRuntimesDir() =>
Path.Concat(Приложение.Настройки.путь_к_кубачу, "java");
public static IOPath GetJavaRuntimeDir(string id) =>
Path.Concat(GetJavaRuntimesDir(), id);
public static IOPath GetJavaExecutablePath(string id) =>
Path.Concat(GetJavaRuntimeDir(id), "bin",
OperatingSystem.IsWindows() ? "javaw.exe" : "javaw");
}