From 687e2db51b6236ec2abf0dd86dc9a98f71b4a307 Mon Sep 17 00:00:00 2001 From: DIKER0K Date: Thu, 1 Jan 2026 17:45:11 +0500 Subject: [PATCH] add neoforge modpacks support --- src/main/minecraft-launcher.ts | 163 ++++++++++++++++++++--------- src/renderer/pages/LaunchPage.tsx | 166 ++++++++++++++++++++---------- 2 files changed, 228 insertions(+), 101 deletions(-) diff --git a/src/main/minecraft-launcher.ts b/src/main/minecraft-launcher.ts index 95c2d1b..53b1d72 100644 --- a/src/main/minecraft-launcher.ts +++ b/src/main/minecraft-launcher.ts @@ -6,15 +6,13 @@ import extract from 'extract-zip'; import { launch, Version, diagnose } from '@xmcl/core'; import { execSync } from 'child_process'; import { - installDependencies, installFabric, - getFabricLoaders, getVersionList, - install, installTask, installDependenciesTask, + installNeoForged, } from '@xmcl/installer'; -import { Dispatcher, Agent } from 'undici'; +import { Agent } from 'undici'; import { spawn } from 'child_process'; import { AuthService } from './auth-service'; import { API_BASE_URL } from '../renderer/api'; @@ -546,12 +544,14 @@ export function initMinecraftHandlers() { username, memory = 4096, baseVersion = '1.21.4', - fabricVersion = '0.16.14', + fabricVersion = null, + neoForgeVersion = null, packName = 'Comfort', // имя сборки (папка с модами) versionToLaunchOverride = '', // переопределение версии для запуска (например, 1.21.10 для ванили) serverIp = 'popa-popa.ru', serverPort, isVanillaVersion = false, + loaderType = 'fabric', } = gameConfig || {}; const userDataPath = app.getPath('userData'); @@ -590,23 +590,35 @@ export function initMinecraftHandlers() { // Ваниль — запускаем baseVersion (или override) versionToLaunch = effectiveBaseVersion; } else { - // Модпак — запускаем именно fabric-версию - const fabricId = `${effectiveBaseVersion}-fabric${fabricVersion}`; - if (versionsContents.includes(fabricId)) { - versionToLaunch = fabricId; + // Определяем ID версии в зависимости от типа загрузчика + if (loaderType === 'neoforge' && neoForgeVersion) { + const neoForgeId = `neoforge-${neoForgeVersion}`; + if (versionsContents.includes(neoForgeId)) { + versionToLaunch = neoForgeId; + } else { + versionToLaunch = neoForgeId; + } + } else if (fabricVersion) { + const fabricId = `${effectiveBaseVersion}-fabric${fabricVersion}`; + if (versionsContents.includes(fabricId)) { + versionToLaunch = fabricId; + } else { + versionToLaunch = fabricId; + } } else { - // даже если папки нет — installFabric её создаст - versionToLaunch = fabricId; + versionToLaunch = effectiveBaseVersion; } } } - console.log('isVanillaVersion:', isVanillaVersion); - console.log('baseVersion:', baseVersion); - console.log('effectiveBaseVersion:', effectiveBaseVersion); - console.log('fabricVersion:', fabricVersion); - console.log('versionToLaunch:', versionToLaunch); - console.log('packDir:', packDir); + console.log('Конфигурация:', { + loaderType, + neoForgeVersion, + fabricVersion, + effectiveBaseVersion, + versionToLaunch, + versionsContents, + }); // --- Поиск Java --- event.sender.send('installation-status', { @@ -705,35 +717,71 @@ export function initMinecraftHandlers() { } // --- 2. Установка Fabric (только для модпаков) --- - if (!isVanillaVersion && fabricVersion) { - try { - event.sender.send('installation-status', { - step: 'fabric-install', - message: `Установка Fabric ${fabricVersion}...`, - }); + if (!isVanillaVersion) { + if (loaderType === 'neoforge' && neoForgeVersion) { + try { + event.sender.send('installation-status', { + step: 'neoforge-install', + message: `Установка NeoForge ${neoForgeVersion}...`, + }); - event.sender.send( - 'overall-progress', - getGlobalProgress('fabric-install', 0), - ); + event.sender.send( + 'overall-progress', + getGlobalProgress('fabric-install', 0), // Используем фазу fabric-install + ); - console.log('installFabric:', { - minecraftVersion: effectiveBaseVersion, - fabricVersion, - minecraftDir, - }); + console.log('installNeoForged:', { + project: 'neoforge', + version: neoForgeVersion, + minecraftVersion: effectiveBaseVersion, + minecraftDir, + }); - await installFabric({ - minecraftVersion: effectiveBaseVersion, - version: fabricVersion, - minecraft: minecraftDir, - }); - event.sender.send( - 'overall-progress', - getGlobalProgress('fabric-install', 1), - ); - } catch (error) { - console.log('Ошибка при установке Fabric, продолжаем:', error); + // Установка NeoForge + await installNeoForged('neoforge', neoForgeVersion, minecraftDir, { + minecraft: effectiveBaseVersion, + java: javaPath, + side: 'client', + }); + + event.sender.send( + 'overall-progress', + getGlobalProgress('fabric-install', 1), + ); + } catch (error) { + console.log('Ошибка при установке NeoForge, продолжаем:', error); + } + } else if (fabricVersion) { + // Существующий код для Fabric + try { + event.sender.send('installation-status', { + step: 'fabric-install', + message: `Установка Fabric ${fabricVersion}...`, + }); + + event.sender.send( + 'overall-progress', + getGlobalProgress('fabric-install', 0), + ); + + console.log('installFabric:', { + minecraftVersion: effectiveBaseVersion, + fabricVersion, + minecraftDir, + }); + + await installFabric({ + minecraftVersion: effectiveBaseVersion, + version: fabricVersion, + minecraft: minecraftDir, + }); + event.sender.send( + 'overall-progress', + getGlobalProgress('fabric-install', 1), + ); + } catch (error) { + console.log('Ошибка при установке Fabric, продолжаем:', error); + } } } @@ -991,12 +1039,17 @@ export function initMinecraftHandlers() { const versionPath = path.join(versionsDir, item); if (!fs.statSync(versionPath).isDirectory()) continue; - // ❗ Прячем технические fabric-версии типа 1.21.10-fabric0.18.1 - if (item.includes('-fabric')) { + // ❗ Прячем технические версии загрузчиков + if (item.includes('-fabric') || item.includes('-neoforge')) { continue; } - const versionJsonPath = path.join(versionPath, `${item}.json`); + const files = fs.readdirSync(versionPath); + const jsonFile = files.find((f) => f.endsWith('.json')); + + if (!jsonFile) continue; + + const versionJsonPath = path.join(versionPath, jsonFile); let versionInfo = { id: item, name: item, @@ -1270,7 +1323,9 @@ ipcMain.handle('get-version-config', async (event, { versionId }) => { memory: 4096, baseVersion: '1.21.10', serverIp: 'popa-popa.ru', - fabricVersion: '0.18.1', + fabricVersion: null, + neoForgeVersion: null, + loaderType: 'fabric', // 'fabric', 'neoforge', 'vanilla' preserveFiles: ['popa-launcher-config.json'], }; @@ -1282,6 +1337,20 @@ ipcMain.handle('get-version-config', async (event, { versionId }) => { 'https://api.github.com/repos/DIKER0K/Comfort/releases/latest'; config.baseVersion = '1.21.10'; config.fabricVersion = '0.18.1'; + config.loaderType = 'fabric'; + } + + // Если это NeoForge сборка, добавьте соответствующие настройки + if (versionId === 'MyNeoForgePack') { + config.downloadUrl = + 'https://github.com/YOUR_USERNAME/YOUR_NEOFORGE_PACK/releases/latest/download/MyNeoForgePack.zip'; + config.apiReleaseUrl = + 'https://api.github.com/repos/YOUR_USERNAME/YOUR_NEOFORGE_PACK/releases/latest'; + config.baseVersion = '1.21.1'; + config.fabricVersion = null; + config.neoForgeVersion = '20.6.2'; + config.loaderType = 'neoforge'; + config.memory = 6144; // NeoForge может требовать больше памяти } // Если есть конфигурационный файл, загружаем из него diff --git a/src/renderer/pages/LaunchPage.tsx b/src/renderer/pages/LaunchPage.tsx index 77e10dd..593dd43 100644 --- a/src/renderer/pages/LaunchPage.tsx +++ b/src/renderer/pages/LaunchPage.tsx @@ -1,9 +1,4 @@ -import { - Box, - Typography, - Button, - LinearProgress, -} from '@mui/material'; +import { Box, Typography, Button, LinearProgress } from '@mui/material'; import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import ServerStatus from '../components/ServerStatus/ServerStatus'; @@ -13,7 +8,10 @@ import React from 'react'; import SettingsModal from '../components/Settings/SettingsModal'; import CustomNotification from '../components/Notifications/CustomNotification'; import type { NotificationPosition } from '../components/Notifications/CustomNotification'; -import { isNotificationsEnabled, getNotifPositionFromSettings } from '../utils/notifications'; +import { + isNotificationsEnabled, + getNotifPositionFromSettings, +} from '../utils/notifications'; declare global { interface Window { @@ -31,7 +29,6 @@ declare global { interface LaunchPageProps { onLaunchPage?: () => void; launchOptions?: { - // Делаем опциональным downloadUrl: string; apiReleaseUrl: string; versionFileName: string; @@ -40,6 +37,8 @@ interface LaunchPageProps { baseVersion: string; serverIp: string; fabricVersion: string; + neoForgeVersion?: string; + loaderType?: string; }; } @@ -50,6 +49,7 @@ const LaunchPage = ({ const navigate = useNavigate(); const { versionId } = useParams(); const [versionConfig, setVersionConfig] = useState(null); + const [fullVersionConfig, setFullVersionConfig] = useState(null); // Полная конфигурация из Gist // Начальное состояние должно быть пустым или с минимальными значениями const [config, setConfig] = useState<{ @@ -161,6 +161,29 @@ const LaunchPage = ({ }; }, [navigate]); + // Функция для загрузки полной конфигурации версии из Gist + const fetchFullVersionConfig = async (): Promise => { + if (!versionId) return null; + + try { + // Загружаем весь список версий из Gist + const result = await window.electron.ipcRenderer.invoke( + 'get-available-versions', + {}, + ); + + if (result.success && result.versions) { + // Находим нужную версию по ID + const version = result.versions.find((v: any) => v.id === versionId); + return version || null; + } + } catch (error) { + console.error('Ошибка при загрузке полной конфигурации:', error); + } + + return null; + }; + useEffect(() => { const fetchVersionConfig = async () => { if (!versionId) return; @@ -174,6 +197,7 @@ const LaunchPage = ({ // Если конфиг пустой — считаем, что он невалидный и идём по IPC-ветке if (Object.keys(parsedConfig).length > 0) { setVersionConfig(parsedConfig); + setFullVersionConfig(parsedConfig); setConfig({ memory: parsedConfig.memory || 4096, @@ -187,36 +211,50 @@ const LaunchPage = ({ } } - // Если нет в localStorage, запрашиваем с сервера - const result = await window.electron.ipcRenderer.invoke( - 'get-version-config', - { versionId }, - ); + // Загружаем полную конфигурацию из Gist + const fullConfig = await fetchFullVersionConfig(); + if (fullConfig) { + setFullVersionConfig(fullConfig); + + // Сохраняем только config часть для совместимости + setVersionConfig(fullConfig.config || {}); - if (result.success) { - setVersionConfig(result.config); setConfig({ - memory: result.config.memory || 4096, - preserveFiles: result.config.preserveFiles || [], + memory: fullConfig.config?.memory || 4096, + preserveFiles: fullConfig.config?.preserveFiles || [], }); } else { - // Если не удалось получить конфигурацию, используем значения по умолчанию - const defaultConfig = { - downloadUrl: '', - apiReleaseUrl: '', - versionFileName: `${versionId}_version.txt`, - packName: versionId || 'Comfort', - memory: 4096, - baseVersion: '1.21.4', - serverIp: 'popa-popa.ru', - fabricVersion: '0.16.14', - preserveFiles: ['popa-launcher-config.json'], - }; - setVersionConfig(defaultConfig); - setConfig({ - memory: defaultConfig.memory, - preserveFiles: defaultConfig.preserveFiles || [], - }); + // Если не удалось получить конфигурацию из Gist, используем IPC + const result = await window.electron.ipcRenderer.invoke( + 'get-version-config', + { versionId }, + ); + + if (result.success) { + setVersionConfig(result.config); + setConfig({ + memory: result.config.memory || 4096, + preserveFiles: result.config.preserveFiles || [], + }); + } else { + // Если не удалось получить конфигурацию, используем значения по умолчанию + const defaultConfig = { + downloadUrl: '', + apiReleaseUrl: '', + versionFileName: `${versionId}_version.txt`, + packName: versionId || 'Comfort', + memory: 4096, + baseVersion: '1.21.4', + serverIp: 'popa-popa.ru', + fabricVersion: '0.16.14', + preserveFiles: ['popa-launcher-config.json'], + }; + setVersionConfig(defaultConfig); + setConfig({ + memory: defaultConfig.memory, + preserveFiles: defaultConfig.preserveFiles || [], + }); + } } } catch (error) { console.error('Ошибка при получении настроек версии:', error); @@ -266,15 +304,30 @@ const LaunchPage = ({ return; } - // Используем настройки выбранной версии или дефолтные - const currentConfig = versionConfig || { - packName: versionId || 'Comfort', - memory: 4096, - baseVersion: '1.21.4', - serverIp: 'popa-popa.ru', - fabricVersion: '0.16.14', - preserveFiles: [], - }; + // Загружаем полную конфигурацию, если еще не загружена + if (!fullVersionConfig) { + const loadedConfig = await fetchFullVersionConfig(); + if (loadedConfig) { + setFullVersionConfig(loadedConfig); + setVersionConfig(loadedConfig.config || {}); + } + } + + // Используем настройки из Gist или дефолтные + const currentConfig = fullVersionConfig?.config || + versionConfig || { + packName: versionId || 'Comfort', + memory: 4096, + baseVersion: '1.21.4', + serverIp: 'popa-popa.ru', + fabricVersion: '0.16.14', + neoForgeVersion: null, + loaderType: 'fabric', + preserveFiles: [], + }; + + // Получаем версию для запуска из Gist (например, "1.21.1-neoforge21.1.215") + const versionFromGist = fullVersionConfig?.version || null; // Проверяем, является ли это ванильной версией const isVanillaVersion = @@ -318,7 +371,8 @@ const LaunchPage = ({ localStorage.getItem('launcher_config') || '{}', ); - const options = { + // Формируем полные опции для запуска + const options: any = { accessToken: savedConfig.accessToken, uuid: savedConfig.uuid, username: savedConfig.username, @@ -327,22 +381,27 @@ const LaunchPage = ({ packName: versionId || currentConfig.packName, serverIp: currentConfig.serverIp, fabricVersion: currentConfig.fabricVersion, - // Для ванильной версии устанавливаем флаг + neoForgeVersion: currentConfig.neoForgeVersion, + loaderType: currentConfig.loaderType || 'fabric', isVanillaVersion: isVanillaVersion, - versionToLaunchOverride: isVanillaVersion ? versionId : undefined, + versionToLaunchOverride: + versionFromGist || (isVanillaVersion ? versionId : undefined), + // Передаем Gist URL для загрузки конфигурации в процессе запуска + gistUrl: + 'https://gist.githubusercontent.com/DIKER0K/06cd12fb3a4d08b1f0f8c763a7d05e06/raw/versions.json', }; const launchContext = { versionId, packName: versionId || currentConfig.packName, - baseVersion: currentConfig.baseVersion, fabricVersion: currentConfig.fabricVersion, + neoForgeVersion: currentConfig.neoForgeVersion, + loaderType: currentConfig.loaderType, serverIp: currentConfig.serverIp, - isVanillaVersion, - versionToLaunchOverride: isVanillaVersion ? versionId : undefined, - + versionToLaunchOverride: + versionFromGist || (isVanillaVersion ? versionId : undefined), memory: config.memory, }; @@ -358,8 +417,10 @@ const LaunchPage = ({ if (launchResult?.success) { showNotification('Minecraft успешно запущен!', 'success'); + } else if (launchResult?.error) { + showNotification(`Ошибка запуска: ${launchResult.error}`, 'error'); } - } catch (error) { + } catch (error: any) { console.error('Error:', error); showNotification(`Ошибка: ${error.message}`, 'error'); } finally { @@ -408,9 +469,6 @@ const LaunchPage = ({ config: configToSave, }); - // Обновляем launchOptions - launchOptions.memory = config.memory; - showNotification('Настройки сохранены', 'success'); } catch (error) { console.error('Ошибка при сохранении настроек:', error);