307 lines
13 KiB
C#
307 lines
13 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Net.Http;
|
|
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Interactivity;
|
|
using Avalonia.ReactiveUI;
|
|
using Avalonia.Threading;
|
|
using ReactiveUI;
|
|
using VaultSmpInstaller.Data;
|
|
using VaultSmpInstaller.ViewModels;
|
|
|
|
namespace VaultSmpInstaller.Views;
|
|
|
|
public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
|
|
{
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
|
|
this.WhenActivated(action => action(ViewModel!.ShowProfileSelectionDialog.RegisterHandler(DoShowDialogAsync)));
|
|
|
|
this.WhenActivated(_ => StartDownload());
|
|
}
|
|
|
|
private async Task ProcessInstance(String instancePath)
|
|
{
|
|
await using FileStream fs = new FileStream(Path.Combine(instancePath, "instance.json"), FileMode.Open);
|
|
ViewModel!.LatestInstanceConfig = await JsonSerializer.DeserializeAsync(fs, JsonContext.Default.InstanceConfig);
|
|
if (ViewModel!.LatestInstanceConfig != null)
|
|
{
|
|
ViewModel!.LatestInstanceConfig.InstancePath = instancePath;
|
|
|
|
foreach (var modProfile in ViewModel!.LatestInstanceConfig.ModProfiles.Values)
|
|
if (modProfile.Required || modProfile.DefaultState)
|
|
modProfile.IsEnabled = true;
|
|
|
|
ViewModel!.LatestVersion = ViewModel!.LatestInstanceConfig.Version;
|
|
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.LatestInstanceConfig));
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfiles));
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfiles));
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfileNames));
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfileNames));
|
|
|
|
ViewModel!.SelectProfileCommand.Execute(null);
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("Failed to download new version");
|
|
}
|
|
}
|
|
|
|
private async Task DoShowDialogAsync(InteractionContext<ProfileWindow1ViewModel, ProfileWindow2ViewModel.InstanceInfo?> interaction)
|
|
{
|
|
var dialog = new ProfileWindow1();
|
|
dialog.DataContext = interaction.Input;
|
|
|
|
var result = await dialog.ShowDialog<ProfileWindow2ViewModel.InstanceInfo?>(this);
|
|
interaction.SetOutput(result);
|
|
}
|
|
|
|
private readonly DownloadingWindow _downloadingWindow = new DownloadingWindow();
|
|
|
|
private void StartDownload()
|
|
{
|
|
var downloadThread = new Thread((arg) =>
|
|
{
|
|
if (arg is CancellationTokenSource tokenSource)
|
|
{
|
|
var archivePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".zip");
|
|
var extractPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
|
try
|
|
{
|
|
HttpClient httpClient = new HttpClient();
|
|
|
|
using var response = httpClient.GetAsync("https://holenode.cdnbcn.net/LatestProfile.zip", tokenSource.Token).Result;
|
|
using var fs = new FileStream(archivePath, FileMode.OpenOrCreate);
|
|
response.Content.CopyTo(fs, null, tokenSource.Token);
|
|
|
|
fs.Close();
|
|
|
|
ZipFile.ExtractToDirectory(archivePath, extractPath);
|
|
|
|
tokenSource.Token.ThrowIfCancellationRequested();
|
|
|
|
File.Delete(archivePath);
|
|
|
|
Dispatcher.UIThread.Invoke(() => ProcessInstance(extractPath));
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
try
|
|
{
|
|
File.Delete(archivePath);
|
|
Directory.Delete(extractPath, true);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// ignored
|
|
}
|
|
Dispatcher.UIThread.Invoke(Close);
|
|
}
|
|
Dispatcher.UIThread.Invoke(() => _downloadingWindow.Close());
|
|
}
|
|
});
|
|
|
|
CancellationTokenSource source = new CancellationTokenSource();
|
|
downloadThread.Start(source);
|
|
_downloadingWindow.DataContext = new ThemeViewModel();
|
|
_downloadingWindow.Closed += (_, _) => source.Cancel();
|
|
_downloadingWindow.ShowDialog(this);
|
|
}
|
|
|
|
private string _disabledSelected = "";
|
|
|
|
private void DisabledSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (e.AddedItems is [String])
|
|
_disabledSelected = (String)e.AddedItems[0]!;
|
|
}
|
|
|
|
private void EnableSelected(object? sender, RoutedEventArgs e)
|
|
{
|
|
if (ViewModel!.LatestInstanceConfig!.ModProfiles.ContainsKey(_disabledSelected))
|
|
{
|
|
ViewModel!.LatestInstanceConfig!.ModProfiles[_disabledSelected].IsEnabled = true;
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfiles));
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfileNames));
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfiles));
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfileNames));
|
|
_disabledSelected = "";
|
|
}
|
|
}
|
|
|
|
private string _enabledSelected = "";
|
|
private void EnabledSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (e.AddedItems is [String])
|
|
_enabledSelected = (String)e.AddedItems[0]!;
|
|
}
|
|
|
|
private void DisableSelected(object? sender, RoutedEventArgs e)
|
|
{
|
|
if (ViewModel!.LatestInstanceConfig!.ModProfiles.ContainsKey(_enabledSelected) && !ViewModel!.LatestInstanceConfig!.ModProfiles[_enabledSelected].Required)
|
|
{
|
|
ViewModel!.LatestInstanceConfig!.ModProfiles[_enabledSelected].IsEnabled = false;
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfiles));
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.EnabledModProfileNames));
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfiles));
|
|
ViewModel!.RaisePropertyChanged(nameof(ViewModel.DisabledModProfileNames));
|
|
_enabledSelected = "";
|
|
}
|
|
}
|
|
|
|
public ManualResetEvent ContinueInstalling = new ManualResetEvent(false);
|
|
|
|
private void InstallProfile(object? sender, RoutedEventArgs e)
|
|
{
|
|
Thread installThread = new Thread(() =>
|
|
{
|
|
var md5 = MD5.Create();
|
|
var viewModel = Dispatcher.UIThread.Invoke(() => ViewModel);
|
|
if (viewModel!.InstalledInstanceConfig == null)
|
|
{
|
|
viewModel.InstalledInstanceConfig = viewModel.LatestInstanceConfig!;
|
|
viewModel.InstalledVersion = viewModel.LatestInstanceConfig!.Version;
|
|
File.WriteAllText(Path.Combine(viewModel.SelectedInstance!.InstancePath, "installedInstance.json"),
|
|
JsonSerializer.Serialize(viewModel.LatestInstanceConfig, JsonContext.Default.InstanceConfig));
|
|
|
|
foreach (var (path, md5Hash) in viewModel.LatestInstanceConfig.ReplaceFiles)
|
|
{
|
|
var stream = File.ReadAllBytes(Path.Combine(viewModel.SelectedInstance.MinecraftPath, path));
|
|
if (BitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower() != md5Hash)
|
|
{
|
|
Dispatcher.UIThread.Invoke(() =>
|
|
{
|
|
var instanceNotIntactWindow = new InstanceNotIntactWindow(this);
|
|
instanceNotIntactWindow.DataContext = new ThemeViewModel();
|
|
|
|
instanceNotIntactWindow.ShowDialog(this);
|
|
});
|
|
|
|
ContinueInstalling.WaitOne();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!Directory.Exists(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles")))
|
|
{
|
|
Directory.CreateDirectory(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles"));
|
|
}
|
|
foreach (var path in viewModel.LatestInstanceConfig.ReplaceFiles.Keys)
|
|
{
|
|
var pathParts = path.Split('/');
|
|
var currentPath = "";
|
|
foreach (var part in pathParts[0..^1])
|
|
{
|
|
currentPath = Path.Combine(currentPath, part);
|
|
if(!Directory.Exists(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", currentPath)))
|
|
Directory.CreateDirectory(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", currentPath));
|
|
}
|
|
File.Copy(Path.Combine(viewModel.SelectedInstance.MinecraftPath, path), Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", path));
|
|
}
|
|
}
|
|
|
|
CopyAll(Path.Combine(viewModel.LatestInstanceConfig!.InstancePath, "config"), viewModel.SelectedInstance!.ConfigPath);
|
|
CopyAll(Path.Combine(viewModel.LatestInstanceConfig.InstancePath, "scripts"), viewModel.SelectedInstance.ScriptsPath);
|
|
|
|
if(!Directory.Exists(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles")))
|
|
{
|
|
Directory.CreateDirectory(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles"));
|
|
}
|
|
if(!Directory.Exists(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", "mods")))
|
|
{
|
|
Directory.CreateDirectory(Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", "mods"));
|
|
}
|
|
|
|
foreach (var (profileName, modProfile) in viewModel.LatestInstanceConfig.ModProfiles)
|
|
{
|
|
if (modProfile.IsEnabled)
|
|
{
|
|
foreach (var (modName, md5Hash) in modProfile.Replaces)
|
|
{
|
|
if (File.Exists(Path.Combine(viewModel.SelectedInstance.ModsPath, modName)))
|
|
{
|
|
var stream = File.ReadAllBytes(Path.Combine(viewModel.SelectedInstance.ModsPath, modName));
|
|
if (BitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower() == md5Hash)
|
|
{
|
|
File.Copy(Path.Combine(viewModel.SelectedInstance.ModsPath, modName), Path.Combine(viewModel.SelectedInstance.InstancePath, "originalFiles", "mods", modName));
|
|
File.Delete(Path.Combine(viewModel.SelectedInstance.ModsPath, modName));
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var modName in modProfile.Mods.Keys)
|
|
{
|
|
File.Copy(Path.Combine(viewModel.LatestInstanceConfig.InstancePath, "mods", modName), Path.Combine(viewModel.SelectedInstance.ModsPath, modName), true);
|
|
}
|
|
} else if (viewModel.InstalledInstanceConfig.ModProfiles[profileName].IsEnabled)
|
|
{
|
|
foreach (var modName in modProfile.Mods.Keys)
|
|
{
|
|
if(File.Exists(Path.Combine(viewModel.SelectedInstance.ModsPath, modName)))
|
|
File.Delete(Path.Combine(viewModel.SelectedInstance.ModsPath, modName));
|
|
}
|
|
}
|
|
}
|
|
|
|
Dispatcher.UIThread.Invoke(() =>
|
|
{
|
|
var successWindow = new SuccessWindow(this);
|
|
successWindow.DataContext = new ThemeViewModel();
|
|
|
|
successWindow.ShowDialog(this);
|
|
});
|
|
|
|
viewModel.InstalledInstanceConfig = viewModel.LatestInstanceConfig!;
|
|
viewModel.InstalledVersion = viewModel.LatestInstanceConfig!.Version;
|
|
|
|
viewModel.RaisePropertyChanged(nameof(viewModel.InstalledInstanceConfig));
|
|
viewModel.RaisePropertyChanged(nameof(viewModel.InstalledModProfiles));
|
|
viewModel.RaisePropertyChanged(nameof(viewModel.InstalledModProfileNames));
|
|
|
|
ContinueInstalling.Reset();
|
|
|
|
ContinueInstalling.WaitOne();
|
|
});
|
|
installThread.Start();
|
|
}
|
|
|
|
public static void CopyAll(string source, string target)
|
|
{
|
|
CopyAll(new DirectoryInfo(source), new DirectoryInfo(target));
|
|
}
|
|
public static void CopyAll(DirectoryInfo source, DirectoryInfo target)
|
|
{
|
|
if (source.FullName.ToLower() == target.FullName.ToLower())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if the target directory exists, if not, create it.
|
|
if (Directory.Exists(target.FullName) == false)
|
|
{
|
|
Directory.CreateDirectory(target.FullName);
|
|
}
|
|
|
|
// Copy each file into it's new directory.
|
|
foreach (FileInfo fi in source.GetFiles())
|
|
{
|
|
fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true);
|
|
}
|
|
|
|
// Copy each subdirectory using recursion.
|
|
foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
|
|
{
|
|
DirectoryInfo nextTargetSubDir =
|
|
target.CreateSubdirectory(diSourceSubDir.Name);
|
|
CopyAll(diSourceSubDir, nextTargetSubDir);
|
|
}
|
|
}
|
|
} |