This commit is contained in:
gardenGnostic 2026-03-05 00:33:04 +01:00 committed by GitHub
parent b7b9c56a83
commit aff0d329ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1206 additions and 1046 deletions

Binary file not shown.

1037
index.html

File diff suppressed because it is too large Load diff

13
main.js
View file

@ -1,7 +1,13 @@
const { app, BrowserWindow, shell, ipcMain } = require('electron');
const path = require('path');
const DiscordRPC = require('discord-rpc');
const Store = require('electron-store');
const fs = require('fs');
const https = require('https');
const extractZip = require('extract-zip');
const { exec } = require('child_process');
const store = new Store();
const clientId = '1346541144141103114';
let rpc;
@ -62,19 +68,19 @@ function createWindow() {
height: 720,
minWidth: 1024,
minHeight: 600,
center: true,
resizable: true,
frame: false,
icon: path.join(__dirname, '256x256.png'),
transparent: true,
autoHideMenuBar: true,
webPreferences: {
nodeIntegration: true,
nodeIntegration: true, // Keeping for now to minimize breakage during refactor, but moving store to main
contextIsolation: false,
enableRemoteModule: true
}
});
win.maximize();
win.loadFile('index.html');
// Handle window controls
@ -88,6 +94,9 @@ function createWindow() {
});
ipcMain.on('window-close', () => win.close());
// Store IPC handlers
ipcMain.handle('store-get', (event, key) => store.get(key));
ipcMain.handle('store-set', (event, key, value) => store.set(key, value));
ipcMain.on('update-rpc', (event, data) => {
setActivity(data.details, data.state, data.startTime);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 987 KiB

229
package-lock.json generated
View file

@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"discord-rpc": "^4.0.1",
"electron-store": "^6.0.1",
"extract-zip": "^2.0.1"
},
"devDependencies": {
@ -913,7 +914,6 @@
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
@ -1198,6 +1198,15 @@
"node": ">= 4.0.0"
}
},
"node_modules/atomically": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
"integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==",
"license": "MIT",
"engines": {
"node": ">=10.12.0"
}
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@ -1712,6 +1721,42 @@
"dev": true,
"license": "MIT"
},
"node_modules/conf": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/conf/-/conf-7.1.2.tgz",
"integrity": "sha512-r8/HEoWPFn4CztjhMJaWNAe5n+gPUCSaJ0oufbqDLFKsA1V8JjAG7G+p0pgoDFAws9Bpk2VtVLLXqOBA7WxLeg==",
"license": "MIT",
"dependencies": {
"ajv": "^6.12.2",
"atomically": "^1.3.1",
"debounce-fn": "^4.0.0",
"dot-prop": "^5.2.0",
"env-paths": "^2.2.0",
"json-schema-typed": "^7.0.3",
"make-dir": "^3.1.0",
"onetime": "^5.1.0",
"pkg-up": "^3.1.0",
"semver": "^7.3.2"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/conf/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@ -1778,6 +1823,30 @@
"node": ">= 8"
}
},
"node_modules/debounce-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz",
"integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==",
"license": "MIT",
"dependencies": {
"mimic-fn": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/debounce-fn/node_modules/mimic-fn": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
"integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@ -2050,6 +2119,18 @@
"node": ">=8"
}
},
"node_modules/dot-prop": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
"integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
"license": "MIT",
"dependencies": {
"is-obj": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@ -2268,6 +2349,31 @@
"node": ">= 10.0.0"
}
},
"node_modules/electron-store": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/electron-store/-/electron-store-6.0.1.tgz",
"integrity": "sha512-8rdM0XEmDGsLuZM2oRABzsLX+XmD5x3rwxPMEPv0MrN9/BWanyy3ilb2v+tCrKtIZVF3MxUiZ9Bfqe8e0popKQ==",
"license": "MIT",
"dependencies": {
"conf": "^7.1.2",
"type-fest": "^0.16.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/electron-store/node_modules/type-fest": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
"integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/electron-winstaller": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
@ -2336,7 +2442,6 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -2472,14 +2577,12 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true,
"license": "MIT"
},
"node_modules/fd-slicer": {
@ -2556,6 +2659,18 @@
"node": ">=10"
}
},
"node_modules/find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"license": "MIT",
"dependencies": {
"locate-path": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@ -3101,6 +3216,15 @@
"node": ">=8"
}
},
"node_modules/is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@ -3205,9 +3329,14 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/json-schema-typed": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz",
"integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==",
"license": "BSD-2-Clause"
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@ -3256,6 +3385,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"license": "MIT",
"dependencies": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
@ -3303,6 +3445,21 @@
"node": ">=10"
}
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"license": "MIT",
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-fetch-happen": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz",
@ -3390,7 +3547,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -3761,7 +3917,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-fn": "^2.1.0"
@ -3823,6 +3978,33 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/p-locate/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-map": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz",
@ -3836,6 +4018,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@ -3843,6 +4034,15 @@
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -3928,6 +4128,18 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pkg-up": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
"integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
"license": "MIT",
"dependencies": {
"find-up": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/plist": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
@ -4033,7 +4245,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -4249,7 +4460,6 @@
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@ -4764,7 +4974,6 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"

View file

@ -39,6 +39,7 @@
"license": "ISC",
"dependencies": {
"discord-rpc": "^4.0.1",
"electron-store": "^6.0.1",
"extract-zip": "^2.0.1"
},
"devDependencies": {

565
renderer.js Normal file
View file

@ -0,0 +1,565 @@
const { ipcRenderer, shell } = require('electron');
const fs = require('fs');
const path = require('path');
const https = require('https');
const extractZip = require('extract-zip');
const childProcess = require('child_process');
const DEFAULT_REPO = "smartcmd/MinecraftConsoles";
const DEFAULT_EXEC = "Minecraft.Client.exe";
const TARGET_FILE = "LCEWindows64.zip";
let releasesData = [];
let currentReleaseIndex = 0;
let isProcessing = false;
let isGameRunning = false;
const Store = {
async get(key, defaultValue) {
const val = await ipcRenderer.invoke('store-get', key);
return val !== undefined ? val : defaultValue;
},
async set(key, value) {
return await ipcRenderer.invoke('store-set', key, value);
}
};
window.onload = async () => {
document.getElementById('repo-input').value = await Store.get('legacy_repo', DEFAULT_REPO);
document.getElementById('exec-input').value = await Store.get('legacy_exec_path', DEFAULT_EXEC);
document.getElementById('username-input').value = await Store.get('legacy_username', "");
document.getElementById('ip-input').value = await Store.get('legacy_ip', "");
document.getElementById('port-input').value = await Store.get('legacy_port', "");
document.getElementById('server-checkbox').checked = await Store.get('legacy_is_server', false);
if (process.platform === 'linux') {
document.getElementById('compat-option-container').style.display = 'block';
scanCompatibilityLayers();
}
ipcRenderer.on('window-is-maximized', (event, isMaximized) => {
document.getElementById('maximize-btn').textContent = isMaximized ? '❐' : '▢';
});
fetchGitHubData();
};
async function scanCompatibilityLayers() {
const select = document.getElementById('compat-select');
const savedValue = await Store.get('legacy_compat_layer', 'direct');
const layers = [
{ name: 'Default (Direct)', cmd: 'direct' },
{ name: 'Wine64', cmd: 'wine64' },
{ name: 'Wine', cmd: 'wine' }
];
const homeDir = require('os').homedir();
const steamPaths = [
path.join(homeDir, '.steam', 'steam', 'steamapps', 'common'),
path.join(homeDir, '.local', 'share', 'Steam', 'steamapps', 'common'),
path.join(homeDir, '.var', 'app', 'com.valvesoftware.Steam', 'data', 'Steam', 'steamapps', 'common')
];
for (const steamPath of steamPaths) {
if (fs.existsSync(steamPath)) {
try {
const dirs = fs.readdirSync(steamPath);
dirs.filter(d => d.startsWith('Proton')).forEach(d => {
const protonPath = path.join(steamPath, d, 'proton');
if (fs.existsSync(protonPath)) {
layers.push({ name: d, cmd: protonPath });
}
});
} catch (e) {}
}
}
select.innerHTML = '';
layers.forEach(l => {
const opt = document.createElement('option');
opt.value = l.cmd;
opt.textContent = l.name;
select.appendChild(opt);
if (l.cmd === savedValue) opt.selected = true;
});
updateCompatDisplay();
}
function updateCompatDisplay() {
const select = document.getElementById('compat-select');
const display = document.getElementById('current-compat-display');
if (select && display && select.selectedIndex !== -1) {
display.textContent = select.options[select.selectedIndex].text;
}
}
async function getInstalledPath() {
const homeDir = require('os').homedir();
const execPath = await Store.get('legacy_exec_path', DEFAULT_EXEC);
return path.join(homeDir, 'Downloads', 'LegacyClient', execPath);
}
async function checkIsInstalled(tag) {
const fullPath = await getInstalledPath();
const installedTag = await Store.get('installed_version_tag');
return fs.existsSync(fullPath) && installedTag === tag;
}
async function updatePlayButtonText() {
const btn = document.getElementById('btn-play-main');
if (isProcessing) return;
if (isGameRunning) {
btn.textContent = "GAME RUNNING";
btn.classList.add('running');
return;
} else {
btn.classList.remove('running');
}
const release = releasesData[currentReleaseIndex];
if (!release) {
btn.textContent = "PLAY";
return;
}
if (await checkIsInstalled(release.tag_name)) {
btn.textContent = "PLAY";
} else {
const fullPath = await getInstalledPath();
if (fs.existsSync(fullPath)) {
btn.textContent = "UPDATE";
} else {
btn.textContent = "INSTALL";
}
}
}
function updateRPC(details, state, startTime = null) {
ipcRenderer.send('update-rpc', { details, state, startTime });
}
function setGameRunning(running) {
isGameRunning = running;
updatePlayButtonText();
if (!running) {
updateRPC('In Menus', 'Ready to Play');
}
}
async function monitorProcess(proc) {
if (!proc) return;
const sessionStart = Date.now();
setGameRunning(true);
const release = releasesData[currentReleaseIndex];
const version = release ? release.tag_name : 'Unknown';
const isServer = await Store.get('legacy_is_server', false);
updateRPC(`Playing Legacy (${version})`, isServer ? 'Running Headless Server' : 'In Game', sessionStart);
proc.on('exit', async () => {
const sessionDuration = Math.floor((Date.now() - sessionStart) / 1000);
const playtime = await Store.get('legacy_playtime', 0);
await Store.set('legacy_playtime', playtime + sessionDuration);
setGameRunning(false);
});
proc.on('error', (err) => {
console.error("Process error:", err);
setGameRunning(false);
});
}
function minimizeWindow() {
ipcRenderer.send('window-minimize');
}
function toggleMaximize() {
ipcRenderer.send('window-maximize');
}
function closeWindow() {
ipcRenderer.send('window-close');
}
async function fetchGitHubData() {
const repo = await Store.get('legacy_repo', DEFAULT_REPO);
const loader = document.getElementById('loader');
const loaderText = document.getElementById('loader-text');
loader.style.display = 'flex';
loaderText.textContent = "SYNCING: " + repo;
try {
const response = await fetch(`https://api.github.com/repos/${repo}/releases`);
if (!response.ok) throw new Error("Rate Limited");
releasesData = await response.json();
populateVersions();
setTimeout(() => {
loader.style.opacity = '0';
setTimeout(() => loader.style.display = 'none', 300);
}, 500);
} catch (err) {
loaderText.textContent = "REPO NOT FOUND OR API ERROR";
showToast("Check repository name in Options.");
setTimeout(() => {
loader.style.opacity = '0';
setTimeout(() => loader.style.display = 'none', 300);
}, 2500);
}
}
function populateVersions() {
const select = document.getElementById('version-select');
const display = document.getElementById('current-version-display');
select.innerHTML = '';
if(releasesData.length === 0) {
display.textContent = "No Releases Found";
return;
}
releasesData.forEach((rel, index) => {
const opt = document.createElement('option');
opt.value = index;
opt.textContent = `Legacy (${rel.tag_name})`;
select.appendChild(opt);
if(index === 0) display.textContent = opt.textContent;
});
currentReleaseIndex = 0;
updatePlayButtonText();
}
function updateSelectedRelease() {
const select = document.getElementById('version-select');
currentReleaseIndex = select.value;
document.getElementById('current-version-display').textContent = select.options[select.selectedIndex].text;
updatePlayButtonText();
}
async function launchGame() {
if (isProcessing || isGameRunning) return;
const release = releasesData[currentReleaseIndex];
if (!release) return;
const asset = release.assets.find(a => a.name === TARGET_FILE);
if (!asset) {
showToast("ZIP Asset missing in this version!");
return;
}
const isInstalled = await checkIsInstalled(release.tag_name);
if (isInstalled) {
setProcessingState(true);
updateProgress(100, "Launching...");
await launchLocalClient();
setProcessingState(false);
} else {
const fullPath = await getInstalledPath();
if (fs.existsSync(fullPath)) {
const choice = await promptUpdate(release.tag_name);
if (choice === 'update') {
setProcessingState(true);
await handleElectronFlow(asset.browser_download_url);
setProcessingState(false);
} else {
setProcessingState(true);
updateProgress(100, "Launching Existing...");
await launchLocalClient();
setProcessingState(false);
}
} else {
setProcessingState(true);
await handleElectronFlow(asset.browser_download_url);
setProcessingState(false);
}
}
updatePlayButtonText();
}
async function promptUpdate(newTag) {
return new Promise(async (resolve) => {
const modal = document.getElementById('update-modal');
const confirmBtn = document.getElementById('btn-confirm-update');
const skipBtn = document.getElementById('btn-skip-update');
const installedTag = await Store.get('installed_version_tag', "Unknown");
document.getElementById('update-modal-text').innerHTML =
`New version <b>${newTag}</b> is available.<br><br>` +
`Currently installed: <b>${installedTag}</b>.<br><br>` +
`Would you like to update now?`;
modal.style.display = 'flex';
modal.style.opacity = '1';
const cleanup = (result) => {
modal.style.opacity = '0';
setTimeout(() => modal.style.display = 'none', 300);
confirmBtn.onclick = null;
skipBtn.onclick = null;
resolve(result);
};
confirmBtn.onclick = () => cleanup('update');
skipBtn.onclick = () => cleanup('launch');
});
}
async function launchLocalClient() {
const fullPath = await getInstalledPath();
if (!fs.existsSync(fullPath)) {
throw new Error("Executable not found! Try reinstalling.");
}
// Ensure the file is executable on Linux/macOS
if (process.platform !== 'win32') {
try {
fs.chmodSync(fullPath, 0o755);
} catch (e) {
console.warn("Failed to set executable permissions:", e);
}
}
return new Promise(async (resolve, reject) => {
const compat = await Store.get('legacy_compat_layer', 'direct');
const username = await Store.get('legacy_username', "");
const ip = await Store.get('legacy_ip', "");
const port = await Store.get('legacy_port', "");
const isServer = await Store.get('legacy_is_server', false);
let args = [];
if (username) args.push("-name", username);
if (isServer) args.push("-server");
if (ip) args.push("-ip", ip);
if (port) args.push("-port", port);
const argString = args.map(a => `"${a}"`).join(" ");
let cmd = `"${fullPath}" ${argString}`;
if (process.platform === 'linux') {
if (compat === 'wine64' || compat === 'wine') {
cmd = `${compat} "${fullPath}" ${argString}`;
} else if (compat.includes('Proton')) {
const prefix = path.join(path.dirname(fullPath), 'pfx');
if (!fs.existsSync(prefix)) fs.mkdirSync(prefix, { recursive: true });
cmd = `STEAM_COMPAT_CLIENT_INSTALL_PATH="" STEAM_COMPAT_DATA_PATH="${prefix}" "${compat}" run "${fullPath}" ${argString}`;
}
}
console.log("Launching command:", cmd);
const startTime = Date.now();
const proc = childProcess.exec(cmd, (error) => {
const duration = Date.now() - startTime;
if (error && duration < 2000) {
showToast("Failed to launch: " + error.message);
reject(error);
} else {
resolve();
}
});
monitorProcess(proc);
});
}
function setProcessingState(active) {
isProcessing = active;
const playBtn = document.getElementById('btn-play-main');
const optionsBtn = document.getElementById('btn-options');
const progressContainer = document.getElementById('progress-container');
if (active) {
playBtn.classList.add('disabled');
optionsBtn.classList.add('disabled');
progressContainer.style.display = 'flex';
updateProgress(0, "Preparing...");
} else {
playBtn.classList.remove('disabled');
optionsBtn.classList.remove('disabled');
progressContainer.style.display = 'none';
}
}
function updateProgress(percent, text) {
document.getElementById('progress-bar-fill').style.width = percent + "%";
if (text) document.getElementById('progress-text').textContent = text;
}
async function handleElectronFlow(url) {
try {
const homeDir = require('os').homedir();
const downloadDir = path.join(homeDir, 'Downloads');
const zipPath = path.join(downloadDir, TARGET_FILE);
const extractDir = path.join(downloadDir, 'LegacyClient');
updateProgress(5, "Downloading " + TARGET_FILE + "...");
await downloadFile(url, zipPath);
updateProgress(75, "Extracting Archive...");
if (!fs.existsSync(extractDir)) {
fs.mkdirSync(extractDir, { recursive: true });
}
await extractZip(zipPath, { dir: extractDir });
const execName = await Store.get('legacy_exec_path', DEFAULT_EXEC);
const fullPath = path.join(extractDir, execName);
if (!fs.existsSync(fullPath)) {
showToast("Executable not found at: " + execName);
return;
}
updateProgress(100, "Launching...");
await Store.set('installed_version_tag', releasesData[currentReleaseIndex].tag_name);
await new Promise(r => setTimeout(r, 800));
await launchLocalClient();
} catch (e) {
showToast("Error: " + e.message);
}
}
function downloadFile(url, destPath) {
return new Promise((resolve, reject) => {
const dir = path.dirname(destPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const file = fs.createWriteStream(destPath);
let totalSize = 0;
let downloadedSize = 0;
https.get(url, (response) => {
if (response.statusCode === 302 || response.statusCode === 301) {
downloadFile(response.headers.location, destPath).then(resolve).catch(reject);
return;
}
totalSize = parseInt(response.headers['content-length'], 10);
response.on('data', (chunk) => {
downloadedSize += chunk.length;
const percent = Math.floor((downloadedSize / totalSize) * 70) + 5;
updateProgress(percent, `Downloading... ${percent}%`);
});
response.pipe(file);
file.on('finish', () => {
file.close(() => resolve());
});
file.on('error', (err) => {
fs.unlink(destPath, () => {});
reject(err);
});
}).on('error', (err) => {
fs.unlink(destPath, () => {});
reject(err);
});
});
}
function toggleOptions(show) {
if (isProcessing) return;
const modal = document.getElementById('options-modal');
if (show) {
modal.style.display = 'flex';
modal.style.opacity = '1';
} else {
modal.style.opacity = '0';
setTimeout(() => modal.style.display = 'none', 300);
}
}
async function toggleProfile(show) {
if (isProcessing) return;
const modal = document.getElementById('profile-modal');
if (show) {
await updatePlaytimeDisplay();
modal.style.display = 'flex';
modal.style.opacity = '1';
} else {
modal.style.opacity = '0';
setTimeout(() => modal.style.display = 'none', 300);
}
}
async function updatePlaytimeDisplay() {
const el = document.getElementById('playtime-display');
const playtime = await Store.get('legacy_playtime', 0);
if (el) el.textContent = formatPlaytime(playtime);
}
function formatPlaytime(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return `${h}h ${m}m ${s}s`;
}
async function saveOptions() {
const newRepo = document.getElementById('repo-input').value.trim();
const newExec = document.getElementById('exec-input').value.trim();
const compatSelect = document.getElementById('compat-select');
const ip = document.getElementById('ip-input').value.trim();
const port = document.getElementById('port-input').value.trim();
const isServer = document.getElementById('server-checkbox').checked;
if (newRepo) await Store.set('legacy_repo', newRepo);
if (newExec) await Store.set('legacy_exec_path', newExec);
await Store.set('legacy_ip', ip);
await Store.set('legacy_port', port);
await Store.set('legacy_is_server', isServer);
if (compatSelect) {
await Store.set('legacy_compat_layer', compatSelect.value);
}
toggleOptions(false);
fetchGitHubData();
updatePlayButtonText();
showToast("Settings Saved");
}
async function saveProfile() {
const username = document.getElementById('username-input').value.trim();
await Store.set('legacy_username', username);
toggleProfile(false);
showToast("Profile Updated");
}
function showToast(msg) {
const t = document.getElementById('toast');
t.textContent = msg;
t.style.display = 'block';
t.style.animation = 'none';
t.offsetHeight;
t.style.animation = 'slideUp 0.3s ease-out';
setTimeout(() => {
t.style.display = 'none';
}, 3000);
}
// Global functions for HTML onclick
window.minimizeWindow = minimizeWindow;
window.toggleMaximize = toggleMaximize;
window.closeWindow = closeWindow;
window.launchGame = launchGame;
window.updateSelectedRelease = updateSelectedRelease;
window.toggleProfile = toggleProfile;
window.toggleOptions = toggleOptions;
window.saveOptions = saveOptions;
window.saveProfile = saveProfile;
window.updateCompatDisplay = updateCompatDisplay;

407
style.css Normal file
View file

@ -0,0 +1,407 @@
@font-face {
font-family: 'Minecraft';
src: url('Minecraft.ttf') format('truetype');
}
:root {
--mc-gui-bg: #c6c6c6;
--mc-progress-bg: #313131;
--mc-progress-fill: #55ff55;
--mc-border-dark: #555;
--mc-border-light: #fff;
--mc-button-bg: #6e6e6e;
--mc-button-hover: #7e7e7e;
}
* {
box-sizing: border-box;
-webkit-font-smoothing: none;
}
body {
margin: 0;
padding: 0;
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
background-color: var(--mc-gui-bg);
font-family: 'Minecraft', monospace;
overflow: hidden;
image-rendering: pixelated;
-webkit-user-select: none;
user-select: none;
border: 2px solid #000;
}
/* Title Bar */
.title-bar {
height: 32px;
background: #222;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
-webkit-app-region: drag;
z-index: 1000;
border-bottom: 2px solid #000;
}
.title-bar-text {
color: #aaa;
font-size: 16px;
text-shadow: 1px 1px 0 #000;
}
.window-controls {
display: flex;
gap: 5px;
-webkit-app-region: no-drag;
}
.win-btn {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 2px;
cursor: pointer;
color: #aaa;
font-size: 18px;
transition: all 0.1s;
}
.win-btn:hover {
background: #444;
color: #fff;
}
.win-btn.close:hover {
background: #c42b1c;
}
.content-area {
flex-grow: 1;
background-image: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.4)), url('minecraft.jpg');
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.mc-logo-img {
width: 650px;
max-width: 95%;
filter: drop-shadow(6px 6px 0px rgba(0,0,0,0.6));
margin-bottom: 50px;
transition: transform 0.3s ease;
}
.mc-logo-img:hover {
transform: scale(1.02);
}
.btn-mc {
width: 500px;
max-width: 90%;
height: 60px;
background-color: var(--mc-button-bg);
border: 2px solid #000;
color: #e0e0e0;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 6px;
cursor: pointer;
box-shadow: inset -3px -3px 0px #333, inset 3px 3px 0px #aaa;
margin-bottom: 16px;
position: relative;
transition: transform 0.05s;
}
.btn-mc:hover:not(.disabled) {
background-color: var(--mc-button-hover);
color: #fff;
outline: 2px solid #fff;
z-index: 5;
}
.btn-mc:active:not(.disabled) {
box-shadow: inset 3px 3px 0px #333, inset -3px -3px 0px #aaa;
transform: translateY(2px);
}
.btn-mc.disabled, .btn-mc.running {
background-color: #4e4e4e;
color: #888;
cursor: default;
box-shadow: inset -3px -3px 0px #222, inset 3px 3px 0px #666;
opacity: 0.8;
transform: none !important;
}
.btn-mc.running {
color: #55ff55;
text-shadow: 2px 2px 0 #000;
}
.btn-play {
height: 96px;
font-size: 44px;
margin-bottom: 36px;
color: #fff;
text-shadow: 2px 2px 0 #000;
}
.version-row {
display: flex;
width: 500px;
max-width: 90%;
margin-bottom: 28px;
align-items: center;
}
.version-label {
color: white;
font-size: 24px;
margin-right: 15px;
text-shadow: 2px 2px 0 #000;
white-space: nowrap;
}
.version-select-box {
flex-grow: 1;
height: 56px;
background: #000;
border: 2px solid #555;
display: flex;
align-items: center;
padding: 0 15px 6px 15px;
color: white;
font-size: 24px;
cursor: pointer;
position: relative;
transition: border-color 0.2s;
}
.version-select-box:hover {
border-color: #fff;
}
.select-arrow {
width: 48px;
height: 52px;
background: #6e6e6e;
border-left: 2px solid #555;
display: flex;
align-items: center;
justify-content: center;
color: white;
position: absolute;
right: 0;
}
/* Progress Bar Styling */
.progress-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 50px;
background: rgba(0,0,0,0.9);
border-top: 4px solid #000;
display: none;
flex-direction: column;
justify-content: center;
padding: 0 30px;
z-index: 50;
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.progress-bar-bg {
width: 100%;
height: 16px;
background: var(--mc-progress-bg);
border: 2px solid #000;
position: relative;
}
.progress-bar-fill {
width: 0%;
height: 100%;
background: var(--mc-progress-fill);
box-shadow: inset 0 3px 0 rgba(255,255,255,0.4);
transition: width 0.2s ease-out;
}
.progress-text {
color: #fff;
font-size: 20px;
text-shadow: 2px 2px 0 #000;
margin-bottom: 6px;
text-align: center;
}
/* Settings Modal */
.modal-overlay {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.9);
display: none;
align-items: center;
justify-content: center;
z-index: 150;
backdrop-filter: blur(4px);
}
.modal-box {
width: 750px;
max-width: 95%;
background: var(--mc-gui-bg);
border: 4px solid #000;
padding: 50px;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: inset 4px 4px 0 #fff;
animation: modalPop 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
@keyframes modalPop {
from { transform: scale(0.8); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.modal-title {
font-size: 42px;
color: #333;
margin-bottom: 30px;
text-shadow: 2px 2px 0 #fff;
text-align: center;
}
.mc-input-group {
width: 100%;
margin-bottom: 20px;
}
.mc-label {
display: block;
color: #444;
font-size: 22px;
margin-bottom: 6px;
}
.mc-input {
width: 100%;
background: #000;
border: 2px solid #555;
color: white;
padding: 12px;
font-size: 22px;
outline: none;
transition: border-color 0.2s;
}
.mc-input:focus {
border-color: #fff;
}
#loader {
position: absolute;
inset: 0;
background: rgba(0,0,0,1);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
font-size: 36px;
z-index: 2000;
background-image: url('minecraft.jpg');
background-size: cover;
background-position: center;
}
#loader::before {
content: '';
position: absolute;
inset: 0;
background: rgba(0,0,0,0.8);
}
.loader-content {
position: relative;
z-index: 1;
text-align: center;
}
.loader-spinner {
width: 80px;
height: 80px;
border: 8px solid #333;
border-top: 8px solid var(--mc-progress-fill);
border-radius: 50%;
margin: 0 auto 20px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#toast {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.95);
color: #fff;
padding: 15px 40px;
border: 3px solid #fff;
font-size: 24px;
display: none;
z-index: 3000;
text-align: center;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
select.hidden-select {
position: absolute;
inset: 0;
opacity: 0;
cursor: pointer;
width: 100%;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-track {
background: #333;
border-left: 2px solid #000;
}
::-webkit-scrollbar-thumb {
background: #6e6e6e;
border: 2px solid #000;
box-shadow: inset -2px -2px 0 #333, inset 2px 2px 0 #aaa;
}
::-webkit-scrollbar-thumb:hover {
background: #7e7e7e;
}