1390 lines
50 KiB
TypeScript
1390 lines
50 KiB
TypeScript
import path from 'path';
|
||
import { app, ipcMain } from 'electron';
|
||
import fs from 'fs';
|
||
import https from 'https';
|
||
import extract from 'extract-zip';
|
||
import { launch, Version, diagnose } from '@xmcl/core';
|
||
import { execSync } from 'child_process';
|
||
import {
|
||
installFabric,
|
||
getVersionList,
|
||
installTask,
|
||
installDependenciesTask,
|
||
installNeoForged,
|
||
} from '@xmcl/installer';
|
||
import { Agent } from 'undici';
|
||
import { spawn } from 'child_process';
|
||
import { AuthService } from './auth-service';
|
||
import { API_BASE_URL } from '../renderer/api';
|
||
|
||
app.setName('.popa-popa');
|
||
|
||
// const CDN = 'https://cdn.minecraft.popa-popa.ru';
|
||
|
||
// const DOWNLOAD_OPTIONS = {
|
||
// // assets (objects/)
|
||
// assetsHost: `${CDN}/assets/objects`,
|
||
|
||
// // библиотеки (jar'ы)
|
||
// libraryHost(library: { path: any }) {
|
||
// return `${CDN}/libraries/${library.path}`;
|
||
// },
|
||
|
||
// assetsIndexUrl: (version: any) =>
|
||
// `${CDN}/assets/indexes/${version.assetIndex.id}.json`,
|
||
|
||
// // версии
|
||
// json(versionInfo: { id: any }) {
|
||
// return `${CDN}/versions/${versionInfo.id}/${versionInfo.id}.json`;
|
||
// },
|
||
// client(resolved: { id: any }) {
|
||
// return `${CDN}/versions/${resolved.id}/${resolved.id}.jar`;
|
||
// },
|
||
// };
|
||
|
||
const INSTALL_PHASES = [
|
||
{ id: 'download', weight: 0.25 }, // 25% — скачивание сборки
|
||
{ id: 'minecraft-install', weight: 0.3 }, // 30% — ваниль
|
||
{ id: 'fabric-install', weight: 0.15 }, // 15% — Fabric
|
||
{ id: 'dependencies', weight: 0.25 }, // 25% — библиотеки/ресурсы
|
||
{ id: 'launch', weight: 0.05 }, // 5% — запуск
|
||
] as const;
|
||
|
||
type InstallPhaseId = (typeof INSTALL_PHASES)[number]['id'];
|
||
|
||
function getGlobalProgress(phaseId: InstallPhaseId, localProgress01: number) {
|
||
let offset = 0;
|
||
let weight = 0;
|
||
|
||
for (const phase of INSTALL_PHASES) {
|
||
if (phase.id === phaseId) {
|
||
weight = phase.weight;
|
||
break;
|
||
}
|
||
offset += phase.weight;
|
||
}
|
||
|
||
if (!weight) return 100;
|
||
|
||
const clampedLocal = Math.max(0, Math.min(1, localProgress01));
|
||
const global = (offset + clampedLocal * weight) * 100;
|
||
|
||
return Math.round(Math.max(0, Math.min(global, 100)));
|
||
}
|
||
|
||
// Константы
|
||
const AUTHLIB_INJECTOR_FILENAME = 'authlib-injector-1.2.5.jar';
|
||
const MCSTATUS_API_URL = 'https://api.mcstatus.io/v2/status/java/';
|
||
|
||
// Создаем экземпляр сервиса аутентификации
|
||
const authService = new AuthService();
|
||
|
||
const agent = new Agent({
|
||
connections: 16, // максимум 16 одновременных соединений (скачиваний)
|
||
// тут можно задать и другие параметры при необходимости
|
||
});
|
||
|
||
let currentMinecraftProcess: any | null = null;
|
||
|
||
// Модифицированная функция для получения последней версии релиза с произвольного URL
|
||
export async function getLatestReleaseVersion(apiUrl: string): Promise<string> {
|
||
try {
|
||
const response = await fetch(apiUrl);
|
||
const data = await response.json();
|
||
return data.tag_name || '0.0.0';
|
||
} catch (error) {
|
||
console.error('Failed to fetch latest version:', error);
|
||
return '0.0.0';
|
||
}
|
||
}
|
||
|
||
// Функция для загрузки файла
|
||
export async function downloadFile(
|
||
url: string,
|
||
dest: string,
|
||
progressCallback: (progress: number) => void,
|
||
): Promise<void> {
|
||
return new Promise((resolve, reject) => {
|
||
const file = fs.createWriteStream(dest);
|
||
let downloadedSize = 0;
|
||
let totalSize = 0;
|
||
let redirectCount = 0;
|
||
|
||
const makeRequest = (requestUrl: string) => {
|
||
https
|
||
.get(requestUrl, (response) => {
|
||
// Обрабатываем редиректы
|
||
if (response.statusCode === 301 || response.statusCode === 302) {
|
||
if (redirectCount++ > 5) {
|
||
reject(new Error('Too many redirects'));
|
||
return;
|
||
}
|
||
makeRequest(response.headers.location!);
|
||
return;
|
||
}
|
||
|
||
if (response.statusCode !== 200) {
|
||
reject(new Error(`Server returned ${response.statusCode}`));
|
||
return;
|
||
}
|
||
|
||
totalSize = parseInt(response.headers['content-length'] || '0', 10);
|
||
|
||
response.on('data', (chunk) => {
|
||
downloadedSize += chunk.length;
|
||
const progress = Math.round((downloadedSize / totalSize) * 100);
|
||
progressCallback(progress);
|
||
});
|
||
|
||
response.pipe(file);
|
||
|
||
file.on('finish', () => {
|
||
file.close();
|
||
// Проверяем, что файл скачан полностью
|
||
if (downloadedSize !== totalSize && totalSize > 0) {
|
||
fs.unlink(dest, () => {
|
||
reject(new Error('File download incomplete'));
|
||
});
|
||
return;
|
||
}
|
||
resolve();
|
||
});
|
||
})
|
||
.on('error', (err) => {
|
||
fs.unlink(dest, () => reject(err));
|
||
});
|
||
};
|
||
|
||
makeRequest(url);
|
||
});
|
||
}
|
||
|
||
// Добавим функцию для определения версии Java
|
||
export async function getJavaVersion(
|
||
javaPath: string,
|
||
): Promise<{ version: string; majorVersion: number }> {
|
||
return new Promise((resolve, reject) => {
|
||
const process = spawn(javaPath, ['-version']);
|
||
let output = '';
|
||
|
||
process.stderr.on('data', (data) => {
|
||
output += data.toString();
|
||
});
|
||
|
||
process.on('close', (code) => {
|
||
if (code === 0) {
|
||
// Извлекаем версию из вывода (например, "java version "1.8.0_291"")
|
||
const versionMatch = output.match(/version "([^"]+)"/);
|
||
if (versionMatch) {
|
||
const version = versionMatch[1];
|
||
// Определяем major версию
|
||
let majorVersion = 8; // По умолчанию предполагаем Java 8
|
||
|
||
if (version.startsWith('1.8')) {
|
||
majorVersion = 8;
|
||
} else if (version.match(/^(9|10|11|12|13|14|15|16)/)) {
|
||
majorVersion = parseInt(version.split('.')[0], 10);
|
||
} else if (version.match(/^1\.(9|10|11|12|13|14|15|16)/)) {
|
||
majorVersion = parseInt(version.split('.')[1], 10);
|
||
} else if (version.match(/^([0-9]+)/)) {
|
||
majorVersion = parseInt(version.match(/^([0-9]+)/)?.[1] || '0', 10);
|
||
}
|
||
|
||
resolve({ version, majorVersion });
|
||
} else {
|
||
reject(new Error('Unable to parse Java version'));
|
||
}
|
||
} else {
|
||
reject(new Error(`Java process exited with code ${code}`));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// Модифицируем функцию findJava, чтобы она находила все версии Java и определяла их версии
|
||
export async function findJavaVersions(): Promise<
|
||
Array<{ path: string; version: string; majorVersion: number }>
|
||
> {
|
||
const javaPaths: string[] = [];
|
||
const results: Array<{
|
||
path: string;
|
||
version: string;
|
||
majorVersion: number;
|
||
}> = [];
|
||
|
||
try {
|
||
// 1. Сначала проверяем переменную JAVA_HOME
|
||
if (process.env.JAVA_HOME) {
|
||
const javaPath = path.join(
|
||
process.env.JAVA_HOME,
|
||
'bin',
|
||
'java' + (process.platform === 'win32' ? '.exe' : ''),
|
||
);
|
||
if (fs.existsSync(javaPath)) {
|
||
javaPaths.push(javaPath);
|
||
}
|
||
}
|
||
|
||
// 2. Проверяем стандартные пути установки в зависимости от платформы
|
||
const checkPaths: string[] = [];
|
||
|
||
if (process.platform === 'win32') {
|
||
// Windows
|
||
const programFiles = process.env['ProgramFiles'] || 'C:\\Program Files';
|
||
const programFilesX86 =
|
||
process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
|
||
|
||
// JDK пути
|
||
[
|
||
'Java',
|
||
'AdoptOpenJDK',
|
||
'Eclipse Adoptium',
|
||
'BellSoft',
|
||
'Zulu',
|
||
'Amazon Corretto',
|
||
'Microsoft',
|
||
].forEach((vendor) => {
|
||
checkPaths.push(path.join(programFiles, vendor));
|
||
checkPaths.push(path.join(programFilesX86, vendor));
|
||
});
|
||
} else if (process.platform === 'darwin') {
|
||
// macOS
|
||
checkPaths.push('/Library/Java/JavaVirtualMachines');
|
||
checkPaths.push('/System/Library/Java/JavaVirtualMachines');
|
||
checkPaths.push('/usr/libexec/java_home');
|
||
} else {
|
||
// Linux
|
||
checkPaths.push('/usr/lib/jvm');
|
||
checkPaths.push('/usr/java');
|
||
checkPaths.push('/opt/java');
|
||
}
|
||
|
||
// Проверяем каждый путь
|
||
for (const basePath of checkPaths) {
|
||
if (fs.existsSync(basePath)) {
|
||
try {
|
||
// Находим подпапки с JDK/JRE
|
||
const entries = fs.readdirSync(basePath, { withFileTypes: true });
|
||
for (const entry of entries) {
|
||
if (entry.isDirectory()) {
|
||
// Проверяем наличие исполняемого файла java в bin
|
||
const javaPath = path.join(
|
||
basePath,
|
||
entry.name,
|
||
'bin',
|
||
'java' + (process.platform === 'win32' ? '.exe' : ''),
|
||
);
|
||
|
||
if (fs.existsSync(javaPath)) {
|
||
javaPaths.push(javaPath);
|
||
}
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error(`Ошибка при сканировании ${basePath}:`, err);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3. Пробуем найти java в PATH через команду which/where
|
||
try {
|
||
const command =
|
||
process.platform === 'win32' ? 'where java' : 'which java';
|
||
const javaPathFromCmd = execSync(command).toString().trim().split('\n');
|
||
|
||
javaPathFromCmd.forEach((path) => {
|
||
if (path && fs.existsSync(path) && !javaPaths.includes(path)) {
|
||
javaPaths.push(path);
|
||
}
|
||
});
|
||
} catch (err) {
|
||
console.error('Ошибка при поиске java через PATH:', err);
|
||
}
|
||
|
||
// Получаем информацию о версиях найденных Java
|
||
for (const javaPath of javaPaths) {
|
||
try {
|
||
const versionInfo = await getJavaVersion(javaPath);
|
||
results.push({
|
||
path: javaPath,
|
||
version: versionInfo.version,
|
||
majorVersion: versionInfo.majorVersion,
|
||
});
|
||
} catch (err) {
|
||
console.error(`Ошибка при определении версии для ${javaPath}:`, err);
|
||
}
|
||
}
|
||
|
||
// Сортируем результаты по majorVersion (от меньшей к большей)
|
||
return results.sort((a, b) => a.majorVersion - b.majorVersion);
|
||
} catch (error) {
|
||
console.error('Ошибка при поиске версий Java:', error);
|
||
return results;
|
||
}
|
||
}
|
||
|
||
// Обновим функцию findJava для использования Java 8
|
||
export async function findJava(): Promise<string> {
|
||
try {
|
||
console.log('Поиск доступных версий Java...');
|
||
|
||
const javaVersions = await findJavaVersions();
|
||
|
||
if (javaVersions.length === 0) {
|
||
throw new Error('Java не найдена. Установите Java и повторите попытку.');
|
||
}
|
||
|
||
console.log('Найденные версии Java:');
|
||
javaVersions.forEach((java) => {
|
||
console.log(
|
||
`- Java ${java.majorVersion} (${java.version}) по пути: ${java.path}`,
|
||
);
|
||
});
|
||
|
||
// Предпочитаем Java 21 или 17 для совместимости с authlib-injector
|
||
const preferredVersions = [24, 21, 17, 11];
|
||
|
||
for (const preferredVersion of preferredVersions) {
|
||
const preferred = javaVersions.find(
|
||
(java) => java.majorVersion === preferredVersion,
|
||
);
|
||
if (preferred) {
|
||
console.log(
|
||
`Выбрана предпочтительная версия Java ${preferredVersion}: ${preferred.path}`,
|
||
);
|
||
return preferred.path;
|
||
}
|
||
}
|
||
|
||
// Если не нашли предпочтительные версии, берем самую старую версию
|
||
console.log(`Выбрана доступная версия Java: ${javaVersions[0].path}`);
|
||
return javaVersions[0].path;
|
||
} catch (error) {
|
||
console.error('Ошибка при поиске Java:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// Добавим функцию для проверки/копирования authlib-injector (как в C#)
|
||
async function ensureAuthlibInjectorExists(appPath: string): Promise<string> {
|
||
const authlibPath = path.join(appPath, AUTHLIB_INJECTOR_FILENAME);
|
||
|
||
// Проверяем, существует ли файл
|
||
if (fs.existsSync(authlibPath)) {
|
||
console.log(
|
||
`Файл ${AUTHLIB_INJECTOR_FILENAME} уже существует: ${authlibPath}`,
|
||
);
|
||
return authlibPath;
|
||
}
|
||
|
||
// Ищем authlib в ресурсах приложения
|
||
const resourcePath = path.join(app.getAppPath(), AUTHLIB_INJECTOR_FILENAME);
|
||
|
||
if (fs.existsSync(resourcePath)) {
|
||
console.log(`Копирование ${AUTHLIB_INJECTOR_FILENAME} из ресурсов...`);
|
||
fs.copyFileSync(resourcePath, authlibPath);
|
||
console.log(`Файл успешно скопирован: ${authlibPath}`);
|
||
return authlibPath;
|
||
}
|
||
|
||
// Если не нашли локальный файл - скачиваем его
|
||
console.log(`Скачивание ${AUTHLIB_INJECTOR_FILENAME}...`);
|
||
await downloadFile(
|
||
`https://github.com/yushijinhun/authlib-injector/releases/download/v1.2.5/${AUTHLIB_INJECTOR_FILENAME}`,
|
||
authlibPath,
|
||
(progress) => {
|
||
console.log(`Прогресс скачивания: ${progress}%`);
|
||
},
|
||
);
|
||
|
||
return authlibPath;
|
||
}
|
||
|
||
// Инициализация IPC обработчиков
|
||
export function initMinecraftHandlers() {
|
||
// Обработчик для скачивания и распаковки
|
||
ipcMain.handle('download-and-extract', async (event, options) => {
|
||
try {
|
||
const {
|
||
downloadUrl = 'https://github.com/DIKER0K/Comfort/releases/latest/download/Comfort.zip',
|
||
apiReleaseUrl = 'https://api.github.com/repos/DIKER0K/Comfort/releases/latest',
|
||
versionFileName = 'comfort_version.txt',
|
||
packName = 'Comfort',
|
||
preserveFiles = [], // Новый параметр: список файлов/папок для сохранения
|
||
} = options || {};
|
||
|
||
const userDataPath = app.getPath('userData');
|
||
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||
const versionsDir = path.join(minecraftDir, 'versions');
|
||
const versionFilePath = path.join(minecraftDir, versionFileName);
|
||
|
||
// Получаем текущую и последнюю версии
|
||
const latestVersion = await getLatestReleaseVersion(apiReleaseUrl);
|
||
let currentVersion = '';
|
||
|
||
// Проверяем текущую версию, если файл существует
|
||
if (fs.existsSync(versionFilePath)) {
|
||
currentVersion = fs.readFileSync(versionFilePath, 'utf-8').trim();
|
||
}
|
||
|
||
// Проверяем, нужно ли обновление
|
||
if (currentVersion === latestVersion) {
|
||
return {
|
||
success: true,
|
||
updated: false,
|
||
version: currentVersion,
|
||
packName,
|
||
};
|
||
}
|
||
|
||
const tempDir = path.join(userDataPath, 'temp');
|
||
const packDir = path.join(versionsDir, packName); // Директория пакета
|
||
|
||
// Создаем/очищаем временную директорию
|
||
if (fs.existsSync(tempDir)) {
|
||
fs.rmSync(tempDir, { recursive: true });
|
||
}
|
||
fs.mkdirSync(tempDir, { recursive: true });
|
||
|
||
const zipPath = path.join(tempDir, `${packName}.zip`);
|
||
|
||
// Скачиваем файл
|
||
await downloadFile(downloadUrl, zipPath, (progress) => {
|
||
event.sender.send('download-progress', progress);
|
||
|
||
const global = getGlobalProgress('download', progress / 100);
|
||
event.sender.send('overall-progress', global);
|
||
});
|
||
|
||
// Проверяем архив
|
||
const fileStats = fs.statSync(zipPath);
|
||
if (fileStats.size < 1024) {
|
||
throw new Error('Downloaded file is too small, likely corrupted');
|
||
}
|
||
|
||
// Создаем папку versions если её нет
|
||
if (!fs.existsSync(versionsDir)) {
|
||
fs.mkdirSync(versionsDir, { recursive: true });
|
||
}
|
||
|
||
// Сохраняем файлы/папки, которые нужно оставить
|
||
const backupDir = path.join(tempDir, 'backup');
|
||
fs.mkdirSync(backupDir, { recursive: true });
|
||
|
||
// Проверка и бэкап указанных файлов/папок
|
||
for (const filePath of preserveFiles) {
|
||
const fullPath = path.join(packDir, filePath);
|
||
|
||
if (fs.existsSync(fullPath)) {
|
||
const backupPath = path.join(backupDir, filePath);
|
||
|
||
// Создаем необходимые директории для бэкапа
|
||
const backupDirPath = path.dirname(backupPath);
|
||
if (!fs.existsSync(backupDirPath)) {
|
||
fs.mkdirSync(backupDirPath, { recursive: true });
|
||
}
|
||
|
||
// Копируем файл или директорию
|
||
if (fs.lstatSync(fullPath).isDirectory()) {
|
||
fs.cpSync(fullPath, backupPath, { recursive: true });
|
||
console.log(`Директория ${filePath} сохранена во временный бэкап`);
|
||
} else {
|
||
fs.copyFileSync(fullPath, backupPath);
|
||
console.log(`Файл ${filePath} сохранен во временный бэкап`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Распаковываем архив напрямую в папку versions
|
||
await extract(zipPath, { dir: versionsDir });
|
||
fs.unlinkSync(zipPath);
|
||
|
||
// Восстанавливаем файлы/папки из бэкапа
|
||
for (const filePath of preserveFiles) {
|
||
const backupPath = path.join(backupDir, filePath);
|
||
if (fs.existsSync(backupPath)) {
|
||
const targetPath = path.join(packDir, filePath);
|
||
|
||
// Создаем необходимые директории для восстановления
|
||
const targetDirPath = path.dirname(targetPath);
|
||
if (!fs.existsSync(targetDirPath)) {
|
||
fs.mkdirSync(targetDirPath, { recursive: true });
|
||
}
|
||
|
||
// Копируем обратно файл или директорию
|
||
if (fs.lstatSync(backupPath).isDirectory()) {
|
||
fs.cpSync(backupPath, targetPath, { recursive: true });
|
||
console.log(`Директория ${filePath} восстановлена из бэкапа`);
|
||
} else {
|
||
fs.copyFileSync(backupPath, targetPath);
|
||
console.log(`Файл ${filePath} восстановлен из бэкапа`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Сохраняем новую версию
|
||
fs.writeFileSync(versionFilePath, latestVersion);
|
||
|
||
// Удаляем временную директорию
|
||
fs.rmSync(tempDir, { recursive: true });
|
||
|
||
return { success: true, updated: true, version: latestVersion, packName };
|
||
} catch (error) {
|
||
console.error('Error in download-and-extract:', error);
|
||
throw error;
|
||
}
|
||
});
|
||
|
||
// Обработчик для запуска Minecraft
|
||
ipcMain.handle('launch-minecraft', async (event, gameConfig) => {
|
||
try {
|
||
const {
|
||
accessToken,
|
||
uuid,
|
||
username,
|
||
memory = 4096,
|
||
baseVersion = '1.21.4',
|
||
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');
|
||
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||
const versionsDir = path.join(minecraftDir, 'versions');
|
||
fs.mkdirSync(versionsDir, { recursive: true });
|
||
|
||
// gamePath:
|
||
// - ваниль → .popa-popa
|
||
// - модпак → .popa-popa/versions/Comfort (или другое packName)
|
||
const packDir = isVanillaVersion
|
||
? minecraftDir
|
||
: path.join(versionsDir, packName);
|
||
if (!fs.existsSync(packDir)) {
|
||
fs.mkdirSync(packDir, { recursive: true });
|
||
}
|
||
|
||
const versionsContents = fs.existsSync(versionsDir)
|
||
? fs.readdirSync(versionsDir)
|
||
: [];
|
||
console.log('Доступные версии:', versionsContents);
|
||
|
||
// --- Определяем базовую / фактическую версию ---
|
||
let effectiveBaseVersion = baseVersion;
|
||
|
||
// Для ванили считаем базовой именно ту, которую хотим запустить
|
||
if (isVanillaVersion && versionToLaunchOverride) {
|
||
effectiveBaseVersion = versionToLaunchOverride;
|
||
}
|
||
|
||
let versionToLaunch: string | undefined =
|
||
versionToLaunchOverride || undefined;
|
||
|
||
if (!versionToLaunch) {
|
||
if (isVanillaVersion) {
|
||
// Ваниль — запускаем baseVersion (или override)
|
||
versionToLaunch = effectiveBaseVersion;
|
||
} else {
|
||
// Определяем ID версии в зависимости от типа загрузчика
|
||
if (loaderType === 'neoforge' && neoForgeVersion) {
|
||
// NeoForge создает версию с ID "neoforge-{version}"
|
||
const neoForgeId = `neoforge-${neoForgeVersion}`;
|
||
|
||
// Проверяем, существует ли такая версия
|
||
if (versionsContents.includes(neoForgeId)) {
|
||
versionToLaunch = neoForgeId;
|
||
} else {
|
||
// Если не существует, пробуем комбинированный ID для совместимости
|
||
const combinedId = `${effectiveBaseVersion}-neoforge${neoForgeVersion}`;
|
||
versionToLaunch = combinedId;
|
||
|
||
// Логируем для отладки
|
||
console.log(
|
||
'NeoForge версия не найдена, используем комбинированный ID:',
|
||
combinedId,
|
||
);
|
||
}
|
||
} else if (fabricVersion) {
|
||
// Fabric создает версию с ID "{minecraftVersion}-fabric{fabricVersion}"
|
||
const fabricId = `${effectiveBaseVersion}-fabric${fabricVersion}`;
|
||
if (versionsContents.includes(fabricId)) {
|
||
versionToLaunch = fabricId;
|
||
} else {
|
||
versionToLaunch = fabricId;
|
||
}
|
||
} else {
|
||
versionToLaunch = effectiveBaseVersion;
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('Конфигурация:', {
|
||
loaderType,
|
||
neoForgeVersion,
|
||
fabricVersion,
|
||
effectiveBaseVersion,
|
||
versionToLaunch,
|
||
versionsContents,
|
||
});
|
||
|
||
// --- Поиск Java ---
|
||
event.sender.send('installation-status', {
|
||
step: 'java',
|
||
message: 'Поиск Java...',
|
||
});
|
||
|
||
console.log('Поиск Java...');
|
||
|
||
let javaPath = 'java';
|
||
try {
|
||
javaPath = await findJava();
|
||
} catch (error) {
|
||
console.warn('Ошибка при поиске Java:', error);
|
||
event.sender.send('installation-status', {
|
||
step: 'java-error',
|
||
message:
|
||
'Не удалось найти Java. Попробуем запустить с системной Java.',
|
||
});
|
||
}
|
||
|
||
console.log('Используем Java:', javaPath);
|
||
|
||
// --- 1. Установка ванильного Minecraft (effectiveBaseVersion) ---
|
||
let resolvedVersion: any;
|
||
|
||
try {
|
||
event.sender.send('installation-status', {
|
||
step: 'minecraft-list',
|
||
message: 'Получение списка версий Minecraft...',
|
||
});
|
||
|
||
console.log('Получение списка версий Minecraft...');
|
||
|
||
const versionList = await getVersionList();
|
||
const minecraftVersion = versionList.versions.find(
|
||
(v) => v.id === effectiveBaseVersion,
|
||
);
|
||
|
||
console.log('minecraftVersion:', minecraftVersion);
|
||
|
||
if (minecraftVersion) {
|
||
const installMcTask = installTask(minecraftVersion, minecraftDir, {
|
||
skipRevalidate: true,
|
||
assetsDownloadConcurrency: 2,
|
||
librariesDownloadConcurrency: 2,
|
||
dispatcher: agent,
|
||
});
|
||
|
||
console.log('installMcTask started for', minecraftVersion.id);
|
||
|
||
event.sender.send('installation-status', {
|
||
step: 'minecraft-install',
|
||
message: `Установка Minecraft ${minecraftVersion.id}...`,
|
||
});
|
||
|
||
await installMcTask.startAndWait({
|
||
onUpdate(task, chunkSize) {
|
||
// локальный прогресс инсталлятора XMCL
|
||
const local =
|
||
installMcTask.total > 0
|
||
? installMcTask.progress / installMcTask.total
|
||
: 0;
|
||
|
||
const global = getGlobalProgress('minecraft-install', local);
|
||
event.sender.send('overall-progress', global);
|
||
},
|
||
onFailed(task, error) {
|
||
const stepName = (task as any).path || task.name || 'unknown';
|
||
console.warn(
|
||
`[minecraft-install] step "${stepName}" failed: ${
|
||
(error as any).code ?? ''
|
||
} ${(error as any).message}`,
|
||
);
|
||
|
||
event.sender.send('installation-status', {
|
||
step: `minecraft-install.${stepName}`,
|
||
message: `Ошибка: ${(error as any).message}`,
|
||
});
|
||
},
|
||
});
|
||
} else {
|
||
console.warn(
|
||
`Версия ${effectiveBaseVersion} не найдена в списке версий Minecraft. Предполагаем, что она уже установлена.`,
|
||
);
|
||
}
|
||
} catch (error: any) {
|
||
const agg = error as any;
|
||
const innerCount = Array.isArray(agg?.errors) ? agg.errors.length : 0;
|
||
|
||
console.log(
|
||
'Ошибка при установке ванильного Minecraft, продолжаем:',
|
||
agg?.message || String(agg),
|
||
innerCount ? `(внутренних ошибок: ${innerCount})` : '',
|
||
);
|
||
}
|
||
|
||
// --- 2. Установка Fabric (только для модпаков) ---
|
||
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),
|
||
);
|
||
|
||
console.log('installNeoForged:', {
|
||
project: 'neoforge',
|
||
version: neoForgeVersion,
|
||
minecraftVersion: effectiveBaseVersion,
|
||
minecraftDir,
|
||
});
|
||
|
||
// Установка NeoForge
|
||
await installNeoForged('neoforge', neoForgeVersion, minecraftDir, {
|
||
minecraft: effectiveBaseVersion,
|
||
java: javaPath,
|
||
side: 'client',
|
||
});
|
||
|
||
console.log('NeoForge установлен успешно!');
|
||
|
||
event.sender.send(
|
||
'overall-progress',
|
||
getGlobalProgress('fabric-install', 1),
|
||
);
|
||
} catch (error) {
|
||
console.error('Ошибка при установке NeoForge:', error);
|
||
event.sender.send('installation-status', {
|
||
step: 'neoforge-error',
|
||
message: `Ошибка установки NeoForge: ${error.message}`,
|
||
});
|
||
}
|
||
} 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);
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- 3. Установка зависимостей для versionToLaunch ---
|
||
try {
|
||
if (!versionToLaunch) {
|
||
throw new Error('versionToLaunch не определён');
|
||
}
|
||
|
||
console.log('version-parse:', {
|
||
minecraftDir,
|
||
versionToLaunch,
|
||
});
|
||
|
||
event.sender.send('installation-status', {
|
||
step: 'version-parse',
|
||
message: 'Подготовка версии...',
|
||
});
|
||
|
||
resolvedVersion = await Version.parse(minecraftDir, versionToLaunch);
|
||
|
||
event.sender.send('installation-status', {
|
||
step: 'dependencies',
|
||
message: 'Установка библиотек и ресурсов...',
|
||
});
|
||
|
||
const depsTask = installDependenciesTask(resolvedVersion, {
|
||
skipRevalidate: true,
|
||
prevalidSizeOnly: true,
|
||
dispatcher: agent,
|
||
assetsDownloadConcurrency: 2,
|
||
librariesDownloadConcurrency: 2,
|
||
checksumValidatorResolver: () => ({
|
||
async validate() {
|
||
// Игнорируем sha1, чтобы не падать из-за несоответствий
|
||
},
|
||
}),
|
||
// ...DOWNLOAD_OPTIONS,
|
||
});
|
||
|
||
await depsTask.startAndWait({
|
||
onUpdate(task, chunkSize) {
|
||
const local =
|
||
depsTask.total > 0 ? depsTask.progress / depsTask.total : 0;
|
||
|
||
const global = getGlobalProgress('dependencies', local);
|
||
event.sender.send('overall-progress', global);
|
||
},
|
||
onFailed(task, error) {
|
||
const stepName = (task as any).path || task.name || 'unknown';
|
||
console.warn(
|
||
`[deps] step "${stepName}" failed: ${
|
||
(error as any).code ?? ''
|
||
} ${(error as any).message}`,
|
||
);
|
||
|
||
event.sender.send('installation-status', {
|
||
step: `dependencies.${stepName}`,
|
||
message: `Ошибка: ${(error as any).message}`,
|
||
});
|
||
},
|
||
});
|
||
} catch (error: any) {
|
||
console.log(
|
||
'Ошибка при подготовке версии/зависимостей, продолжаем запуск:',
|
||
error.message || error,
|
||
);
|
||
}
|
||
|
||
// --- authlib-injector ---
|
||
const authlibPath = await ensureAuthlibInjectorExists(userDataPath);
|
||
console.log('authlibPath:', authlibPath);
|
||
|
||
event.sender.send('installation-status', {
|
||
step: 'authlib-injector',
|
||
message: 'authlib-injector готов',
|
||
});
|
||
|
||
// --- Запуск игры ---
|
||
console.log('Запуск игры...');
|
||
|
||
event.sender.send('installation-status', {
|
||
step: 'launch',
|
||
message: 'Запуск игры...',
|
||
});
|
||
|
||
event.sender.send('overall-progress', getGlobalProgress('launch', 0));
|
||
|
||
const proc = await launch({
|
||
gamePath: packDir,
|
||
resourcePath: minecraftDir,
|
||
javaPath,
|
||
version: versionToLaunch!,
|
||
launcherName: 'popa-popa',
|
||
extraJVMArgs: [
|
||
'-Dlog4j2.formatMsgNoLookups=true',
|
||
`-javaagent:${authlibPath}=${API_BASE_URL}`,
|
||
`-Xmx${memory}M`,
|
||
'-Dauthlibinjector.skinWhitelist=https://minecraft.api.popa-popa.ru/',
|
||
'-Dauthlibinjector.debug=verbose,authlib',
|
||
'-Dauthlibinjector.legacySkinPolyfill=enabled',
|
||
'-Dauthlibinjector.mojangAntiFeatures=disabled',
|
||
'-Dcom.mojang.authlib.disableSecureProfileEndpoints=true',
|
||
],
|
||
extraMCArgs: [
|
||
'--quickPlayMultiplayer',
|
||
`${serverIp}:${serverPort || 25565}`,
|
||
],
|
||
accessToken,
|
||
gameProfile: {
|
||
id: uuid,
|
||
name: username,
|
||
},
|
||
});
|
||
|
||
event.sender.send('minecraft-started', { pid: proc.pid });
|
||
|
||
currentMinecraftProcess = proc;
|
||
|
||
event.sender.send('overall-progress', getGlobalProgress('launch', 1));
|
||
|
||
let stderrBuffer = '';
|
||
|
||
proc.stdout?.on('data', (data) => {
|
||
console.log(`Minecraft stdout: ${data}`);
|
||
});
|
||
|
||
proc.stderr?.on('data', (data) => {
|
||
const text = data.toString();
|
||
console.error(`Minecraft stderr: ${text}`);
|
||
stderrBuffer += text;
|
||
|
||
// Пробрасываем сырой лог клиенту (если захочешь где-то выводить)
|
||
event.sender.send('minecraft-log', text);
|
||
|
||
// Если это ошибка — сразу уведомим пользователя
|
||
if (text.toLowerCase().includes('error')) {
|
||
event.sender.send('minecraft-error', {
|
||
message: text,
|
||
});
|
||
}
|
||
});
|
||
|
||
proc.on('exit', (code) => {
|
||
console.log('Minecraft exited with code', code);
|
||
|
||
currentMinecraftProcess = null;
|
||
event.sender.send('minecraft-stopped', { code });
|
||
|
||
if (code !== 0) {
|
||
event.sender.send('installation-status', {
|
||
step: 'error',
|
||
message: `Minecraft завершился с ошибкой (код ${code})`,
|
||
});
|
||
|
||
event.sender.send('minecraft-error', {
|
||
message: `Minecraft завершился с ошибкой (код ${code})`,
|
||
stderr: stderrBuffer,
|
||
code,
|
||
});
|
||
}
|
||
});
|
||
|
||
console.log('Запуск игры...');
|
||
|
||
return { success: true, pid: proc.pid };
|
||
} catch (error: any) {
|
||
console.error('Ошибка при запуске Minecraft:', error);
|
||
event.sender.send('installation-status', {
|
||
step: 'error',
|
||
message: `Ошибка запуска: ${error.message || String(error)}`,
|
||
});
|
||
|
||
return { success: false, error: error.message || String(error) };
|
||
}
|
||
});
|
||
|
||
ipcMain.handle('stop-minecraft', async (event) => {
|
||
try {
|
||
if (currentMinecraftProcess && !currentMinecraftProcess.killed) {
|
||
console.log('Останавливаем Minecraft по запросу пользователя...');
|
||
// На Windows этого обычно достаточно
|
||
currentMinecraftProcess.kill();
|
||
|
||
// Можно чуть подождать, но не обязательно
|
||
return { success: true };
|
||
}
|
||
|
||
return { success: false, error: 'Minecraft сейчас не запущен' };
|
||
} catch (error: any) {
|
||
console.error('Ошибка при остановке Minecraft:', error);
|
||
return { success: false, error: error.message || String(error) };
|
||
}
|
||
});
|
||
|
||
// Добавьте в функцию initMinecraftHandlers или создайте новую
|
||
ipcMain.handle('get-pack-files', async (event, packName) => {
|
||
try {
|
||
const userDataPath = app.getPath('userData');
|
||
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||
const packDir = path.join(minecraftDir, 'versions', packName);
|
||
|
||
if (!fs.existsSync(packDir)) {
|
||
return { success: false, error: 'Директория сборки не найдена' };
|
||
}
|
||
|
||
// Функция для рекурсивного обхода директории
|
||
const scanDir: any = (dir: any, basePath: any = '') => {
|
||
const result = [];
|
||
const items = fs.readdirSync(dir);
|
||
|
||
for (const item of items) {
|
||
const itemPath = path.join(dir, item);
|
||
const relativePath = basePath ? path.join(basePath, item) : item;
|
||
const isDirectory = fs.statSync(itemPath).isDirectory();
|
||
|
||
result.push({
|
||
name: item,
|
||
path: relativePath,
|
||
isDirectory,
|
||
// Если это директория, рекурсивно сканируем ее
|
||
children: isDirectory ? scanDir(itemPath, relativePath) : [],
|
||
});
|
||
}
|
||
|
||
return result;
|
||
};
|
||
|
||
const files = scanDir(packDir);
|
||
return { success: true, files };
|
||
} catch (error) {
|
||
console.error('Ошибка при получении файлов сборки:', error);
|
||
return { success: false, error: error.message };
|
||
}
|
||
});
|
||
|
||
// ПРОБЛЕМА: У вас два обработчика для одного и того же канала 'get-installed-versions'
|
||
// РЕШЕНИЕ: Объединим логику в один обработчик, а из второго обработчика вызовем функцию getInstalledVersions
|
||
|
||
// Сначала создаем общую функцию для получения установленных версий
|
||
function getInstalledVersions() {
|
||
try {
|
||
const userDataPath = app.getPath('userData');
|
||
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||
const versionsDir = path.join(minecraftDir, 'versions');
|
||
|
||
if (!fs.existsSync(versionsDir)) {
|
||
return { success: true, versions: [] };
|
||
}
|
||
|
||
const items = fs.readdirSync(versionsDir);
|
||
const versions = [];
|
||
|
||
for (const item of items) {
|
||
const versionPath = path.join(versionsDir, item);
|
||
if (!fs.statSync(versionPath).isDirectory()) continue;
|
||
|
||
// ❗ Прячем технические версии загрузчиков
|
||
if (item.includes('-fabric') || item.includes('neoforge')) {
|
||
continue;
|
||
}
|
||
|
||
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,
|
||
version: item,
|
||
};
|
||
|
||
if (fs.existsSync(versionJsonPath)) {
|
||
try {
|
||
const versionData = JSON.parse(
|
||
fs.readFileSync(versionJsonPath, 'utf8'),
|
||
);
|
||
versionInfo.version = versionData.id || item;
|
||
} catch (error) {
|
||
console.warn(`Ошибка при чтении файла версии ${item}:`, error);
|
||
}
|
||
}
|
||
|
||
versions.push(versionInfo);
|
||
}
|
||
|
||
return { success: true, versions };
|
||
} catch (error) {
|
||
console.error('Ошибка при получении установленных версий:', error);
|
||
return { success: false, error: error.message, versions: [] };
|
||
}
|
||
}
|
||
|
||
// Регистрируем обработчик для get-installed-versions
|
||
ipcMain.handle('get-installed-versions', async () => {
|
||
return getInstalledVersions();
|
||
});
|
||
|
||
// Обработчик get-available-versions использует функцию getInstalledVersions
|
||
ipcMain.handle('get-available-versions', async (event, { gistUrl }) => {
|
||
try {
|
||
// Используем URL из параметров или значение по умолчанию
|
||
const url =
|
||
gistUrl ||
|
||
'https://gist.githubusercontent.com/DIKER0K/06cd12fb3a4d08b1f0f8c763a7d05e06/raw/versions.json';
|
||
|
||
const response = await fetch(url);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(
|
||
`Failed to fetch versions from Gist: ${response.status} ${response.statusText}`,
|
||
);
|
||
}
|
||
|
||
const versions = await response.json();
|
||
|
||
// Получаем уже установленные версии
|
||
const installedResult = getInstalledVersions();
|
||
const installedVersions = installedResult.success
|
||
? installedResult.versions
|
||
: [];
|
||
|
||
// Добавляем флаг installed к каждой версии
|
||
const versionsWithInstallStatus = versions.map((version: any) => {
|
||
const isInstalled = installedVersions.some(
|
||
(installed: any) => installed.id === version.id,
|
||
);
|
||
return {
|
||
...version,
|
||
installed: isInstalled,
|
||
};
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
versions: versionsWithInstallStatus,
|
||
};
|
||
} catch (error) {
|
||
console.error('Ошибка при получении доступных версий:', error);
|
||
return { success: false, error: error.message, versions: [] };
|
||
}
|
||
});
|
||
}
|
||
|
||
// Добавляем обработчики IPC для аутентификации
|
||
export function initAuthHandlers() {
|
||
// Аутентификация
|
||
ipcMain.handle('authenticate', async (event, { username, password }) => {
|
||
try {
|
||
const auth = await authService.login(username, password);
|
||
return {
|
||
accessToken: auth.accessToken,
|
||
clientToken: auth.clientToken,
|
||
selectedProfile: auth.selectedProfile,
|
||
};
|
||
} catch (error) {
|
||
console.error('Ошибка аутентификации:', error);
|
||
throw error;
|
||
}
|
||
});
|
||
|
||
// Валидация токена
|
||
ipcMain.handle(
|
||
'validate-token',
|
||
async (event, { accessToken, clientToken }) => {
|
||
try {
|
||
const valid = await authService.validate(accessToken, clientToken);
|
||
return { valid };
|
||
} catch (error) {
|
||
console.error('Ошибка валидации токена:', error);
|
||
return { valid: false };
|
||
}
|
||
},
|
||
);
|
||
|
||
// Обновление токена
|
||
ipcMain.handle(
|
||
'refresh-token',
|
||
async (event, { accessToken, clientToken }) => {
|
||
try {
|
||
const auth = await authService.refresh(accessToken, clientToken);
|
||
if (!auth) return null;
|
||
|
||
return {
|
||
accessToken: auth.accessToken,
|
||
clientToken: auth.clientToken,
|
||
selectedProfile: auth.selectedProfile,
|
||
};
|
||
} catch (error) {
|
||
console.error('Ошибка обновления токена:', error);
|
||
return null;
|
||
}
|
||
},
|
||
);
|
||
}
|
||
|
||
// Функция для получения статуса сервера
|
||
export function initServerStatusHandler() {
|
||
ipcMain.handle('get-server-status', async (event, { host, port }) => {
|
||
try {
|
||
// Формируем адрес с портом, если указан
|
||
const serverAddress = port ? `${host}:${port}` : host;
|
||
|
||
// Делаем запрос к API mcstatus.io
|
||
const response = await fetch(`${MCSTATUS_API_URL}${serverAddress}`);
|
||
|
||
// Проверяем статус ответа
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
throw new Error(`API вернул ошибку ${response.status}: ${errorText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.online) {
|
||
return {
|
||
success: true,
|
||
online: data.players?.online || 0,
|
||
max: data.players?.max || 0,
|
||
version: data.version?.name_clean || 'Unknown',
|
||
icon: data.icon || null, // Возвращаем иконку
|
||
motd: data.motd?.clean || '', // Название сервера
|
||
};
|
||
} else {
|
||
return { success: false, error: 'Сервер не доступен' };
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка при получении статуса сервера:', error);
|
||
return { success: false, error: error.message };
|
||
}
|
||
});
|
||
}
|
||
|
||
// Функция для работы с конфигурацией сборки
|
||
export function initPackConfigHandlers() {
|
||
// Файл конфигурации
|
||
const CONFIG_FILENAME = 'popa-launcher-config.json';
|
||
|
||
// Обработчик для сохранения настроек сборки
|
||
ipcMain.handle('save-pack-config', async (event, { packName, config }) => {
|
||
try {
|
||
const userDataPath = app.getPath('userData');
|
||
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||
const packDir = path.join(minecraftDir, 'versions', packName);
|
||
|
||
// Создаем папку для сборки, если она не существует
|
||
if (!fs.existsSync(packDir)) {
|
||
fs.mkdirSync(packDir, { recursive: true });
|
||
}
|
||
|
||
const configPath = path.join(packDir, CONFIG_FILENAME);
|
||
|
||
// Сохраняем конфигурацию в файл
|
||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||
|
||
// Добавляем файл конфигурации в список файлов, которые не удаляются
|
||
if (!config.preserveFiles.includes(CONFIG_FILENAME)) {
|
||
config.preserveFiles.push(CONFIG_FILENAME);
|
||
// Перезаписываем файл с обновленным списком
|
||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||
}
|
||
|
||
return { success: true };
|
||
} catch (error) {
|
||
console.error('Ошибка при сохранении настроек сборки:', error);
|
||
return { success: false, error: error.message };
|
||
}
|
||
});
|
||
|
||
// Обработчик для загрузки настроек сборки
|
||
ipcMain.handle('load-pack-config', async (event, { packName }) => {
|
||
try {
|
||
const userDataPath = app.getPath('userData');
|
||
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||
const packDir = path.join(minecraftDir, 'versions', packName);
|
||
const configPath = path.join(packDir, CONFIG_FILENAME);
|
||
|
||
// Проверяем существование файла конфигурации
|
||
if (!fs.existsSync(configPath)) {
|
||
// Если файла нет, возвращаем дефолтную конфигурацию
|
||
return {
|
||
success: true,
|
||
config: {
|
||
memory: 4096,
|
||
preserveFiles: [CONFIG_FILENAME], // По умолчанию сохраняем файл конфигурации
|
||
},
|
||
};
|
||
}
|
||
|
||
// Читаем и парсим конфигурацию
|
||
const configData = fs.readFileSync(configPath, 'utf8');
|
||
const config = JSON.parse(configData);
|
||
|
||
// Добавляем файл конфигурации в список сохраняемых файлов, если его там нет
|
||
if (!config.preserveFiles.includes(CONFIG_FILENAME)) {
|
||
config.preserveFiles.push(CONFIG_FILENAME);
|
||
}
|
||
|
||
return { success: true, config };
|
||
} catch (error) {
|
||
console.error('Ошибка при загрузке настроек сборки:', error);
|
||
return {
|
||
success: false,
|
||
error: error.message,
|
||
// Возвращаем дефолтную конфигурацию при ошибке
|
||
config: {
|
||
memory: 4096,
|
||
preserveFiles: [CONFIG_FILENAME],
|
||
},
|
||
};
|
||
}
|
||
});
|
||
}
|
||
|
||
// Добавляем после обработчика get-available-versions
|
||
ipcMain.handle('get-version-config', async (event, { versionId }) => {
|
||
try {
|
||
const userDataPath = app.getPath('userData');
|
||
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||
const versionsDir = path.join(minecraftDir, 'versions');
|
||
const versionPath = path.join(versionsDir, versionId);
|
||
|
||
// Проверяем существование директории версии
|
||
if (!fs.existsSync(versionPath)) {
|
||
return { success: false, error: `Версия ${versionId} не найдена` };
|
||
}
|
||
|
||
// Проверяем конфигурационный файл версии
|
||
const configPath = path.join(versionPath, 'popa-launcher-config.json');
|
||
|
||
// Определяем базовые настройки по умолчанию
|
||
let config = {
|
||
downloadUrl: '',
|
||
apiReleaseUrl: '',
|
||
versionFileName: `${versionId}_version.txt`,
|
||
packName: versionId,
|
||
memory: 4096,
|
||
baseVersion: '1.21.10',
|
||
serverIp: 'popa-popa.ru',
|
||
fabricVersion: null,
|
||
neoForgeVersion: null,
|
||
loaderType: 'fabric', // 'fabric', 'neoforge', 'vanilla'
|
||
preserveFiles: ['popa-launcher-config.json'],
|
||
};
|
||
|
||
// Если это Comfort, используем настройки по умолчанию
|
||
if (versionId === 'Comfort') {
|
||
config.downloadUrl =
|
||
'https://github.com/DIKER0K/Comfort/releases/latest/download/Comfort.zip';
|
||
config.apiReleaseUrl =
|
||
'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 может требовать больше памяти
|
||
}
|
||
|
||
// Если есть конфигурационный файл, загружаем из него
|
||
if (fs.existsSync(configPath)) {
|
||
try {
|
||
const savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||
config = { ...config, ...savedConfig };
|
||
} catch (error) {
|
||
console.warn(`Ошибка чтения конфигурации ${versionId}:`, error);
|
||
}
|
||
}
|
||
|
||
return { success: true, config };
|
||
} catch (error) {
|
||
console.error('Ошибка получения настроек версии:', error);
|
||
return { success: false, error: error.message };
|
||
}
|
||
});
|