add neoforge modpacks support

This commit is contained in:
2026-01-01 17:45:11 +05:00
parent 5dc1744cfd
commit 687e2db51b
2 changed files with 228 additions and 101 deletions

View File

@ -6,15 +6,13 @@ import extract from 'extract-zip';
import { launch, Version, diagnose } from '@xmcl/core'; import { launch, Version, diagnose } from '@xmcl/core';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { import {
installDependencies,
installFabric, installFabric,
getFabricLoaders,
getVersionList, getVersionList,
install,
installTask, installTask,
installDependenciesTask, installDependenciesTask,
installNeoForged,
} from '@xmcl/installer'; } from '@xmcl/installer';
import { Dispatcher, Agent } from 'undici'; import { Agent } from 'undici';
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { AuthService } from './auth-service'; import { AuthService } from './auth-service';
import { API_BASE_URL } from '../renderer/api'; import { API_BASE_URL } from '../renderer/api';
@ -546,12 +544,14 @@ export function initMinecraftHandlers() {
username, username,
memory = 4096, memory = 4096,
baseVersion = '1.21.4', baseVersion = '1.21.4',
fabricVersion = '0.16.14', fabricVersion = null,
neoForgeVersion = null,
packName = 'Comfort', // имя сборки (папка с модами) packName = 'Comfort', // имя сборки (папка с модами)
versionToLaunchOverride = '', // переопределение версии для запуска (например, 1.21.10 для ванили) versionToLaunchOverride = '', // переопределение версии для запуска (например, 1.21.10 для ванили)
serverIp = 'popa-popa.ru', serverIp = 'popa-popa.ru',
serverPort, serverPort,
isVanillaVersion = false, isVanillaVersion = false,
loaderType = 'fabric',
} = gameConfig || {}; } = gameConfig || {};
const userDataPath = app.getPath('userData'); const userDataPath = app.getPath('userData');
@ -590,23 +590,35 @@ export function initMinecraftHandlers() {
// Ваниль — запускаем baseVersion (или override) // Ваниль — запускаем baseVersion (или override)
versionToLaunch = effectiveBaseVersion; versionToLaunch = effectiveBaseVersion;
} else { } else {
// Модпак — запускаем именно fabric-версию // Определяем ID версии в зависимости от типа загрузчика
const fabricId = `${effectiveBaseVersion}-fabric${fabricVersion}`; if (loaderType === 'neoforge' && neoForgeVersion) {
if (versionsContents.includes(fabricId)) { const neoForgeId = `neoforge-${neoForgeVersion}`;
versionToLaunch = fabricId; 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 { } else {
// даже если папки нет — installFabric её создаст versionToLaunch = effectiveBaseVersion;
versionToLaunch = fabricId;
} }
} }
} }
console.log('isVanillaVersion:', isVanillaVersion); console.log('Конфигурация:', {
console.log('baseVersion:', baseVersion); loaderType,
console.log('effectiveBaseVersion:', effectiveBaseVersion); neoForgeVersion,
console.log('fabricVersion:', fabricVersion); fabricVersion,
console.log('versionToLaunch:', versionToLaunch); effectiveBaseVersion,
console.log('packDir:', packDir); versionToLaunch,
versionsContents,
});
// --- Поиск Java --- // --- Поиск Java ---
event.sender.send('installation-status', { event.sender.send('installation-status', {
@ -705,35 +717,71 @@ export function initMinecraftHandlers() {
} }
// --- 2. Установка Fabric (только для модпаков) --- // --- 2. Установка Fabric (только для модпаков) ---
if (!isVanillaVersion && fabricVersion) { if (!isVanillaVersion) {
try { if (loaderType === 'neoforge' && neoForgeVersion) {
event.sender.send('installation-status', { try {
step: 'fabric-install', event.sender.send('installation-status', {
message: `Установка Fabric ${fabricVersion}...`, step: 'neoforge-install',
}); message: `Установка NeoForge ${neoForgeVersion}...`,
});
event.sender.send( event.sender.send(
'overall-progress', 'overall-progress',
getGlobalProgress('fabric-install', 0), getGlobalProgress('fabric-install', 0), // Используем фазу fabric-install
); );
console.log('installFabric:', { console.log('installNeoForged:', {
minecraftVersion: effectiveBaseVersion, project: 'neoforge',
fabricVersion, version: neoForgeVersion,
minecraftDir, minecraftVersion: effectiveBaseVersion,
}); minecraftDir,
});
await installFabric({ // Установка NeoForge
minecraftVersion: effectiveBaseVersion, await installNeoForged('neoforge', neoForgeVersion, minecraftDir, {
version: fabricVersion, minecraft: effectiveBaseVersion,
minecraft: minecraftDir, java: javaPath,
}); side: 'client',
event.sender.send( });
'overall-progress',
getGlobalProgress('fabric-install', 1), event.sender.send(
); 'overall-progress',
} catch (error) { getGlobalProgress('fabric-install', 1),
console.log('Ошибка при установке Fabric, продолжаем:', error); );
} 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); const versionPath = path.join(versionsDir, item);
if (!fs.statSync(versionPath).isDirectory()) continue; 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; 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 = { let versionInfo = {
id: item, id: item,
name: item, name: item,
@ -1270,7 +1323,9 @@ ipcMain.handle('get-version-config', async (event, { versionId }) => {
memory: 4096, memory: 4096,
baseVersion: '1.21.10', baseVersion: '1.21.10',
serverIp: 'popa-popa.ru', serverIp: 'popa-popa.ru',
fabricVersion: '0.18.1', fabricVersion: null,
neoForgeVersion: null,
loaderType: 'fabric', // 'fabric', 'neoforge', 'vanilla'
preserveFiles: ['popa-launcher-config.json'], 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'; 'https://api.github.com/repos/DIKER0K/Comfort/releases/latest';
config.baseVersion = '1.21.10'; config.baseVersion = '1.21.10';
config.fabricVersion = '0.18.1'; 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 может требовать больше памяти
} }
// Если есть конфигурационный файл, загружаем из него // Если есть конфигурационный файл, загружаем из него

View File

@ -1,9 +1,4 @@
import { import { Box, Typography, Button, LinearProgress } from '@mui/material';
Box,
Typography,
Button,
LinearProgress,
} from '@mui/material';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import ServerStatus from '../components/ServerStatus/ServerStatus'; import ServerStatus from '../components/ServerStatus/ServerStatus';
@ -13,7 +8,10 @@ import React from 'react';
import SettingsModal from '../components/Settings/SettingsModal'; import SettingsModal from '../components/Settings/SettingsModal';
import CustomNotification from '../components/Notifications/CustomNotification'; import CustomNotification from '../components/Notifications/CustomNotification';
import type { NotificationPosition } 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 { declare global {
interface Window { interface Window {
@ -31,7 +29,6 @@ declare global {
interface LaunchPageProps { interface LaunchPageProps {
onLaunchPage?: () => void; onLaunchPage?: () => void;
launchOptions?: { launchOptions?: {
// Делаем опциональным
downloadUrl: string; downloadUrl: string;
apiReleaseUrl: string; apiReleaseUrl: string;
versionFileName: string; versionFileName: string;
@ -40,6 +37,8 @@ interface LaunchPageProps {
baseVersion: string; baseVersion: string;
serverIp: string; serverIp: string;
fabricVersion: string; fabricVersion: string;
neoForgeVersion?: string;
loaderType?: string;
}; };
} }
@ -50,6 +49,7 @@ const LaunchPage = ({
const navigate = useNavigate(); const navigate = useNavigate();
const { versionId } = useParams(); const { versionId } = useParams();
const [versionConfig, setVersionConfig] = useState<any>(null); const [versionConfig, setVersionConfig] = useState<any>(null);
const [fullVersionConfig, setFullVersionConfig] = useState<any>(null); // Полная конфигурация из Gist
// Начальное состояние должно быть пустым или с минимальными значениями // Начальное состояние должно быть пустым или с минимальными значениями
const [config, setConfig] = useState<{ const [config, setConfig] = useState<{
@ -161,6 +161,29 @@ const LaunchPage = ({
}; };
}, [navigate]); }, [navigate]);
// Функция для загрузки полной конфигурации версии из Gist
const fetchFullVersionConfig = async (): Promise<any> => {
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(() => { useEffect(() => {
const fetchVersionConfig = async () => { const fetchVersionConfig = async () => {
if (!versionId) return; if (!versionId) return;
@ -174,6 +197,7 @@ const LaunchPage = ({
// Если конфиг пустой — считаем, что он невалидный и идём по IPC-ветке // Если конфиг пустой — считаем, что он невалидный и идём по IPC-ветке
if (Object.keys(parsedConfig).length > 0) { if (Object.keys(parsedConfig).length > 0) {
setVersionConfig(parsedConfig); setVersionConfig(parsedConfig);
setFullVersionConfig(parsedConfig);
setConfig({ setConfig({
memory: parsedConfig.memory || 4096, memory: parsedConfig.memory || 4096,
@ -187,36 +211,50 @@ const LaunchPage = ({
} }
} }
// Если нет в localStorage, запрашиваем с сервера // Загружаем полную конфигурацию из Gist
const result = await window.electron.ipcRenderer.invoke( const fullConfig = await fetchFullVersionConfig();
'get-version-config', if (fullConfig) {
{ versionId }, setFullVersionConfig(fullConfig);
);
// Сохраняем только config часть для совместимости
setVersionConfig(fullConfig.config || {});
if (result.success) {
setVersionConfig(result.config);
setConfig({ setConfig({
memory: result.config.memory || 4096, memory: fullConfig.config?.memory || 4096,
preserveFiles: result.config.preserveFiles || [], preserveFiles: fullConfig.config?.preserveFiles || [],
}); });
} else { } else {
// Если не удалось получить конфигурацию, используем значения по умолчанию // Если не удалось получить конфигурацию из Gist, используем IPC
const defaultConfig = { const result = await window.electron.ipcRenderer.invoke(
downloadUrl: '', 'get-version-config',
apiReleaseUrl: '', { versionId },
versionFileName: `${versionId}_version.txt`, );
packName: versionId || 'Comfort',
memory: 4096, if (result.success) {
baseVersion: '1.21.4', setVersionConfig(result.config);
serverIp: 'popa-popa.ru', setConfig({
fabricVersion: '0.16.14', memory: result.config.memory || 4096,
preserveFiles: ['popa-launcher-config.json'], preserveFiles: result.config.preserveFiles || [],
}; });
setVersionConfig(defaultConfig); } else {
setConfig({ // Если не удалось получить конфигурацию, используем значения по умолчанию
memory: defaultConfig.memory, const defaultConfig = {
preserveFiles: defaultConfig.preserveFiles || [], 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) { } catch (error) {
console.error('Ошибка при получении настроек версии:', error); console.error('Ошибка при получении настроек версии:', error);
@ -266,15 +304,30 @@ const LaunchPage = ({
return; return;
} }
// Используем настройки выбранной версии или дефолтные // Загружаем полную конфигурацию, если еще не загружена
const currentConfig = versionConfig || { if (!fullVersionConfig) {
packName: versionId || 'Comfort', const loadedConfig = await fetchFullVersionConfig();
memory: 4096, if (loadedConfig) {
baseVersion: '1.21.4', setFullVersionConfig(loadedConfig);
serverIp: 'popa-popa.ru', setVersionConfig(loadedConfig.config || {});
fabricVersion: '0.16.14', }
preserveFiles: [], }
};
// Используем настройки из 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 = const isVanillaVersion =
@ -318,7 +371,8 @@ const LaunchPage = ({
localStorage.getItem('launcher_config') || '{}', localStorage.getItem('launcher_config') || '{}',
); );
const options = { // Формируем полные опции для запуска
const options: any = {
accessToken: savedConfig.accessToken, accessToken: savedConfig.accessToken,
uuid: savedConfig.uuid, uuid: savedConfig.uuid,
username: savedConfig.username, username: savedConfig.username,
@ -327,22 +381,27 @@ const LaunchPage = ({
packName: versionId || currentConfig.packName, packName: versionId || currentConfig.packName,
serverIp: currentConfig.serverIp, serverIp: currentConfig.serverIp,
fabricVersion: currentConfig.fabricVersion, fabricVersion: currentConfig.fabricVersion,
// Для ванильной версии устанавливаем флаг neoForgeVersion: currentConfig.neoForgeVersion,
loaderType: currentConfig.loaderType || 'fabric',
isVanillaVersion: isVanillaVersion, 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 = { const launchContext = {
versionId, versionId,
packName: versionId || currentConfig.packName, packName: versionId || currentConfig.packName,
baseVersion: currentConfig.baseVersion, baseVersion: currentConfig.baseVersion,
fabricVersion: currentConfig.fabricVersion, fabricVersion: currentConfig.fabricVersion,
neoForgeVersion: currentConfig.neoForgeVersion,
loaderType: currentConfig.loaderType,
serverIp: currentConfig.serverIp, serverIp: currentConfig.serverIp,
isVanillaVersion, isVanillaVersion,
versionToLaunchOverride: isVanillaVersion ? versionId : undefined, versionToLaunchOverride:
versionFromGist || (isVanillaVersion ? versionId : undefined),
memory: config.memory, memory: config.memory,
}; };
@ -358,8 +417,10 @@ const LaunchPage = ({
if (launchResult?.success) { if (launchResult?.success) {
showNotification('Minecraft успешно запущен!', 'success'); showNotification('Minecraft успешно запущен!', 'success');
} else if (launchResult?.error) {
showNotification(`Ошибка запуска: ${launchResult.error}`, 'error');
} }
} catch (error) { } catch (error: any) {
console.error('Error:', error); console.error('Error:', error);
showNotification(`Ошибка: ${error.message}`, 'error'); showNotification(`Ошибка: ${error.message}`, 'error');
} finally { } finally {
@ -408,9 +469,6 @@ const LaunchPage = ({
config: configToSave, config: configToSave,
}); });
// Обновляем launchOptions
launchOptions.memory = config.memory;
showNotification('Настройки сохранены', 'success'); showNotification('Настройки сохранены', 'success');
} catch (error) { } catch (error) {
console.error('Ошибка при сохранении настроек:', error); console.error('Ошибка при сохранении настроек:', error);