Files
popa-launcher/src/main/minecraft-launcher.ts

1083 lines
40 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {
installDependencies,
installFabric,
getFabricLoaders,
getVersionList,
install,
installTask,
installDependenciesTask,
} from '@xmcl/installer';
import { spawn } from 'child_process';
import { AuthService } from './auth-service';
// Константы
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();
// Модифицированная функция для получения последней версии релиза с произвольного 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 = [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 appPath = path.dirname(app.getPath('exe'));
const minecraftDir = path.join(appPath, '.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(appPath, '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 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 = 'fabric0.16.14',
packName = 'Comfort', // Название основной сборки
versionToLaunchOverride = '', // Возможность переопределить версию для запуска
serverIp = 'popa-popa.ru',
serverPort, // Добавляем опциональный порт без значения по умолчанию
} = gameConfig || {};
const appPath = path.dirname(app.getPath('exe'));
const minecraftDir = path.join(appPath, '.minecraft');
const versionsDir = path.join(minecraftDir, 'versions');
// Определяем версию для запуска
const versionsContents = fs.existsSync(versionsDir)
? fs.readdirSync(versionsDir)
: [];
console.log('Доступные версии:', versionsContents);
// Найти версию пакета, Fabric или базовую версию
let versionToLaunch = versionToLaunchOverride;
if (!versionToLaunch) {
if (
versionsContents.includes(`${baseVersion}-fabric${fabricVersion}`)
) {
versionToLaunch = `${baseVersion}-fabric${fabricVersion}`;
} else if (versionsContents.includes(packName)) {
versionToLaunch = packName;
} else {
versionToLaunch = baseVersion;
}
}
console.log('Запускаем версию:', versionToLaunch);
// Находим путь к Java
event.sender.send('installation-status', {
step: 'java',
message: 'Поиск Java...',
});
let javaPath;
try {
javaPath = await findJava();
} catch (error) {
console.warn('Ошибка при поиске Java:', error);
event.sender.send('installation-status', {
step: 'java-error',
message: 'Не удалось найти Java. Используем системную Java.',
});
javaPath = 'java';
}
// Далее пробуем установить Minecraft, но продолжаем даже при ошибках
let resolvedVersion;
try {
// 1. Получаем список версий и устанавливаем ванильный Minecraft
event.sender.send('installation-status', {
step: 'minecraft-list',
message: 'Получение списка версий Minecraft...',
});
const versionList = await getVersionList();
const minecraftVersion = versionList.versions.find(
(v) => v.id === baseVersion,
);
if (minecraftVersion) {
// Устанавливаем базовую версию Minecraft
event.sender.send('installation-status', {
step: 'minecraft-install',
message: `Установка Minecraft ${baseVersion}...`,
});
try {
const installMcTask = installTask(minecraftVersion, minecraftDir, {
skipRevalidate: true,
});
await installMcTask.startAndWait({
onStart(task) {
event.sender.send('installation-status', {
step: `minecraft-install.${task.path}`,
message: `Начало: ${task.name || task.path}`,
});
},
onUpdate(task) {
const percentage =
Math.round(
(installMcTask.progress / installMcTask.total) * 100,
) || 0;
event.sender.send('download-progress', percentage);
event.sender.send('installation-status', {
step: `minecraft-install.${task.path}`,
message: `Прогресс ${task.name || task.path}: ${percentage}% (${installMcTask.progress}/${installMcTask.total})`,
});
},
onFailed(task, error) {
console.warn(
`Ошибка при установке ${task.path}, продолжаем:`,
error,
);
event.sender.send('installation-status', {
step: `minecraft-install.${task.path}`,
message: `Ошибка: ${error.message}`,
});
},
onSucceed(task) {
event.sender.send('installation-status', {
step: `minecraft-install.${task.path}`,
message: `Завершено: ${task.name || task.path}`,
});
},
});
} catch (error) {
console.warn('Ошибка при установке Minecraft, продолжаем:', error);
}
// 2. Устанавливаем Fabric
try {
event.sender.send('installation-status', {
step: 'fabric-list',
message: 'Получение списка версий Fabric...',
});
if (fabricVersion) {
event.sender.send('installation-status', {
step: 'fabric-install',
message: `Установка Fabric ${fabricVersion}...`,
});
await installFabric({
minecraftVersion: baseVersion,
version: fabricVersion, // Используйте напрямую, без .version
minecraft: minecraftDir,
});
}
} catch (error) {
console.warn('Ошибка при установке Fabric, продолжаем:', error);
}
// 3. Подготовка версии и установка зависимостей
try {
// Используем идентификатор Fabric-версии
const fabricVersionId = `${baseVersion}-fabric${fabricVersion}`;
event.sender.send('installation-status', {
step: 'version-parse',
message: 'Подготовка версии...',
});
resolvedVersion = await Version.parse(
minecraftDir,
fabricVersionId,
);
event.sender.send('installation-status', {
step: 'dependencies',
message: 'Установка библиотек и ресурсов...',
});
const depsTask = installDependenciesTask(resolvedVersion, {
assetsDownloadConcurrency: 4,
skipRevalidate: true,
prevalidSizeOnly: true,
checksumValidatorResolver: (checksum) => ({
validate: async () => {
/* void */
},
}),
});
try {
await depsTask.startAndWait({
onStart(task) {
event.sender.send('installation-status', {
step: `dependencies.${task.path}`,
message: `Начало: ${task.name || task.path}`,
});
},
onUpdate(task) {
const percentage =
Math.round((depsTask.progress / depsTask.total) * 100) || 0;
event.sender.send('download-progress', percentage);
event.sender.send('installation-status', {
step: `dependencies.${task.path}`,
message: `Установка ${task.name || task.path}: ${percentage}%`,
});
},
onFailed(task, error) {
console.warn(
`Ошибка при установке ${task.path}, продолжаем:`,
error,
);
event.sender.send('installation-status', {
step: `dependencies.${task.path}`,
message: `Ошибка: ${error.message}`,
});
},
onSucceed(task) {
event.sender.send('installation-status', {
step: `dependencies.${task.path}`,
message: `Завершено: ${task.name || task.path}`,
});
},
});
} catch (error) {
console.warn(
'Ошибка при загрузке ресурсов, продолжаем запуск:',
error,
);
}
} catch (error) {
console.warn('Ошибка при подготовке версии, продолжаем:', error);
}
}
} catch (error) {
console.warn('Произошла ошибка при подготовке Minecraft:', error);
}
// Загрузка и проверка authlib-injector
const authlibPath = await ensureAuthlibInjectorExists(appPath);
event.sender.send('installation-status', {
step: 'authlib-injector',
message: 'authlib-injector готов',
});
// Запускаем Minecraft с authlib-injector для Ely.by
event.sender.send('installation-status', {
step: 'launch',
message: 'Запуск игры...',
});
// При запуске используем переданные параметры
const packDir = path.join(versionsDir, packName);
// При формировании конфигурации запуска создаем объект server только с нужными параметрами
const serverConfig: any = { ip: serverIp };
// Добавляем порт только если он был передан
if (serverPort) {
serverConfig.port = serverPort;
}
const proc = await launch({
gamePath: packDir,
resourcePath: minecraftDir,
javaPath,
version: versionToLaunch,
launcherName: 'popa-popa',
server: serverConfig, // Используем созданный объект конфигурации
extraJVMArgs: [
'-Dlog4j2.formatMsgNoLookups=true',
`-javaagent:${authlibPath}=ely.by`,
`-Xmx${memory}M`,
],
// Используем данные аутентификации Yggdrasil
accessToken,
gameProfile: {
id: uuid,
name: username,
},
});
// Логирование
proc.stdout?.on('data', (data) => {
console.log(`Minecraft stdout: ${data}`);
});
proc.stderr?.on('data', (data) => {
console.error(`Minecraft stderr: ${data}`);
});
return { success: true, pid: proc.pid };
} catch (error) {
console.error('Ошибка при запуске Minecraft:', error);
event.sender.send('installation-status', {
step: 'error',
message: `Ошибка запуска: ${error.message}`,
});
return { success: false, error: error.message };
}
});
// Добавьте в функцию initMinecraftHandlers или создайте новую
ipcMain.handle('get-pack-files', async (event, packName) => {
try {
const appPath = path.dirname(app.getPath('exe'));
const minecraftDir = path.join(appPath, '.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 };
}
});
// Добавьте в функцию initMinecraftHandlers новый обработчик
ipcMain.handle('get-available-versions', async (event) => {
try {
const appPath = path.dirname(app.getPath('exe'));
const minecraftDir = path.join(appPath, '.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()) {
// Проверяем, есть ли конфигурация для пакета
const versionJsonPath = path.join(versionPath, `${item}.json`);
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 };
}
});
}
// Добавляем обработчики 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) => {
try {
const clientToken = JSON.parse(
fs.readFileSync(
path.join(app.getPath('userData'), 'config.json'),
'utf8',
),
).clientToken;
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 appPath = path.dirname(app.getPath('exe'));
const minecraftDir = path.join(appPath, '.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 appPath = path.dirname(app.getPath('exe'));
const minecraftDir = path.join(appPath, '.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 appPath = path.dirname(app.getPath('exe'));
const minecraftDir = path.join(appPath, '.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.4',
serverIp: 'popa-popa.ru',
fabricVersion: '0.16.14',
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';
}
// Если есть конфигурационный файл, загружаем из него
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 };
}
});