Compare commits
No commits in common. "2c3f19d7cec87340b5af98f86057e53ec17feeae" and "ab49284f2fe06d6efb64e71efeee162fb609631b" have entirely different histories.
2c3f19d7ce
...
ab49284f2f
42 changed files with 1922 additions and 328 deletions
24
MetaforceInstaller.Cli/MetaforceInstaller.Cli.csproj
Normal file
24
MetaforceInstaller.Cli/MetaforceInstaller.Cli.csproj
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AdvancedSharpAdbClient" Version="3.4.14" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
133
MetaforceInstaller.Cli/Program.cs
Normal file
133
MetaforceInstaller.Cli/Program.cs
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
using MetaforceInstaller.Cli.Utils;
|
||||||
|
using MetaforceInstaller.Core.Services;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.Cli;
|
||||||
|
|
||||||
|
static class Program
|
||||||
|
{
|
||||||
|
static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var installationRequest = ArgumentParser.ParseArguments(args);
|
||||||
|
|
||||||
|
if (installationRequest is null ||
|
||||||
|
string.IsNullOrEmpty(installationRequest.ApkPath) ||
|
||||||
|
string.IsNullOrEmpty(installationRequest.ZipPath))
|
||||||
|
{
|
||||||
|
ShowUsage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var adbService = new AdbService();
|
||||||
|
|
||||||
|
var apkInfo = ApkScrapper.GetApkInfo(installationRequest.ApkPath);
|
||||||
|
var zipName = Path.GetFileName(installationRequest.ZipPath);
|
||||||
|
var outputPath =
|
||||||
|
@$"/storage/emulated/0/Android/data/{apkInfo.PackageName}/files/{zipName}";
|
||||||
|
|
||||||
|
// Подписка на события прогресса
|
||||||
|
adbService.ProgressChanged += OnProgressChanged;
|
||||||
|
adbService.StatusChanged += OnStatusChanged;
|
||||||
|
|
||||||
|
// Получение информации об устройстве
|
||||||
|
var deviceInfo = adbService.GetDeviceInfo();
|
||||||
|
Console.WriteLine($"Найдено устройство: {deviceInfo.SerialNumber}");
|
||||||
|
Console.WriteLine($"Состояние: {deviceInfo.State}");
|
||||||
|
Console.WriteLine($"Модель: {deviceInfo.Model} - {deviceInfo.Name}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Создание объекта для отслеживания прогресса
|
||||||
|
var progress = new Progress<MetaforceInstaller.Core.Models.ProgressInfo>(OnProgressReport);
|
||||||
|
|
||||||
|
// Установка APK
|
||||||
|
await adbService.InstallApkAsync(installationRequest.ApkPath, progress);
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Копирование файла
|
||||||
|
await adbService.CopyFileAsync(installationRequest.ZipPath, outputPath, progress);
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
Console.WriteLine("Операция завершена успешно!");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Ошибка: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnProgressChanged(object? sender, MetaforceInstaller.Core.Models.ProgressInfo e)
|
||||||
|
{
|
||||||
|
DrawProgressBar(e.PercentageComplete, e.BytesTransferred, e.TotalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnStatusChanged(object? sender, string e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnProgressReport(MetaforceInstaller.Core.Models.ProgressInfo progressInfo)
|
||||||
|
{
|
||||||
|
if (progressInfo.TotalBytes > 0)
|
||||||
|
{
|
||||||
|
DrawProgressBar(progressInfo.PercentageComplete, progressInfo.BytesTransferred, progressInfo.TotalBytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Для случаев без информации о байтах (например, установка APK)
|
||||||
|
DrawProgressBar(progressInfo.PercentageComplete, 0, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ShowUsage()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Использование:");
|
||||||
|
Console.WriteLine(
|
||||||
|
" MetaforceInstaller.exe --apk <путь_к_apk> --content <путь_к_zip> --output <путь_для контента>");
|
||||||
|
Console.WriteLine(" MetaforceInstaller.exe -a <путь_к_apk> -c <путь_к_zip> -o <путь_для_контента>");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Параметры:");
|
||||||
|
Console.WriteLine(" --apk, -a Путь к APK файлу");
|
||||||
|
Console.WriteLine(" --content, -c Путь к ZIP файлу с контентом");
|
||||||
|
Console.WriteLine(" --output, -o Путь для копирования контента");
|
||||||
|
Console.WriteLine(" --help, -h Показать эту справку");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Пример:");
|
||||||
|
Console.WriteLine(
|
||||||
|
" MetaforceInstaller.exe --apk \"C:\\app.apk\" --content \"C:\\data.zip\" --output \"/sdcard/data.zip\"");
|
||||||
|
Console.WriteLine(" MetaforceInstaller.exe -a app.apk -c data.zip -o /sdcard/data.zip");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawProgressBar(int progress, long receivedBytes, long totalBytes)
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(0, Console.CursorTop);
|
||||||
|
|
||||||
|
var barLength = 40;
|
||||||
|
var filledLength = (int)(barLength * progress / 100.0);
|
||||||
|
|
||||||
|
var bar = "[" + new string('█', filledLength) + new string('░', barLength - filledLength) + "]";
|
||||||
|
|
||||||
|
string bytesText = "";
|
||||||
|
if (totalBytes > 0)
|
||||||
|
{
|
||||||
|
bytesText = $" {FormatBytes(receivedBytes)} / {FormatBytes(totalBytes)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.Write($"\r{bar} {progress}%{bytesText}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static 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]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
53
MetaforceInstaller.Cli/Utils/ArgumentParser.cs
Normal file
53
MetaforceInstaller.Cli/Utils/ArgumentParser.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.Cli.Utils;
|
||||||
|
|
||||||
|
public static class ArgumentParser
|
||||||
|
{
|
||||||
|
public static InstallationRequest? ParseArguments(string[] args)
|
||||||
|
{
|
||||||
|
var result = new InstallationRequest();
|
||||||
|
|
||||||
|
for (var i = 0; i < args.Length; i++)
|
||||||
|
{
|
||||||
|
switch (args[i].ToLower())
|
||||||
|
{
|
||||||
|
case "--apk":
|
||||||
|
case "-a":
|
||||||
|
if (i + 1 < args.Length)
|
||||||
|
{
|
||||||
|
result.ApkPath = args[i + 1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "--content":
|
||||||
|
case "-c":
|
||||||
|
if (i + 1 < args.Length)
|
||||||
|
{
|
||||||
|
result.ZipPath = args[i + 1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "--output":
|
||||||
|
case "-o":
|
||||||
|
if (i + 1 < args.Length)
|
||||||
|
{
|
||||||
|
result.OutputPath = args[i + 1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "--help":
|
||||||
|
case "-h":
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
using MetaforceInstaller.Cloud.Models;
|
|
||||||
|
|
||||||
namespace MetaforceInstaller.Cloud;
|
|
||||||
|
|
||||||
public interface ICloudService
|
|
||||||
{
|
|
||||||
Task<List<CloudObject>> GetObjects(string webLink);
|
|
||||||
}
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace MetaforceInstaller.Cloud.Implementations.MailRu.DTO;
|
|
||||||
|
|
||||||
public sealed class CloudObjectsResponse
|
|
||||||
{
|
|
||||||
[JsonPropertyName("count")]
|
|
||||||
public CountInfo? Count { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public string? Name { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("weblink")]
|
|
||||||
public string? WebLink { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("size")]
|
|
||||||
public long? Size { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("rev")]
|
|
||||||
public long? Rev { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("kind")]
|
|
||||||
public string? Kind { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("type")]
|
|
||||||
public string? Type { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("public")]
|
|
||||||
public PublicInfo? Public { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("list")]
|
|
||||||
public List<CloudObjectBase>? List { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("owner")]
|
|
||||||
public OwnerInfo? Owner { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CountInfo
|
|
||||||
{
|
|
||||||
[JsonPropertyName("folders")]
|
|
||||||
public int? Folders { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("files")]
|
|
||||||
public int? Files { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class PublicInfo
|
|
||||||
{
|
|
||||||
[JsonPropertyName("type")]
|
|
||||||
public string? Type { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public string? Name { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("id")]
|
|
||||||
public string? Id { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("ctime")]
|
|
||||||
public long? CTime { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("views")]
|
|
||||||
public long? Views { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("downloads")]
|
|
||||||
public long? Downloads { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class OwnerInfo
|
|
||||||
{
|
|
||||||
[JsonPropertyName("email")]
|
|
||||||
public string? Email { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("user_flags")]
|
|
||||||
public UserFlags? UserFlags { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class UserFlags
|
|
||||||
{
|
|
||||||
[JsonPropertyName("PAID_ACCOUNT")]
|
|
||||||
public bool? PaidAccount { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Базовый тип элемента из "list". Конкретный тип выбирается по полю "type".
|
|
||||||
/// </summary>
|
|
||||||
[JsonConverter(typeof(CloudObjectJsonConverter))]
|
|
||||||
public abstract class CloudObjectBase
|
|
||||||
{
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public string? Name { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("weblink")]
|
|
||||||
public string? WebLink { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("size")]
|
|
||||||
public long? Size { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("kind")]
|
|
||||||
public string? Kind { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("type")]
|
|
||||||
public string? Type { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CloudFolderObject : CloudObjectBase
|
|
||||||
{
|
|
||||||
[JsonPropertyName("count")]
|
|
||||||
public CountInfo? Count { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("rev")]
|
|
||||||
public long? Rev { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CloudFileObject : CloudObjectBase
|
|
||||||
{
|
|
||||||
[JsonPropertyName("mtime")]
|
|
||||||
public long? MTime { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("hash")]
|
|
||||||
public string? Hash { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ZipWebLinkRequest
|
|
||||||
{
|
|
||||||
[JsonPropertyName("x-email")]
|
|
||||||
public string XEmail { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("weblink_list")]
|
|
||||||
public IReadOnlyList<string> WeblinkList { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public string Name { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ZipWebLinkResponse
|
|
||||||
{
|
|
||||||
[JsonPropertyName("key")]
|
|
||||||
public string Key { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Конвертер, который смотрит на поле "type" и десериализует в CloudFolderObject или CloudFileObject.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class CloudObjectJsonConverter : JsonConverter<CloudObjectBase>
|
|
||||||
{
|
|
||||||
public override CloudObjectBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
using var doc = JsonDocument.ParseValue(ref reader);
|
|
||||||
|
|
||||||
var root = doc.RootElement;
|
|
||||||
if (!root.TryGetProperty("type", out var typeProp))
|
|
||||||
throw new JsonException("Missing required property 'type' for cloud object.");
|
|
||||||
|
|
||||||
var type = typeProp.GetString();
|
|
||||||
|
|
||||||
return type switch
|
|
||||||
{
|
|
||||||
"folder" => root.Deserialize<CloudFolderObject>(options)
|
|
||||||
?? throw new JsonException("Failed to deserialize folder object."),
|
|
||||||
"file" => root.Deserialize<CloudFileObject>(options)
|
|
||||||
?? throw new JsonException("Failed to deserialize file object."),
|
|
||||||
_ => throw new JsonException($"Unknown cloud object type '{type}'.")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, CloudObjectBase value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
switch (value)
|
|
||||||
{
|
|
||||||
case CloudFolderObject folder:
|
|
||||||
JsonSerializer.Serialize(writer, folder, options);
|
|
||||||
break;
|
|
||||||
case CloudFileObject file:
|
|
||||||
JsonSerializer.Serialize(writer, file, options);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new JsonException($"Unknown runtime type '{value.GetType().Name}'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Web;
|
|
||||||
using MetaforceInstaller.Cloud.Implementations.MailRu.DTO;
|
|
||||||
using MetaforceInstaller.Cloud.Models;
|
|
||||||
|
|
||||||
namespace MetaforceInstaller.Cloud.Implementations.MailRu;
|
|
||||||
|
|
||||||
public class MailRuCloudService : ICloudService
|
|
||||||
{
|
|
||||||
private const string BaseUrl = "https://cloud.mail.ru";
|
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
||||||
{
|
|
||||||
PropertyNameCaseInsensitive = true
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public MailRuCloudService(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
_httpClient = httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<CloudObject>> GetObjects(string webLink)
|
|
||||||
{
|
|
||||||
var dto = await GetFromPublicApi<CloudObjectsResponse>(
|
|
||||||
path: "/api/v4/public/list",
|
|
||||||
query: new Dictionary<string, string?>
|
|
||||||
{
|
|
||||||
["weblink"] = webLink,
|
|
||||||
["sort"] = "name",
|
|
||||||
["order"] = "asc",
|
|
||||||
["offset"] = "0",
|
|
||||||
["limit"] = "500",
|
|
||||||
["version"] = "4"
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (dto?.List is null || dto.List.Count == 0)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var result = new List<CloudObject>(dto.List.Count);
|
|
||||||
foreach (var entry in dto.List)
|
|
||||||
result.Add(Map(entry));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string?> GetDownloadLink(IEnumerable<string> webLinks, string outputFileName = "archive")
|
|
||||||
{
|
|
||||||
var result = await PostToPublicApi<ZipWebLinkResponse>(
|
|
||||||
path: "/api/v3/zip/weblink",
|
|
||||||
query: new Dictionary<string, string?>(),
|
|
||||||
new ZipWebLinkRequest
|
|
||||||
{
|
|
||||||
XEmail = "anonym",
|
|
||||||
WeblinkList = webLinks.ToList(),
|
|
||||||
Name = outputFileName
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return result?.Key;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<TResponse?> GetFromPublicApi<TResponse>(string path, IReadOnlyDictionary<string, string?> query)
|
|
||||||
{
|
|
||||||
var uri = BuildUri(path, query);
|
|
||||||
|
|
||||||
using var response = await _httpClient.GetAsync(uri).ConfigureAwait(false);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
return JsonSerializer.Deserialize<TResponse>(json, JsonOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<TResponse?> PostToPublicApi<TResponse>(string path, IReadOnlyDictionary<string, string?> query,
|
|
||||||
object body)
|
|
||||||
{
|
|
||||||
var uri = BuildUri(path, query);
|
|
||||||
|
|
||||||
var jsonBody = JsonSerializer.Serialize(body, JsonOptions);
|
|
||||||
using var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
|
|
||||||
|
|
||||||
using var response = await _httpClient.PostAsync(uri, content).ConfigureAwait(false);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
return JsonSerializer.Deserialize<TResponse>(json, JsonOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Uri BuildUri(string path, IReadOnlyDictionary<string, string?> query)
|
|
||||||
{
|
|
||||||
var builder = new UriBuilder(BaseUrl)
|
|
||||||
{
|
|
||||||
Port = -1,
|
|
||||||
Path = path
|
|
||||||
};
|
|
||||||
|
|
||||||
var qs = HttpUtility.ParseQueryString(builder.Query);
|
|
||||||
foreach (var (key, value) in query)
|
|
||||||
qs[key] = value;
|
|
||||||
|
|
||||||
builder.Query = qs.ToString();
|
|
||||||
return builder.Uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CloudObject Map(CloudObjectBase entry)
|
|
||||||
{
|
|
||||||
return new CloudObject
|
|
||||||
{
|
|
||||||
Name = entry.Name,
|
|
||||||
WebLink = entry.WebLink,
|
|
||||||
Type = entry.Type == "folder" ? CloudObjectType.Folder : CloudObjectType.File
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
namespace MetaforceInstaller.Cloud.Models;
|
|
||||||
|
|
||||||
public class CloudObject
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string WebLink { get; set; }
|
|
||||||
public CloudObjectType Type { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum CloudObjectType
|
|
||||||
{
|
|
||||||
Folder,
|
|
||||||
File
|
|
||||||
}
|
|
||||||
7
MetaforceInstaller.Core/Defaults.cs
Normal file
7
MetaforceInstaller.Core/Defaults.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace MetaforceInstaller.Core;
|
||||||
|
|
||||||
|
public static class Defaults
|
||||||
|
{
|
||||||
|
public static readonly string StoragePath =
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + "MetaforceInstaller";
|
||||||
|
}
|
||||||
18
MetaforceInstaller.Core/Intefaces/IAdbService.cs
Normal file
18
MetaforceInstaller.Core/Intefaces/IAdbService.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.Core.Intefaces;
|
||||||
|
|
||||||
|
public interface IAdbService
|
||||||
|
{
|
||||||
|
event EventHandler<ProgressInfo>? ProgressChanged;
|
||||||
|
event EventHandler<string>? StatusChanged;
|
||||||
|
|
||||||
|
Task InstallApkAsync(string apkPath, IProgress<ProgressInfo>? progress = null, CancellationToken cancellationToken = default);
|
||||||
|
Task CopyFileAsync(string localPath, string remotePath, IProgress<ProgressInfo>? progress = null, CancellationToken cancellationToken = default);
|
||||||
|
DeviceInfo GetDeviceInfo();
|
||||||
|
|
||||||
|
// Синхронные версии для обратной совместимости
|
||||||
|
void InstallApk(string apkPath);
|
||||||
|
void CopyFile(string localPath, string remotePath);
|
||||||
|
}
|
||||||
9
MetaforceInstaller.Core/Intefaces/IStorageService.cs
Normal file
9
MetaforceInstaller.Core/Intefaces/IStorageService.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.Core.Intefaces;
|
||||||
|
|
||||||
|
public interface IStorageService
|
||||||
|
{
|
||||||
|
AppData Load();
|
||||||
|
void Save(AppData data);
|
||||||
|
}
|
||||||
32
MetaforceInstaller.Core/MetaforceInstaller.Core.csproj
Normal file
32
MetaforceInstaller.Core/MetaforceInstaller.Core.csproj
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</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>
|
||||||
|
</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"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
3
MetaforceInstaller.Core/Models/ApkInfo.cs
Normal file
3
MetaforceInstaller.Core/Models/ApkInfo.cs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
namespace MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
public record ApkInfo(string PackageName, string VersionName, string VersionCode);
|
||||||
6
MetaforceInstaller.Core/Models/AppData.cs
Normal file
6
MetaforceInstaller.Core/Models/AppData.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
public class AppData
|
||||||
|
{
|
||||||
|
public List<InstallationData> Installations { get; set; } = new();
|
||||||
|
}
|
||||||
8
MetaforceInstaller.Core/Models/DeviceInfo.cs
Normal file
8
MetaforceInstaller.Core/Models/DeviceInfo.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
public record DeviceInfo(
|
||||||
|
string SerialNumber,
|
||||||
|
string State,
|
||||||
|
string Model,
|
||||||
|
string Name
|
||||||
|
);
|
||||||
9
MetaforceInstaller.Core/Models/InstallationData.cs
Normal file
9
MetaforceInstaller.Core/Models/InstallationData.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
12
MetaforceInstaller.Core/Models/InstallationParts.cs
Normal file
12
MetaforceInstaller.Core/Models/InstallationParts.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
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; }
|
||||||
|
}
|
||||||
8
MetaforceInstaller.Core/Models/InstallationRequest.cs
Normal file
8
MetaforceInstaller.Core/Models/InstallationRequest.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
public class InstallationRequest
|
||||||
|
{
|
||||||
|
public string ApkPath { get; set; }
|
||||||
|
public string ZipPath { get; set; }
|
||||||
|
public string OutputPath { get; set; }
|
||||||
|
}
|
||||||
19
MetaforceInstaller.Core/Models/ProgressInfo.cs
Normal file
19
MetaforceInstaller.Core/Models/ProgressInfo.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
namespace MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
public class ProgressInfo
|
||||||
|
{
|
||||||
|
public int PercentageComplete { get; set; }
|
||||||
|
public long BytesTransferred { get; set; }
|
||||||
|
public long TotalBytes { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public string? CurrentFile { get; set; }
|
||||||
|
public ProgressType Type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ProgressType
|
||||||
|
{
|
||||||
|
Installation,
|
||||||
|
FileCopy,
|
||||||
|
Extraction,
|
||||||
|
General
|
||||||
|
}
|
||||||
263
MetaforceInstaller.Core/Services/AdbService.cs
Normal file
263
MetaforceInstaller.Core/Services/AdbService.cs
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using AdvancedSharpAdbClient;
|
||||||
|
using AdvancedSharpAdbClient.DeviceCommands;
|
||||||
|
using AdvancedSharpAdbClient.Models;
|
||||||
|
using AdvancedSharpAdbClient.Receivers;
|
||||||
|
using MetaforceInstaller.Core.Intefaces;
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.Core.Services;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_logger = logger ?? new NullLogger<AdbService>();
|
||||||
|
var adbPath = GetAdbPath();
|
||||||
|
var server = new AdbServer();
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
var devices = _adbClient.GetDevices();
|
||||||
|
_deviceData = devices.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExtractResource(string resourceName, string outputPath)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Extracting resource: {resourceName} to {outputPath}");
|
||||||
|
using var stream = Assembly.GetAssembly(typeof(AdbService)).GetManifestResourceStream(resourceName);
|
||||||
|
using var fileStream = File.Create(outputPath);
|
||||||
|
stream.CopyTo(fileStream);
|
||||||
|
_logger.LogInformation($"Resource extracted: {resourceName} to {outputPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetAdbPath()
|
||||||
|
{
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), "MetaforceInstaller", "adb");
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
|
||||||
|
var adbPath = Path.Combine(tempDir, "adb.exe");
|
||||||
|
|
||||||
|
if (File.Exists(adbPath)) return adbPath;
|
||||||
|
ExtractResource("MetaforceInstaller.Core.adb.adb.exe", adbPath);
|
||||||
|
ExtractResource("MetaforceInstaller.Core.adb.AdbWinApi.dll", Path.Combine(tempDir, "AdbWinApi.dll"));
|
||||||
|
ExtractResource("MetaforceInstaller.Core.adb.AdbWinUsbApi.dll", Path.Combine(tempDir, "AdbWinUsbApi.dll"));
|
||||||
|
|
||||||
|
return adbPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnProgressChanged(ProgressInfo progressInfo)
|
||||||
|
{
|
||||||
|
ProgressChanged?.Invoke(this, progressInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStatusChanged(string status)
|
||||||
|
{
|
||||||
|
StatusChanged?.Invoke(this, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstallApk(string apkPath)
|
||||||
|
{
|
||||||
|
InstallApkAsync(apkPath).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InstallApkAsync(string apkPath, IProgress<ProgressInfo>? progress = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!File.Exists(apkPath))
|
||||||
|
{
|
||||||
|
_logger.LogCritical("Error: Could not find APK file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnStatusChanged("Начинаем установку APK...");
|
||||||
|
_logger.LogInformation($"Installing APK: {apkPath}");
|
||||||
|
|
||||||
|
progress?.Report(new ProgressInfo
|
||||||
|
{
|
||||||
|
PercentageComplete = 0,
|
||||||
|
Message = "Подготовка к установке APK...",
|
||||||
|
Type = ProgressType.Installation,
|
||||||
|
CurrentFile = Path.GetFileName(apkPath)
|
||||||
|
});
|
||||||
|
|
||||||
|
var packageManager = new PackageManager(_adbClient, _deviceData);
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
packageManager.InstallPackage(apkPath, installProgress =>
|
||||||
|
{
|
||||||
|
var progressInfo = new ProgressInfo
|
||||||
|
{
|
||||||
|
PercentageComplete = (int)installProgress.UploadProgress,
|
||||||
|
Message = $"Установка APK: {installProgress.UploadProgress:F1}%",
|
||||||
|
Type = ProgressType.Installation,
|
||||||
|
CurrentFile = Path.GetFileName(apkPath)
|
||||||
|
};
|
||||||
|
|
||||||
|
progress?.Report(progressInfo);
|
||||||
|
OnProgressChanged(progressInfo);
|
||||||
|
});
|
||||||
|
}, cancellationToken);
|
||||||
|
|
||||||
|
OnStatusChanged("APK успешно установлен!");
|
||||||
|
_logger.LogInformation("APK successfully installed!");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogCritical($"Error: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyFile(string localPath, string remotePath)
|
||||||
|
{
|
||||||
|
CopyFileAsync(localPath, remotePath).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CopyFileAsync(string localPath, string remotePath, IProgress<ProgressInfo>? progress = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!File.Exists(localPath))
|
||||||
|
{
|
||||||
|
_logger.LogCritical($"Error: Could not find file: {localPath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnStatusChanged("Начинаем копирование файла...");
|
||||||
|
_logger.LogInformation($"Copying file: {localPath} to {remotePath}");
|
||||||
|
|
||||||
|
var fileInfo = new FileInfo(localPath);
|
||||||
|
|
||||||
|
progress?.Report(new ProgressInfo
|
||||||
|
{
|
||||||
|
PercentageComplete = 0,
|
||||||
|
Message = "Подготовка к копированию файла...",
|
||||||
|
Type = ProgressType.FileCopy,
|
||||||
|
CurrentFile = Path.GetFileName(localPath),
|
||||||
|
TotalBytes = fileInfo.Length
|
||||||
|
});
|
||||||
|
|
||||||
|
var remoteDir = Path.GetDirectoryName(remotePath)?.Replace('\\', '/');
|
||||||
|
if (!string.IsNullOrEmpty(remoteDir))
|
||||||
|
{
|
||||||
|
var reciever = new ConsoleOutputReceiver();
|
||||||
|
await Task.Run(
|
||||||
|
() => { _adbClient.ExecuteRemoteCommand($"mkdir -p \"{remoteDir}\"", _deviceData, reciever); },
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"Ensured remote directory: {remoteDir}");
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
using var fileStream = File.OpenRead(localPath);
|
||||||
|
var syncService = new SyncService(_adbClient, _deviceData);
|
||||||
|
|
||||||
|
syncService.Push(fileStream, remotePath, UnixFileStatus.DefaultFileMode, DateTime.Now,
|
||||||
|
copyProgress =>
|
||||||
|
{
|
||||||
|
var progressInfo = new ProgressInfo
|
||||||
|
{
|
||||||
|
PercentageComplete = (int)copyProgress.ProgressPercentage,
|
||||||
|
BytesTransferred = copyProgress.ReceivedBytesSize,
|
||||||
|
TotalBytes = copyProgress.TotalBytesToReceive,
|
||||||
|
Message = $"Копирование: {copyProgress.ProgressPercentage:F1}%",
|
||||||
|
Type = ProgressType.FileCopy,
|
||||||
|
CurrentFile = Path.GetFileName(localPath)
|
||||||
|
};
|
||||||
|
|
||||||
|
progress?.Report(progressInfo);
|
||||||
|
OnProgressChanged(progressInfo);
|
||||||
|
});
|
||||||
|
}, cancellationToken);
|
||||||
|
|
||||||
|
OnStatusChanged("Файл успешно скопирован!");
|
||||||
|
_logger.LogInformation("File successfully copied!");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogCritical($"Error: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceInfo GetDeviceInfo()
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
MetaforceInstaller.Core/Services/ApkScrapper.cs
Normal file
19
MetaforceInstaller.Core/Services/ApkScrapper.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
using AlphaOmega.Debug;
|
||||||
|
using MetaforceInstaller.Core.Models;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.Core.Services;
|
||||||
|
|
||||||
|
public static class ApkScrapper
|
||||||
|
{
|
||||||
|
public static ApkInfo GetApkInfo(string apkPath)
|
||||||
|
{
|
||||||
|
using var apk = new ApkFile(apkPath);
|
||||||
|
if (apk is { IsValid: true, AndroidManifest: not null })
|
||||||
|
{
|
||||||
|
return new ApkInfo(apk.AndroidManifest.Package, apk.AndroidManifest.VersionName,
|
||||||
|
apk.AndroidManifest.VersionCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Invalid APK file");
|
||||||
|
}
|
||||||
|
}
|
||||||
35
MetaforceInstaller.Core/Services/StorageService.cs
Normal file
35
MetaforceInstaller.Core/Services/StorageService.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
160
MetaforceInstaller.Core/Services/ZipScrapper.cs
Normal file
160
MetaforceInstaller.Core/Services/ZipScrapper.cs
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
MetaforceInstaller.Core/adb/AdbWinApi.dll
Normal file
BIN
MetaforceInstaller.Core/adb/AdbWinApi.dll
Normal file
Binary file not shown.
BIN
MetaforceInstaller.Core/adb/AdbWinUsbApi.dll
Normal file
BIN
MetaforceInstaller.Core/adb/AdbWinUsbApi.dll
Normal file
Binary file not shown.
BIN
MetaforceInstaller.Core/adb/adb.exe
Normal file
BIN
MetaforceInstaller.Core/adb/adb.exe
Normal file
Binary file not shown.
10
MetaforceInstaller.UI/App.axaml
Normal file
10
MetaforceInstaller.UI/App.axaml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="MetaforceInstaller.UI.App"
|
||||||
|
RequestedThemeVariant="Default">
|
||||||
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
<FluentTheme />
|
||||||
|
</Application.Styles>
|
||||||
|
</Application>
|
||||||
25
MetaforceInstaller.UI/App.axaml.cs
Normal file
25
MetaforceInstaller.UI/App.axaml.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using MetaforceInstaller.UI.Windows;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.UI;
|
||||||
|
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
Lang.Resources.Culture = System.Globalization.CultureInfo.CurrentCulture;
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
desktop.MainWindow = new MainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
MetaforceInstaller.UI/Images/logo_black.svg
Normal file
16
MetaforceInstaller.UI/Images/logo_black.svg
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="_Слой_2" data-name="Слой 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 678.69 65.86">
|
||||||
|
<g id="_Слой_1-2" data-name="Слой 1">
|
||||||
|
<g>
|
||||||
|
<polygon points="43.16 44.66 42.89 44.66 20.1 1.24 0 1.24 0 64.58 13.87 64.58 13.87 16.65 14.22 16.65 38.65 64.58 47.49 64.58 71.83 16.65 72.31 16.65 72.31 64.58 86.05 64.58 86.05 1.24 65.95 1.24 43.16 44.66"/>
|
||||||
|
<polygon points="97.93 64.58 159.2 64.58 159.2 51.99 111.8 51.99 111.8 39.45 129.1 39.45 129.1 36.26 155.85 36.26 155.85 24.95 129.1 24.95 129.1 28.13 111.8 28.13 111.8 13.82 159.2 13.82 159.2 1.24 97.93 1.24 97.93 64.58"/>
|
||||||
|
<polygon points="232.77 1.24 168.9 1.24 168.9 13.83 193.94 13.83 193.94 64.58 207.68 64.58 207.68 13.83 232.77 13.83 232.77 1.24"/>
|
||||||
|
<polygon points="254.69 1.24 223.77 64.58 239.15 64.58 263.04 14 263.4 14 287.47 64.58 302.75 64.58 271.79 1.24 254.69 1.24"/>
|
||||||
|
<path d="m445.01,9.28c-7.36-6.18-16.83-9.28-28.4-9.28s-21.06,3.09-28.45,9.28c-7.39,6.18-11.09,14.06-11.09,23.63s3.7,17.49,11.09,23.68c7.39,6.18,16.87,9.28,28.45,9.28s21.04-3.09,28.4-9.28c7.36-6.18,11.04-14.08,11.04-23.68s-3.68-17.45-11.04-23.63Zm-9.89,38.25c-4.8,3.83-10.97,5.74-18.51,5.74s-13.75-1.91-18.55-5.74c-4.83-3.86-7.24-8.73-7.24-14.62s2.41-10.78,7.24-14.58c4.8-3.8,10.98-5.7,18.55-5.7s13.68,1.9,18.51,5.7c4.8,3.8,7.2,8.66,7.2,14.58s-2.4,10.79-7.2,14.62Z"/>
|
||||||
|
<path d="m593.41,51.9c-4.39.91-8.58,1.37-12.59,1.37-8.54,0-15.45-1.78-20.72-5.34-5.33-3.56-8-8.39-8-14.49s2.59-10.76,7.77-14.8c5.15-4,12.09-6.01,20.81-6.01,8.13.03,14.83,1.4,20.1,4.11.03-.03.6-2.09,1.72-6.18,1.09-3.98,1.63-6.05,1.63-6.23-7.16-2.88-14.84-4.33-23.06-4.33-12.99,0-23.37,3.14-31.14,9.41-7.77,6.27-11.66,14.28-11.66,24.03s3.9,17.63,11.71,23.54c7.77,5.92,18.14,8.88,31.1,8.88,9.81,0,18.13-1.72,24.96-5.17l-2.25-12.1c-2.47,1.33-5.93,2.43-10.38,3.31Z"/>
|
||||||
|
<polygon points="307.02 31 311.21 31 311.21 64.58 325.08 64.58 325.08 42.4 367.23 42.4 367.23 31 320.89 31 320.89 13.82 367.23 13.82 367.23 1.24 307.02 1.24 307.02 31"/>
|
||||||
|
<path d="m530.77,21.99h0c.05-.53.08-1.07.08-1.62,0-5.86-2.48-10.52-7.43-13.96-5.01-3.44-11.5-5.16-19.48-5.16h-42.14v20.75h3.88v42.59h13.87V21.98h-3.88v-8.06l29.82-.11c3.27,0,5.98.77,8.12,2.31,2.09,1.52,3.15,3.49,3.19,5.86v.14c0,2.47-1.06,4.5-3.19,6.09-2.14,1.62-4.86,2.43-8.12,2.43h-20.14v11.23h12.62l17.95,22.7h16.18l-19.2-24.29c18.08-3.97,17.63-15.7,17.88-18.3Z"/>
|
||||||
|
<polygon points="678.69 13.82 678.69 1.24 616.36 1.24 616.36 28.21 621.5 28.21 621.5 39.45 616.36 39.45 616.36 64.58 678.69 64.58 678.69 51.99 630.23 51.99 630.23 39.45 674.27 39.45 674.27 28.13 630.23 28.13 630.23 13.82 678.69 13.82"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
31
MetaforceInstaller.UI/Images/logo_red.svg
Normal file
31
MetaforceInstaller.UI/Images/logo_red.svg
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 796.5 168.5" style="enable-background:new 0 0 796.5 168.5;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
.st1{fill:#E62027;}
|
||||||
|
</style>
|
||||||
|
<g id="Guides_For_Artboard">
|
||||||
|
</g>
|
||||||
|
<g id="Layer_1">
|
||||||
|
<rect class="st0" width="796.5" height="168.5"/>
|
||||||
|
<g>
|
||||||
|
<path class="st1" d="M478.5,42.2c-3,0-8.1,0.6-8.1,7.2v68.7c0,6.6,5.1,8,8.1,8c4.4,0,8.1-2.7,8.1-8V49.4
|
||||||
|
C486.6,44.1,482.9,42.2,478.5,42.2z"/>
|
||||||
|
<path class="st1" d="M332.6,39.4c1.2-2.2-0.4-4.8-2.9-4.8c-2.5,0-4.1,2.6-2.9,4.8l1.1,2.1l-6.1,63.1h14.6l-4.8-63.1L332.6,39.4z"
|
||||||
|
/>
|
||||||
|
<path class="st1" d="M0,0v168.5h796.5V0H0z M139.3,141.2h-14.4V76.5l-17.1,64.7H93.4L76.6,76.5v64.7H62.2V28.1h0.2h14.3h0.1
|
||||||
|
l23.9,86.5l24.1-86.5h0.1h14.3h0.2V141.2z M206.6,76.5l-4.2,13.7h-17.8v37.1h18.1l4.2,14h-36.9V28.1l36.9,0.3l-4.2,13.9h-18.1
|
||||||
|
v34.2H206.6z M278.2,42.3H264v98.9h-16.8V42.3h-14.1V28.4h45.1V42.3z M339.3,141.2l-1.6-22.8h-17.3l-2.4,22.8h-13.8l10.5-112h30.1
|
||||||
|
l10.4,112H339.3z M425.4,77.2l-4.2,13.7h-18v50.3h-14.7V28.4h36.9l-4.2,13.9h-18v34.9H425.4z M502.3,49.3v69v0.1
|
||||||
|
c0,14.9-7.8,21.8-23.6,21.8c-15.9,0-24-6.9-24-21.8v-0.1v-69v-0.1c0-14.9,8.2-21.8,24-21.8c15.9,0,23.6,6.9,23.6,21.8V49.3z
|
||||||
|
M566.8,141.2l-7.9-48.1c4.8-0.3,7.5-2.3,7.5-8.1V50.2c0-6.2-3.1-8-8.6-8h-8.1v99h-14.5V28.4h27c16.4,0,19.2,6.7,19.2,21.8V84
|
||||||
|
c0,7.5-2.2,12.9-6.7,16.4l-0.3,0.2l7.3,40.6H566.8z M660.8,75.8L645.5,71h-0.1V49.3c0-3.3-1.4-5.6-3.5-6.9
|
||||||
|
c-1.5-0.8-3.1-1.1-4.4-1.1c-2.5,0-8.2,1-8.2,8v69.1c0,2.8,0.9,4.6,2.2,5.9c1.9,1.7,4.4,2.1,5.9,2.1c3,0,8-1.4,8-8V96.4l15.3-4.5
|
||||||
|
v0.4l0.1,26.3c0,14.9-7.4,21.8-23.3,21.8c-11.4,0-18.4-3.6-21.5-11.1c-1.3-3-1.9-6.6-1.9-10.8V49.1c0-14.9,7.4-21.8,23.3-21.8
|
||||||
|
c10,0,16.7,2.8,20.2,8.6c2.1,3.4,3.2,7.8,3.2,13.3V75.8z M733.9,76.5l-4.2,13.7h-17.8v37.1h18.1l4.2,14h-36.9V28.4h36.9l-4.2,13.9
|
||||||
|
h-18.1v34.2H733.9z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2 KiB |
23
MetaforceInstaller.UI/Images/logo_white.svg
Normal file
23
MetaforceInstaller.UI/Images/logo_white.svg
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="_Слой_2" data-name="Слой 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 678.69 65.86">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="_Слой_1-2" data-name="Слой 1">
|
||||||
|
<g>
|
||||||
|
<polygon class="cls-1" points="43.16 44.66 42.89 44.66 20.1 1.24 0 1.24 0 64.58 13.87 64.58 13.87 16.65 14.22 16.65 38.65 64.58 47.49 64.58 71.83 16.65 72.31 16.65 72.31 64.58 86.05 64.58 86.05 1.24 65.95 1.24 43.16 44.66"/>
|
||||||
|
<polygon class="cls-1" points="97.93 64.58 159.2 64.58 159.2 51.99 111.8 51.99 111.8 39.45 129.1 39.45 129.1 36.26 155.85 36.26 155.85 24.95 129.1 24.95 129.1 28.13 111.8 28.13 111.8 13.82 159.2 13.82 159.2 1.24 97.93 1.24 97.93 64.58"/>
|
||||||
|
<polygon class="cls-1" points="232.77 1.24 168.9 1.24 168.9 13.83 193.94 13.83 193.94 64.58 207.68 64.58 207.68 13.83 232.77 13.83 232.77 1.24"/>
|
||||||
|
<polygon class="cls-1" points="254.69 1.24 223.77 64.58 239.15 64.58 263.04 14 263.4 14 287.47 64.58 302.75 64.58 271.79 1.24 254.69 1.24"/>
|
||||||
|
<path class="cls-1" d="m445.01,9.28c-7.36-6.18-16.83-9.28-28.4-9.28s-21.06,3.09-28.45,9.28c-7.39,6.18-11.09,14.06-11.09,23.63s3.7,17.49,11.09,23.68c7.39,6.18,16.87,9.28,28.45,9.28s21.04-3.09,28.4-9.28c7.36-6.18,11.04-14.08,11.04-23.68s-3.68-17.45-11.04-23.63Zm-9.89,38.25c-4.8,3.83-10.97,5.74-18.51,5.74s-13.75-1.91-18.55-5.74c-4.83-3.86-7.24-8.73-7.24-14.62s2.41-10.78,7.24-14.58c4.8-3.8,10.98-5.7,18.55-5.7s13.68,1.9,18.51,5.7c4.8,3.8,7.2,8.66,7.2,14.58s-2.4,10.79-7.2,14.62Z"/>
|
||||||
|
<path class="cls-1" d="m593.41,51.9c-4.39.91-8.58,1.37-12.59,1.37-8.54,0-15.45-1.78-20.72-5.34-5.33-3.56-8-8.39-8-14.49s2.59-10.76,7.77-14.8c5.15-4,12.09-6.01,20.81-6.01,8.13.03,14.83,1.4,20.1,4.11.03-.03.6-2.09,1.72-6.18,1.09-3.98,1.63-6.05,1.63-6.23-7.16-2.88-14.84-4.33-23.06-4.33-12.99,0-23.37,3.14-31.14,9.41-7.77,6.27-11.66,14.28-11.66,24.03s3.9,17.63,11.71,23.54c7.77,5.92,18.14,8.88,31.1,8.88,9.81,0,18.13-1.72,24.96-5.17l-2.25-12.1c-2.47,1.33-5.93,2.43-10.38,3.31Z"/>
|
||||||
|
<polygon class="cls-1" points="307.02 31 311.21 31 311.21 64.58 325.08 64.58 325.08 42.4 367.23 42.4 367.23 31 320.89 31 320.89 13.82 367.23 13.82 367.23 1.24 307.02 1.24 307.02 31"/>
|
||||||
|
<path class="cls-1" d="m530.77,21.99h0c.05-.53.08-1.07.08-1.62,0-5.86-2.48-10.52-7.43-13.96-5.01-3.44-11.5-5.16-19.48-5.16h-42.14v20.75h3.88v42.59h13.87V21.98h-3.88v-8.06l29.82-.11c3.27,0,5.98.77,8.12,2.31,2.09,1.52,3.15,3.49,3.19,5.86v.14c0,2.47-1.06,4.5-3.19,6.09-2.14,1.62-4.86,2.43-8.12,2.43h-20.14v11.23h12.62l17.95,22.7h16.18l-19.2-24.29c18.08-3.97,17.63-15.7,17.88-18.3Z"/>
|
||||||
|
<polygon class="cls-1" points="678.69 13.82 678.69 1.24 616.36 1.24 616.36 28.21 621.5 28.21 621.5 39.45 616.36 39.45 616.36 64.58 678.69 64.58 678.69 51.99 630.23 51.99 630.23 39.45 674.27 39.45 674.27 28.13 630.23 28.13 630.23 13.82 678.69 13.82"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
251
MetaforceInstaller.UI/Lang/Resources.Designer.cs
generated
Normal file
251
MetaforceInstaller.UI/Lang/Resources.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
MetaforceInstaller.UI/Lang/Resources.resx
Normal file
84
MetaforceInstaller.UI/Lang/Resources.resx
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?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>
|
||||||
77
MetaforceInstaller.UI/Lang/Resources.ru.resx
Normal file
77
MetaforceInstaller.UI/Lang/Resources.ru.resx
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
<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>
|
||||||
52
MetaforceInstaller.UI/MetaforceInstaller.UI.csproj
Normal file
52
MetaforceInstaller.UI/MetaforceInstaller.UI.csproj
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
|
<FileVersion>2.0.0-b1</FileVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="11.3.6"/>
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.6"/>
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.6"/>
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.6"/>
|
||||||
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.6">
|
||||||
|
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||||
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Svg.Controls.Skia.Avalonia" Version="11.3.0.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MetaforceInstaller.Core\MetaforceInstaller.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AvaloniaResource Include="Images\logo_black.svg" />
|
||||||
|
<AvaloniaResource Include="Images\logo_white.svg" />
|
||||||
|
<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>
|
||||||
21
MetaforceInstaller.UI/Program.cs
Normal file
21
MetaforceInstaller.UI/Program.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
using Avalonia;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MetaforceInstaller.UI;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||||
|
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||||
|
// yet and stuff might break.
|
||||||
|
[STAThread]
|
||||||
|
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||||
|
.StartWithClassicDesktopLifetime(args);
|
||||||
|
|
||||||
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
=> AppBuilder.Configure<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.WithInterFont()
|
||||||
|
.LogToTrace();
|
||||||
|
}
|
||||||
65
MetaforceInstaller.UI/ViewModels/MainWindowViewModel.cs
Normal file
65
MetaforceInstaller.UI/ViewModels/MainWindowViewModel.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
89
MetaforceInstaller.UI/Windows/MainWindow.axaml
Normal file
89
MetaforceInstaller.UI/Windows/MainWindow.axaml
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
<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>
|
||||||
150
MetaforceInstaller.UI/Windows/MainWindow.axaml.cs
Normal file
150
MetaforceInstaller.UI/Windows/MainWindow.axaml.cs
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml
Normal file
28
MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<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>
|
||||||
164
MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml.cs
Normal file
164
MetaforceInstaller.UI/Windows/NewInstallationDialog.axaml.cs
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
MetaforceInstaller.UI/app.manifest
Normal file
18
MetaforceInstaller.UI/app.manifest
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<!-- This manifest is used on Windows only.
|
||||||
|
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||||
|
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="MetaforceInstaller.UI.Desktop"/>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- A list of the Windows versions that this application has been tested on
|
||||||
|
and is designed to work with. Uncomment the appropriate elements
|
||||||
|
and Windows will automatically select the most compatible environment. -->
|
||||||
|
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
</assembly>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue