Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
| 520bcb7965 |
21 changed files with 312 additions and 1238 deletions
|
|
@ -1,7 +0,0 @@
|
|||
namespace MetaforceInstaller.Core;
|
||||
|
||||
public static class Defaults
|
||||
{
|
||||
public static readonly string StoragePath =
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + "MetaforceInstaller";
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
using MetaforceInstaller.Core.Models;
|
||||
|
||||
namespace MetaforceInstaller.Core.Intefaces;
|
||||
|
||||
public interface IStorageService
|
||||
{
|
||||
AppData Load();
|
||||
void Save(AppData data);
|
||||
}
|
||||
|
|
@ -7,26 +7,20 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="adb\adb.exe"/>
|
||||
<EmbeddedResource Include="adb\AdbWinApi.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Link>AdbWinApi.dll</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="adb\AdbWinUsbApi.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Link>AdbWinUsbApi.dll</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="adb\adb.exe" />
|
||||
<EmbeddedResource Include="adb\AdbWinApi.dll" />
|
||||
<EmbeddedResource Include="adb\AdbWinUsbApi.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AlphaOmega.ApkReader" Version="2.0.5"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0"/>
|
||||
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AlphaOmega.ApkReader" Version="2.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3"/>
|
||||
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
namespace MetaforceInstaller.Core.Models;
|
||||
|
||||
public class AppData
|
||||
{
|
||||
public List<InstallationData> Installations { get; set; } = new();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -15,15 +15,9 @@ public class AdbService : IAdbService
|
|||
private readonly ILogger<AdbService> _logger;
|
||||
private readonly AdbClient _adbClient;
|
||||
private DeviceData _deviceData;
|
||||
private DeviceMonitor? _deviceMonitor;
|
||||
|
||||
public event EventHandler<ProgressInfo>? ProgressChanged;
|
||||
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)
|
||||
{
|
||||
|
|
@ -33,46 +27,6 @@ public class AdbService : IAdbService
|
|||
var serverStatus = server.StartServer(adbPath, restartServerIfNewer: false);
|
||||
_adbClient = new AdbClient();
|
||||
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()
|
||||
|
|
@ -249,15 +203,4 @@ public class AdbService : IAdbService
|
|||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using MetaforceInstaller.UI.Windows;
|
||||
|
||||
namespace MetaforceInstaller.UI;
|
||||
|
||||
|
|
@ -14,7 +13,6 @@ public partial class App : Application
|
|||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
Lang.Resources.Culture = System.Globalization.CultureInfo.CurrentCulture;
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow();
|
||||
|
|
|
|||
251
MetaforceInstaller.UI/Lang/Resources.Designer.cs
generated
251
MetaforceInstaller.UI/Lang/Resources.Designer.cs
generated
|
|
@ -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't find Android content.
|
||||
/// </summary>
|
||||
public static string NoAndroidContentError {
|
||||
get {
|
||||
return ResourceManager.GetString("NoAndroidContentError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Couldn'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'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'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'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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
70
MetaforceInstaller.UI/MainWindow.axaml
Normal file
70
MetaforceInstaller.UI/MainWindow.axaml
Normal 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>
|
||||
230
MetaforceInstaller.UI/MainWindow.axaml.cs
Normal file
230
MetaforceInstaller.UI/MainWindow.axaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<FileVersion>2.0.0-b1</FileVersion>
|
||||
<FileVersion>1.2.1</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -34,19 +34,4 @@
|
|||
<AvaloniaResource Include="Images\logo_red.svg" />
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue