diff --git a/Minecraft.ttf b/Minecraft.ttf index 85c1472..ba2c52d 100644 Binary files a/Minecraft.ttf and b/Minecraft.ttf differ diff --git a/index.html b/index.html index d84d69b..bfc065e 100644 --- a/index.html +++ b/index.html @@ -5,414 +5,8 @@ LegacyLauncher + - @@ -448,7 +42,7 @@ -
+
PROFILE
OPTIONS
@@ -551,631 +145,6 @@
NOTIFICATION
- + diff --git a/main.js b/main.js index 0761e5a..114d773 100644 --- a/main.js +++ b/main.js @@ -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); diff --git a/minecraft.jpg b/minecraft.jpg index 6e1d46c..eeaa748 100644 Binary files a/minecraft.jpg and b/minecraft.jpg differ diff --git a/package-lock.json b/package-lock.json index 662ae4f..4144eb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index b43bbe7..31a8edf 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "license": "ISC", "dependencies": { "discord-rpc": "^4.0.1", + "electron-store": "^6.0.1", "extract-zip": "^2.0.1" }, "devDependencies": { diff --git a/renderer.js b/renderer.js new file mode 100644 index 0000000..9189bde --- /dev/null +++ b/renderer.js @@ -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 ${newTag} is available.

` + + `Currently installed: ${installedTag}.

` + + `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; diff --git a/style.css b/style.css new file mode 100644 index 0000000..f2dd1d0 --- /dev/null +++ b/style.css @@ -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; +}