package main

import (
	"MineLauncherTest/launcher"
	"fmt"
	"strings"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/widget"
)

func main() {
	// Инициализация приложения Fyne с уникальным ID
	myApp := app.NewWithID("com.mycompany.minecraftlauncher")
	myWindow := myApp.NewWindow("Minecraft Launcher")

	// Инициализация лаунчера
	lan := launcher.NewLauncher()
	lan.SetMinecraftPath(".legit") // Установить путь по умолчанию

	// Создание прогресс-баров
	labelOverall := widget.NewLabel("Общий прогресс загрузки:")
	overallProgressBar := widget.NewProgressBar()
	overallProgressBar.SetValue(0)

	labelVersion := widget.NewLabel("Прогресс загрузки версии:")
	versionProgressBar := widget.NewProgressBar()
	versionProgressBar.SetValue(0)

	// Получение версий Minecraft
	versions, err := lan.GetMinecraftVersions()
	if err != nil {
		dialog.ShowError(err, myWindow)
		myApp.Quit()
		return
	}

	// Создание виджета выбора версии
	versionSelect := widget.NewSelect(versions, func(selected string) {})
	if len(versions) > 0 {
		versionSelect.SetSelected(versions[0]) // Установить выбор по умолчанию
	}

	// Создание поля ввода для имени пользователя
	usernameEntry := widget.NewEntry()
	usernameEntry.SetPlaceHolder("Введите имя пользователя")

	// Создание кнопки запуска Minecraft
	playButton := widget.NewButton("Играть", func() {
		selectedVersion := versionSelect.Selected
		username := usernameEntry.Text

		if selectedVersion == "" {
			dialog.ShowError(fmt.Errorf("пожалуйста, выберите версию Minecraft"), myWindow)
			return
		}

		if username == "" {
			dialog.ShowError(fmt.Errorf("пожалуйста, введите имя пользователя"), myWindow)
			return
		}

		versionID := extractVersionID(selectedVersion)
		if versionID == "" {
			dialog.ShowError(fmt.Errorf("не удалось определить ID версии из выбранной строки"), myWindow)
			return
		}

		// Сброс прогресс-баров
		overallProgressBar.SetValue(0)
		versionProgressBar.SetValue(0)

		go func() {
			err := lan.PlayMinecraftVersion(versionID, username)
			if err != nil {
				fyne.CurrentApp().SendNotification(&fyne.Notification{
					Title:   "Ошибка",
					Content: err.Error(),
				})
				return
			}
			fyne.CurrentApp().SendNotification(&fyne.Notification{
				Title:   "Успех",
				Content: "Minecraft запущен успешно!",
			})
		}()
	})

	content := container.NewVBox(
		labelOverall,
		overallProgressBar,
		labelVersion,
		versionProgressBar,
		widget.NewLabel("Выберите версию Minecraft:"),
		versionSelect,
		widget.NewLabel("Имя пользователя:"),
		usernameEntry,
		playButton,
	)

	myWindow.SetContent(content)
	myWindow.Resize(fyne.NewSize(500, 400))
	myWindow.Show()

	go func() {
		for {
			select {
			case progress := <-lan.OverallProgressChan:
				// Обновление UI с использованием thread-safe методов Fyne
				overallProgressBar.SetValue(progress)
			case progress := <-lan.VersionProgressChan:
				versionProgressBar.SetValue(progress)
			}
		}
	}()

	myApp.Run()
}

// Функция для извлечения versionID из строки формата "1.14.2 (release)"
func extractVersionID(selected string) string {
	if idx := strings.Index(selected, " ("); idx != -1 {
		return selected[:idx]
	}
	return selected
}


// launcher.go
package launcher

import (
	"archive/zip"
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
)

const versionManifestURL = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"

type VersionManifest struct {
	Latest struct {
		Release  string `json:"release"`
		Snapshot string `json:"snapshot"`
	} `json:"latest"`
	Versions []struct {
		ID          string `json:"id"`
		Type        string `json:"type"`
		URL         string `json:"url"`
		Time        string `json:"time"`
		ReleaseTime string `json:"releaseTime"`
	} `json:"versions"`
}

type VersionData map[string]interface{}

type Launcher struct {
	minelauncherPath string
	versionManifest  *VersionManifest

	// Channels для передачи прогресса
	OverallProgressChan chan float64
	VersionProgressChan chan float64
}

func NewLauncher() *Launcher {
	return &Launcher{
		minelauncherPath:    ".minecraft", // Задаем путь по умолчанию
		OverallProgressChan: make(chan float64),
		VersionProgressChan: make(chan float64),
	}
}

func (l *Launcher) SetMinecraftPath(path string) {
	l.minelauncherPath = path
}

func (l *Launcher) GetMinecraftPath() string {
	return l.minelauncherPath
}

func (l *Launcher) getVersionManifest() (*VersionManifest, error) {
	fmt.Println("Получение манифеста версий...")
	resp, err := http.Get(versionManifestURL)
	if err != nil {
		return nil, fmt.Errorf("не удалось получить манифест версий: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("не удалось получить манифест версий, статус код: %d", resp.StatusCode)
	}

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("не удалось прочитать тело ответа манифеста версий: %v", err)
	}

	var manifest VersionManifest
	err = json.Unmarshal(bodyBytes, &manifest)
	if err != nil {
		return nil, fmt.Errorf("ошибка при разборе манифеста версий: %v", err)
	}
	return &manifest, nil
}

func (l *Launcher) GetMinecraftVersions() ([]string, error) {
	if l.versionManifest == nil {
		manifest, err := l.getVersionManifest()
		if err != nil {
			return nil, err
		}
		l.versionManifest = manifest
	}

	versionsMap := make(map[string]string)
	for _, v := range l.versionManifest.Versions {
		versionsMap[v.ID] = fmt.Sprintf("%s (%s)", v.ID, v.Type)
	}

	// Сканируем папку versions на наличие дополнительных версий
	versionsDir := filepath.Join(l.minelauncherPath, "versions")
	versionFolders, err := os.ReadDir(versionsDir)
	if err == nil {
		for _, folder := range versionFolders {
			if folder.IsDir() {
				versionID := folder.Name()
				if _, exists := versionsMap[versionID]; !exists {
					versionsMap[versionID] = fmt.Sprintf("%s (custom)", versionID)
				}
			}
		}
	}

	// Собираем версии в слайс
	versions := []string{}
	for _, v := range versionsMap {
		versions = append(versions, v)
	}
	return versions, nil
}

func (l *Launcher) getVersionDetails(versionID string) (VersionData, error) {
	fmt.Printf("Получение данных для версии: %s\n", versionID)

	// Проверяем локальный JSON-файл версии
	versionJsonPath := filepath.Join(l.minelauncherPath, "versions", versionID, versionID+".json")
	if _, err := os.Stat(versionJsonPath); err == nil {
		// Локальный JSON существует, загружаем его
		data, err := os.ReadFile(versionJsonPath)
		if err != nil {
			return nil, fmt.Errorf("не удалось прочитать локальный файл версии %s: %v", versionJsonPath, err)
		}
		var versionData VersionData
		err = json.Unmarshal(data, &versionData)
		if err != nil {
			return nil, fmt.Errorf("ошибка при разборе локальных данных версии %s: %v", versionJsonPath, err)
		}
		return versionData, nil
	}

	// Иначе получаем данные версии из официального манифеста
	var versionURL string
	for _, version := range l.versionManifest.Versions {
		if version.ID == versionID {
			versionURL = version.URL
			break
		}
	}
	if versionURL == "" {
		return nil, fmt.Errorf("версия %s не найдена в манифесте", versionID)
	}

	resp, err := http.Get(versionURL)
	if err != nil {
		return nil, fmt.Errorf("не удалось получить данные версии: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("не удалось получить данные версии, статус код: %d", resp.StatusCode)
	}

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("не удалось прочитать тело ответа версии: %v", err)
	}

	var versionData VersionData
	err = json.Unmarshal(bodyBytes, &versionData)
	if err != nil {
		return nil, fmt.Errorf("ошибка при разборе данных версии: %v", err)
	}
	return versionData, nil
}

func (l *Launcher) mergeVersionData(versionData VersionData) (VersionData, error) {
	if parentID, ok := versionData["inheritsFrom"].(string); ok {
		parentData, err := l.getVersionDetails(parentID)
		if err != nil {
			return nil, fmt.Errorf("ошибка при получении родительской версии %s: %v", parentID, err)
		}
		// Рекурсивно объединяем данные родительской версии
		parentData, err = l.mergeVersionData(parentData)
		if err != nil {
			return nil, err
		}
		// Объединяем данные parentData в versionData
		mergedData := l.deepMergeVersionData(parentData, versionData)
		return mergedData, nil
	}
	return versionData, nil
}

func (l *Launcher) deepMergeVersionData(parent, child VersionData) VersionData {
	merged := make(VersionData)
	// Копируем данные родительской версии
	for k, v := range parent {
		merged[k] = v
	}
	// Переопределяем данными дочерней версии
	for k, v := range child {
		if k == "libraries" {
			// Объединяем массивы библиотек
			parentLibs, _ := parent["libraries"].([]interface{})
			childLibs, _ := v.([]interface{})
			mergedLibs := append(parentLibs, childLibs...)
			merged["libraries"] = mergedLibs
		} else if k == "arguments" {
			// Объединяем аргументы
			parentArgs, _ := parent["arguments"].(map[string]interface{})
			childArgs, _ := v.(map[string]interface{})
			mergedArgs := make(map[string]interface{})
			for ak, av := range parentArgs {
				if childArgValue, hasChildArg := childArgs[ak]; hasChildArg {
					if isNonEmpty(childArgValue) {
						// Если дочерний аргумент не пустой, используем его
						mergedArgs[ak] = childArgValue
					} else {
						// Если дочерний аргумент пустой, сохраняем родительский
						mergedArgs[ak] = av
					}
				} else {
					// Если в дочерней версии нет этого аргумента, сохраняем родительский
					mergedArgs[ak] = av
				}
			}
			// Добавляем новые аргументы из дочерней версии, которых нет в родительской
			for ak, av := range childArgs {
				if _, exists := parentArgs[ak]; !exists {
					mergedArgs[ak] = av
				}
			}
			merged["arguments"] = mergedArgs
		} else if k == "logging" {
			// Переопределяем logging
			merged[k] = v
		} else {
			merged[k] = v
		}
	}
	return merged
}

func isNonEmpty(value interface{}) bool {
	if value == nil {
		return false
	}
	switch v := value.(type) {
	case []interface{}:
		return len(v) > 0
	case map[string]interface{}:
		return len(v) > 0
	default:
		return true
	}
}

func (l *Launcher) downloadFile(url, filepath string) error {
	fmt.Printf("Скачивание файла: %s\n", url)
	client := http.Client{
		Timeout: 30 * time.Second,
	}

	resp, err := client.Get(url)
	if err != nil {
		return fmt.Errorf("ошибка при выполнении GET-запроса: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("не удалось скачать файл: %s, статус код: %d", url, resp.StatusCode)
	}

	out, err := os.Create(filepath)
	if err != nil {
		return fmt.Errorf("не удалось создать файл %s: %v", filepath, err)
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return fmt.Errorf("ошибка при записи файла %s: %v", filepath, err)
	}

	fmt.Printf("Файл скачан и сохранён: %s\n", filepath)
	return nil
}

func (l *Launcher) downloadFileWithRetry(url, filepath string, retries int) error {
	for i := 0; i <= retries; i++ {
		err := l.downloadFile(url, filepath)
		if err == nil {
			return nil
		}
		fmt.Printf("Попытка %d скачать файл %s не удалась, ошибка: %v\n", i+1, url, err)
		time.Sleep(2 * time.Second)
	}
	return fmt.Errorf("не удалось скачать файл после %d попыток: %s", retries+1, url)
}

func (l *Launcher) downloadFileWithProgress(url, filepath string, expectedHash string, progressChan chan float64, retries int) error {
	fmt.Printf("Скачивание файла: %s\n", url)

	var lastProgress float64 = 0.0

	for i := 0; i <= retries; i++ {
		resp, err := http.Get(url)
		if err != nil {
			fmt.Printf("Ошибка при выполнении GET-запроса: %v\n", err)
			time.Sleep(2 * time.Second)
			continue
		}

		if resp.StatusCode != http.StatusOK {
			fmt.Printf("Не удалось скачать файл: %s, статус код: %d\n", url, resp.StatusCode)
			resp.Body.Close()
			time.Sleep(2 * time.Second)
			continue
		}

		out, err := os.Create(filepath)
		if err != nil {
			fmt.Printf("Не удалось создать файл %s: %v\n", filepath, err)
			resp.Body.Close()
			time.Sleep(2 * time.Second)
			continue
		}

		// Получаем размер содержимого
		totalSizeStr := resp.Header.Get("Content-Length")
		totalSize := int64(0)
		if totalSizeStr != "" {
			fmt.Sscanf(totalSizeStr, "%d", &totalSize)
		}

		// Создаем репликатор для отслеживания прогресса
		progressReader := &ProgressReader{
			Reader: resp.Body,
			Total:  totalSize,
			ProgressFunc: func(progress float64) {
				if progress-lastProgress >= 1.0 { // Обновлять каждую 1%
					lastProgress = progress
					progressChan <- progress
				}
			},
		}

		_, err = io.Copy(out, progressReader)
		resp.Body.Close()
		out.Close()
		if err != nil {
			fmt.Printf("Ошибка при записи файла %s: %v\n", filepath, err)
			os.Remove(filepath)
			time.Sleep(2 * time.Second)
			continue
		}

		// Проверяем хэш
		if expectedHash != "" && !l.verifyFileChecksum(filepath, expectedHash) {
			fmt.Printf("Хэш файла %s не совпадает после загрузки\n", filepath)
			os.Remove(filepath)
			time.Sleep(2 * time.Second)
			continue
		}

		// Отправляем 100% после завершения
		progressChan <- 100.0
		return nil
	}

	return fmt.Errorf("не удалось скачать файл после %d попыток: %s", retries+1, url)
}

// Структура для отслеживания прогресса чтения
type ProgressReader struct {
	Reader       io.Reader
	Total        int64
	BytesRead    int64
	ProgressFunc func(float64)
}

func (p *ProgressReader) Read(buf []byte) (int, error) {
	n, err := p.Reader.Read(buf)
	if n > 0 {
		p.BytesRead += int64(n)
		if p.Total > 0 {
			progress := (float64(p.BytesRead) / float64(p.Total)) * 100
			p.ProgressFunc(progress)
		}
	}
	return n, err
}

func (l *Launcher) verifyFileChecksum(filePath, expectedHash string) bool {
	file, err := os.Open(filePath)
	if err != nil {
		fmt.Printf("Не удалось открыть файл для проверки хэша: %s, ошибка: %v\n", filePath, err)
		return false
	}
	defer file.Close()

	hash := sha1.New()
	if _, err := io.Copy(hash, file); err != nil {
		fmt.Printf("Не удалось вычислить хэш для файла: %s, ошибка: %v\n", filePath, err)
		return false
	}
	calculatedHash := hex.EncodeToString(hash.Sum(nil))
	if expectedHash != calculatedHash {
		fmt.Printf("Хэш не совпадает для файла: %s (ожидалось: %s, получено: %s)\n", filePath, expectedHash, calculatedHash)
		return false
	}
	return true
}

func (l *Launcher) shouldIncludeLibrary(libInfo map[string]interface{}) bool {
	allowed := true
	if rules, ok := libInfo["rules"].([]interface{}); ok {
		for _, ruleInterface := range rules {
			rule, ok := ruleInterface.(map[string]interface{})
			if !ok {
				continue
			}
			action, ok := rule["action"].(string)
			if !ok {
				continue
			}

			applies := true

			if osDict, ok := rule["os"].(map[string]interface{}); ok {
				osName := runtime.GOOS
				if osName == "darwin" {
					osName = "osx"
				}
				if name, ok := osDict["name"].(string); ok && name != osName {
					applies = false
				}
			}

			if applies {
				if action == "allow" {
					allowed = true
				} else if action == "disallow" {
					allowed = false
				}
			}
		}
	}
	return allowed
}

func (l *Launcher) getNatives(libInfo map[string]interface{}) string {
	archType := "64"
	if runtime.GOARCH == "386" || runtime.GOARCH == "arm" {
		archType = "32"
	}

	natives, ok := libInfo["natives"].(map[string]interface{})
	if !ok {
		return ""
	}

	var classifierName string
	switch runtime.GOOS {
	case "windows":
		if val, exists := natives["windows"].(string); exists {
			classifierName = strings.ReplaceAll(val, "${arch}", archType)
		}
	case "darwin":
		if val, exists := natives["osx"].(string); exists {
			classifierName = strings.ReplaceAll(val, "${arch}", archType)
		}
	case "linux":
		if val, exists := natives["linux"].(string); exists {
			classifierName = strings.ReplaceAll(val, "${arch}", archType)
		}
	default:
		return ""
	}

	return classifierName
}

func (l *Launcher) unzip(src, dest string) error {
	fmt.Printf("Распаковка архива: %s в %s\n", src, dest)
	r, err := zip.OpenReader(src)
	if err != nil {
		return fmt.Errorf("не удалось открыть ZIP-архив %s: %v", src, err)
	}
	defer r.Close()

	for _, f := range r.File {
		if strings.HasPrefix(f.Name, "META-INF") {
			continue
		}

		fpath := filepath.Join(dest, f.Name)

		// Проверка на безопасность пути
		if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
			return fmt.Errorf("недопустимый путь в архиве: %s", fpath)
		}

		if f.FileInfo().IsDir() {
			err := os.MkdirAll(fpath, os.ModePerm)
			if err != nil {
				return fmt.Errorf("не удалось создать директорию %s: %v", fpath, err)
			}
			continue
		}

		if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
			return fmt.Errorf("не удалось создать директорию %s: %v", filepath.Dir(fpath), err)
		}

		outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			return fmt.Errorf("не удалось открыть файл для записи %s: %v", fpath, err)
		}

		rc, err := f.Open()
		if err != nil {
			outFile.Close()
			return fmt.Errorf("не удалось открыть файл внутри архива %s: %v", f.Name, err)
		}

		_, err = io.Copy(outFile, rc)
		outFile.Close()
		rc.Close()

		if err != nil {
			return fmt.Errorf("ошибка при копировании файла %s: %v", fpath, err)
		}
	}
	return nil
}

func (l *Launcher) verifyMainClass(clientJarPath string) bool {
	r, err := zip.OpenReader(clientJarPath)
	if err != nil {
		fmt.Printf("Не удалось открыть client.jar для проверки main class: %v\n", err)
		return false
	}
	defer r.Close()

	target := "net/minecraft/client/main/Main.class"
	for _, f := range r.File {
		if f.Name == target {
			return true
		}
	}
	fmt.Println("Main class не найден в client.jar")
	return false
}

func (l *Launcher) downloadClientJar(versionData VersionData, versionID string) error {
	fmt.Println("Скачивание клиентского JAR-файла...")

	dirPath := filepath.Join(l.minelauncherPath, "versions", versionID)
	os.MkdirAll(dirPath, os.ModePerm)
	filePath := filepath.Join(dirPath, versionID+".jar")

	// Проверяем, существует ли файл client.jar
	if _, err := os.Stat(filePath); err == nil {
		fmt.Println("Клиентский JAR-файл уже существует, пропускаем скачивание.")
		// Предположим, что версия №1.0 загружена
		l.VersionProgressChan <- 100.0
		return nil
	}

	// Проверяем, есть ли указание на загрузку клиентского JAR-файла
	downloads, ok := versionData["downloads"].(map[string]interface{})
	if ok {
		client, ok := downloads["client"].(map[string]interface{})
		if ok {
			url, ok := client["url"].(string)
			if !ok {
				return fmt.Errorf("не удалось получить URL клиента")
			}
			expectedHash, _ := client["sha1"].(string)

			absFilePath, err := filepath.Abs(filePath)
			if err != nil {
				return fmt.Errorf("не удалось получить абсолютный путь к файлу %s: %v", filePath, err)
			}

			// Отправляем прогресс 0%
			l.VersionProgressChan <- 0.0

			// Скачиваем файл с обновлением прогресса
			err = l.downloadFileWithProgress(url, absFilePath, expectedHash, l.VersionProgressChan, 4)
			if err != nil {
				return fmt.Errorf("ошибка при скачивании клиентского JAR-файла: %v", err)
			}

			// Проверяем наличие mainClass
			if !l.verifyMainClass(absFilePath) {
				return fmt.Errorf("файл client.jar не содержит необходимого основного класса")
			}

			return nil
		}
	}

	// Если не удалось найти информацию о загрузке, проверяем наличие "inheritsFrom"
	if parentID, ok := versionData["inheritsFrom"].(string); ok {
		// Пытаемся использовать client.jar родительской версии
		parentClientJar := filepath.Join(l.minelauncherPath, "versions", parentID, parentID+".jar")
		if _, err := os.Stat(parentClientJar); err != nil {
			// Если файл не существует, скачиваем родительскую версию
			parentData, err := l.getVersionDetails(parentID)
			if err != nil {
				return fmt.Errorf("не удалось получить данные родительской версии %s: %v", parentID, err)
			}
			err = l.downloadClientJar(parentData, parentID)
			if err != nil {
				return err
			}
		}
		// Копируем client.jar из родительской версии
		err := copyFile(parentClientJar, filePath)
		if err != nil {
			return fmt.Errorf("не удалось скопировать client.jar из родительской версии: %v", err)
		}
		// Предположим, что копирование завершено
		l.VersionProgressChan <- 100.0
		return nil
	}

	return fmt.Errorf("не удалось скачать или найти client.jar для версии %s", versionID)
}

func copyFile(src, dst string) error {
	input, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("не удалось открыть файл %s для копирования: %v", src, err)
	}
	defer input.Close()

	output, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("не удалось создать файл %s для копирования: %v", dst, err)
	}
	defer output.Close()

	_, err = io.Copy(output, input)
	if err != nil {
		return fmt.Errorf("ошибка при копировании файла %s в %s: %v", src, dst, err)
	}

	return nil
}

func (l *Launcher) downloadLibraries(versionData VersionData) error {
	fmt.Println("Скачивание библиотек...")
	libraries, ok := versionData["libraries"].([]interface{})
	if !ok {
		return fmt.Errorf("не удалось получить библиотеки из данных версии")
	}

	// Подсчитываем общее количество библиотек для загрузки
	totalLibraries := 0
	for _, lib := range libraries {
		libInfo, ok := lib.(map[string]interface{})
		if !ok {
			continue
		}
		if !l.shouldIncludeLibrary(libInfo) {
			continue
		}
		totalLibraries++
	}

	if totalLibraries == 0 {
		// Ничего не загружать
		return nil
	}

	// Отправляем начальный прогресс
	l.OverallProgressChan <- 0.0

	var wg sync.WaitGroup
	semaphore := make(chan struct{}, 10)
	completed := 0
	var mutex sync.Mutex

	for _, lib := range libraries {
		libInfo, ok := lib.(map[string]interface{})
		if !ok {
			continue
		}

		if !l.shouldIncludeLibrary(libInfo) {
			continue
		}

		var downloadInfo map[string]interface{}
		downloads, hasDownloads := libInfo["downloads"].(map[string]interface{})
		if hasDownloads {
			artifact, hasArtifact := downloads["artifact"].(map[string]interface{})
			if hasArtifact {
				downloadInfo = artifact
			}
		} else {
			// Обработка старого формата
			name, ok := libInfo["name"].(string)
			if !ok {
				continue
			}
			var url string
			if customURL, ok := libInfo["url"].(string); ok {
				url = customURL
			} else {
				url = "https://libraries.minecraft.net/"
			}

			path := l.mavenArtifactPath(name)
			if path == "" {
				continue
			}
			fullURL := url + path

			downloadInfo = map[string]interface{}{
				"url":  fullURL,
				"path": path,
			}
		}

		url, ok := downloadInfo["url"].(string)
		if !ok {
			continue
		}
		path, ok := downloadInfo["path"].(string)
		if !ok {
			continue
		}
		expectedHash, _ := downloadInfo["sha1"].(string)

		filePath := filepath.Join(l.minelauncherPath, "libraries", filepath.FromSlash(path))
		absFilePath, err := filepath.Abs(filePath)
		if err != nil {
			fmt.Printf("Не удалось получить абсолютный путь для файла %s: %v\n", filePath, err)
			continue
		}
		dir := filepath.Dir(absFilePath)
		os.MkdirAll(dir, os.ModePerm)

		// Проверяем, существует ли файл и совпадает ли хэш
		if _, err := os.Stat(absFilePath); err == nil {
			if expectedHash != "" && l.verifyFileChecksum(absFilePath, expectedHash) {
				// Файл уже загружен и корректен
				mutex.Lock()
				completed++
				progress := (float64(completed) / float64(totalLibraries)) * 100
				l.OverallProgressChan <- progress
				mutex.Unlock()
				continue
			} else {
				fmt.Printf("Хэш библиотеки не совпадает, перезагружаем файл: %s\n", absFilePath)
				os.Remove(absFilePath)
			}
		}

		wg.Add(1)
		semaphore <- struct{}{}

		go func(url, absFilePath, expectedHash string) {
			defer wg.Done()
			defer func() { <-semaphore }()

			err := l.downloadFileWithRetry(url, absFilePath, 4)
			if err != nil {
				fmt.Printf("Ошибка при скачивании библиотеки %s: %v\n", url, err)
				return
			}

			if expectedHash != "" && !l.verifyFileChecksum(absFilePath, expectedHash) {
				fmt.Printf("Хэш библиотеки не совпадает после загрузки: %s\n", absFilePath)
				os.Remove(absFilePath)
				return
			}

			// Увеличиваем счетчик завершенных загрузок
			mutex.Lock()
			completed++
			progress := (float64(completed) / float64(totalLibraries)) * 100
			l.OverallProgressChan <- progress
			mutex.Unlock()
		}(url, absFilePath, expectedHash)
	}
	wg.Wait()

	return nil
}

func (l *Launcher) mavenArtifactPath(name string) string {
	parts := strings.Split(name, ":")
	if len(parts) != 3 {
		return ""
	}
	groupID := parts[0]
	artifactID := parts[1]
	version := parts[2]
	path := fmt.Sprintf("%s/%s/%s/%s-%s.jar",
		strings.ReplaceAll(groupID, ".", "/"),
		artifactID,
		version,
		artifactID,
		version)
	return path
}

func (l *Launcher) downloadAssets(versionData VersionData) error {
	fmt.Println("Скачивание ассетов...")
	assetIndex, ok := versionData["assetIndex"].(map[string]interface{})
	if !ok {
		return fmt.Errorf("не удалось найти assetIndex в данных версии")
	}
	url, ok := assetIndex["url"].(string)
	if !ok {
		return fmt.Errorf("не удалось получить URL assetIndex")
	}
	expectedHash, ok := assetIndex["sha1"].(string)
	if !ok {
		return fmt.Errorf("не удалось получить sha1 assetIndex")
	}

	// Скачиваем индекс ассетов
	fmt.Println("Скачивание индекса ассетов...")
	resp, err := http.Get(url)
	if err != nil {
		return fmt.Errorf("не удалось скачать индекс ассетов: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("не удалось скачать индекс ассетов, статус код: %d", resp.StatusCode)
	}

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("не удалось прочитать индекс ассетов: %v", err)
	}

	hash := sha1.New()
	hash.Write(bodyBytes)
	calculatedHash := hex.EncodeToString(hash.Sum(nil))
	if expectedHash != calculatedHash {
		return fmt.Errorf("хэш индекса ассетов не совпадает (ожидалось: %s, получено: %s)", expectedHash, calculatedHash)
	}

	var assetIndexData map[string]interface{}
	err = json.Unmarshal(bodyBytes, &assetIndexData)
	if err != nil {
		return fmt.Errorf("ошибка при разборе индекса ассетов: %v", err)
	}

	fmt.Println("Сохранение индекса ассетов...")
	assetsDir := filepath.Join(l.minelauncherPath, "assets", "indexes")
	os.MkdirAll(assetsDir, os.ModePerm)
	indexFilePath := filepath.Join(assetsDir, fmt.Sprintf("%s.json", assetIndex["id"].(string)))
	err = os.WriteFile(indexFilePath, bodyBytes, 0644)
	if err != nil {
		return fmt.Errorf("ошибка при сохранении индекса ассетов: %v", err)
	}

	objects, ok := assetIndexData["objects"].(map[string]interface{})
	if !ok {
		return fmt.Errorf("не удалось получить объекты ассетов")
	}

	totalAssets := len(objects)
	if totalAssets == 0 {
		return nil
	}

	// Отправляем начальный прогресс
	l.OverallProgressChan <- 0.0

	var wg sync.WaitGroup
	semaphore := make(chan struct{}, 20)
	completedAssets := 0
	var mutex sync.Mutex

	for _, obj := range objects {
		wg.Add(1)
		semaphore <- struct{}{}

		go func(obj interface{}) {
			defer wg.Done()
			defer func() { <-semaphore }()

			objInfo, ok := obj.(map[string]interface{})
			if !ok {
				return
			}
			hashStr, ok := objInfo["hash"].(string)
			if !ok {
				return
			}
			sizeFloat, ok := objInfo["size"].(float64)
			if !ok {
				return
			}
			size := int64(sizeFloat)
			if len(hashStr) < 2 {
				fmt.Printf("Некорректный хэш ассета: %s\n", hashStr)
				return
			}
			subHash := hashStr[:2]

			assetURL := fmt.Sprintf("https://resources.download.minecraft.net/%s/%s", subHash, hashStr)
			filePath := filepath.Join(l.minelauncherPath, "assets", "objects", subHash, hashStr)
			absFilePath, err := filepath.Abs(filePath)
			if err != nil {
				fmt.Printf("Не удалось получить абсолютный путь для ассета %s: %v\n", filePath, err)
				return
			}
			dir := filepath.Dir(absFilePath)
			os.MkdirAll(dir, os.ModePerm)

			if fi, err := os.Stat(absFilePath); err == nil && fi.Size() == size {
				// Ассет уже загружен
			} else {
				err := l.downloadFileWithRetry(assetURL, absFilePath, 4)
				if err != nil {
					fmt.Printf("\nОшибка при скачивании ассета %s: %v\n", assetURL, err)
					return
				}
			}

			mutex.Lock()
			completedAssets++
			progress := (float64(completedAssets) / float64(totalAssets)) * 100
			l.OverallProgressChan <- progress
			mutex.Unlock()
		}(obj)
	}

	wg.Wait()
	fmt.Println("\nЗагрузка ассетов завершена.")
	return nil
}

func (l *Launcher) extractNatives(versionData VersionData, nativesDir string) error {
	fmt.Println("Извлечение нативных библиотек...")
	libraries, ok := versionData["libraries"].([]interface{})
	if !ok {
		return fmt.Errorf("не удалось получить библиотеки для распаковки нативов")
	}

	for _, lib := range libraries {
		libInfo, ok := lib.(map[string]interface{})
		if !ok {
			continue
		}

		if !l.shouldIncludeLibrary(libInfo) {
			continue
		}

		downloads, ok := libInfo["downloads"].(map[string]interface{})
		if !ok {
			continue
		}

		classifiers, ok := downloads["classifiers"].(map[string]interface{})
		if !ok {
			continue
		}

		classifierName := l.getNatives(libInfo)
		if classifierName == "" {
			continue
		}

		native, ok := classifiers[classifierName].(map[string]interface{})
		if !ok {
			continue
		}

		url, ok := native["url"].(string)
		if !ok {
			continue
		}
		path, ok := native["path"].(string)
		if !ok {
			continue
		}
		expectedHash, _ := native["sha1"].(string)

		filePath := filepath.Join(l.minelauncherPath, "libraries", filepath.FromSlash(path))
		absFilePath, err := filepath.Abs(filePath)
		if err != nil {
			fmt.Printf("Не удалось получить абсолютный путь для файла %s: %v\n", filePath, err)
			continue
		}
		dir := filepath.Dir(absFilePath)
		os.MkdirAll(dir, os.ModePerm)

		if _, err := os.Stat(absFilePath); err == nil {
			if expectedHash != "" && l.verifyFileChecksum(absFilePath, expectedHash) {
				fmt.Printf("Нативная библиотека уже существует и корректна: %s\n", absFilePath)
			} else {
				fmt.Printf("Хэш нативной библиотеки не совпадает, перезагружаем файл: %s\n", absFilePath)
				os.Remove(absFilePath)
			}
		}

		if _, err := os.Stat(absFilePath); os.IsNotExist(err) {
			fmt.Printf("Скачивание нативной библиотеки: %s\n", absFilePath)
			err := l.downloadFileWithRetry(url, absFilePath, 4)
			if err != nil {
				fmt.Printf("Ошибка при скачивании нативной библиотеки %s: %v\n", url, err)
				return err
			}
		}

		if expectedHash != "" && !l.verifyFileChecksum(absFilePath, expectedHash) {
			return fmt.Errorf("хэш нативной библиотеки не совпадает после загрузки: %s", absFilePath)
		}

		fmt.Printf("Распаковка нативных библиотек из %s в %s\n", absFilePath, nativesDir)
		err = l.unzip(absFilePath, nativesDir)
		if err != nil {
			return fmt.Errorf("ошибка при распаковке нативной библиотеки %s: %v", absFilePath, err)
		}
	}

	return nil
}

func (l *Launcher) buildClasspath(versionData VersionData, versionID string) string {
	var classpath []string

	libraries, ok := versionData["libraries"].([]interface{})
	if !ok {
		fmt.Println("Не удалось получить библиотеки для classpath")
		return ""
	}
	for _, lib := range libraries {
		libInfo, ok := lib.(map[string]interface{})
		if !ok {
			continue
		}
		if !l.shouldIncludeLibrary(libInfo) {
			continue
		}
		var libPath string

		downloads, hasDownloads := libInfo["downloads"].(map[string]interface{})
		if hasDownloads {
			artifact, hasArtifact := downloads["artifact"].(map[string]interface{})
			if hasArtifact {
				path, ok := artifact["path"].(string)
				if !ok {
					continue
				}
				libPath = filepath.Join(l.minelauncherPath, "libraries", filepath.FromSlash(path))
			}
		} else {
			// Обработка старого формата
			name, ok := libInfo["name"].(string)
			if !ok {
				continue
			}
			path := l.mavenArtifactPath(name)
			if path == "" {
				continue
			}
			libPath = filepath.Join(l.minelauncherPath, "libraries", filepath.FromSlash(path))
		}

		absLibPath, err := filepath.Abs(libPath)
		if err != nil {
			fmt.Printf("Не удалось получить абсолютный путь для библиотеки %s: %v\n", libPath, err)
			continue
		}
		classpath = append(classpath, absLibPath)
	}

	clientJar := filepath.Join(l.minelauncherPath, "versions", versionID, versionID+".jar")
	absClientJar, err := filepath.Abs(clientJar)
	if err != nil {
		fmt.Printf("Не удалось получить абсолютный путь для client.jar %s: %v\n", clientJar, err)
		return ""
	}
	classpath = append(classpath, absClientJar)

	return strings.Join(classpath, string(os.PathListSeparator))
}

func (l *Launcher) replaceVariables(arg, username, versionID string, versionData VersionData, uuid, authToken string) string {
	assetIndex := l.versionDataGetAssetIndex(versionData)

	arg = strings.ReplaceAll(arg, "${auth_player_name}", username)
	arg = strings.ReplaceAll(arg, "${version_name}", versionID)
	arg = strings.ReplaceAll(arg, "${game_directory}", l.minelauncherPath)
	arg = strings.ReplaceAll(arg, "${assets_root}", filepath.Join(l.minelauncherPath, "assets"))
	arg = strings.ReplaceAll(arg, "${assets_index_name}", l.getAssetIndexName(assetIndex))
	arg = strings.ReplaceAll(arg, "${auth_uuid}", uuid)
	arg = strings.ReplaceAll(arg, "${auth_access_token}", authToken)
	arg = strings.ReplaceAll(arg, "${clientid}", "")
	arg = strings.ReplaceAll(arg, "${auth_xuid}", "")
	arg = strings.ReplaceAll(arg, "${version_type}", l.versionDataGetVersionType(versionData))
	arg = strings.ReplaceAll(arg, "${user_type}", "legacy")
	arg = strings.ReplaceAll(arg, "--demo", "")
	arg = strings.ReplaceAll(arg, "${natives_directory}", filepath.Join(l.minelauncherPath, "versions", versionID, "natives"))
	arg = strings.ReplaceAll(arg, "${launcher_name}", "MyCustomLauncher")
	arg = strings.ReplaceAll(arg, "${launcher_version}", "1.0")

	// Устанавливаем значения разрешения (ширины и высоты)
	resolutionWidth := "854"  // Значение ширины по умолчанию
	resolutionHeight := "480" // Значение высоты по умолчанию

	if resolutionWidth != "" {
		arg = strings.ReplaceAll(arg, "${resolution_width}", resolutionWidth)
	} else {
		// Удаляем опцию '--width' и ее значение из аргументов
		arg = strings.ReplaceAll(arg, "--width ${resolution_width}", "")
	}

	if resolutionHeight != "" {
		arg = strings.ReplaceAll(arg, "${resolution_height}", resolutionHeight)
	} else {
		// Удаляем опцию '--height' и ее значение из аргументов
		arg = strings.ReplaceAll(arg, "--height ${resolution_height}", "")
	}

	return arg
}

func (l *Launcher) getAssetIndexName(assetIndex map[string]interface{}) string {
	if id, ok := assetIndex["id"].(string); ok {
		return id
	}
	return "legacy"
}

func (l *Launcher) versionDataGetAssetIndex(versionData VersionData) map[string]interface{} {
	if assetIndex, ok := versionData["assetIndex"].(map[string]interface{}); ok {
		return assetIndex
	}
	return map[string]interface{}{}
}

func (l *Launcher) versionDataGetVersionType(versionData VersionData) string {
	if versionType, ok := versionData["type"].(string); ok {
		return versionType
	}
	return "release"
}

func (l *Launcher) checkRule(rules []interface{}) bool {
	allowed := false
	for _, ruleInterface := range rules {
		rule, ok := ruleInterface.(map[string]interface{})
		if !ok {
			continue
		}
		action, ok := rule["action"].(string)
		if !ok {
			continue
		}

		applies := true

		if osDict, ok := rule["os"].(map[string]interface{}); ok {
			osName := runtime.GOOS
			if osName == "darwin" {
				osName = "osx"
			}
			if name, ok := osDict["name"].(string); ok && name != osName {
				applies = false
			}
		}

		if applies {
			if action == "allow" {
				allowed = true
			} else if action == "disallow" {
				allowed = false
			}
		}
	}
	return allowed
}

func (l *Launcher) appendArgumentValueWithoutQuickPlay(args []string, value interface{}, username, versionID string, versionData VersionData, uuid, authToken string) []string {
	switch v := value.(type) {
	case string:
		if strings.Contains(strings.ToLower(v), "quickplay") {
			return args
		}
		v = l.replaceVariables(v, username, versionID, versionData, uuid, authToken)
		args = append(args, v)
	case []interface{}:
		for _, item := range v {
			if sv, ok := item.(string); ok {
				if strings.Contains(strings.ToLower(sv), "quickplay") {
					continue
				}
				sv = l.replaceVariables(sv, username, versionID, versionData, uuid, authToken)
				args = append(args, sv)
			}
		}
	}
	return args
}

func (l *Launcher) buildGameArguments(versionData VersionData, username, versionID, uuid, authToken string) []string {
	var args []string

	if arguments, ok := versionData["arguments"].(map[string]interface{}); ok {
		if gameArgsList, ok := arguments["game"].([]interface{}); ok {
			for _, arg := range gameArgsList {
				switch arg := arg.(type) {
				case string:
					if strings.Contains(strings.ToLower(arg), "quickplay") {
						continue
					}
					arg = l.replaceVariables(arg, username, versionID, versionData, uuid, authToken)
					args = append(args, arg)
				case map[string]interface{}:
					if rules, ok := arg["rules"].([]interface{}); ok {
						if l.checkRule(rules) {
							if value, ok := arg["value"].(interface{}); ok {
								args = l.appendArgumentValueWithoutQuickPlay(args, value, username, versionID, versionData, uuid, authToken)
							}
						}
					} else {
						if value, ok := arg["value"].(interface{}); ok {
							args = l.appendArgumentValueWithoutQuickPlay(args, value, username, versionID, versionData, uuid, authToken)
						}
					}
				}
			}
		}
	} else if minecraftArguments, ok := versionData["minecraftArguments"].(string); ok {
		splitArgs := strings.Split(minecraftArguments, " ")
		for _, arg := range splitArgs {
			if strings.Contains(strings.ToLower(arg), "quickplay") {
				continue
			}
			arg = l.replaceVariables(arg, username, versionID, versionData, uuid, authToken)
			args = append(args, arg)
		}
	}

	return args
}

func (l *Launcher) appendJvmArgumentValue(args []string, value interface{}, versionData VersionData) []string {
	switch v := value.(type) {
	case string:
		v = l.replaceVariables(v, "", "", versionData, "", "")
		args = append(args, v)
	case []interface{}:
		for _, item := range v {
			if sv, ok := item.(string); ok {
				sv = l.replaceVariables(sv, "", "", versionData, "", "")
				args = append(args, sv)
			}
		}
	}
	return args
}

func (l *Launcher) buildJvmArguments(versionData VersionData, nativesDir string) []string {
	var args []string

	// Добавляем стандартные JVM аргументы
	args = append(args, "-Djava.library.path="+nativesDir)
	args = append(args, "-Xmx1G")   // Максимальный объем памяти
	args = append(args, "-Xms512M") // Минимальный объем памяти

	if arguments, ok := versionData["arguments"].(map[string]interface{}); ok {
		if jvmArgsList, ok := arguments["jvm"].([]interface{}); ok {
			for _, arg := range jvmArgsList {
				switch arg := arg.(type) {
				case string:
					arg = l.replaceVariables(arg, "", "", versionData, "", "")
					args = append(args, arg)
				case map[string]interface{}:
					if rules, ok := arg["rules"].([]interface{}); ok {
						if l.checkRule(rules) {
							if value, ok := arg["value"].(interface{}); ok {
								args = l.appendJvmArgumentValue(args, value, versionData)
							}
						}
					} else {
						if value, ok := arg["value"].(interface{}); ok {
							args = l.appendJvmArgumentValue(args, value, versionData)
						}
					}
				}
			}
		}
	}

	return args
}

func (l *Launcher) launchMinecraft(versionData VersionData, authToken, username, uuid, versionID string) error {
	fmt.Println("Запуск Minecraft...")
	mainClass, ok := versionData["mainClass"].(string)
	if !ok {
		return fmt.Errorf("не удалось найти mainClass в данных версии")
	}
	fmt.Println("Main class:", mainClass)
	classpath := l.buildClasspath(versionData, versionID)
	fmt.Println("Classpath длина:", len(classpath))

	if classpath == "" {
		return fmt.Errorf("classpath пустой")
	}

	nativesDir := filepath.Join(l.minelauncherPath, "versions", versionID, "natives")
	absNativesDir, err := filepath.Abs(nativesDir)
	if err != nil {
		return fmt.Errorf("не удалось получить абсолютный путь для nativesDir %s: %v", nativesDir, err)
	}
	os.MkdirAll(absNativesDir, os.ModePerm)

	// Распаковка нативных библиотек
	err = l.extractNatives(versionData, absNativesDir)
	if err != nil {
		return fmt.Errorf("ошибка при извлечении нативных библиотек: %v", err)
	}

	// Build JVM and game arguments
	jvmArgs := l.buildJvmArguments(versionData, absNativesDir)
	gameArgs := l.buildGameArguments(versionData, username, versionID, uuid, authToken)

	if len(gameArgs) == 0 {
		return fmt.Errorf("аргументы игры пустые")
	}

	// Combine all arguments
	args := append(jvmArgs, "-cp", classpath, mainClass)
	args = append(args, gameArgs...)

	// Write arguments to a temporary file
	argsFilePath := filepath.Join(os.TempDir(), "minecraft_args.txt")
	err = l.writeArgsToFile(argsFilePath, args)
	if err != nil {
		return fmt.Errorf("ошибка при создании файла аргументов: %v", err)
	}

	// Prepare the command to use the argument file
	cmd := exec.Command("java", "@"+argsFilePath)
	cmd.Dir = l.minelauncherPath
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	fmt.Println("Команда запуска: java @" + argsFilePath)

	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("ошибка при запуске Minecraft: %v", err)
	}

	return nil
}

func (l *Launcher) writeArgsToFile(filePath string, args []string) error {
	var contentBuilder strings.Builder
	for _, arg := range args {
		arg = strings.ReplaceAll(arg, "\\", "\\\\")
		arg = strings.ReplaceAll(arg, `"`, `\"`)

		if strings.ContainsAny(arg, " \t\n\"") {
			arg = `"` + arg + `"`
		}

		contentBuilder.WriteString(arg)
		contentBuilder.WriteString(" ")
	}

	content := contentBuilder.String()
	return os.WriteFile(filePath, []byte(content), 0644)
}

func (l *Launcher) verifyAllFiles(versionData VersionData, versionID string) error {
	fmt.Println("Проверка наличия всех необходимых файлов...")
	clientJar := filepath.Join(l.minelauncherPath, "versions", versionID, versionID+".jar")
	if _, err := os.Stat(clientJar); os.IsNotExist(err) {
		return fmt.Errorf("клиентский JAR-файл отсутствует: %s", clientJar)
	}

	libraries, ok := versionData["libraries"].([]interface{})
	if !ok {
		return fmt.Errorf("не удалось получить библиотеки для проверки")
	}
	for _, lib := range libraries {
		libInfo, ok := lib.(map[string]interface{})
		if !ok {
			continue
		}
		if !l.shouldIncludeLibrary(libInfo) {
			continue
		}
		var libPath string

		downloads, hasDownloads := libInfo["downloads"].(map[string]interface{})
		if hasDownloads {
			artifact, hasArtifact := downloads["artifact"].(map[string]interface{})
			if hasArtifact {
				path, ok := artifact["path"].(string)
				if !ok {
					continue
				}
				libPath = filepath.Join(l.minelauncherPath, "libraries", filepath.FromSlash(path))
			}
		} else {
			// Обработка старого формата
			name, ok := libInfo["name"].(string)
			if !ok {
				continue
			}
			path := l.mavenArtifactPath(name)
			if path == "" {
				continue
			}
			libPath = filepath.Join(l.minelauncherPath, "libraries", filepath.FromSlash(path))
		}

		if _, err := os.Stat(libPath); os.IsNotExist(err) {
			return fmt.Errorf("отсутствует библиотека: %s", libPath)
		}
	}

	fmt.Println("Все необходимые файлы присутствуют.")
	return nil
}

func (l *Launcher) generateOfflineUUID() string {
	uuid := make([]byte, 16)
	_, err := rand.Read(uuid)
	if err != nil {
		panic(err)
	}

	uuid[6] = (uuid[6] & 0x0F) | 0x40
	uuid[8] = (uuid[8] & 0x3F) | 0x80

	return fmt.Sprintf("%x-%x-%x-%x-%x",
		uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
}

func (l *Launcher) PlayMinecraftVersion(versionID string, username string) error {
	absPath, err := filepath.Abs(l.minelauncherPath)
	if err != nil {
		return fmt.Errorf("не удалось получить абсолютный путь для %s: %v", l.minelauncherPath, err)
	}
	l.minelauncherPath = absPath

	err = os.MkdirAll(l.minelauncherPath, os.ModePerm)
	if err != nil {
		return fmt.Errorf("не удалось создать директорию %s: %v", l.minelauncherPath, err)
	}

	versionData, err := l.getVersionDetails(versionID)
	if err != nil {
		return err
	}

	// Объединяем данные версий с учетом inheritsFrom
	versionData, err = l.mergeVersionData(versionData)
	if err != nil {
		return err
	}

	versionDir := filepath.Join(l.minelauncherPath, "versions", versionID)
	err = os.MkdirAll(versionDir, os.ModePerm)
	if err != nil {
		return fmt.Errorf("не удалось создать директорию %s: %v", versionDir, err)
	}
	versionJsonPath := filepath.Join(versionDir, versionID+".json")

	versionDataBytes, err := json.MarshalIndent(versionData, "", "  ")
	if err != nil {
		return fmt.Errorf("ошибка при сериализации данных версии: %v", err)
	}

	err = os.WriteFile(versionJsonPath, versionDataBytes, 0644)
	if err != nil {
		return fmt.Errorf("ошибка при сохранении файла version.json: %v", err)
	}

	// Скачиваем клиентский JAR-файл
	err = l.downloadClientJar(versionData, versionID)
	if err != nil {
		return fmt.Errorf("ошибка при скачивании клиентского JAR-файла: %v", err)
	}

	// Скачиваем библиотеки
	err = l.downloadLibraries(versionData)
	if err != nil {
		return fmt.Errorf("ошибка при скачивании библиотек: %v", err)
	}

	// Скачиваем ассеты
	err = l.downloadAssets(versionData)
	if err != nil {
		return fmt.Errorf("ошибка при скачивании ассетов: %v", err)
	}

	// Валидация файлов
	err = l.verifyAllFiles(versionData, versionID)
	if err != nil {
		return fmt.Errorf("ошибка валидации файлов: %v", err)
	}

	// Создаем необходимые директории
	dirs := []string{
		filepath.Join(l.minelauncherPath, "saves"),
		filepath.Join(l.minelauncherPath, "resourcepacks"),
		filepath.Join(l.minelauncherPath, "logs"),
		filepath.Join(l.minelauncherPath, "runtime"),
	}
	for _, dir := range dirs {
		err := os.MkdirAll(dir, os.ModePerm)
		if err != nil {
			return fmt.Errorf("не удалось создать директорию %s: %v", dir, err)
		}
	}

	// Создаем файл опций, если его нет
	optionsFilePath := filepath.Join(l.minelauncherPath, "options.txt")
	if _, err := os.Stat(optionsFilePath); os.IsNotExist(err) {
		err := os.WriteFile(optionsFilePath, []byte(""), 0644)
		if err != nil {
			return fmt.Errorf("не удалось создать файл %s: %v", optionsFilePath, err)
		}
	}

	// Проверка версии Java
	javaVersionCmd := exec.Command("java", "-version")
	javaVersionOutput, err := javaVersionCmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("ошибка при проверке версии Java: %v", err)
	}
	fmt.Println("Java версия:", string(javaVersionOutput))

	uname := username
	uuid := l.generateOfflineUUID()
	authToken := ""

	// Запуск Minecraft
	err = l.launchMinecraft(versionData, authToken, uname, uuid, versionID)
	if err != nil {
		return fmt.Errorf("ошибка при запуске Minecraft: %v", err)
	}

	fmt.Println("Minecraft запущен успешно!")
	return nil
}



исправь ошибку 


Completely ignored arguments: [legacy]
[16:28:53] [Render thread/INFO]: [STDERR]: [LWJGL] [ERROR] Incompatible Java and native library versions detected.
Possible reasons:
        a) -Djava.library.path is set to a folder containing shared libraries of an older LWJGL version.
        b) The classpath contains jar files of an older LWJGL version.
Possible solutions:
        a) Make sure to not set -Djava.library.path (it is not needed for developing with LWJGL 3) or make
           sure the folder it points to contains the shared libraries of the correct LWJGL version.
        b) Check the classpath and make sure to only have jar files of the same LWJGL version in it.
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ff992e22af7, pid=25696, tid=31668
#
# JRE version: Java(TM) SE Runtime Environment (17.0.6+9) (build 17.0.6+9-LTS-190)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (17.0.6+9-LTS-190, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, windows-amd64)
# Problematic frame:
# V  [jvm.dll+0x3e2af7]
#
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# D:\JBProjects\GOLAND\MineLauncherTest\.legit\hs_err_pid25696.log
#
# If you would like to submit a bug report, please visit:
#   https://bugreport.java.com/bugreport/crash.jsp
#