mirror of
https://gitlab.com/vyla-entertainment/go.git
synced 2026-11-09 13:07:21 -05:00
187 lines
5.6 KiB
JavaScript
187 lines
5.6 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const crypto = require('crypto');
|
|
const AdmZip = require('adm-zip');
|
|
const CryptoUtils = require('./crypto-utils');
|
|
|
|
class Patcher {
|
|
constructor(appDataPath) {
|
|
this.appDataPath = appDataPath;
|
|
}
|
|
|
|
getCurrentVersion() {
|
|
const versionFile = path.join(this.appDataPath, 'version.json');
|
|
if (fs.existsSync(versionFile)) {
|
|
try {
|
|
return JSON.parse(fs.readFileSync(versionFile, 'utf8')).version || '0.0.0';
|
|
} catch (e) {
|
|
return '0.0.0';
|
|
}
|
|
}
|
|
return '0.0.0';
|
|
}
|
|
|
|
getCurrentSha() {
|
|
const versionFile = path.join(this.appDataPath, 'version.json');
|
|
if (fs.existsSync(versionFile)) {
|
|
try {
|
|
return JSON.parse(fs.readFileSync(versionFile, 'utf8')).sha256 || null;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
bridgeCredentials(credentialManager) {
|
|
const encryptedPath = path.join(this.appDataPath, 'encrypted-credentials.json');
|
|
if (!fs.existsSync(encryptedPath)) {
|
|
return false;
|
|
}
|
|
|
|
const buildKey = CryptoUtils.deriveKey('vyla-home-build-key', 'vyla-home-payload-salt-v1');
|
|
const encryptedData = fs.readFileSync(encryptedPath, 'utf8');
|
|
const credentials = CryptoUtils.decryptObject(encryptedData, buildKey);
|
|
|
|
for (const [service, keys] of Object.entries(credentials)) {
|
|
credentialManager.setServiceCredentials(service, keys);
|
|
}
|
|
|
|
fs.unlinkSync(encryptedPath);
|
|
return true;
|
|
}
|
|
|
|
saveCurrentVersion(version, sha256 = null) {
|
|
const versionFile = path.join(this.appDataPath, 'version.json');
|
|
fs.mkdirSync(this.appDataPath, { recursive: true });
|
|
fs.writeFileSync(versionFile, JSON.stringify({ version, sha256 }, null, 2), 'utf8');
|
|
}
|
|
|
|
purgeOldCode() {
|
|
const dirs = ['frontend', 'player', 'stream-api', 'auth-proxy', 'config'];
|
|
for (const dir of dirs) {
|
|
const fullPath = path.join(this.appDataPath, dir);
|
|
if (fs.existsSync(fullPath)) {
|
|
try {
|
|
const trashPath = path.join(this.appDataPath, `.trash_${dir}_${Date.now()}`);
|
|
fs.renameSync(fullPath, trashPath);
|
|
fs.rmSync(trashPath, { recursive: true, force: true });
|
|
} catch (e) {
|
|
try {
|
|
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
} catch (err2) { }
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
const files = fs.readdirSync(this.appDataPath);
|
|
for (const file of files) {
|
|
if (file.startsWith('.trash_')) {
|
|
fs.rmSync(path.join(this.appDataPath, file), { recursive: true, force: true });
|
|
}
|
|
}
|
|
} catch (e) { }
|
|
}
|
|
|
|
extractZipPayload(zipPath) {
|
|
const AdmZip = require('adm-zip');
|
|
const zip = new AdmZip(zipPath);
|
|
const entries = zip.getEntries();
|
|
|
|
if (!entries || entries.length === 0) {
|
|
throw new Error('Downloaded archive is empty, invalid, or truncated.');
|
|
}
|
|
|
|
zip.extractAllTo(this.appDataPath, true);
|
|
|
|
const testPath = path.join(this.appDataPath, 'frontend', 'index.html');
|
|
if (!fs.existsSync(testPath)) {
|
|
const { execSync } = require('child_process');
|
|
if (process.platform === 'win32') {
|
|
execSync(`powershell.exe -NoProfile -NonInteractive -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${this.appDataPath}' -Force"`, { windowsHide: true });
|
|
} else {
|
|
execSync(`unzip -o "${zipPath}" -d "${this.appDataPath}"`);
|
|
}
|
|
}
|
|
}
|
|
|
|
verifyChecksum(zipPath, expectedSha256) {
|
|
if (!expectedSha256) return true;
|
|
const hash = crypto.createHash('sha256');
|
|
const data = fs.readFileSync(zipPath);
|
|
hash.update(data);
|
|
const calculated = hash.digest('hex');
|
|
return calculated === expectedSha256;
|
|
}
|
|
|
|
verifyExtraction() {
|
|
const requiredFiles = [
|
|
'frontend/index.html',
|
|
'player/server.js'
|
|
];
|
|
|
|
for (const file of requiredFiles) {
|
|
const fullPath = path.join(this.appDataPath, file);
|
|
if (!fs.existsSync(fullPath)) {
|
|
return false;
|
|
}
|
|
const stat = fs.statSync(fullPath);
|
|
if (stat.size === 0) {
|
|
return false;
|
|
}
|
|
try {
|
|
const sampleSize = Math.min(stat.size, 4096);
|
|
const fd = fs.openSync(fullPath, 'r');
|
|
const buffer = Buffer.alloc(sampleSize);
|
|
fs.readSync(fd, buffer, 0, sampleSize, 0);
|
|
fs.closeSync(fd);
|
|
if (buffer.every(byte => byte === 0)) {
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
processDirectory(dir, replacementList = []) {
|
|
const files = fs.readdirSync(dir);
|
|
for (const file of files) {
|
|
const fullPath = path.join(dir, file);
|
|
const stat = fs.statSync(fullPath);
|
|
|
|
if (stat.isDirectory()) {
|
|
if (file === 'node_modules' || file === '.git') continue;
|
|
this.processDirectory(fullPath, replacementList);
|
|
} else if (stat.isFile()) {
|
|
const ext = path.extname(fullPath).toLowerCase();
|
|
let content = fs.readFileSync(fullPath, 'utf8');
|
|
let modified = false;
|
|
|
|
for (const rep of replacementList) {
|
|
if (content.includes(rep.find)) {
|
|
content = content.split(rep.find).join(rep.replace);
|
|
modified = true;
|
|
}
|
|
}
|
|
|
|
if (modified) {
|
|
fs.writeFileSync(fullPath, content, 'utf8');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
applyPatches(replacements) {
|
|
const targetDirs = ['frontend', 'player'];
|
|
for (const dirName of targetDirs) {
|
|
const dirPath = path.join(this.appDataPath, dirName);
|
|
if (!fs.existsSync(dirPath)) continue;
|
|
const replacementList = replacements[dirName] || [];
|
|
this.processDirectory(dirPath, replacementList);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = Patcher; |