PLAYER PROFILE
diff --git a/main.js b/main.js
index 298b40f..27ac42d 100644
--- a/main.js
+++ b/main.js
@@ -1,4 +1,4 @@
-const { app, BrowserWindow, shell, ipcMain, dialog } = require('electron');
+const { app, BrowserWindow, shell, ipcMain, dialog, globalShortcut, desktopCapturer } = require('electron');
const path = require('path');
const Store = require('electron-store');
const fs = require('fs');
@@ -7,9 +7,11 @@ const extractZip = require('extract-zip');
const { exec } = require('child_process');
const store = new Store();
+let mainWindow = null;
+let isGameRunning = false;
function createWindow() {
- const win = new BrowserWindow({
+ mainWindow = new BrowserWindow({
width: 1280,
height: 720,
minWidth: 1024,
@@ -27,22 +29,115 @@ function createWindow() {
}
});
- win.loadFile('index.html');
+ mainWindow.loadFile('index.html');
- ipcMain.on('window-minimize', () => win.minimize());
+ ipcMain.on('window-minimize', () => mainWindow.minimize());
ipcMain.on('window-maximize', () => {
- if (win.isMaximized()) {
- win.unmaximize();
+ if (mainWindow.isMaximized()) {
+ mainWindow.unmaximize();
} else {
- win.maximize();
+ mainWindow.maximize();
}
});
- ipcMain.on('window-close', () => win.close());
+ ipcMain.on('window-close', () => mainWindow.close());
ipcMain.on('window-fullscreen', () => {
- win.setFullScreen(!win.isFullScreen());
+ mainWindow.setFullScreen(!mainWindow.isFullScreen());
});
ipcMain.on('window-set-fullscreen', (event, enabled) => {
- win.setFullScreen(Boolean(enabled));
+ mainWindow.setFullScreen(Boolean(enabled));
+ });
+
+ ipcMain.handle('take-screenshot', async (event) => {
+ try {
+ const screenshotsDir = path.join(app.getPath('userData'), 'Screenshots');
+ if (!fs.existsSync(screenshotsDir)) {
+ fs.mkdirSync(screenshotsDir, { recursive: true });
+ }
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
+ const fileName = `screenshot-${timestamp}.png`;
+ const filePath = path.join(screenshotsDir, fileName);
+
+ if (isGameRunning) {
+ const sources = await desktopCapturer.getSources({
+ types: ['window', 'screen'],
+ thumbnailSize: { width: 3840, height: 2160 } // High res for screenshot
+ });
+
+ // Find Minecraft window or fallback to primary screen
+ const source = sources.find(s => s.name.toLowerCase().includes('minecraft')) ||
+ sources.find(s => s.id.startsWith('screen'));
+
+ if (source) {
+ fs.writeFileSync(filePath, source.thumbnail.toPNG());
+ return filePath;
+ }
+ }
+
+ // Fallback to launcher capture if game isn't running or not found
+ const win = BrowserWindow.fromWebContents(event.sender) || mainWindow;
+ if (!win) throw new Error("Window not found");
+ const image = await win.capturePage();
+ fs.writeFileSync(filePath, image.toPNG());
+ return filePath;
+ } catch (err) {
+ console.error("Screenshot capture error:", err);
+ throw err;
+ }
+ });
+
+ ipcMain.on('game-running-state', (event, running) => {
+ isGameRunning = running;
+ if (running) {
+ globalShortcut.register('F2', () => {
+ if (mainWindow) {
+ mainWindow.webContents.send('trigger-screenshot');
+ }
+ });
+ } else {
+ globalShortcut.unregister('F2');
+ }
+ });
+
+ ipcMain.handle('list-screenshots', async () => {
+ const screenshotsDir = path.join(app.getPath('userData'), 'Screenshots');
+ if (!fs.existsSync(screenshotsDir)) {
+ return [];
+ }
+ const files = fs.readdirSync(screenshotsDir);
+ return files
+ .filter(f => f.toLowerCase().endsWith('.png'))
+ .sort((a, b) => {
+ try {
+ // Extract timestamp from 'screenshot-YYYY-MM-DDTHH-mm-ss-SSSZ.png'
+ const timeA = a.replace('screenshot-', '').replace('.png', '').replace(/-/g, ':');
+ const timeB = b.replace('screenshot-', '').replace('.png', '').replace(/-/g, ':');
+ return new Date(timeB) - new Date(timeA);
+ } catch (e) {
+ return 0;
+ }
+ })
+ .map(f => ({
+ name: f,
+ path: path.join(screenshotsDir, f),
+ url: `file://${path.join(screenshotsDir, f)}`
+ }));
+ });
+
+ ipcMain.handle('delete-screenshot', async (event, fileName) => {
+ const filePath = path.join(app.getPath('userData'), 'Screenshots', fileName);
+ if (fs.existsSync(filePath)) {
+ fs.unlinkSync(filePath);
+ return true;
+ }
+ return false;
+ });
+
+ ipcMain.handle('open-screenshots-dir', async () => {
+ const screenshotsDir = path.join(app.getPath('userData'), 'Screenshots');
+ if (!fs.existsSync(screenshotsDir)) {
+ fs.mkdirSync(screenshotsDir, { recursive: true });
+ }
+ shell.openPath(screenshotsDir);
});
ipcMain.handle('store-get', (event, key) => store.get(key));
@@ -55,10 +150,10 @@ function createWindow() {
return result.filePaths[0];
});
- win.on('maximize', () => win.webContents.send('window-is-maximized', true));
- win.on('unmaximize', () => win.webContents.send('window-is-maximized', false));
+ mainWindow.on('maximize', () => mainWindow.webContents.send('window-is-maximized', true));
+ mainWindow.on('unmaximize', () => mainWindow.webContents.send('window-is-maximized', false));
- win.webContents.setWindowOpenHandler(({ url }) => {
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
diff --git a/package-lock.json b/package-lock.json
index 23a5cd5..ce6cfa6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "legacylauncher",
- "version": "3.0.0",
+ "version": "3.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "legacylauncher",
- "version": "3.0.0",
+ "version": "3.0.1",
"license": "ISC",
"dependencies": {
"electron-store": "^6.0.1",
@@ -4690,9 +4690,9 @@
}
},
"node_modules/tar": {
- "version": "7.5.10",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz",
- "integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==",
+ "version": "7.5.11",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",
+ "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
diff --git a/package.json b/package.json
index 9b11205..4a9991c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "legacylauncher",
- "version": "3.0.1",
+ "version": "3.5.0",
"description": "",
"main": "main.js",
"scripts": {
@@ -30,7 +30,15 @@
"flatpak"
],
"category": "Game",
- "icon": "512x512.png"
+ "icon": "512x512.png",
+ "desktop": {
+ "Name": "Minecraft LCE Launcher",
+ "GenericName": "Minecraft Launcher",
+ "Comment": "A Minecraft: Legacy Console Edition launcher",
+ "Categories": "Game;Emulation;",
+ "StartupNotify": "true"
+ },
+ "executableName": "legacylauncher"
},
"mac": {
"target": [
diff --git a/renderer.js b/renderer.js
index c52a9c3..df621a9 100644
--- a/renderer.js
+++ b/renderer.js
@@ -353,7 +353,7 @@ const GamepadManager = {
const UiSoundManager = {
files: {
cursor: 'JDSherbert - Ultimate UI SFX Pack - Cursor - 1.mp3',
- select: 'JDSherbert - Ultimate UI SFX Pack - Select - 1.mp3',
+ select: 'Click_stereo.ogg.mp3',
cancel: 'JDSherbert - Ultimate UI SFX Pack - Cancel - 1.mp3',
popupOpen: 'JDSherbert - Ultimate UI SFX Pack - Popup Open - 1.mp3',
popupClose: 'JDSherbert - Ultimate UI SFX Pack - Popup Close - 1.mp3',
@@ -370,7 +370,7 @@ const UiSoundManager = {
},
shouldPlay() {
- return this.inputSource === 'controller';
+ return true; // Play sounds for both controller and mouse/keyboard
},
init() {
@@ -448,11 +448,20 @@ const MusicManager = {
}
const slider = document.getElementById('volume-slider');
+ const percentText = document.getElementById('volume-percent');
+ const updatePercent = () => {
+ if (percentText) {
+ percentText.textContent = Math.round(this.audio.volume * 100) + "%";
+ }
+ };
+
if (slider) {
slider.value = this.audio.volume;
+ updatePercent();
slider.oninput = async () => {
this.audio.volume = slider.value;
- await Store.set('legacy_music_volume', this.audio.volume);
+ updatePercent();
+ await Store.set('legacy_music_volume', parseFloat(slider.value));
};
}
},
@@ -722,7 +731,31 @@ window.onload = async () => {
setTimeout(() => focusPrimaryPlayButton(), 150);
}
- window.addEventListener('keydown', (e) => {
+ async function takeScreenshot() {
+ try {
+ const filePath = await ipcRenderer.invoke('take-screenshot');
+ showToast(`Screenshot saved to: ${path.basename(filePath)}`);
+ UiSoundManager.play('select');
+
+ // Refresh gallery if it is visible
+ const galleryModal = document.getElementById('gallery-modal');
+ if (galleryModal && galleryModal.style.display === 'flex') {
+ renderGallery();
+ }
+ } catch (err) {
+ console.error("Screenshot error:", err);
+ showToast("Failed to take screenshot.");
+ }
+ }
+
+ ipcRenderer.on('trigger-screenshot', () => {
+ takeScreenshot();
+ });
+
+ window.addEventListener('keydown', async (e) => {
+ if (e.key === 'F2') {
+ takeScreenshot();
+ }
if (e.key === 'F9') {
checkForLauncherUpdates(true);
}
@@ -910,6 +943,85 @@ async function browseInstallDir() {
}
}
+async function openScreenshotsGallery() {
+ await toggleGallery(true);
+}
+
+async function toggleGallery(show) {
+ const modal = document.getElementById('gallery-modal');
+ if (show) {
+ await renderGallery();
+ modal.style.display = 'flex';
+ modal.style.opacity = '1';
+ UiSoundManager.play('popupOpen');
+ } else {
+ modal.style.opacity = '0';
+ UiSoundManager.play('popupClose');
+ setTimeout(() => modal.style.display = 'none', 300);
+ }
+}
+
+async function renderGallery() {
+ const container = document.getElementById('gallery-container');
+ container.innerHTML = '
LOADING...
';
+
+ try {
+ const screenshots = await ipcRenderer.invoke('list-screenshots');
+ container.innerHTML = '';
+
+ if (screenshots.length === 0) {
+ container.innerHTML = `
+
+
NO SCREENSHOTS FOUND
+
PRESS F2 TO TAKE ONE!
+
+ `;
+ return;
+ }
+
+ screenshots.forEach(ss => {
+ const item = document.createElement('div');
+ item.className = 'gallery-item nav-item';
+ item.tabIndex = 0;
+ item.innerHTML = `
+
})
+
+ `;
+ item.onkeydown = (e) => {
+ if (e.key === 'Enter' || e.key === ' ') viewScreenshot(ss.path);
+ if (e.key === 'Delete') deleteScreenshot(ss.name);
+ };
+ container.appendChild(item);
+ });
+ } catch (err) {
+ console.error("Gallery render error:", err);
+ container.innerHTML = '
ERROR LOADING GALLERY
';
+ }
+}
+
+async function deleteScreenshot(fileName) {
+ if (confirm(`Are you sure you want to delete this screenshot?`)) {
+ const success = await ipcRenderer.invoke('delete-screenshot', fileName);
+ if (success) {
+ showToast("Screenshot deleted.");
+ renderGallery();
+ } else {
+ showToast("Failed to delete screenshot.");
+ }
+ }
+}
+
+function viewScreenshot(path) {
+ shell.openPath(path);
+}
+
+async function openScreenshotsDir() {
+ ipcRenderer.invoke('open-screenshots-dir');
+}
+
async function openGameDir() {
const dir = await getInstallDir();
if (fs.existsSync(dir)) {
@@ -971,6 +1083,7 @@ async function updatePlayButtonText() {
function setGameRunning(running) {
isGameRunning = running;
updatePlayButtonText();
+ ipcRenderer.send('game-running-state', running);
}
async function monitorProcess(proc) {
@@ -1746,7 +1859,8 @@ async function loadSplashText() {
// ============================================================
async function loadTheme() {
- const isClassic = await Store.get('legacy_classic_theme', false);
+ // Force default UI on startup
+ const isClassic = false;
const cb = document.getElementById('classic-theme-checkbox');
if (cb) cb.checked = isClassic;
applyTheme(isClassic);
diff --git a/style.css b/style.css
index 8a14551..bd8aabc 100644
--- a/style.css
+++ b/style.css
@@ -113,15 +113,16 @@ body {
.sidebar {
width: 380px;
- background: rgba(0, 0, 0, 0.7);
+ background: rgba(0, 0, 0, 0.75);
border-right: 4px solid #000;
display: flex;
flex-direction: column;
padding: 30px 20px;
overflow-y: auto;
z-index: 10;
- backdrop-filter: blur(8px);
+ backdrop-filter: blur(12px);
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1), padding 0.3s ease;
+ box-shadow: 10px 0 20px rgba(0,0,0,0.5);
}
.sidebar.collapsed {
@@ -345,7 +346,7 @@ body {
width: 500px;
max-width: 90%;
height: 60px;
- background-color: var(--mc-button-bg);
+ background: linear-gradient(to bottom, #7e7e7e, #6e6e6e);
border: 2px solid #000;
color: #e0e0e0;
font-size: 24px;
@@ -354,39 +355,42 @@ body {
justify-content: center;
padding-bottom: 6px;
cursor: pointer;
- box-shadow: inset -3px -3px 0px #333, inset 3px 3px 0px #aaa;
+ box-shadow: inset -3px -3px 0px #333, inset 3px 3px 0px #aaa, 0 4px 6px rgba(0,0,0,0.3);
margin-bottom: 16px;
position: relative;
- transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
+ transition: transform 0.1s ease, box-shadow 0.1s ease, filter 0.1s ease;
+ text-shadow: 2px 2px 0 #333;
}
.nav-item {
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+ transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
outline: none !important;
}
.nav-item:focus {
- transform: scale(1.04);
+ transform: scale(1.02);
z-index: 1000;
- box-shadow: 0 0 0 1px rgba(255,255,255,0.92), 0 0 10px rgba(255, 255, 255, 0.62), 0 0 20px rgba(90, 170, 255, 0.28) !important;
- filter: brightness(1.08);
+ box-shadow: 0 0 0 2px #fff, 0 0 10px rgba(255, 255, 255, 0.62), 0 0 20px rgba(90, 170, 255, 0.28) !important;
+ filter: brightness(1.1);
}
.nav-item:hover {
- outline: 1px solid rgba(255,255,255,0.92) !important;
+ outline: 2px solid rgba(255,255,255,0.92) !important;
box-shadow: 0 0 0 1px rgba(255,255,255,0.85), 0 0 12px rgba(255, 255, 255, 0.52), 0 0 24px rgba(90, 170, 255, 0.22) !important;
}
.nav-item.active-bump {
- transform: scale(0.95) !important;
- transition: transform 0.1s !important;
+ transform: scale(0.96) !important;
+ transition: transform 0.05s !important;
+ box-shadow: inset 3px 3px 0px #333, inset -3px -3px 0px #aaa !important;
+ background: linear-gradient(to bottom, #555, #666) !important;
}
.btn-mc:hover:not(.disabled) {
- background-color: var(--mc-button-hover);
+ background: linear-gradient(to bottom, #8e8e8e, #7e7e7e);
color: #fff;
- outline: 1px solid rgba(255,255,255,0.95) !important;
+ outline: 2px solid rgba(255,255,255,0.95) !important;
box-shadow: inset -3px -3px 0px #333, inset 3px 3px 0px #aaa, 0 0 12px rgba(255,255,255,0.25);
}
@@ -588,14 +592,14 @@ body.steamdeck-mode .sidebar {
max-width: 850px;
min-width: 600px;
max-height: 85vh;
- background: var(--mc-gui-bg);
+ background: #c6c6c6;
border: 4px solid #000;
padding: clamp(20px, 4vw, 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);
+ box-shadow: inset 4px 4px 0 #fff, inset -4px -4px 0 #555, 0 20px 50px rgba(0,0,0,0.8);
+ animation: modalPop 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow-y: auto;
}
@@ -676,11 +680,14 @@ body.steamdeck-mode .sidebar {
padding: 12px;
font-size: 22px;
outline: none;
- transition: border-color 0.2s;
+ transition: all 0.2s;
+ box-shadow: inset 2px 2px 5px rgba(0,0,0,0.5);
}
.mc-input:focus, .mc-input.focused {
border-color: #fff !important;
+ background: #111;
+ box-shadow: 0 0 10px rgba(255,255,255,0.2), inset 2px 2px 5px rgba(0,0,0,0.5);
}
@@ -872,6 +879,25 @@ select.hidden-select {
opacity: 0;
cursor: pointer;
width: 100%;
+ /* Fix for Linux contrast issues in native dropdowns */
+ background-color: #1a1a1a !important;
+ color: #ffffff !important;
+}
+
+select.hidden-select option {
+ background-color: #1a1a1a !important;
+ color: #ffffff !important;
+}
+
+/* Ensure all select elements have good contrast on Linux */
+select.mc-input {
+ background-color: #000000 !important;
+ color: #ffffff !important;
+}
+
+select.mc-input option {
+ background-color: #1a1a1a !important;
+ color: #ffffff !important;
}
::-webkit-scrollbar {
@@ -893,6 +919,135 @@ select.hidden-select {
background: #7e7e7e;
}
+/* --- Minecraft Style Slider --- */
+.mc-slider-container {
+ position: relative;
+ width: 100%;
+ height: 44px;
+ background: #000;
+ border: 2px solid #555;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.mc-slider {
+ -webkit-appearance: none;
+ width: 100%;
+ height: 100%;
+ background: transparent;
+ outline: none;
+ position: relative;
+ z-index: 2;
+ cursor: pointer;
+ margin: 0;
+}
+
+.mc-slider::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 100%;
+ background: transparent;
+}
+
+.mc-slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 16px;
+ height: 100%;
+ background: #6e6e6e;
+ border: 2px solid #fff;
+ box-shadow: inset -2px -2px 0 #333, inset 2px 2px 0 #aaa;
+}
+
+.mc-slider:focus::-webkit-slider-thumb {
+ background: #7e7e7e;
+ box-shadow: inset -2px -2px 0 #333, inset 2px 2px 0 #aaa, 0 0 8px #fff;
+}
+
+.mc-slider-percent {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ color: #fff;
+ font-size: 20px;
+ text-shadow: 2px 2px 0 #000;
+ pointer-events: none;
+ z-index: 1;
+}
+
+/* --- Screenshots Gallery --- */
+.gallery-item {
+ position: relative;
+ aspect-ratio: 16 / 9;
+ background: #111;
+ border: 2px solid #444;
+ overflow: hidden;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.gallery-item:hover, .gallery-item:focus {
+ border-color: #fff;
+ transform: scale(1.02);
+ z-index: 10;
+ box-shadow: 0 0 15px rgba(255,255,255,0.2);
+}
+
+.gallery-thumb {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ image-rendering: auto;
+}
+
+.gallery-item-actions {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0,0,0,0.8);
+ display: flex;
+ justify-content: space-around;
+ padding: 4px;
+ transform: translateY(100%);
+ transition: transform 0.2s;
+}
+
+.gallery-item:hover .gallery-item-actions,
+.gallery-item:focus .gallery-item-actions {
+ transform: translateY(0);
+}
+
+.gallery-action-btn {
+ color: #fff;
+ font-size: 14px;
+ padding: 2px 8px;
+ cursor: pointer;
+ text-transform: uppercase;
+}
+
+.gallery-action-btn:hover {
+ color: #55ff55;
+ text-shadow: 0 0 5px rgba(85,255,85,0.5);
+}
+
+.gallery-action-btn.delete:hover {
+ color: #ff5555;
+ text-shadow: 0 0 5px rgba(255,85,85,0.5);
+}
+
+.gallery-empty {
+ grid-column: 1 / -1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 300px;
+ color: #888;
+ font-size: 24px;
+}
+
/* ============================================================
CLASSIC MINECRAFT LAUNCHER THEME
Applied when is set