Compare commits

..

1 commit
v2.0 ... master

Author SHA1 Message Date
520bcb7965 Merge pull request 'fix secure_mkdirs' (#3) from unstable into master
Reviewed-on: #3
2025-09-24 15:36:32 +05:00
21 changed files with 312 additions and 1238 deletions

View file

@ -1,7 +0,0 @@
namespace MetaforceInstaller.Core;
public static class Defaults
{
public static readonly string StoragePath =
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + "MetaforceInstaller";
}

View file

@ -1,9 +0,0 @@
using MetaforceInstaller.Core.Models;
namespace MetaforceInstaller.Core.Intefaces;
public interface IStorageService
{
AppData Load();
void Save(AppData data);
}

View file

@ -7,26 +7,20 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="adb\adb.exe"/> <EmbeddedResource Include="adb\adb.exe" />
<EmbeddedResource Include="adb\AdbWinApi.dll"> <EmbeddedResource Include="adb\AdbWinApi.dll" />
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <EmbeddedResource Include="adb\AdbWinUsbApi.dll" />
<Link>AdbWinApi.dll</Link>
</EmbeddedResource>
<EmbeddedResource Include="adb\AdbWinUsbApi.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>AdbWinUsbApi.dll</Link>
</EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AlphaOmega.ApkReader" Version="2.0.5"/> <PackageReference Include="AlphaOmega.ApkReader" Version="2.0.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0"/> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14"/> <PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14"/> <PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3"/> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -1,6 +0,0 @@
namespace MetaforceInstaller.Core.Models;
public class AppData
{
public List<InstallationData> Installations { get; set; } = new();
}

View file

@ -1,9 +0,0 @@
namespace MetaforceInstaller.Core.Models;
public class InstallationData
{
public Guid Id { get; set; } = Guid.NewGuid();
public required string Title { get; set; }
public required InstallationParts Parts { get; set; }
public DateTime InstalledAt { get; set; } = DateTime.Now;
}

View file

@ -1,12 +0,0 @@
namespace MetaforceInstaller.Core.Models;
public class InstallationParts
{
public string? OculusClientPath { get; init; }
public string? PicoClientPath { get; init; }
public string? AndroidAdminPath { get; init; }
public string? AndroidContentPath { get; init; }
public string? WindowsContentPath { get; init; }
public string? WindowsAdminPath { get; init; }
public string? WindowsServerPath { get; init; }
}

View file

@ -15,15 +15,9 @@ public class AdbService : IAdbService
private readonly ILogger<AdbService> _logger; private readonly ILogger<AdbService> _logger;
private readonly AdbClient _adbClient; private readonly AdbClient _adbClient;
private DeviceData _deviceData; private DeviceData _deviceData;
private DeviceMonitor? _deviceMonitor;
public event EventHandler<ProgressInfo>? ProgressChanged; public event EventHandler<ProgressInfo>? ProgressChanged;
public event EventHandler<string>? StatusChanged; public event EventHandler<string>? StatusChanged;
public EventHandler<DeviceDataEventArgs>? DeviceConnected;
public EventHandler<DeviceDataEventArgs>? DeviceDisconnected;
public EventHandler<DeviceDataEventArgs>? DeviceChanged;
public bool IsDeviceConnected => _deviceData != null && _deviceData.State == DeviceState.Online;
public AdbService(ILogger<AdbService>? logger = null) public AdbService(ILogger<AdbService>? logger = null)
{ {
@ -33,46 +27,6 @@ public class AdbService : IAdbService
var serverStatus = server.StartServer(adbPath, restartServerIfNewer: false); var serverStatus = server.StartServer(adbPath, restartServerIfNewer: false);
_adbClient = new AdbClient(); _adbClient = new AdbClient();
RefreshDeviceData(); RefreshDeviceData();
StartDeviceMonitoring();
}
private void StartDeviceMonitoring()
{
try
{
_deviceMonitor = new DeviceMonitor(new AdbSocket(_adbClient.EndPoint));
_deviceMonitor.DeviceConnected += OnDeviceConnected;
_deviceMonitor.DeviceDisconnected += OnDeviceDisconnected;
_deviceMonitor.DeviceChanged += OnDeviceChanged;
_deviceMonitor.Start();
_logger.LogInformation("Device monitoring started");
}
catch (Exception ex)
{
_logger.LogError($"Failed to start device monitoring: {ex.Message}");
}
}
private void OnDeviceConnected(object? sender, DeviceDataEventArgs e)
{
_logger.LogInformation($"Device conn: {e.Device.Serial}");
RefreshDeviceData();
DeviceConnected?.Invoke(this, e);
}
private void OnDeviceDisconnected(object? sender, DeviceDataEventArgs e)
{
_logger.LogInformation($"Device disconnected: {e.Device.Serial}");
RefreshDeviceData();
DeviceDisconnected?.Invoke(this, e);
}
private void OnDeviceChanged(object? sender, DeviceDataEventArgs e)
{
_logger.LogInformation($"Device changed: {e.Device.Serial}");
RefreshDeviceData();
DeviceChanged?.Invoke(this, e);
} }
public void RefreshDeviceData() public void RefreshDeviceData()
@ -249,15 +203,4 @@ public class AdbService : IAdbService
{ {
return new DeviceInfo(_deviceData.Serial, _deviceData.State.ToString(), _deviceData.Model, _deviceData.Name); return new DeviceInfo(_deviceData.Serial, _deviceData.State.ToString(), _deviceData.Model, _deviceData.Name);
} }
public void Dispose()
{
if (_deviceMonitor != null)
{
_deviceMonitor.DeviceConnected -= OnDeviceConnected;
_deviceMonitor.DeviceDisconnected -= OnDeviceDisconnected;
_deviceMonitor.DeviceChanged -= OnDeviceChanged;
_deviceMonitor.Dispose();
}
}
} }

View file

@ -1,35 +0,0 @@
using System.Text.Json;
using MetaforceInstaller.Core.Intefaces;
using MetaforceInstaller.Core.Models;
namespace MetaforceInstaller.Core.Services;
public class StorageService : IStorageService
{
private readonly string _storagePath;
public StorageService()
{
var appDirectory = Defaults.StoragePath;
Directory.CreateDirectory(appDirectory);
_storagePath = Path.Combine(appDirectory, "installations.json");
}
public AppData Load()
{
if (!File.Exists(_storagePath))
return new AppData();
var json = File.ReadAllText(_storagePath);
return JsonSerializer.Deserialize<AppData>(json) ?? new AppData();
}
public void Save(AppData data)
{
var json = JsonSerializer.Serialize(data, new JsonSerializerOptions
{
WriteIndented = true
});
File.WriteAllText(_storagePath, json);
}
}

View file

@ -1,160 +0,0 @@
using System.IO.Compression;
using MetaforceInstaller.Core.Models;
namespace MetaforceInstaller.Core.Services;
public class ZipScrapper
{
public static InstallationParts PeekFiles(ZipArchive archive)
{
return new InstallationParts
{
AndroidContentPath = FindAndroidContent(archive),
OculusClientPath = FindOculusClient(archive),
PicoClientPath = FindPicoClient(archive),
AndroidAdminPath = FindAndroidAdmin(archive),
WindowsAdminPath = FindPcAdmin(archive),
WindowsContentPath = FindWindowsContent(archive),
WindowsServerPath = FindServer(archive),
};
}
/// <summary>
/// Extracts ZIP archive to a unique folder based on installation GUID
/// </summary>
/// <param name="archive">ZIP archive to extract</param>
/// <param name="baseOutputPath">Base storage path</param>
/// <param name="installationGuid">Unique GUID for this installation</param>
/// <param name="progress">Progress reporter</param>
/// <returns>Full path to the extracted folder</returns>
public static string ExtractZip(
ZipArchive archive,
string baseOutputPath,
Guid installationGuid,
IProgress<double>? progress = null)
{
// Create unique folder for this installation
var installationFolder = Path.Combine(baseOutputPath, installationGuid.ToString());
Directory.CreateDirectory(installationFolder);
var entries = archive.Entries.Where(e => !string.IsNullOrEmpty(e.Name)).ToList();
var totalEntries = entries.Count;
var processedEntries = 0;
foreach (var entry in entries)
{
var destinationPath = Path.Combine(installationFolder, entry.FullName);
var destinationDir = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrEmpty(destinationDir))
{
Directory.CreateDirectory(destinationDir);
}
entry.ExtractToFile(destinationPath, overwrite: true);
processedEntries++;
progress?.Report((double)processedEntries / totalEntries * 100);
}
return installationFolder;
}
/// <summary>
/// Updates InstallationParts paths to reflect the actual extracted location
/// </summary>
public static InstallationParts UpdatePathsAfterExtraction(
InstallationParts parts,
string extractedFolderPath)
{
return new InstallationParts
{
AndroidContentPath = UpdatePath(parts.AndroidContentPath, extractedFolderPath),
OculusClientPath = UpdatePath(parts.OculusClientPath, extractedFolderPath),
PicoClientPath = UpdatePath(parts.PicoClientPath, extractedFolderPath),
AndroidAdminPath = UpdatePath(parts.AndroidAdminPath, extractedFolderPath),
WindowsAdminPath = UpdatePath(parts.WindowsAdminPath, extractedFolderPath),
WindowsContentPath = UpdatePath(parts.WindowsContentPath, extractedFolderPath),
WindowsServerPath = UpdatePath(parts.WindowsServerPath, extractedFolderPath),
};
}
private static string? UpdatePath(string? relativePath, string basePath)
{
if (string.IsNullOrEmpty(relativePath))
return null;
return Path.Combine(basePath, relativePath);
}
private static string? FindPicoClient(ZipArchive archive)
{
return FindEntry(archive,
name: "MetaforcePico",
extension: ".apk");
}
private static string? FindOculusClient(ZipArchive archive)
{
return FindEntry(archive,
name: "MetaforceOculus",
extension: ".apk");
}
private static string? FindAndroidAdmin(ZipArchive archive)
{
return FindEntry(archive,
name: "MetaforceAdmin",
extension: ".apk");
}
private static string? FindAndroidContent(ZipArchive archive)
{
return FindEntry(archive,
name: "Content_Android",
extension: ".zip");
}
private static string? FindWindowsContent(ZipArchive archive)
{
return FindEntry(archive,
name: "Content_StandaloneWindows",
extension: ".zip");
}
private static string? FindPcAdmin(ZipArchive archive)
{
return FindExecutable(archive, "MetaforceAdminPC");
}
private static string? FindServer(ZipArchive archive)
{
return FindExecutable(archive, "MetaforceServer");
}
/// <summary>
/// Finds an entry in archive by name and extension
/// </summary>
private static string? FindEntry(ZipArchive archive, string name, string extension)
{
var entry = archive.Entries.FirstOrDefault(e =>
e.Name.Contains(name, StringComparison.OrdinalIgnoreCase) &&
e.Name.EndsWith(extension, StringComparison.OrdinalIgnoreCase));
return entry?.FullName;
}
/// <summary>
/// Finds an executable in archive, excluding crash handlers
/// </summary>
private static string? FindExecutable(ZipArchive archive, string containsName)
{
var entry = archive.Entries.FirstOrDefault(e =>
e.FullName.Contains(containsName, StringComparison.OrdinalIgnoreCase) &&
e.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) &&
!e.Name.Contains("UnityCrashHandler", StringComparison.OrdinalIgnoreCase) &&
!e.Name.Contains("crashpad_handler", StringComparison.OrdinalIgnoreCase));
return entry?.FullName;
}
}

View file

@ -1,7 +1,6 @@
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using MetaforceInstaller.UI.Windows;
namespace MetaforceInstaller.UI; namespace MetaforceInstaller.UI;
@ -14,7 +13,6 @@ public partial class App : Application
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
Lang.Resources.Culture = System.Globalization.CultureInfo.CurrentCulture;
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
desktop.MainWindow = new MainWindow(); desktop.MainWindow = new MainWindow();

View file

@ -1,251 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace MetaforceInstaller.UI.Lang {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MetaforceInstaller.UI.Lang.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Add new installation.
/// </summary>
public static string AddInstallation {
get {
return ResourceManager.GetString("AddInstallation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cancel.
/// </summary>
public static string CancelButton {
get {
return ResourceManager.GetString("CancelButton", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Choose .zip.
/// </summary>
public static string ChooseZip {
get {
return ResourceManager.GetString("ChooseZip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connected to.
/// </summary>
public static string ConnectedTo {
get {
return ResourceManager.GetString("ConnectedTo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete.
/// </summary>
public static string Delete {
get {
return ResourceManager.GetString("Delete", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install admin.
/// </summary>
public static string InstallAdminCheckbox {
get {
return ResourceManager.GetString("InstallAdminCheckbox", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install Android admin.
/// </summary>
public static string InstallAndroidAdmin {
get {
return ResourceManager.GetString("InstallAndroidAdmin", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install.
/// </summary>
public static string InstallButton {
get {
return ResourceManager.GetString("InstallButton", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install server.
/// </summary>
public static string InstallServerCheckbox {
get {
return ResourceManager.GetString("InstallServerCheckbox", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install VR client.
/// </summary>
public static string InstallVRClient {
get {
return ResourceManager.GetString("InstallVRClient", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Launch PC admin.
/// </summary>
public static string LaunchPCAdmin {
get {
return ResourceManager.GetString("LaunchPCAdmin", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Launch server.
/// </summary>
public static string LaunchServer {
get {
return ResourceManager.GetString("LaunchServer", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name of new installation.
/// </summary>
public static string NameOfNewInstallation {
get {
return ResourceManager.GetString("NameOfNewInstallation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Couldn&apos;t find Android content.
/// </summary>
public static string NoAndroidContentError {
get {
return ResourceManager.GetString("NoAndroidContentError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Couldn&apos;t find directory with PC admin.
/// </summary>
public static string NoPCAdminError {
get {
return ResourceManager.GetString("NoPCAdminError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Couldn&apos;t find directory with server.
/// </summary>
public static string NoServerError {
get {
return ResourceManager.GetString("NoServerError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not connected.
/// </summary>
public static string NotConnected {
get {
return ResourceManager.GetString("NotConnected", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Couldn&apos;t find any VR clients.
/// </summary>
public static string NoVRClientsError {
get {
return ResourceManager.GetString("NoVRClientsError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Couldn&apos;t find Windows content.
/// </summary>
public static string NoWindowsContentError {
get {
return ResourceManager.GetString("NoWindowsContentError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save Android admin.
/// </summary>
public static string SaveAndroidAdminCheckbox {
get {
return ResourceManager.GetString("SaveAndroidAdminCheckbox", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save VR client.
/// </summary>
public static string SaveVRClientCheckbox {
get {
return ResourceManager.GetString("SaveVRClientCheckbox", resourceCulture);
}
}
}
}

View file

@ -1,84 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="LaunchServer" xml:space="preserve">
<value>Launch server</value>
</data>
<data name="LaunchPCAdmin" xml:space="preserve">
<value>Launch PC admin</value>
</data>
<data name="InstallVRClient" xml:space="preserve">
<value>Install VR client</value>
</data>
<data name="InstallAndroidAdmin" xml:space="preserve">
<value>Install Android admin</value>
</data>
<data name="Delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="AddInstallation" xml:space="preserve">
<value>Add new installation</value>
</data>
<data name="NotConnected" xml:space="preserve">
<value>Not connected</value>
</data>
<data name="ConnectedTo" xml:space="preserve">
<value>Connected to</value>
</data>
<data name="NameOfNewInstallation" xml:space="preserve">
<value>Name of new installation</value>
</data>
<data name="ChooseZip" xml:space="preserve">
<value>Choose .zip</value>
</data>
<data name="InstallServerCheckbox" xml:space="preserve">
<value>Install server</value>
</data>
<data name="InstallAdminCheckbox" xml:space="preserve">
<value>Install admin</value>
</data>
<data name="SaveAndroidAdminCheckbox" xml:space="preserve">
<value>Save Android admin</value>
</data>
<data name="SaveVRClientCheckbox" xml:space="preserve">
<value>Save VR client</value>
</data>
<data name="InstallButton" xml:space="preserve">
<value>Install</value>
</data>
<data name="CancelButton" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="NoServerError" xml:space="preserve">
<value>Couldn't find directory with server</value>
</data>
<data name="NoPCAdminError" xml:space="preserve">
<value>Couldn't find directory with PC admin</value>
</data>
<data name="NoWindowsContentError" xml:space="preserve">
<value>Couldn't find Windows content</value>
</data>
<data name="NoAndroidContentError" xml:space="preserve">
<value>Couldn't find Android content</value>
</data>
<data name="NoVRClientsError" xml:space="preserve">
<value>Couldn't find any VR clients</value>
</data>
</root>

View file

@ -1,77 +0,0 @@
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="LaunchServer" xml:space="preserve">
<value>Запустить сервер</value>
</data>
<data name="LaunchPCAdmin" xml:space="preserve">
<value>Запустить админку</value>
</data>
<data name="Delete" xml:space="preserve">
<value>Удалить</value>
</data>
<data name="InstallAndroidAdmin" xml:space="preserve">
<value>Установить андроид-админку</value>
</data>
<data name="InstallVRClient" xml:space="preserve">
<value>Установить на шлем</value>
</data>
<data name="AddInstallation" xml:space="preserve">
<value>Добавить версию</value>
</data>
<data name="ConnectedTo" xml:space="preserve">
<value>Подключено:</value>
</data>
<data name="NotConnected" xml:space="preserve">
<value>Не подключено</value>
</data>
<data name="ChooseZip" xml:space="preserve">
<value>Выбрать .zip</value>
</data>
<data name="CancelButton" xml:space="preserve">
<value>Отмена</value>
</data>
<data name="InstallAdminCheckbox" xml:space="preserve">
<value>Установить админку</value>
</data>
<data name="InstallButton" xml:space="preserve">
<value>Установить</value>
</data>
<data name="InstallServerCheckbox" xml:space="preserve">
<value>Установить сервер</value>
</data>
<data name="NameOfNewInstallation" xml:space="preserve">
<value>Название версии</value>
</data>
<data name="SaveAndroidAdminCheckbox" xml:space="preserve">
<value>Сохранить андроид-админку</value>
</data>
<data name="SaveVRClientCheckbox" xml:space="preserve">
<value>Сохранить VR-клиенты</value>
</data>
<data name="NoAndroidContentError" xml:space="preserve">
<value>Не удалось обнаружить Андроид контент</value>
</data>
<data name="NoPCAdminError" xml:space="preserve">
<value>Не удалось обнаружить ПК-админку</value>
</data>
<data name="NoServerError" xml:space="preserve">
<value>Не удалось обнаружить сервер</value>
</data>
<data name="NoVRClientsError" xml:space="preserve">
<value>Не удалось обнаружить VR-клиенты</value>
</data>
<data name="NoWindowsContentError" xml:space="preserve">
<value>Не удалось обнаружить Windows контент</value>
</data>
</root>

View file

@ -0,0 +1,70 @@
<Window 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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MetaforceInstaller.UI.MainWindow"
Title="MetaforceInstaller">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<SvgImage x:Key="Logo" Source="/Images/logo_red.svg"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SvgImage x:Key="Logo" Source="/Images/logo_red.svg"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Main Content Area -->
<Grid Grid.Row="0" Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" /> <!-- 30% -->
<ColumnDefinition Width="7*" /> <!-- 70% -->
</Grid.ColumnDefinitions>
<!-- Left Panel - Buttons -->
<StackPanel Grid.Column="0" Margin="20" Spacing="15">
<Button Name="ChooseApkButton" Content="Choose .apk"
HorizontalAlignment="Stretch" />
<Button Name="ChooseContentButton" Content="Choose .zip"
HorizontalAlignment="Stretch" />
<Button Name="InstallButton" Content="Install"
HorizontalAlignment="Stretch" />
</StackPanel>
<Image Grid.Column="0"
Margin="20"
VerticalAlignment="Bottom"
Source="{DynamicResource Logo}"
HorizontalAlignment="Center">
</Image>
<!-- Right Panel - Logs -->
<TextBox Grid.Column="1" Name="LogsTextBox"
IsReadOnly="True"
AcceptsReturn="True"
TextWrapping="Wrap"
FontFamily="Consolas,Courier New,monospace"
Focusable="False"
Margin="4,0,0,0" />
</Grid>
<!-- Progress Bar at Bottom -->
<ProgressBar Grid.Row="1" Name="InstallProgressBar"
Height="20" Minimum="0" Maximum="100" Value="0"
Margin="8" />
</Grid>
</Window>

View file

@ -0,0 +1,230 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Avalonia.VisualTree;
using MetaforceInstaller.Core.Models;
using MetaforceInstaller.Core.Services;
namespace MetaforceInstaller.UI;
public partial class MainWindow : Window
{
private string? _apkPath;
private string? _zipPath;
private AdbService _adbService;
private const int PROGRESS_LOG_STEP = 10;
private const int PROGRESS_UPDATE_STEP = 1;
private int _lastLoggedProgress = -1;
private int _lastUpdatedProgress = -1;
public MainWindow()
{
InitializeComponent();
LogMessage("MetaforceInstaller by slavagm");
_adbService = new AdbService();
_adbService.ProgressChanged += OnAdbProgressChanged;
_adbService.StatusChanged += OnAdbStatusChanged;
CheckAndEnableInstallButton();
ChooseApkButton.Click += OnChooseApkClicked;
ChooseContentButton.Click += OnChooseContentClicked;
InstallButton.Click += OnInstallClicked;
}
private void OnAdbProgressChanged(object? sender, ProgressInfo e)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
if (e.PercentageComplete != _lastUpdatedProgress &&
e.PercentageComplete % PROGRESS_UPDATE_STEP == 0)
{
InstallProgressBar.Value = e.PercentageComplete;
_lastUpdatedProgress = e.PercentageComplete;
}
if (e.PercentageComplete != _lastLoggedProgress &&
e.PercentageComplete % PROGRESS_LOG_STEP == 0 || e.PercentageComplete == 100)
{
LogMessage(
e.TotalBytes > 0
? $"Прогресс: {e.PercentageComplete}% ({FormatBytes(e.BytesTransferred)} / {FormatBytes(e.TotalBytes)})"
: $"Прогресс: {e.PercentageComplete}%");
_lastLoggedProgress = e.PercentageComplete;
}
});
}
private void OnProgressReport(ProgressInfo progressInfo)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
if (progressInfo.PercentageComplete != _lastUpdatedProgress &&
progressInfo.PercentageComplete % PROGRESS_UPDATE_STEP == 0)
{
InstallProgressBar.Value = progressInfo.PercentageComplete;
_lastUpdatedProgress = progressInfo.PercentageComplete;
}
if (progressInfo.PercentageComplete != _lastLoggedProgress &&
(progressInfo.PercentageComplete % PROGRESS_LOG_STEP == 0 || progressInfo.PercentageComplete == 100))
{
LogMessage(
progressInfo.TotalBytes > 0
? $"Прогресс: {progressInfo.PercentageComplete}% ({FormatBytes(progressInfo.BytesTransferred)} / {FormatBytes(progressInfo.TotalBytes)})"
: $"Прогресс: {progressInfo.PercentageComplete}%");
_lastLoggedProgress = progressInfo.PercentageComplete;
}
});
}
private void OnAdbStatusChanged(object? sender, string e)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
InstallProgressBar.Value = 0;
_lastLoggedProgress = -1;
_lastUpdatedProgress = -1;
LogMessage(e);
});
}
private string FormatBytes(long bytes)
{
string[] suffixes = ["B", "KB", "MB", "GB", "TB"];
var counter = 0;
double number = bytes;
while (Math.Round(number / 1024) >= 1)
{
number /= 1024;
counter++;
}
return $"{number:N1} {suffixes[counter]}";
}
private async void CheckAndEnableInstallButton()
{
InstallButton.IsEnabled = !string.IsNullOrEmpty(_apkPath) && !string.IsNullOrEmpty(_zipPath);
}
private async void OnChooseApkClicked(object? sender, RoutedEventArgs e)
{
var topLevel = GetTopLevel(this);
var files = await topLevel!.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Выберите APK файл",
AllowMultiple = false,
FileTypeFilter = new[]
{
new FilePickerFileType("APK Files")
{
Patterns = new[] { "*.apk" }
}
}
});
if (files.Count >= 1)
{
_apkPath = files[0].Path.LocalPath;
LogMessage($"APK выбран: {Path.GetFileName(_apkPath)}");
}
CheckAndEnableInstallButton();
}
private async void OnChooseContentClicked(object? sender, RoutedEventArgs e)
{
var topLevel = GetTopLevel(this);
var files = await topLevel!.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Выберите архив с контентом",
AllowMultiple = false,
FileTypeFilter = new[]
{
new FilePickerFileType("ZIP Files")
{
Patterns = new[] { "*.zip" }
}
}
});
if (files.Count >= 1)
{
_zipPath = files[0].Path.LocalPath;
LogMessage($"Контент выбран: {Path.GetFileName(_zipPath)}");
}
CheckAndEnableInstallButton();
}
private async void OnInstallClicked(object? sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(_apkPath) || string.IsNullOrEmpty(_zipPath))
{
LogMessage("Ошибка: Выберите APK файл и папку с контентом");
return;
}
_adbService.RefreshDeviceData();
InstallButton.IsEnabled = false;
InstallProgressBar.Value = 0;
try
{
LogMessage("Начинаем установку...");
var deviceInfo = _adbService.GetDeviceInfo();
LogMessage($"Найдено устройство: {deviceInfo.SerialNumber}");
LogMessage($"Состояние: {deviceInfo.State}");
LogMessage($"Модель: {deviceInfo.Model} - {deviceInfo.Name}");
var progress = new Progress<ProgressInfo>(OnProgressReport);
await _adbService.InstallApkAsync(_apkPath, progress);
var apkInfo = ApkScrapper.GetApkInfo(_apkPath);
LogMessage($"Ставим {apkInfo.PackageName} версии {apkInfo.VersionName}");
var zipName = Path.GetFileName(_zipPath);
var outputPath =
@$"/storage/emulated/0/Android/data/{apkInfo.PackageName}/files/{zipName}";
LogMessage($"Начинаем копирование контента в {outputPath}");
await _adbService.CopyFileAsync(_zipPath, outputPath, progress);
LogMessage("Установка завершена успешно!");
}
catch (Exception ex)
{
LogMessage($"Ошибка установки: {ex.Message}");
}
finally
{
InstallButton.IsEnabled = true;
InstallProgressBar.Value = 0;
}
}
private void LogMessage(string message)
{
var timestamp = DateTime.Now.ToString("HH:mm:ss");
LogsTextBox.Text += $"[{timestamp}] {message}\n";
var scrollViewer = LogsTextBox.FindAncestorOfType<ScrollViewer>();
scrollViewer?.ScrollToEnd();
}
}

View file

@ -8,7 +8,7 @@
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<FileVersion>2.0.0-b1</FileVersion> <FileVersion>1.2.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -34,19 +34,4 @@
<AvaloniaResource Include="Images\logo_red.svg" /> <AvaloniaResource Include="Images\logo_red.svg" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Lang\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Lang\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project> </Project>

View file

@ -1,65 +0,0 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Media;
using MetaforceInstaller.Core.Models;
namespace MetaforceInstaller.UI.ViewModels;
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public ObservableCollection<InstallationData> Installations { get; set; } = new();
private bool _isDeviceConnected;
public bool IsDeviceConnected
{
get => _isDeviceConnected;
set
{
if (_isDeviceConnected != value)
{
_isDeviceConnected = value;
OnPropertyChanged();
OnPropertyChanged(nameof(StatusColor));
OnPropertyChanged(nameof(StatusText));
}
}
}
private string _deviceSerial = string.Empty;
public string DeviceSerial
{
get => _deviceSerial;
set
{
if (_deviceSerial != value)
{
_deviceSerial = value;
OnPropertyChanged();
OnPropertyChanged(nameof(StatusText));
}
}
}
public IBrush StatusColor => IsDeviceConnected ? Brushes.Green : Brushes.Red;
public string StatusText => IsDeviceConnected ? $"{Lang.Resources.ConnectedTo} {_deviceSerial}" : Lang.Resources.NotConnected;
public void LoadInstallations(IEnumerable<InstallationData> data)
{
Installations.Clear();
foreach (var installation in data)
{
Installations.Add(installation);
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View file

@ -1,89 +0,0 @@
<Window 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:vm="using:MetaforceInstaller.UI.ViewModels"
xmlns:lang="using:MetaforceInstaller.UI.Lang"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:CompileBindings="True"
x:DataType="vm:MainWindowViewModel"
x:Class="MetaforceInstaller.UI.Windows.MainWindow"
Title="MetaforceInstaller">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<SvgImage x:Key="Logo" Source="/Images/logo_red.svg" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SvgImage x:Key="Logo" Source="/Images/logo_red.svg" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Window.Resources>
<Design.DataContext>
<vm:MainWindowViewModel />
</Design.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="96" />
<RowDefinition Height="*" />
<RowDefinition Height="24" />
</Grid.RowDefinitions>
<Image Grid.Row="0"
Margin="20"
VerticalAlignment="Center"
Source="{DynamicResource Logo}"
HorizontalAlignment="Left">
</Image>
<Button Grid.Row="0" Name="NewInstallationButton" Margin="20" VerticalAlignment="Center"
HorizontalAlignment="Right">
<Button.Content>
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="+" />
<TextBlock Text="{x:Static lang:Resources.AddInstallation}" />
</StackPanel>
</Button.Content>
</Button>
<ScrollViewer Grid.Row="1">
<ItemsControl ItemsSource="{Binding Installations}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Title}" Margin="16, 8" HorizontalAlignment="Stretch">
<StackPanel Orientation="Vertical">
<Label Content="{Binding Id, StringFormat='ID: {0}'}" />
<Label Content="{Binding InstalledAt, StringFormat='Installed: {0}'}" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Name="LaunchServerButton" Click="OnLaunchServerClick"
Content="{x:Static lang:Resources.LaunchServer}" />
<Button Name="LaunchPcAdminButton" Click="OnLaunchAdminClick"
Content="{x:Static lang:Resources.LaunchPCAdmin}" />
<Button Name="InstallVrClientButton"
Content="{x:Static lang:Resources.InstallVRClient}" />
<Button Name="InstallAndroidAdminButton"
Content="{x:Static lang:Resources.InstallAndroidAdmin}" />
<Button Name="DeleteButton" Click="OnDeleteInstallationClick"
Content="{x:Static lang:Resources.Delete}" />
</StackPanel>
</StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<DockPanel Grid.Row="2">
<Border Name="StatusCircle" Margin="4" Width="16" Height="16" Background="{Binding StatusColor}"
CornerRadius="50" HorizontalAlignment="Left" />
<Label Name="SerialNumberLabel" Content="{Binding StatusText}" />
<Label Name="VersionLabel" HorizontalAlignment="Right" VerticalAlignment="Center" />
</DockPanel>
</Grid>
</Window>

View file

@ -1,150 +0,0 @@
using System;
using System.Diagnostics;
using System.Reflection;
using AdvancedSharpAdbClient.Models;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using MetaforceInstaller.Core.Intefaces;
using MetaforceInstaller.Core.Models;
using MetaforceInstaller.Core.Services;
using MetaforceInstaller.UI.ViewModels;
namespace MetaforceInstaller.UI.Windows;
public partial class MainWindow : Window
{
private MainWindowViewModel _viewModel;
private IStorageService _storageService;
private AdbService _adbService;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainWindowViewModel();
_storageService = new StorageService();
_adbService = new AdbService();
DataContext = _viewModel;
VersionLabel.Content = Assembly.GetExecutingAssembly()
.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version;
NewInstallationButton.Click += OnNewInstalltionClick;
_adbService.DeviceConnected += OnAdbDeviceConnected;
_adbService.DeviceDisconnected += OnAdbDeviceDisconnected;
_adbService.DeviceChanged += OnAdbDeviceChanged;
LoadInstallations();
UpdateDeviceStatus();
}
private void UpdateDeviceStatus()
{
var isConnected = _adbService.IsDeviceConnected;
_viewModel.IsDeviceConnected = isConnected;
if (isConnected)
{
try
{
var deviceInfo = _adbService.GetDeviceInfo();
_viewModel.DeviceSerial = deviceInfo.SerialNumber;
}
catch
{
_viewModel.DeviceSerial = "Unknown";
}
}
else
{
_viewModel.DeviceSerial = string.Empty;
}
}
private void OnAdbDeviceConnected(object? sender, DeviceDataEventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
UpdateDeviceStatus();
});
}
private void OnAdbDeviceDisconnected(object? sender, DeviceDataEventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
UpdateDeviceStatus();
});
}
private void OnAdbDeviceChanged(object? sender, DeviceDataEventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
UpdateDeviceStatus();
});
}
private void LoadInstallations()
{
var appData = _storageService.Load();
_viewModel.LoadInstallations(appData.Installations);
}
public async void OnNewInstalltionClick(object? sender, RoutedEventArgs e)
{
var newInstallationDialog = new NewInstallationDialog(_storageService);
await newInstallationDialog.ShowDialog<NewInstallationDialog>(this);
LoadInstallations();
}
public async void OnDeleteInstallationClick(object? sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is InstallationData installationData)
{
var name = installationData.Title;
Console.WriteLine($"Delete {name}");
}
}
public async void OnLaunchServerClick(object? sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is InstallationData installationData)
{
var exePath = installationData.Parts.WindowsServerPath;
var processInfo = new ProcessStartInfo
{
FileName = exePath,
UseShellExecute = false,
};
Process.Start(processInfo);
}
}
public async void OnLaunchAdminClick(object? sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is InstallationData installationData)
{
var exePath = installationData.Parts.WindowsAdminPath;
var processInfo = new ProcessStartInfo
{
FileName = exePath,
UseShellExecute = false,
};
Process.Start(processInfo);
}
}
protected override void OnClosed(EventArgs e)
{
_adbService.DeviceConnected -= OnAdbDeviceConnected;
_adbService.DeviceDisconnected -= OnAdbDeviceDisconnected;
_adbService.DeviceChanged -= OnAdbDeviceChanged;
_adbService.Dispose();
base.OnClosed(e);
}
}

View file

@ -1,28 +0,0 @@
<Window 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:lang="clr-namespace:MetaforceInstaller.UI.Lang"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="400"
x:Class="MetaforceInstaller.UI.Windows.NewInstallationDialog"
Title="MetaforceInstaller - Add new installation"
SizeToContent="WidthAndHeight"
CanResize="False">
<StackPanel Margin="24" Spacing="12">
<Label FontSize="36" Content="{x:Static lang:Resources.AddInstallation}" />
<TextBox Name="TitleTextBox" Watermark="{x:Static lang:Resources.NameOfNewInstallation}" />
<Button Name="ChooseZip" Content="{x:Static lang:Resources.ChooseZip}" />
<CheckBox Name="ServerCheckBox" Content="{x:Static lang:Resources.InstallServerCheckbox}" />
<CheckBox Name="PcAdminCheckBox" Content="{x:Static lang:Resources.InstallAdminCheckbox}" />
<CheckBox Name="AndroidAdminCheckbox" Content="{x:Static lang:Resources.SaveAndroidAdminCheckbox}" />
<CheckBox Name="VrClientCheckbox" Content="{x:Static lang:Resources.SaveVRClientCheckbox}" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Spacing="8">
<Button Name="InstallButton" Content="{x:Static lang:Resources.InstallButton}" />
<Button Name="CancelButton" Content="{x:Static lang:Resources.CancelButton}" />
</StackPanel>
<ProgressBar Name="ProgressBar" Minimum="0" Maximum="100" Value="0" />
</StackPanel>
</Window>

View file

@ -1,164 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using MetaforceInstaller.Core;
using MetaforceInstaller.Core.Intefaces;
using MetaforceInstaller.Core.Models;
using MetaforceInstaller.Core.Services;
namespace MetaforceInstaller.UI.Windows;
public partial class NewInstallationDialog : Window
{
private string? _zipPath;
private InstallationParts? _installationParts;
private readonly IStorageService _storageService;
public NewInstallationDialog(IStorageService storageService)
{
InitializeComponent();
_storageService = storageService;
RefreshCheckboxes();
CancelButton.Click += OnCancelClick;
ChooseZip.Click += OnChooseZipClick;
InstallButton.IsEnabled = false;
InstallButton.Click += OnInstallClick;
}
private void RefreshCheckboxes()
{
var serverCheckbox = ServerCheckBox;
var pcAdminCheckbox = PcAdminCheckBox;
var androidAdminCheckbox = AndroidAdminCheckbox;
var vrClientCheckbox = VrClientCheckbox;
serverCheckbox.Content = Lang.Resources.InstallServerCheckbox;
serverCheckbox.IsEnabled = true;
pcAdminCheckbox.Content = Lang.Resources.InstallAdminCheckbox;
pcAdminCheckbox.IsEnabled = true;
androidAdminCheckbox.Content = Lang.Resources.SaveAndroidAdminCheckbox;
androidAdminCheckbox.IsEnabled = true;
vrClientCheckbox.Content = Lang.Resources.SaveVRClientCheckbox;
vrClientCheckbox.IsEnabled = true;
}
private async void OnChooseZipClick(object? sender, RoutedEventArgs e)
{
var topLevel = GetTopLevel(this);
var files = await topLevel!.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Выберите архив с контентом",
AllowMultiple = false,
FileTypeFilter =
[
new FilePickerFileType("ZIP Files")
{
Patterns = ["*.zip"]
}
]
});
if (files.Count >= 1)
{
_zipPath = files[0].Path.LocalPath;
using var archive = ZipFile.OpenRead(_zipPath);
_installationParts = ZipScrapper.PeekFiles(archive);
UpdateCheckboxes();
archive.Dispose();
}
}
private async void OnInstallClick(object? sender, RoutedEventArgs e)
{
using var archive = ZipFile.OpenRead(_zipPath);
var title = TitleTextBox.Text ?? Path.GetFileNameWithoutExtension(_zipPath);
var installationGuid = Guid.NewGuid();
var progress = new Progress<double>(value => { ProgressBar.Value = value; });
string extractedPath = null;
await Task.Run(() =>
{
extractedPath = ZipScrapper.ExtractZip(
archive,
Defaults.StoragePath,
installationGuid,
progress);
});
InstallButton.IsEnabled = false;
var appData = _storageService.Load();
var updatedParts = ZipScrapper.UpdatePathsAfterExtraction(_installationParts, extractedPath);
var installationData = new InstallationData
{
Id = installationGuid,
Title = title,
Parts = updatedParts
};
appData.Installations.Add(installationData);
_storageService.Save(appData);
Close();
}
private void UpdateCheckboxes()
{
RefreshCheckboxes();
var serverCheckbox = ServerCheckBox;
var pcAdminCheckbox = PcAdminCheckBox;
var androidAdminCheckbox = AndroidAdminCheckbox;
var vrClientCheckbox = VrClientCheckbox;
if (string.IsNullOrEmpty(_installationParts.WindowsServerPath))
{
serverCheckbox.IsEnabled = false;
serverCheckbox.Content += $"\n{Lang.Resources.NoServerError}";
}
if (string.IsNullOrEmpty(_installationParts.WindowsAdminPath))
{
pcAdminCheckbox.IsEnabled = false;
pcAdminCheckbox.Content += $"\n{Lang.Resources.NoPCAdminError}";
}
if (string.IsNullOrEmpty(_installationParts.WindowsContentPath))
{
pcAdminCheckbox.IsEnabled = false;
pcAdminCheckbox.Content += $"\n{Lang.Resources.NoWindowsContentError}";
}
if (string.IsNullOrEmpty(_installationParts.AndroidContentPath))
{
vrClientCheckbox.IsEnabled = false;
vrClientCheckbox.Content += $"\n{Lang.Resources.NoAndroidContentError}";
androidAdminCheckbox.IsEnabled = false;
androidAdminCheckbox.Content += $"\n{Lang.Resources.NoAndroidContentError}";
}
if (string.IsNullOrEmpty(_installationParts.PicoClientPath) &&
string.IsNullOrEmpty(_installationParts.OculusClientPath))
{
vrClientCheckbox.IsEnabled = false;
vrClientCheckbox.Content += $"\n{Lang.Resources.NoVRClientsError}";
}
InstallButton.IsEnabled = new List<CheckBox?>
{ serverCheckbox, pcAdminCheckbox, androidAdminCheckbox, vrClientCheckbox }.Any(x => x?.IsEnabled == true);
}
private void OnCancelClick(object? sender, RoutedEventArgs e)
{
Close();
}
}