working authirization
This commit is contained in:
74
package-lock.json
generated
74
package-lock.json
generated
@ -14,6 +14,7 @@
|
|||||||
"@mui/material": "^7.2.0",
|
"@mui/material": "^7.2.0",
|
||||||
"@xmcl/core": "^2.14.1",
|
"@xmcl/core": "^2.14.1",
|
||||||
"@xmcl/installer": "^6.1.0",
|
"@xmcl/installer": "^6.1.0",
|
||||||
|
"@xmcl/user": "^4.2.0",
|
||||||
"electron-debug": "^4.1.0",
|
"electron-debug": "^4.1.0",
|
||||||
"electron-log": "^5.3.2",
|
"electron-log": "^5.3.2",
|
||||||
"electron-updater": "^6.3.9",
|
"electron-updater": "^6.3.9",
|
||||||
@ -25,7 +26,8 @@
|
|||||||
"react-router-dom": "^7.3.0",
|
"react-router-dom": "^7.3.0",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"undici": "^7.11.0",
|
"undici": "^7.11.0",
|
||||||
"util": "^0.12.5"
|
"util": "^0.12.5",
|
||||||
|
"uuid": "^11.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/rebuild": "^3.7.1",
|
"@electron/rebuild": "^3.7.1",
|
||||||
@ -2806,6 +2808,15 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fastify/busboy": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@gar/promisify": {
|
"node_modules/@gar/promisify": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||||
@ -5972,6 +5983,44 @@
|
|||||||
"yauzl": "^2.10.0"
|
"yauzl": "^2.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@xmcl/user": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xmcl/user/-/user-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-/+AeKzb1An9OmmIKsRPKcQFYi0bIroXfA/R3VwpjcroPUc0IUXenz2vEBoxiyszu6KJkWT2t6nb/GyqjYEQQow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici": "6.0.1",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@xmcl/user/node_modules/undici": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-eZFYQLeS9BiXpsU0cuFhCwfeda2MnC48EVmmOz/eCjsTgmyTdaHdVsPSC/kwC2GtW2e0uH0HIPbadf3/bRWSxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/busboy": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@xmcl/user/node_modules/uuid": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@xmldom/xmldom": {
|
"node_modules/@xmldom/xmldom": {
|
||||||
"version": "0.8.10",
|
"version": "0.8.10",
|
||||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||||
@ -19640,6 +19689,16 @@
|
|||||||
"websocket-driver": "^0.7.4"
|
"websocket-driver": "^0.7.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sockjs/node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/socks": {
|
"node_modules/socks": {
|
||||||
"version": "2.8.4",
|
"version": "2.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
|
||||||
@ -21419,13 +21478,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "8.3.2",
|
"version": "11.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||||
"dev": true,
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"uuid": "dist/bin/uuid"
|
"uuid": "dist/esm/bin/uuid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/v8-compile-cache-lib": {
|
"node_modules/v8-compile-cache-lib": {
|
||||||
|
@ -107,6 +107,7 @@
|
|||||||
"@mui/material": "^7.2.0",
|
"@mui/material": "^7.2.0",
|
||||||
"@xmcl/core": "^2.14.1",
|
"@xmcl/core": "^2.14.1",
|
||||||
"@xmcl/installer": "^6.1.0",
|
"@xmcl/installer": "^6.1.0",
|
||||||
|
"@xmcl/user": "^4.2.0",
|
||||||
"electron-debug": "^4.1.0",
|
"electron-debug": "^4.1.0",
|
||||||
"electron-log": "^5.3.2",
|
"electron-log": "^5.3.2",
|
||||||
"electron-updater": "^6.3.9",
|
"electron-updater": "^6.3.9",
|
||||||
@ -118,7 +119,8 @@
|
|||||||
"react-router-dom": "^7.3.0",
|
"react-router-dom": "^7.3.0",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"undici": "^7.11.0",
|
"undici": "^7.11.0",
|
||||||
"util": "^0.12.5"
|
"util": "^0.12.5",
|
||||||
|
"uuid": "^11.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/rebuild": "^3.7.1",
|
"@electron/rebuild": "^3.7.1",
|
||||||
|
90
src/main/auth-service.ts
Normal file
90
src/main/auth-service.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { YggdrasilClient, YggrasilAuthentication } from '@xmcl/user';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
// Ely.by сервер
|
||||||
|
const ELY_BY_AUTH_SERVER = 'https://authserver.ely.by';
|
||||||
|
|
||||||
|
export class AuthService {
|
||||||
|
private client: YggdrasilClient;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.client = new YggdrasilClient(ELY_BY_AUTH_SERVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
): Promise<YggrasilAuthentication> {
|
||||||
|
try {
|
||||||
|
// Генерируем уникальный clientToken
|
||||||
|
const clientToken = uuidv4();
|
||||||
|
|
||||||
|
// Выполняем запрос напрямую к правильному URL
|
||||||
|
const response = await fetch(`${ELY_BY_AUTH_SERVER}/auth/authenticate`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
clientToken,
|
||||||
|
requestUser: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Ошибка авторизации: ${response.status} ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth = await response.json();
|
||||||
|
console.log(`Аутентификация успешна для ${auth.selectedProfile?.name}`);
|
||||||
|
return auth;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при авторизации:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(accessToken: string, clientToken: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${ELY_BY_AUTH_SERVER}/auth/validate`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ accessToken, clientToken }),
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.ok;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при валидации токена:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async refresh(
|
||||||
|
accessToken: string,
|
||||||
|
clientToken: string,
|
||||||
|
): Promise<YggrasilAuthentication | null> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${ELY_BY_AUTH_SERVER}/auth/refresh`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ accessToken, clientToken }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при обновлении токена:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
517
src/main/main.ts
517
src/main/main.ts
@ -14,121 +14,7 @@ import { autoUpdater } from 'electron-updater';
|
|||||||
import log from 'electron-log';
|
import log from 'electron-log';
|
||||||
import MenuBuilder from './menu';
|
import MenuBuilder from './menu';
|
||||||
import { resolveHtmlPath } from './util';
|
import { resolveHtmlPath } from './util';
|
||||||
import fs from 'fs';
|
import { initMinecraftHandlers, initAuthHandlers } from './minecraft-launcher';
|
||||||
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 { Task } from '@xmcl/task';
|
|
||||||
// import findJavaHome from 'find-java-home';
|
|
||||||
|
|
||||||
// Функция для поиска Java
|
|
||||||
async function findJava(): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
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)) {
|
|
||||||
return resolve(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',
|
|
||||||
].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)) {
|
|
||||||
return resolve(javaPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Ошибка при сканировании ${basePath}:`, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Пробуем найти java в PATH через команду which/where
|
|
||||||
try {
|
|
||||||
const command =
|
|
||||||
process.platform === 'win32' ? 'where java' : 'which java';
|
|
||||||
const javaPath = execSync(command).toString().trim();
|
|
||||||
|
|
||||||
if (javaPath && fs.existsSync(javaPath)) {
|
|
||||||
return resolve(javaPath);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Ошибка при поиске java через PATH:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если Java не найдена, выдаем ошибку
|
|
||||||
reject(
|
|
||||||
new Error('Java не найдена. Установите Java и повторите попытку.'),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppUpdater {
|
class AppUpdater {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -158,402 +44,8 @@ if (isDebug) {
|
|||||||
require('electron-debug').default();
|
require('electron-debug').default();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minecraft
|
// Инициализация обработчиков Minecraft
|
||||||
|
initMinecraftHandlers();
|
||||||
const COMFORT_DOWNLOAD_URL =
|
|
||||||
'https://github.com/DIKER0K/Comfort/releases/latest/download/Comfort.zip';
|
|
||||||
const GITHUB_API_RELEASE_URL =
|
|
||||||
'https://api.github.com/repos/DIKER0K/Comfort/releases/latest';
|
|
||||||
const COMFORT_VERSION_FILE = 'comfort_version.txt';
|
|
||||||
|
|
||||||
async function getLatestReleaseVersion(): Promise<string> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(GITHUB_API_RELEASE_URL);
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Модифицируем обработчик IPC
|
|
||||||
ipcMain.handle('download-and-extract', async (event) => {
|
|
||||||
try {
|
|
||||||
const appPath = path.dirname(app.getPath('exe'));
|
|
||||||
const minecraftDir = path.join(appPath, '.minecraft');
|
|
||||||
const versionsDir = path.join(minecraftDir, 'versions');
|
|
||||||
const versionFilePath = path.join(minecraftDir, COMFORT_VERSION_FILE);
|
|
||||||
|
|
||||||
// Получаем текущую и последнюю версии
|
|
||||||
const latestVersion = await getLatestReleaseVersion();
|
|
||||||
let currentVersion = '';
|
|
||||||
|
|
||||||
// Проверяем текущую версию, если файл существует
|
|
||||||
if (fs.existsSync(versionFilePath)) {
|
|
||||||
currentVersion = fs.readFileSync(versionFilePath, 'utf-8').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем, нужно ли обновление
|
|
||||||
if (currentVersion === latestVersion) {
|
|
||||||
return { success: true, updated: false, version: currentVersion };
|
|
||||||
}
|
|
||||||
|
|
||||||
const tempDir = path.join(appPath, 'temp');
|
|
||||||
|
|
||||||
// Создаем/очищаем временную директорию
|
|
||||||
if (fs.existsSync(tempDir)) {
|
|
||||||
fs.rmSync(tempDir, { recursive: true });
|
|
||||||
}
|
|
||||||
fs.mkdirSync(tempDir, { recursive: true });
|
|
||||||
|
|
||||||
const zipPath = path.join(tempDir, 'Comfort.zip');
|
|
||||||
|
|
||||||
// Скачиваем файл
|
|
||||||
await downloadFile(COMFORT_DOWNLOAD_URL, 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 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Распаковываем архив напрямую в папку versions
|
|
||||||
await extract(zipPath, { dir: versionsDir });
|
|
||||||
fs.unlinkSync(zipPath);
|
|
||||||
|
|
||||||
// Сохраняем новую версию
|
|
||||||
fs.writeFileSync(versionFilePath, latestVersion);
|
|
||||||
|
|
||||||
// Удаляем временную директорию
|
|
||||||
fs.rmSync(tempDir, { recursive: true });
|
|
||||||
|
|
||||||
// После распаковки архива и перед запуском
|
|
||||||
const versionsContents = fs.readdirSync(versionsDir);
|
|
||||||
console.log('Доступные версии:', versionsContents);
|
|
||||||
|
|
||||||
return { success: true, updated: true, version: latestVersion };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in download-and-extract:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcMain.handle('launch-minecraft', async (event) => {
|
|
||||||
try {
|
|
||||||
const baseVersion = '1.21.4';
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Найти версию Comfort или версию с Fabric
|
|
||||||
let versionToLaunch = '';
|
|
||||||
if (versionsContents.includes('1.21.4-fabric0.16.14')) {
|
|
||||||
versionToLaunch = '1.21.4-fabric0.16.14';
|
|
||||||
} else if (versionsContents.includes('Comfort')) {
|
|
||||||
versionToLaunch = 'Comfort';
|
|
||||||
} else {
|
|
||||||
versionToLaunch = '1.21.4';
|
|
||||||
}
|
|
||||||
|
|
||||||
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'; // Пробуем использовать системную 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...',
|
|
||||||
});
|
|
||||||
|
|
||||||
const fabricVersions = await getFabricLoaders();
|
|
||||||
const fabricVersion = fabricVersions[0]; // Последняя версия
|
|
||||||
|
|
||||||
if (fabricVersion) {
|
|
||||||
event.sender.send('installation-status', {
|
|
||||||
step: 'fabric-install',
|
|
||||||
message: `Установка Fabric ${fabricVersion.version}...`,
|
|
||||||
});
|
|
||||||
|
|
||||||
await installFabric({
|
|
||||||
minecraftVersion: baseVersion,
|
|
||||||
version: fabricVersion.version,
|
|
||||||
minecraft: minecraftDir,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Ошибка при установке Fabric, продолжаем:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Подготовка версии и установка зависимостей
|
|
||||||
try {
|
|
||||||
// Используем идентификатор Fabric-версии
|
|
||||||
const fabricVersionId = `${baseVersion}-fabric0.16.14`;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Запускаем Minecraft - НЕЗАВИСИМО ОТ ПРЕДЫДУЩИХ ОШИБОК
|
|
||||||
event.sender.send('installation-status', {
|
|
||||||
step: 'launch',
|
|
||||||
message: 'Запуск игры...',
|
|
||||||
});
|
|
||||||
|
|
||||||
const comfortDir = path.join(versionsDir, 'Comfort');
|
|
||||||
const proc = await launch({
|
|
||||||
gamePath: comfortDir,
|
|
||||||
resourcePath: minecraftDir,
|
|
||||||
javaPath,
|
|
||||||
version: '1.21.4-fabric0.16.14',
|
|
||||||
extraJVMArgs: ['-Dlog4j2.formatMsgNoLookups=true', '-Xmx2G'], // Добавляем больше памяти
|
|
||||||
});
|
|
||||||
|
|
||||||
// Логирование
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const installExtensions = async () => {
|
const installExtensions = async () => {
|
||||||
const installer = require('electron-devtools-installer');
|
const installer = require('electron-devtools-installer');
|
||||||
@ -623,6 +115,9 @@ const createWindow = async () => {
|
|||||||
// Remove this if your app does not use auto updates
|
// Remove this if your app does not use auto updates
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
new AppUpdater();
|
new AppUpdater();
|
||||||
|
|
||||||
|
initAuthHandlers();
|
||||||
|
initMinecraftHandlers();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
739
src/main/minecraft-launcher.ts
Normal file
739
src/main/minecraft-launcher.ts
Normal file
@ -0,0 +1,739 @@
|
|||||||
|
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 COMFORT_DOWNLOAD_URL =
|
||||||
|
'https://github.com/DIKER0K/Comfort/releases/latest/download/Comfort.zip';
|
||||||
|
const GITHUB_API_RELEASE_URL =
|
||||||
|
'https://api.github.com/repos/DIKER0K/Comfort/releases/latest';
|
||||||
|
const COMFORT_VERSION_FILE = 'comfort_version.txt';
|
||||||
|
const AUTHLIB_INJECTOR_FILENAME = 'authlib-injector-1.2.5.jar';
|
||||||
|
|
||||||
|
// Создаем экземпляр сервиса аутентификации
|
||||||
|
const authService = new AuthService();
|
||||||
|
|
||||||
|
// Функция для получения последней версии релиза
|
||||||
|
export async function getLatestReleaseVersion(): Promise<string> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(GITHUB_API_RELEASE_URL);
|
||||||
|
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) => {
|
||||||
|
try {
|
||||||
|
const appPath = path.dirname(app.getPath('exe'));
|
||||||
|
const minecraftDir = path.join(appPath, '.minecraft');
|
||||||
|
const versionsDir = path.join(minecraftDir, 'versions');
|
||||||
|
const versionFilePath = path.join(minecraftDir, COMFORT_VERSION_FILE);
|
||||||
|
|
||||||
|
// Получаем текущую и последнюю версии
|
||||||
|
const latestVersion = await getLatestReleaseVersion();
|
||||||
|
let currentVersion = '';
|
||||||
|
|
||||||
|
// Проверяем текущую версию, если файл существует
|
||||||
|
if (fs.existsSync(versionFilePath)) {
|
||||||
|
currentVersion = fs.readFileSync(versionFilePath, 'utf-8').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, нужно ли обновление
|
||||||
|
if (currentVersion === latestVersion) {
|
||||||
|
return { success: true, updated: false, version: currentVersion };
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempDir = path.join(appPath, 'temp');
|
||||||
|
|
||||||
|
// Создаем/очищаем временную директорию
|
||||||
|
if (fs.existsSync(tempDir)) {
|
||||||
|
fs.rmSync(tempDir, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.mkdirSync(tempDir, { recursive: true });
|
||||||
|
|
||||||
|
const zipPath = path.join(tempDir, 'Comfort.zip');
|
||||||
|
|
||||||
|
// Скачиваем файл
|
||||||
|
await downloadFile(COMFORT_DOWNLOAD_URL, 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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Распаковываем архив напрямую в папку versions
|
||||||
|
await extract(zipPath, { dir: versionsDir });
|
||||||
|
fs.unlinkSync(zipPath);
|
||||||
|
|
||||||
|
// Сохраняем новую версию
|
||||||
|
fs.writeFileSync(versionFilePath, latestVersion);
|
||||||
|
|
||||||
|
// Удаляем временную директорию
|
||||||
|
fs.rmSync(tempDir, { recursive: true });
|
||||||
|
|
||||||
|
// После распаковки архива и перед запуском
|
||||||
|
const versionsContents = fs.readdirSync(versionsDir);
|
||||||
|
console.log('Доступные версии:', versionsContents);
|
||||||
|
|
||||||
|
return { success: true, updated: true, version: latestVersion };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in download-and-extract:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик для запуска Minecraft
|
||||||
|
ipcMain.handle('launch-minecraft', async (event, gameConfig) => {
|
||||||
|
try {
|
||||||
|
const baseVersion = '1.21.4';
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Найти версию Comfort или версию с Fabric
|
||||||
|
let versionToLaunch = '';
|
||||||
|
if (versionsContents.includes('1.21.4-fabric0.16.14')) {
|
||||||
|
versionToLaunch = '1.21.4-fabric0.16.14';
|
||||||
|
} else if (versionsContents.includes('Comfort')) {
|
||||||
|
versionToLaunch = 'Comfort';
|
||||||
|
} else {
|
||||||
|
versionToLaunch = '1.21.4';
|
||||||
|
}
|
||||||
|
|
||||||
|
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...',
|
||||||
|
});
|
||||||
|
|
||||||
|
const fabricVersions = await getFabricLoaders();
|
||||||
|
const fabricVersion = fabricVersions[0]; // Последняя версия
|
||||||
|
|
||||||
|
if (fabricVersion) {
|
||||||
|
event.sender.send('installation-status', {
|
||||||
|
step: 'fabric-install',
|
||||||
|
message: `Установка Fabric ${fabricVersion.version}...`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await installFabric({
|
||||||
|
minecraftVersion: baseVersion,
|
||||||
|
version: fabricVersion.version,
|
||||||
|
minecraft: minecraftDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Ошибка при установке Fabric, продолжаем:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Подготовка версии и установка зависимостей
|
||||||
|
try {
|
||||||
|
// Используем идентификатор Fabric-версии
|
||||||
|
const fabricVersionId = `${baseVersion}-fabric0.16.14`;
|
||||||
|
|
||||||
|
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 comfortDir = path.join(versionsDir, 'Comfort');
|
||||||
|
const proc = await launch({
|
||||||
|
gamePath: comfortDir,
|
||||||
|
resourcePath: minecraftDir,
|
||||||
|
javaPath,
|
||||||
|
version: versionToLaunch,
|
||||||
|
extraJVMArgs: [
|
||||||
|
'-Dlog4j2.formatMsgNoLookups=true',
|
||||||
|
`-javaagent:${authlibPath}=ely.by`,
|
||||||
|
`-Xmx${gameConfig.memory || 2048}M`,
|
||||||
|
],
|
||||||
|
// Используем данные аутентификации Yggdrasil
|
||||||
|
accessToken: gameConfig.accessToken,
|
||||||
|
gameProfile: {
|
||||||
|
id: gameConfig.uuid,
|
||||||
|
name: gameConfig.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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем обработчики 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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
@ -1,177 +1,94 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
const useAuth = () => {
|
interface AuthSession {
|
||||||
const [status, setStatus] = useState('');
|
accessToken: string;
|
||||||
|
clientToken: string;
|
||||||
|
selectedProfile: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const validateSession = async (accessToken: string) => {
|
export default function useAuth() {
|
||||||
|
const [status, setStatus] = useState('idle');
|
||||||
|
|
||||||
|
// Проверка валидности токена
|
||||||
|
const validateSession = async (accessToken: string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://authserver.ely.by/auth/validate', {
|
setStatus('validating');
|
||||||
method: 'POST',
|
const response = await window.electron.ipcRenderer.invoke(
|
||||||
headers: {
|
'validate-token',
|
||||||
'Content-Type': 'application/json',
|
accessToken,
|
||||||
},
|
);
|
||||||
body: JSON.stringify({
|
setStatus('idle');
|
||||||
accessToken: accessToken,
|
return response.valid;
|
||||||
}),
|
|
||||||
});
|
|
||||||
return response.ok;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Ошибка при проверке токена: ${error.message}`);
|
console.error('Ошибка при валидации токена:', error);
|
||||||
|
setStatus('error');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshSession = async (accessToken: string, clientToken: string) => {
|
// Обновление токена
|
||||||
|
const refreshSession = async (
|
||||||
|
accessToken: string,
|
||||||
|
clientToken: string,
|
||||||
|
): Promise<AuthSession | null> => {
|
||||||
try {
|
try {
|
||||||
const refreshData = {
|
setStatus('refreshing');
|
||||||
accessToken: accessToken,
|
const response = await window.electron.ipcRenderer.invoke(
|
||||||
clientToken: clientToken,
|
'refresh-token',
|
||||||
};
|
{ accessToken, clientToken },
|
||||||
|
);
|
||||||
const response = await fetch('https://authserver.ely.by/auth/refresh', {
|
setStatus('idle');
|
||||||
method: 'POST',
|
return response;
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(refreshData),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
const newAccessToken = data.accessToken;
|
|
||||||
const profile = data.selectedProfile;
|
|
||||||
const uuid = profile.id;
|
|
||||||
const name = profile.name;
|
|
||||||
|
|
||||||
if (newAccessToken && uuid && name) {
|
|
||||||
return {
|
|
||||||
accessToken: newAccessToken,
|
|
||||||
uuid: uuid,
|
|
||||||
username: name,
|
|
||||||
clientToken: clientToken,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Ошибка при обновлении сессии: ${error.message}`);
|
console.error('Ошибка при обновлении токена:', error);
|
||||||
|
setStatus('error');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Аутентификация в Ely.by
|
||||||
const authenticateWithElyBy = async (
|
const authenticateWithElyBy = async (
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
saveConfig: Function,
|
saveConfigFunc: Function,
|
||||||
) => {
|
): Promise<AuthSession | null> => {
|
||||||
try {
|
try {
|
||||||
const clientToken = crypto.randomUUID();
|
setStatus('authenticating');
|
||||||
const authData = {
|
const response = await window.electron.ipcRenderer.invoke(
|
||||||
username: username,
|
'authenticate',
|
||||||
password: password,
|
{ username, password },
|
||||||
clientToken: clientToken,
|
|
||||||
requestUser: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(`Аутентификация пользователя ${username} на Ely.by...`);
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
'https://authserver.ely.by/auth/authenticate',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(authData),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const responseData = await response.json();
|
if (response && response.accessToken) {
|
||||||
if (response.ok) {
|
// Правильно сохраняем данные в конфигурации
|
||||||
const accessToken = responseData.accessToken;
|
saveConfigFunc({
|
||||||
const profile = responseData.selectedProfile;
|
username: response.selectedProfile.name, // Имя игрока как строка
|
||||||
const uuid = profile.id;
|
uuid: response.selectedProfile.id,
|
||||||
const name = profile.name;
|
accessToken: response.accessToken,
|
||||||
|
clientToken: response.clientToken,
|
||||||
|
memory: 4096, // Сохраняем значение по умолчанию или из предыдущей конфигурации
|
||||||
|
});
|
||||||
|
|
||||||
if (accessToken && uuid && name) {
|
setStatus('authenticated');
|
||||||
saveConfig(
|
return response;
|
||||||
username,
|
|
||||||
4096, // default memory
|
|
||||||
accessToken,
|
|
||||||
clientToken,
|
|
||||||
'',
|
|
||||||
password,
|
|
||||||
);
|
|
||||||
console.log(`Аутентификация успешна: UUID=${uuid}, Username=${name}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
accessToken: accessToken,
|
|
||||||
uuid: uuid,
|
|
||||||
username: name,
|
|
||||||
clientToken: clientToken,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (responseData.error === 'Account protected with two factor auth') {
|
|
||||||
const totpToken = prompt(
|
|
||||||
'Введите код двухфакторной аутентификации:',
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (totpToken) {
|
|
||||||
authData.password = `${password}:${totpToken}`;
|
|
||||||
const totpResponse = await fetch(
|
|
||||||
'https://authserver.ely.by/auth/authenticate',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(authData),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (totpResponse.ok) {
|
|
||||||
const totpData = await totpResponse.json();
|
|
||||||
const newAccessToken = totpData.accessToken;
|
|
||||||
const newProfile = totpData.selectedProfile;
|
|
||||||
const newUuid = newProfile.id;
|
|
||||||
const newName = newProfile.name;
|
|
||||||
|
|
||||||
if (newAccessToken && newUuid && newName) {
|
|
||||||
saveConfig(
|
|
||||||
username,
|
|
||||||
4096, // default memory
|
|
||||||
newAccessToken,
|
|
||||||
clientToken,
|
|
||||||
'',
|
|
||||||
password,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
accessToken: newAccessToken,
|
|
||||||
uuid: newUuid,
|
|
||||||
username: newName,
|
|
||||||
clientToken: clientToken,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(responseData.error || 'Ошибка авторизации');
|
setStatus('error');
|
||||||
}
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Ошибка авторизации: ${error.message}`);
|
console.error('Ошибка при аутентификации:', error);
|
||||||
|
setStatus('error');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status,
|
status,
|
||||||
setStatus,
|
|
||||||
validateSession,
|
validateSession,
|
||||||
refreshSession,
|
refreshSession,
|
||||||
authenticateWithElyBy,
|
authenticateWithElyBy,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export default useAuth;
|
|
||||||
|
@ -1,7 +1,18 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
// Добавляем определение типа Config
|
||||||
|
interface Config {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
memory: number;
|
||||||
|
comfortVersion: string;
|
||||||
|
accessToken: string;
|
||||||
|
clientToken: string;
|
||||||
|
uuid?: string; // Добавляем uuid, который используется для авторизации
|
||||||
|
}
|
||||||
|
|
||||||
const useConfig = () => {
|
const useConfig = () => {
|
||||||
const [config, setConfig] = useState({
|
const [config, setConfig] = useState<Config>({
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
memory: 4096,
|
memory: 4096,
|
||||||
@ -34,28 +45,10 @@ const useConfig = () => {
|
|||||||
setConfig(savedConfig);
|
setConfig(savedConfig);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const saveConfig = (
|
const saveConfig = (newConfig: Partial<Config>) => {
|
||||||
username: string,
|
const updatedConfig = { ...config, ...newConfig };
|
||||||
memory: number,
|
setConfig(updatedConfig);
|
||||||
accessToken = '',
|
localStorage.setItem('launcher_config', JSON.stringify(updatedConfig));
|
||||||
clientToken = '',
|
|
||||||
comfortVersion = '',
|
|
||||||
password = '',
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const newConfig = {
|
|
||||||
username,
|
|
||||||
memory,
|
|
||||||
accessToken: accessToken || config.accessToken,
|
|
||||||
clientToken: clientToken || config.clientToken,
|
|
||||||
comfortVersion: comfortVersion || config.comfortVersion,
|
|
||||||
password: password || config.password,
|
|
||||||
};
|
|
||||||
setConfig(newConfig);
|
|
||||||
localStorage.setItem('launcher_config', JSON.stringify(newConfig));
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Ошибка при сохранении конфигурации: ${error.message}`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -33,22 +33,6 @@ const LaunchPage = () => {
|
|||||||
navigate('/login');
|
navigate('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
window.electron.ipcRenderer.on('download-progress', (progress: any) => {
|
|
||||||
setDownloadProgress(progress as number);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Добавляем слушатель для статуса установки
|
|
||||||
window.electron.ipcRenderer.on('installation-progress', (data: any) => {
|
|
||||||
setInstallStatus((data as { status: string }).status);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обновляем слушатель для статуса установки
|
|
||||||
window.electron.ipcRenderer.on('installation-status', (data: any) => {
|
|
||||||
const { step, message } = data as { step: string; message: string };
|
|
||||||
setInstallStep(step);
|
|
||||||
setInstallMessage(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.electron.ipcRenderer.removeAllListeners('download-progress');
|
window.electron.ipcRenderer.removeAllListeners('download-progress');
|
||||||
window.electron.ipcRenderer.removeAllListeners('installation-progress');
|
window.electron.ipcRenderer.removeAllListeners('installation-progress');
|
||||||
@ -77,6 +61,11 @@ const LaunchPage = () => {
|
|||||||
setIsDownloading(true);
|
setIsDownloading(true);
|
||||||
setDownloadProgress(0);
|
setDownloadProgress(0);
|
||||||
|
|
||||||
|
// Загружаем настройки и токены
|
||||||
|
const savedConfig = JSON.parse(
|
||||||
|
localStorage.getItem('launcher_config') || '{}',
|
||||||
|
);
|
||||||
|
|
||||||
// Сначала проверяем и обновляем файлы
|
// Сначала проверяем и обновляем файлы
|
||||||
const downloadResult = await window.electron.ipcRenderer.invoke(
|
const downloadResult = await window.electron.ipcRenderer.invoke(
|
||||||
'download-and-extract',
|
'download-and-extract',
|
||||||
@ -95,9 +84,16 @@ const LaunchPage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Затем запускаем Minecraft
|
// Затем запускаем Minecraft с данными авторизации
|
||||||
const launchResult =
|
const launchResult = await window.electron.ipcRenderer.invoke(
|
||||||
await window.electron.ipcRenderer.invoke('launch-minecraft');
|
'launch-minecraft',
|
||||||
|
{
|
||||||
|
accessToken: savedConfig.accessToken,
|
||||||
|
uuid: savedConfig.uuid,
|
||||||
|
username: savedConfig.username,
|
||||||
|
memory: savedConfig.memory || 4096,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (launchResult?.success) {
|
if (launchResult?.success) {
|
||||||
showNotification('Minecraft успешно запущен!', 'success');
|
showNotification('Minecraft успешно запущен!', 'success');
|
||||||
|
Reference in New Issue
Block a user