Загрузка данных


using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

namespace AlexSelectLauncher;

internal static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}

public sealed class MainForm : Form
{
private readonly TextBox keyBox = new();
private readonly Label status = new();
private readonly Label code = new();
private readonly Button activate = new();
private readonly Button install = new();

private const string ApiBase = "http://localhost:5255/api/activation";
public MainForm()
{
    Text = "ALEX SELECT — MOD BY ALEX";
    Width = 620;
    Height = 420;
    StartPosition = FormStartPosition.CenterScreen;
    BackColor = Color.FromArgb(16, 12, 24);
    ForeColor = Color.White;
    Font = new Font("Segoe UI", 10);
    var title = new Label
    {
        Text = "ALEX SELECT",
        Font = new Font("Segoe UI", 28, FontStyle.Bold),
        AutoSize = true,
        Location = new Point(35, 30)
    };
    var subtitle = new Label
    {
        Text = "GTA V • INSTALLER & LICENSE",
        AutoSize = true,
        Location = new Point(40, 82),
        ForeColor = Color.Plum
    };
    var keyLabel = new Label
    {
        Text = "Ключ активации",
        AutoSize = true,
        Location = new Point(40, 125)
    };
    keyBox.Location = new Point(40, 153);
    keyBox.Width = 520;
    keyBox.PlaceholderText = "XXXX-XXXX-XXXX";
    activate.Text = "АКТИВИРОВАТЬ";
    activate.Location = new Point(40, 200);
    activate.Width = 250;
    activate.Height = 42;
    activate.Click += async (_, _) => await ActivateAsync();
    install.Text = "УСТАНОВИТЬ";
    install.Location = new Point(310, 200);
    install.Width = 250;
    install.Height = 42;
    install.Enabled = false;
    install.Click += async (_, _) => await DownloadFilesAsync();
    status.Text = "Статус: не активирован";
    status.AutoSize = true;
    status.Location = new Point(40, 270);
    code.Text = "ONLINE CODE: —";
    code.AutoSize = true;
    code.Location = new Point(40, 305);
    code.Font = new Font("Consolas", 12, FontStyle.Bold);
    Controls.AddRange(
    [
        title,
        subtitle,
        keyLabel,
        keyBox,
        activate,
        install,
        status,
        code
    ]);
}
private async Task ActivateAsync()
{
    var key = keyBox.Text.Trim();
    if (string.IsNullOrWhiteSpace(key))
    {
        status.Text = "Статус: введите ключ";
        return;
    }
    activate.Enabled = false;
    status.Text = "Статус: проверяем ключ онлайн…";
    try
    {
        using var http = new HttpClient
        {
            Timeout = TimeSpan.FromSeconds(10)
        };
        var machineId = GetMachineId();
        var response = await http.PostAsJsonAsync(
            $"{ApiBase}/activate",
            new
            {
                key,
                machine_id = machineId
            });
        if (!response.IsSuccessStatusCode)
        {
            status.Text = "Статус: ключ недействителен или заблокирован";
            return;
        }
        var data =
            await response.Content.ReadFromJsonAsync<ActivationResponse>();
        if (data is null || !data.active)
        {
            status.Text = "Статус: активация отклонена";
            return;
        }
        status.Text =
            $"Статус: АКТИВИРОВАН • версия {data.version}";
        code.Text =
            $"ONLINE CODE: {data.online_code}";
        install.Enabled = true;
    }
    catch
    {
        status.Text = "Статус: нет связи с сервером";
    }
    finally
    {
        activate.Enabled = true;
    }
}
private async Task DownloadFilesAsync()
{
    using var dialog = new FolderBrowserDialog
    {
        Description = "Выберите папку для файлов ALEX SELECT"
    };
    if (dialog.ShowDialog() != DialogResult.OK)
        return;
    var folder = dialog.SelectedPath;
    const string publicUrl =
        "https://cloud.mail.ru/public/Aeig/Mi6K8oZZz/Setup%20GPF%20v2.2.part1.exe";
    var filePath =
        Path.Combine(folder, "Setup GPF v2.2.part1.exe");
    install.Enabled = false;
    status.Text = "Статус: получаем данные Mail.ru…";
    try
    {
        using var http = new HttpClient
        {
            Timeout = TimeSpan.FromMinutes(30)
        };
        var pageResponse = await http.GetAsync(publicUrl);
        pageResponse.EnsureSuccessStatusCode();
        var page = await pageResponse.Content.ReadAsStringAsync();
        string? directUrl = null;
        try
        {
            using var json = JsonDocument.Parse(page);
            directUrl = FindDownloadUrl(json.RootElement);
        }
        catch
        {
            // Если страница не является JSON,
            // ниже покажем понятную ошибку.
        }
        if (string.IsNullOrWhiteSpace(directUrl))
        {
            throw new Exception(
                "Mail.ru не передал прямую ссылку на файл. " +
                "Получена страница Cloud Mail.ru вместо файла.");
        }
        status.Text = "Статус: скачиваем настоящий файл…";
        using var response = await http.GetAsync(
            directUrl,
            HttpCompletionOption.ResponseHeadersRead);
        response.EnsureSuccessStatusCode();
        var totalBytes =
            response.Content.Headers.ContentLength;
        await using var input =
            await response.Content.ReadAsStreamAsync();
        await using var output =
            File.Create(filePath);
        var buffer = new byte[1024 * 1024];
        long downloaded = 0;
        int read;
        while ((read = await input.ReadAsync(buffer)) > 0)
        {
            await output.WriteAsync(buffer.AsMemory(0, read));
            downloaded += read;
            if (totalBytes.HasValue && totalBytes.Value > 0)
            {
                var percent =
                    downloaded * 100 / totalBytes.Value;
                var downloadedMb =
                    downloaded / 1024.0 / 1024.0;
                var totalMb =
                    totalBytes.Value / 1024.0 / 1024.0;
                status.Text =
                    $"Статус: скачивание {percent}% " +
                    $"({downloadedMb:F0} / {totalMb:F0} МБ)";
            }
        }
        var finalSize =
            new FileInfo(filePath).Length;
        status.Text = "Статус: файл скачан ✅";
        MessageBox.Show(
            $"Файл успешно скачан!\n\n" +
            $"Размер: {finalSize / 1024.0 / 1024.0:F1} МБ\n\n" +
            $"Путь:\n{filePath}",
            "ALEX SELECT",
            MessageBoxButtons.OK,
            MessageBoxIcon.Information);
    }
    catch (Exception ex)
    {
        status.Text = "Статус: ошибка скачивания";
        MessageBox.Show(
            $"Не удалось скачать файл.\n\n{ex.Message}",
            "ALEX SELECT",
            MessageBoxButtons.OK,
            MessageBoxIcon.Error);
    }
    finally
    {
        install.Enabled = true;
    }
}
private static string? FindDownloadUrl(JsonElement element)
{
    if (element.ValueKind == JsonValueKind.Object)
    {
        foreach (var property in element.EnumerateObject())
        {
            if (property.Name.Equals(
                "url",
                StringComparison.OrdinalIgnoreCase))
            {
                if (property.Value.ValueKind == JsonValueKind.String)
                {
                    var value = property.Value.GetString();
                    if (!string.IsNullOrWhiteSpace(value) &&
                        value.StartsWith("http", StringComparison.OrdinalIgnoreCase))
                    {
                        return value;
                    }
                }
            }
            var result = FindDownloadUrl(property.Value);
            if (!string.IsNullOrWhiteSpace(result))
                return result;
        }
    }
    else if (element.ValueKind == JsonValueKind.Array)
    {
        foreach (var item in element.EnumerateArray())
        {
            var result = FindDownloadUrl(item);
            if (!string.IsNullOrWhiteSpace(result))
                return result;
        }
    }
    return null;
}
private static string GetMachineId()
{
    var raw =
        $"{Environment.MachineName}|{Environment.UserName}|AlexSelect";
    return Convert.ToHexString(
        SHA256.HashData(
            Encoding.UTF8.GetBytes(raw)))[..32];
}
private sealed record ActivationResponse(
    bool active,
    string version,
    string online_code);

}
private const string ApiBase = "http://localhost:5255/api/activation";

public MainForm()
{
    Text = "ALEX SELECT — MOD BY ALEX";
    Width = 620;
    Height = 420;
    StartPosition = FormStartPosition.CenterScreen;
    BackColor = Color.FromArgb(16, 12, 24);
    ForeColor = Color.White;
    Font = new Font("Segoe UI", 10);

    var title = new Label
    {
        Text = "ALEX SELECT",
        Font = new Font("Segoe UI", 28, FontStyle.Bold),
        AutoSize = true,
        Location = new Point(35, 30)
    };

    var subtitle = new Label
    {
        Text = "GTA V • INSTALLER & LICENSE",
        AutoSize = true,
        Location = new Point(40, 82),
        ForeColor = Color.Plum
    };

    var keyLabel = new Label
    {
        Text = "Ключ активации",
        AutoSize = true,
        Location = new Point(40, 125)
    };

    keyBox.Location = new Point(40, 153);
    keyBox.Width = 520;
    keyBox.PlaceholderText = "XXXX-XXXX-XXXX";

    activate.Text = "АКТИВИРОВАТЬ";
    activate.Location = new Point(40, 200);
    activate.Width = 250;
    activate.Height = 42;
    activate.Click += async (_, _) => await ActivateAsync();

    install.Text = "УСТАНОВИТЬ";
    install.Location = new Point(310, 200);
    install.Width = 250;
    install.Height = 42;
    install.Enabled = false;
    install.Click += async (_, _) => await DownloadFilesAsync();

    status.Text = "Статус: не активирован";
    status.AutoSize = true;
    status.Location = new Point(40, 270);

    code.Text = "ONLINE CODE: —";
    code.AutoSize = true;
    code.Location = new Point(40, 305);
    code.Font = new Font("Consolas", 12, FontStyle.Bold);

    Controls.AddRange(
    [
        title,
        subtitle,
        keyLabel,
        keyBox,
        activate,
        install,
        status,
        code
    ]);
}

private async Task ActivateAsync()
{
    var key = keyBox.Text.Trim();

    if (string.IsNullOrWhiteSpace(key))
    {
        status.Text = "Статус: введите ключ";
        return;
    }

    activate.Enabled = false;
    status.Text = "Статус: проверяем ключ онлайн…";

    try
    {
        using var http = new HttpClient
        {
            Timeout = TimeSpan.FromSeconds(10)
        };

        var machineId = GetMachineId();

        var response = await http.PostAsJsonAsync(
            $"{ApiBase}/activate",
            new
            {
                key,
                machine_id = machineId
            });

        if (!response.IsSuccessStatusCode)
        {
            status.Text = "Статус: ключ недействителен или заблокирован";
            return;
        }

        var data =
            await response.Content.ReadFromJsonAsync<ActivationResponse>();

        if (data is null || !data.active)
        {
            status.Text = "Статус: активация отклонена";
            return;
        }

        status.Text =
            $"Статус: АКТИВИРОВАН • версия {data.version}";

        code.Text =
            $"ONLINE CODE: {data.online_code}";

        install.Enabled = true;
    }
    catch
    {
        status.Text = "Статус: нет связи с сервером";
    }
    finally
    {
        activate.Enabled = true;
    }
}

private async Task DownloadFilesAsync()
{
    using var dialog = new FolderBrowserDialog
    {
        Description = "Выберите папку для файлов ALEX SELECT"
    };

    if (dialog.ShowDialog() != DialogResult.OK)
        return;

    var folder = dialog.SelectedPath;

    const string publicUrl =
        "https://cloud.mail.ru/public/Aeig/Mi6K8oZZz/Setup%20GPF%20v2.2.part1.exe";

    var filePath =
        Path.Combine(folder, "Setup GPF v2.2.part1.exe");

    install.Enabled = false;
    status.Text = "Статус: получаем данные Mail.ru…";

    try
    {
        using var http = new HttpClient
        {
            Timeout = TimeSpan.FromMinutes(30)
        };

        var pageResponse = await http.GetAsync(publicUrl);

        pageResponse.EnsureSuccessStatusCode();

        var page = await pageResponse.Content.ReadAsStringAsync();

        string? directUrl = null;

        try
        {
            using var json = JsonDocument.Parse(page);

            directUrl = FindDownloadUrl(json.RootElement);
        }
        catch
        {
            // Если страница не является JSON,
            // ниже покажем понятную ошибку.
        }

        if (string.IsNullOrWhiteSpace(directUrl))
        {
            throw new Exception(
                "Mail.ru не передал прямую ссылку на файл. " +
                "Получена страница Cloud Mail.ru вместо файла.");
        }

        status.Text = "Статус: скачиваем настоящий файл…";

        using var response = await http.GetAsync(
            directUrl,
            HttpCompletionOption.ResponseHeadersRead);

        response.EnsureSuccessStatusCode();

        var totalBytes =
            response.Content.Headers.ContentLength;

        await using var input =
            await response.Content.ReadAsStreamAsync();

        await using var output =
            File.Create(filePath);

        var buffer = new byte[1024 * 1024];
        long downloaded = 0;
        int read;

        while ((read = await input.ReadAsync(buffer)) > 0)
        {
            await output.WriteAsync(buffer.AsMemory(0, read));

            downloaded += read;

            if (totalBytes.HasValue && totalBytes.Value > 0)
            {
                var percent =
                    downloaded * 100 / totalBytes.Value;

                var downloadedMb =
                    downloaded / 1024.0 / 1024.0;

                var totalMb =
                    totalBytes.Value / 1024.0 / 1024.0;

                status.Text =
                    $"Статус: скачивание {percent}% " +
                    $"({downloadedMb:F0} / {totalMb:F0} МБ)";
            }
        }

        var finalSize =
            new FileInfo(filePath).Length;

        status.Text = "Статус: файл скачан ✅";

        MessageBox.Show(
            $"Файл успешно скачан!\n\n" +
            $"Размер: {finalSize / 1024.0 / 1024.0:F1} МБ\n\n" +
            $"Путь:\n{filePath}",
            "ALEX SELECT",
            MessageBoxButtons.OK,
            MessageBoxIcon.Information);
    }
    catch (Exception ex)
    {
        status.Text = "Статус: ошибка скачивания";

        MessageBox.Show(
            $"Не удалось скачать файл.\n\n{ex.Message}",
            "ALEX SELECT",
            MessageBoxButtons.OK,
            MessageBoxIcon.Error);
    }
    finally
    {
        install.Enabled = true;
    }
}

private static string? FindDownloadUrl(JsonElement element)
{
    if (element.ValueKind == JsonValueKind.Object)
    {
        foreach (var property in element.EnumerateObject())
        {
            if (property.Name.Equals(
                "url",
                StringComparison.OrdinalIgnoreCase))
            {
                if (property.Value.ValueKind == JsonValueKind.String)
                {
                    var value = property.Value.GetString();

                    if (!string.IsNullOrWhiteSpace(value) &&
                        value.StartsWith("http", StringComparison.OrdinalIgnoreCase))
                    {
                        return value;
                    }
                }
            }

            var result = FindDownloadUrl(property.Value);

            if (!string.IsNullOrWhiteSpace(result))
                return result;
        }
    }
    else if (element.ValueKind == JsonValueKind.Array)
    {
        foreach (var item in element.EnumerateArray())
        {
            var result = FindDownloadUrl(item);

            if (!string.IsNullOrWhiteSpace(result))
                return result;
        }
    }

    return null;
}

private static string GetMachineId()
{
    var raw =
        $"{Environment.MachineName}|{Environment.UserName}|AlexSelect";

    return Convert.ToHexString(
        SHA256.HashData(
            Encoding.UTF8.GetBytes(raw)))[..32];
}

private sealed record ActivationResponse(
    bool active,
    string version,
    string online_code);