Compare commits

..

67 commits

Author SHA1 Message Date
gardenGnostic a42c1a9b0b
Merge pull request #254 from Ratintosh/assets-refactor
Move static assets into assets/ directory
2026-04-17 17:32:10 +02:00
Ratintosh cb4c62aff2 Moved images and audio into assets folder, updated paths accordingly 2026-03-27 01:02:44 -04:00
gardenGnostic e733a250ec
Update README.md 2026-03-16 14:41:09 +01:00
gardenGnostic aa7c257553
Merge pull request #209 from nothingman4ik/feat-fullscreen-new
feat: add fullscreen launch option to new version
2026-03-14 13:30:57 +01:00
nothingman4ik 3e094c6a1d feat: add fullscreen launch option to new version 2026-03-14 14:18:13 +02:00
gardenGnostic 90d40ba2af
Merge pull request #204 from rubiidev18alt/main
Add loading circle + better looking downloading/connecting text
2026-03-14 11:36:43 +01:00
rubiidev18alt c514c981c7
Merge pull request #7 from rubiidev18alt/codex/add-2d-mode-to-skin-renderer
Add 2D/3D toggle for main skin viewer and UI button
2026-03-13 16:50:12 -07:00
rubiidev18alt 4858f83ea1 Keep 2D skin mode fully static 2026-03-13 16:49:42 -07:00
rubiidev18alt 124fb9dbcb
Merge pull request #6 from rubiidev18alt/codex/add-loading-circle-next-to-downloading-text
Add animated loader/progress UI and smarter progress text parsing
2026-03-13 16:28:17 -07:00
rubiidev18alt 8c36195731 Left-align download status row and shrink spinner 2026-03-13 16:27:48 -07:00
gardenGnostic 1c1fbc3d46
Update package.json 2026-03-12 23:10:55 +01:00
gardenGnostic 1e27d63fae
Update release.yml 2026-03-12 23:10:34 +01:00
gardenGnostic 8e05e6b29d
Update release.yml 2026-03-12 23:05:44 +01:00
gardenGnostic 033315526c
v3.5.0 2026-03-12 22:58:49 +01:00
gardenGnostic 1377ece419
Delete JDSherbert - Ultimate UI SFX Pack - Select - 1.mp3 2026-03-12 22:57:23 +01:00
gardenGnostic 38092692a7
Delete JDSherbert - Ultimate UI SFX Pack - Popup Open - 1.mp3 2026-03-12 22:57:15 +01:00
gardenGnostic b54331475a
Delete JDSherbert - Ultimate UI SFX Pack - Popup Close - 1.mp3 2026-03-12 22:57:08 +01:00
gardenGnostic 6cda5ad9ac
Delete JDSherbert - Ultimate UI SFX Pack - Error - 1.mp3 2026-03-12 22:56:59 +01:00
gardenGnostic 9c189b56df
Delete JDSherbert - Ultimate UI SFX Pack - Cursor - 1.mp3 2026-03-12 22:56:51 +01:00
gardenGnostic 32b2909fee
Delete JDSherbert - Ultimate UI SFX Pack - Cancel - 1.mp3 2026-03-12 22:56:45 +01:00
gardenGnostic 4c42fd0e55
Update release.yml 2026-03-12 22:44:58 +01:00
gardenGnostic 3a104398f4
Merge pull request #171 from rubiidev18alt/main
Add Steam Deck UI mode, repo presets, and stronger hover glow (#2)
2026-03-12 14:28:09 +01:00
rubiidev18alt bb721c5ae0
Merge pull request #5 from rubiidev18alt/codex/integrate-controller-button-assets-into-ui
Add controller layout preset UI with interactions and sprite attribution
2026-03-12 00:02:01 -07:00
rubiidev18alt 08c332ecb7 Use exact spritesheet frame coords for controller layout icons 2026-03-11 23:56:53 -07:00
rubiidev18alt 0a06148338
Controller button sprites for ui 2026-03-11 23:01:30 -07:00
rubiidev18alt a8a972a0f6
Merge branch 'gradenGnostic:main' into main 2026-03-11 23:00:58 -07:00
rubiidev18alt 9d3e4fcc57
Add controller layout setting and guide-button quit (#3) 2026-03-11 20:09:16 -07:00
rubiidev18alt 25116b6eba Add controller layout setting and guide-button quit 2026-03-11 20:00:30 -07:00
rubiidev18alt e44695860c
Add Steam Deck UI mode, repo presets, and stronger hover glow (#2) 2026-03-11 19:32:58 -07:00
gardenGnostic 6c59ad20d6
Merge pull request #167 from rubiidev18alt/main
Better accuracy to steam big picture mode
2026-03-12 00:10:16 +01:00
rubiidev18alt 3355244dc4
Merge pull request #1 from rubiidev18alt/codex/integrate-ui-sounds-and-enhance-console-ui
Add UI sound effects, Steam Deck fullscreen support, Proton detection improvements, and UI polish
2026-03-11 15:38:37 -07:00
rubiidev18alt e1a550674a Refine Linux compat scan to better detect Proton-GE runtimes 2026-03-11 15:37:45 -07:00
rubiidev18alt 12b8a503d9
Placeholder sounds
Thanks JDSherbert on itch!
2026-03-11 14:54:20 -07:00
gardenGnostic 3a5b37a8ca
Bump version from 3.0.0 to 3.0.1 2026-03-10 15:56:56 +01:00
gardenGnostic 8f1aec38be
Merge pull request #121 from dtentiion/main
Fix UI overflow, add classic MC launcher UI, text truncation
2026-03-10 10:29:56 +01:00
dtentiion dceef71f87 Fix UI overflow, add classic MC launcher UI, text truncation 2026-03-09 19:46:24 +00:00
gardenGnostic 7b4942ee7e
v3.0.0 2026-03-09 16:34:54 +01:00
gardenGnostic 1937c941ea
v3.0.0 2026-03-09 16:32:42 +01:00
gardenGnostic c50b36e50d
Bump version from 2.9.0 to 2.9.1 2026-03-09 14:08:32 +01:00
gardenGnostic 0fa8df9a21
Add support for building macOS DMG for Intel 2026-03-09 14:08:02 +01:00
gardenGnostic 68f77fc77a
v2.9.0 2026-03-08 22:03:45 +01:00
gardenGnostic 32b02f11f8
Replace old image with new image in README
Updated the image in the README to a new version.
2026-03-08 21:58:26 +01:00
gardenGnostic 46a484d5df
v2.2.0 2026-03-07 23:41:21 +01:00
gardenGnostic 69a0ebbfce
Update README.md 2026-03-07 22:47:21 +01:00
gardenGnostic c37a43449f
Add star history chart with responsive design
Added responsive star history chart to README.
2026-03-07 22:46:48 +01:00
gardenGnostic c9ba08d5d2
Add star history chart to README
Added a star history chart to the README for better visibility of project popularity.
2026-03-07 22:46:06 +01:00
gardenGnostic acb1c41d8d
Bump version from 2.0.0 to 2.0.2 2026-03-07 12:38:55 +01:00
gardenGnostic 49526d4d9d
Merge pull request #26 from Dettomi/patch-1
Fix options, servers, profile buttons staying on screen
2026-03-07 12:17:29 +01:00
Dettomi e231529a47
Fix options, servers, profile buttons staying on screen 2026-03-07 03:48:50 +01:00
gardenGnostic cf5f6d882a
Add image to README
Add an image to the README for visual representation.
2026-03-07 01:52:09 +01:00
gardenGnostic d6399ad939
Add image to README for LegacyLauncher
Added an image to the README for visual enhancement.
2026-03-07 01:51:45 +01:00
gardenGnostic 708916d956
v2.0.0 2026-03-07 01:44:51 +01:00
gardenGnostic 2081965f7a
v2.0.0 2026-03-07 01:44:10 +01:00
gardenGnostic 911f70875f
v1.1.2 2026-03-06 15:55:54 +01:00
gardenGnostic d8f243f334
Add MIT License to the project 2026-03-06 15:22:27 +01:00
gardenGnostic 1a4b604c40
Update README.md
Added an image to the README for visual enhancement.
2026-03-05 23:41:39 +01:00
gardenGnostic f5d44043a7
bug fixes 2026-03-05 23:35:01 +01:00
gardenGnostic 944fdc5232
Revise CI workflow for multi-platform builds and releases
Updated CI workflow to include build jobs for Linux, Windows, and macOS, and added a release step to create GitHub releases.
2026-03-05 20:04:42 +01:00
gardenGnostic 4f3eb870e3
Delete 256x256.png 2026-03-05 20:00:25 +01:00
gardenGnostic 0030eb8ee4
Add files via upload 2026-03-05 19:57:46 +01:00
gardenGnostic 3cd31fe7ef
Add image to README for visual enhancement
Added an image to the README for better visual appeal.
2026-03-05 19:48:08 +01:00
gardenGnostic 9daf69968a
updated to v1.1.0 2026-03-05 19:45:09 +01:00
gardenGnostic c2d3eedf3f
Add macOS build job to release workflow 2026-03-05 19:38:35 +01:00
gardenGnostic cabb16a5e5
Update README 2026-03-05 19:30:50 +01:00
gardenGnostic e7a0cc01f2
Add GitHub Actions workflow for releases 2026-03-05 00:42:24 +01:00
gardenGnostic 98eeffc461
Add CI workflow for linting and smoke testing 2026-03-05 00:41:50 +01:00
gardenGnostic aff0d329ba
ui fixes 2026-03-05 00:33:04 +01:00
25 changed files with 5583 additions and 1282 deletions

107
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,107 @@
name: Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
jobs:
build-linux:
name: Build Linux AppImage
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Install Linux build deps
run: |
sudo apt-get update
sudo apt-get install -y libarchive-tools rpm fakeroot
- name: Build AppImage
run: npm run dist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Linux artifact
uses: actions/upload-artifact@v4
with:
name: linux-dist
path: dist/*.AppImage
if-no-files-found: error
build-windows:
name: Build Windows Installer
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build Windows installer
run: npm run dist:win
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Windows artifact
uses: actions/upload-artifact@v4
with:
name: windows-dist
path: |
dist/*.exe
dist/*.msi
if-no-files-found: error
build-mac:
name: Build macOS DMG
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Clear electron-builder cache
run: rm -rf ~/Library/Caches/electron-builder
- name: Build macOS DMG
run: npm run dist:mac
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload macOS artifact
uses: actions/upload-artifact@v4
with:
name: mac-dist
path: dist/*.dmg
if-no-files-found: error
release:
name: Create GitHub Release
needs: [build-linux, build-windows, build-mac]
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create release and upload assets
uses: softprops/action-gh-release@v2
with:
name: LegacyLauncher ${{ github.ref_name }}
draft: false
prerelease: ${{ contains(github.ref_name, '-') }}
generate_release_notes: true
files: |
artifacts/linux-dist/*
artifacts/windows-dist/*
artifacts/mac-dist/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

129
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,129 @@
name: Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
jobs:
build-linux:
name: Build Linux AppImage
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Install Linux build deps
run: |
sudo apt-get update
sudo apt-get install -y libarchive-tools rpm fakeroot
- name: Build AppImage
run: npm run dist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Linux artifact
uses: actions/upload-artifact@v4
with:
name: linux-dist
path: dist/*.AppImage
if-no-files-found: error
build-windows:
name: Build Windows Installer
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build Windows installer
run: npm run dist:win
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Windows artifact
uses: actions/upload-artifact@v4
with:
name: windows-dist
path: |
dist/*.exe
dist/*.msi
if-no-files-found: error
build-mac:
name: Build macOS DMG (Apple Silicon)
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build macOS DMG
run: npm run dist:mac
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload macOS artifact
uses: actions/upload-artifact@v4
with:
name: mac-dist
path: dist/*.dmg
if-no-files-found: error
build-mac-intel:
name: Build macOS DMG (Intel)
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build macOS DMG (Intel)
run: npm run dist:mac -- --x64
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload macOS Intel artifact
uses: actions/upload-artifact@v4
with:
name: mac-intel-dist
path: dist/*.dmg
if-no-files-found: error
release:
name: Create GitHub Release
needs: [build-linux, build-windows, build-mac, build-mac-intel]
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create release and upload assets
uses: softprops/action-gh-release@v2
with:
name: LegacyLauncher ${{ github.ref_name }}
draft: false
prerelease: ${{ contains(github.ref_name, '-') }}
generate_release_notes: true
files: |
artifacts/linux-dist/*
artifacts/windows-dist/*
artifacts/mac-dist/*
artifacts/mac-intel-dist/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 gardenGnostic
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

View file

@ -2,6 +2,11 @@
A custom launcher for Minecraft Legacy Console Edition. A custom launcher for Minecraft Legacy Console Edition.
<img width="1277" height="717" alt="image" src="https://github.com/user-attachments/assets/eaa9bae6-3b3b-4e39-a3c1-156e34abf3cc" />
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/gradengnostic)
## Features ## Features
- **Minecraft-style GUI**: Authentic pixelated interface with Minecraft font and styling - **Minecraft-style GUI**: Authentic pixelated interface with Minecraft font and styling
@ -74,6 +79,11 @@ The launcher supports several compatibility options for Linux:
- **extract-zip**: ZIP archive extraction - **extract-zip**: ZIP archive extraction
- **Tailwind CSS**: UI styling (via CDN) - **Tailwind CSS**: UI styling (via CDN)
## Assets
- Controller button sprites: [greatdocbrown](https://greatdocbrown.itch.io/gamepad-ui)
- UI Sounds: Using the free version of [JDSherbert's Ultimate UI SFX Pack on itch.io](https://jdsherbert.itch.io/ultimate-ui-sfx-pack)
## Development ## Development
The launcher is built with: The launcher is built with:
@ -99,3 +109,13 @@ The launcher is built with:
## Contributing ## Contributing
Feel free to submit issues and pull requests for improvements.</content> Feel free to submit issues and pull requests for improvements.</content>
## Star History
<a href="https://www.star-history.com/?repos=gradenGnostic%2FLegacyLauncher&type=date&legend=top-left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=gradenGnostic/LegacyLauncher&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=gradenGnostic/LegacyLauncher&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=gradenGnostic/LegacyLauncher&type=date&legend=top-left" />
</picture>
</a>

BIN
assets/512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
assets/Click_stereo.ogg.mp3 Normal file

Binary file not shown.

BIN
assets/Minecraft.ttf Normal file

Binary file not shown.

BIN
assets/gdb-keyboard-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/gdb-switch-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
assets/gdb-xbox-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
assets/minecraft.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 987 KiB

View file

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 320 KiB

BIN
assets/restart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

1444
index.html

File diff suppressed because it is too large Load diff

200
main.js
View file

@ -1,70 +1,25 @@
const { app, BrowserWindow, shell, ipcMain } = require('electron'); const { app, BrowserWindow, shell, ipcMain, dialog, globalShortcut, desktopCapturer } = require('electron');
const path = require('path'); 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 clientId = '1346541144141103114'; const store = new Store();
let rpc; let mainWindow = null;
let isGameRunning = false;
function initRPC() {
rpc = new DiscordRPC.Client({ transport: 'ipc' });
rpc.on('ready', () => {
console.log('Discord RPC ready');
setActivity();
});
rpc.on('error', (err) => {
console.error('Discord RPC Error:', err);
});
rpc.on('disconnected', () => {
console.log('Discord RPC disconnected, retrying...');
setTimeout(connectRPC, 15000);
});
connectRPC();
}
function connectRPC() {
rpc.login({ clientId }).catch(err => {
console.log('Discord RPC connection failed, retrying in 20s...');
setTimeout(connectRPC, 20000);
});
}
initRPC();
function setActivity(details = 'In Menus', state = 'Ready to Play', startTime = null) {
if (!rpc || !rpc.user) return;
const activity = {
details: details,
state: state,
largeImageKey: 'logo',
largeImageText: 'LegacyLauncher',
instance: false,
};
if (startTime) {
activity.startTimestamp = startTime;
}
rpc.setActivity(activity).catch(() => {
});
}
function createWindow() { function createWindow() {
const win = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 1280, width: 1280,
height: 720, height: 720,
minWidth: 1024, minWidth: 1024,
minHeight: 600, minHeight: 600,
center: true,
resizable: true, resizable: true,
frame: false, frame: false,
icon: path.join(__dirname, '256x256.png'), icon: path.join(__dirname, '512x512.png'),
transparent: true, transparent: true,
autoHideMenuBar: true, autoHideMenuBar: true,
webPreferences: { webPreferences: {
@ -74,30 +29,131 @@ function createWindow() {
} }
}); });
win.maximize(); mainWindow.loadFile('index.html');
win.loadFile('index.html');
// Handle window controls ipcMain.on('window-minimize', () => mainWindow.minimize());
ipcMain.on('window-minimize', () => win.minimize());
ipcMain.on('window-maximize', () => { ipcMain.on('window-maximize', () => {
if (win.isMaximized()) { if (mainWindow.isMaximized()) {
win.unmaximize(); mainWindow.unmaximize();
} else { } else {
win.maximize(); mainWindow.maximize();
} }
}); });
ipcMain.on('window-close', () => win.close()); ipcMain.on('window-close', () => mainWindow.close());
ipcMain.on('window-fullscreen', () => {
mainWindow.setFullScreen(!mainWindow.isFullScreen());
ipcMain.on('update-rpc', (event, data) => { });
setActivity(data.details, data.state, data.startTime); ipcMain.on('window-set-fullscreen', (event, enabled) => {
mainWindow.setFullScreen(Boolean(enabled));
}); });
win.on('maximize', () => win.webContents.send('window-is-maximized', true)); ipcMain.handle('take-screenshot', async (event) => {
win.on('unmaximize', () => win.webContents.send('window-is-maximized', false)); 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
});
win.webContents.setWindowOpenHandler(({ url }) => { // 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));
ipcMain.handle('store-set', (event, key, value) => store.set(key, value));
ipcMain.handle('select-directory', async () => {
const result = await dialog.showOpenDialog({
properties: ['openDirectory', 'createDirectory']
});
return result.filePaths[0];
});
mainWindow.on('maximize', () => mainWindow.webContents.send('window-is-maximized', true));
mainWindow.on('unmaximize', () => mainWindow.webContents.send('window-is-maximized', false));
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url); shell.openExternal(url);
return { action: 'deny' }; return { action: 'deny' };
}); });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

350
package-lock.json generated
View file

@ -1,15 +1,15 @@
{ {
"name": "legacylauncher", "name": "legacylauncher",
"version": "1.0.0", "version": "3.5.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "legacylauncher", "name": "legacylauncher",
"version": "1.0.0", "version": "3.5.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"discord-rpc": "^4.0.1", "electron-store": "^6.0.1",
"extract-zip": "^2.0.1" "extract-zip": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
@ -913,7 +913,6 @@
"version": "6.14.0", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
@ -1198,6 +1197,15 @@
"node": ">= 4.0.0" "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": { "node_modules/balanced-match": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@ -1229,16 +1237,6 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bl": { "node_modules/bl": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
@ -1712,6 +1710,42 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/core-util-is": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@ -1778,6 +1812,30 @@
"node": ">= 8" "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": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@ -1955,19 +2013,6 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/discord-rpc": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/discord-rpc/-/discord-rpc-4.0.1.tgz",
"integrity": "sha512-HOvHpbq5STRZJjQIBzwoKnQ0jHplbEWFWlPDwXXKm/bILh4nzjcg7mNqll0UY7RsjFoaXA7e/oYb/4lvpda2zA==",
"license": "MIT",
"dependencies": {
"node-fetch": "^2.6.1",
"ws": "^7.3.1"
},
"optionalDependencies": {
"register-scheme": "github:devsnek/node-register-scheme"
}
},
"node_modules/dmg-builder": { "node_modules/dmg-builder": {
"version": "26.8.1", "version": "26.8.1",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz", "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz",
@ -2050,6 +2095,18 @@
"node": ">=8" "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": { "node_modules/dotenv": {
"version": "16.6.1", "version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@ -2268,6 +2325,31 @@
"node": ">= 10.0.0" "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": { "node_modules/electron-winstaller": {
"version": "5.4.0", "version": "5.4.0",
"resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
@ -2317,6 +2399,7 @@
"version": "0.1.13", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@ -2336,7 +2419,6 @@
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -2472,14 +2554,12 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-json-stable-stringify": { "node_modules/fast-json-stable-stringify": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fd-slicer": { "node_modules/fd-slicer": {
@ -2509,13 +2589,6 @@
} }
} }
}, },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"license": "MIT",
"optional": true
},
"node_modules/filelist": { "node_modules/filelist": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
@ -2556,6 +2629,18 @@
"node": ">=10" "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": { "node_modules/foreground-child": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@ -3012,7 +3097,7 @@
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"devOptional": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0" "safer-buffer": ">= 2.1.2 < 3.0.0"
@ -3101,6 +3186,15 @@
"node": ">=8" "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": { "node_modules/is-unicode-supported": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@ -3205,9 +3299,14 @@
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT" "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": { "node_modules/json-stringify-safe": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@ -3256,6 +3355,19 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/lodash": {
"version": "4.17.23", "version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
@ -3303,6 +3415,21 @@
"node": ">=10" "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": { "node_modules/make-fetch-happen": {
"version": "14.0.3", "version": "14.0.3",
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz",
@ -3390,7 +3517,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -3624,6 +3750,7 @@
"version": "1.7.2", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz",
"integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==",
"dev": true,
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
@ -3650,26 +3777,6 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-gyp": { "node_modules/node-gyp": {
"version": "11.5.0", "version": "11.5.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz",
@ -3761,7 +3868,6 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mimic-fn": "^2.1.0" "mimic-fn": "^2.1.0"
@ -3823,6 +3929,33 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/p-map": {
"version": "7.0.4", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz",
@ -3836,6 +3969,15 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/package-json-from-dist": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@ -3843,6 +3985,15 @@
"dev": true, "dev": true,
"license": "BlueOak-1.0.0" "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": { "node_modules/path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -3928,6 +4079,18 @@
"url": "https://github.com/sponsors/jonschlinkert" "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": { "node_modules/plist": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
@ -4033,7 +4196,6 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -4080,17 +4242,6 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/register-scheme": {
"version": "0.0.2",
"resolved": "git+ssh://git@github.com/devsnek/node-register-scheme.git#e7cc9a63a1f512565da44cb57316d9fb10750e17",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"bindings": "^1.3.0",
"node-addon-api": "^1.3.0"
}
},
"node_modules/require-directory": { "node_modules/require-directory": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -4222,7 +4373,7 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"devOptional": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/sanitize-filename": { "node_modules/sanitize-filename": {
@ -4249,7 +4400,6 @@
"version": "6.3.1", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@ -4540,9 +4690,9 @@
} }
}, },
"node_modules/tar": { "node_modules/tar": {
"version": "7.5.10", "version": "7.5.11",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",
"integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==", "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==",
"dev": true, "dev": true,
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
@ -4687,12 +4837,6 @@
"tmp": "^0.2.0" "tmp": "^0.2.0"
} }
}, },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/truncate-utf8-bytes": { "node_modules/truncate-utf8-bytes": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@ -4764,7 +4908,6 @@
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"punycode": "^2.1.0" "punycode": "^2.1.0"
@ -4810,22 +4953,6 @@
"defaults": "^1.0.3" "defaults": "^1.0.3"
} }
}, },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz",
@ -4885,27 +5012,6 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlbuilder": { "node_modules/xmlbuilder": {
"version": "15.1.1", "version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",

View file

@ -1,13 +1,15 @@
{ {
"name": "legacylauncher", "name": "legacylauncher",
"version": "1.0.0", "version": "3.5.0",
"description": "", "description": "A Minecraft: Legacy Console Edition launcher",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"start": "electron .", "start": "electron .",
"dist": "electron-builder --linux AppImage", "dist": "electron-builder --linux AppImage",
"dist:win": "electron-builder --win nsis" "dist:flatpak": "electron-builder --linux flatpak",
"dist:win": "electron-builder --win nsis",
"dist:mac": "electron-builder --mac dmg"
}, },
"build": { "build": {
"appId": "com.legacylauncher.app", "appId": "com.legacylauncher.app",
@ -16,7 +18,7 @@
"target": [ "target": [
"nsis" "nsis"
], ],
"icon": "256x256.png" "icon": "512x512.png"
}, },
"nsis": { "nsis": {
"oneClick": false, "oneClick": false,
@ -24,10 +26,28 @@
}, },
"linux": { "linux": {
"target": [ "target": [
"AppImage" "AppImage",
"flatpak"
], ],
"category": "Game", "category": "Game",
"icon": "256x256.png" "icon": "512x512.png",
"desktop": {
"entry": {
"Name": "Minecraft LCE Launcher",
"GenericName": "Minecraft Launcher",
"Comment": "A Minecraft: Legacy Console Edition launcher",
"Categories": "Game;Emulation;",
"StartupNotify": "true"
}
},
"executableName": "legacylauncher"
},
"mac": {
"target": [
"dmg"
],
"icon": "512x512.png",
"category": "public.app-category.games"
}, },
"files": [ "files": [
"**/*", "**/*",
@ -38,7 +58,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"discord-rpc": "^4.0.1", "electron-store": "^6.0.1",
"extract-zip": "^2.0.1" "extract-zip": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {

2181
renderer.js Normal file

File diff suppressed because it is too large Load diff

469
skin_manager.js Normal file
View file

@ -0,0 +1,469 @@
// skin_manager.js
// Handles skin uploading, conversion, 3D preview, and saving to char.png
let mainMenuScene, mainMenuCamera, mainMenuRenderer, mainMenuPlayerGroup;
let isMainSkinDragging = false;
let mainMenuSkinRenderMode = '3d';
let skinScene, skinCamera, skinRenderer, skinPlayerGroup;
let isSkinDragging = false;
let previousSkinMousePosition = { x: 0, y: 0 };
let processedSkinDataUrl = null;
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
const skinInput = document.getElementById('skin-input');
const dropZone = document.getElementById('drop-zone');
const saveSkinBtn = document.getElementById('save-skin-btn');
const closeSkinBtn = document.getElementById('btn-close-skin');
if (skinInput) skinInput.addEventListener('change', (e) => handleSkinFile(e.target.files[0]));
if (dropZone) {
dropZone.addEventListener('click', () => skinInput?.click());
dropZone.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
skinInput?.click();
}
});
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('border-green-500');
});
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('border-green-500'));
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('border-green-500');
handleSkinFile(e.dataTransfer.files[0]);
});
}
if (saveSkinBtn) {
saveSkinBtn.addEventListener('click', saveSkinToDisk);
}
if (closeSkinBtn) {
closeSkinBtn.addEventListener('click', closeSkinManager);
}
// Initialize Main Menu Viewer
initMainMenuSkinViewer();
});
function initMainMenuSkinViewer() {
const container = document.getElementById('main-skin-viewer');
if (!container) return;
mainMenuScene = new THREE.Scene();
mainMenuCamera = new THREE.PerspectiveCamera(45, container.offsetWidth / container.offsetHeight, 0.1, 1000);
mainMenuCamera.position.set(0, 5, 60);
mainMenuRenderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
mainMenuRenderer.setSize(container.offsetWidth, container.offsetHeight);
mainMenuRenderer.setPixelRatio(window.devicePixelRatio);
mainMenuRenderer.outputEncoding = THREE.sRGBEncoding;
container.appendChild(mainMenuRenderer.domElement);
mainMenuScene.add(new THREE.AmbientLight(0xffffff, 1.1));
const dl = new THREE.DirectionalLight(0xffffff, 0.5);
dl.position.set(10, 20, 15);
mainMenuScene.add(dl);
mainMenuPlayerGroup = new THREE.Group();
mainMenuScene.add(mainMenuPlayerGroup);
// Interaction for Main Menu Viewer
let prevX = 0;
container.addEventListener('mousedown', (e) => {
if (mainMenuSkinRenderMode !== '3d') return;
isMainSkinDragging = true;
prevX = e.clientX;
});
window.addEventListener('mouseup', () => isMainSkinDragging = false);
window.addEventListener('mousemove', (e) => {
if (mainMenuSkinRenderMode === '3d' && isMainSkinDragging && mainMenuPlayerGroup) {
mainMenuPlayerGroup.rotation.y += (e.clientX - prevX) * 0.01;
prevX = e.clientX;
}
});
// Auto-rotate slowly
function animateMain() {
requestAnimationFrame(animateMain);
if (mainMenuSkinRenderMode === '3d' && !isMainSkinDragging && mainMenuPlayerGroup) {
mainMenuPlayerGroup.rotation.y += 0.005;
}
if (mainMenuRenderer && mainMenuScene && mainMenuCamera) mainMenuRenderer.render(mainMenuScene, mainMenuCamera);
}
animateMain();
// Load current skin after short delay to ensure paths are ready
setTimeout(loadMainMenuSkin, 500);
// Handle window resize
window.addEventListener('resize', () => {
if (mainMenuCamera && mainMenuRenderer && container) {
mainMenuCamera.aspect = container.offsetWidth / container.offsetHeight;
mainMenuCamera.updateProjectionMatrix();
mainMenuRenderer.setSize(container.offsetWidth, container.offsetHeight);
}
});
}
async function loadMainMenuSkin() {
try {
// Ensure install dir is available (currentInstance might not be ready)
const installDir = await window.getInstallDir();
if (!installDir) return;
const skinPath = path.join(installDir, 'Common', 'res', 'mob', 'char.png');
if (fs.existsSync(skinPath)) {
const skinData = fs.readFileSync(skinPath);
const blob = new Blob([skinData]);
const url = URL.createObjectURL(blob);
const img = new Image();
img.onload = () => {
const isLegacy = img.height === 32;
updateSkinModel(img.src, isLegacy, mainMenuPlayerGroup, mainMenuSkinRenderMode);
};
img.src = url;
} else {
console.log("No skin found at " + skinPath);
}
} catch (e) {
console.warn("Could not load main menu skin (startup race condition?):", e);
}
}
function toggleMainSkinRenderMode() {
mainMenuSkinRenderMode = mainMenuSkinRenderMode === '3d' ? '2d' : '3d';
const modeButton = document.getElementById('btn-skin-render-mode');
if (modeButton) modeButton.textContent = mainMenuSkinRenderMode.toUpperCase();
const container = document.getElementById('main-skin-viewer');
if (container) {
container.style.cursor = mainMenuSkinRenderMode === '3d' ? 'grab' : 'default';
}
isMainSkinDragging = false;
if (mainMenuSkinRenderMode === '2d' && mainMenuPlayerGroup) {
mainMenuPlayerGroup.rotation.y = 0;
}
loadMainMenuSkin();
}
function updateSkinModel(dataUrl, isLegacy, targetGroup, renderMode = '3d') {
if (!targetGroup) return;
new THREE.TextureLoader().load(dataUrl, (texture) => {
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.NearestFilter;
texture.encoding = THREE.sRGBEncoding;
// Clear existing children
while(targetGroup.children.length > 0) targetGroup.remove(targetGroup.children[0]);
const createFaceMaterial = (tex, x, y, w, h) => {
const texWidth = tex.image.width;
const texHeight = tex.image.height;
const matTex = tex.clone();
matTex.magFilter = THREE.NearestFilter;
matTex.minFilter = THREE.NearestFilter;
matTex.repeat.set(w / texWidth, h / texHeight);
matTex.offset.set(x / texWidth, 1 - (y + h) / texHeight);
matTex.needsUpdate = true;
return new THREE.MeshLambertMaterial({ map: matTex, transparent: true, alphaTest: 0.5, side: THREE.FrontSide });
};
const createBodyPart = (w, h, d, tex, uv, offset = 0) => {
const geometry = new THREE.BoxGeometry(w, h, d);
const materials = [
createFaceMaterial(tex, uv.left[0], uv.left[1], uv.left[2], uv.left[3]), // Left (Standard MC "Right")
createFaceMaterial(tex, uv.right[0], uv.right[1], uv.right[2], uv.right[3]), // Right (Standard MC "Left")
createFaceMaterial(tex, uv.top[0], uv.top[1], uv.top[2], uv.top[3]),
createFaceMaterial(tex, uv.bottom[0], uv.bottom[1], uv.bottom[2], uv.bottom[3]),
createFaceMaterial(tex, uv.front[0], uv.front[1], uv.front[2], uv.front[3]),
createFaceMaterial(tex, uv.back[0], uv.back[1], uv.back[2], uv.back[3])
];
const mesh = new THREE.Mesh(geometry, materials);
if (offset !== 0) mesh.scale.set(1 + offset, 1 + offset, 1 + offset);
return mesh;
};
const limbUv = (x, y) => ({
top: [x+4, y, 4, 4], bottom: [x+8, y, 4, 4],
right: [x, y+4, 4, 12], front: [x+4, y+4, 4, 12],
left: [x+8, y+4, 4, 12], back: [x+12, y+4, 4, 12]
});
const create2DPart = (tex, uvFront, width, height, x, y, scale = 1) => {
const texWidth = tex.image.width;
const texHeight = tex.image.height;
const partTex = tex.clone();
partTex.magFilter = THREE.NearestFilter;
partTex.minFilter = THREE.NearestFilter;
partTex.repeat.set(uvFront[2] / texWidth, uvFront[3] / texHeight);
partTex.offset.set(uvFront[0] / texWidth, 1 - (uvFront[1] + uvFront[3]) / texHeight);
partTex.needsUpdate = true;
const geometry = new THREE.PlaneGeometry(width, height);
const material = new THREE.MeshBasicMaterial({ map: partTex, transparent: true, alphaTest: 0.5, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, y, 0);
mesh.scale.set(scale, scale, 1);
return mesh;
};
if (renderMode === '2d') {
const frontParts = [
create2DPart(texture, [8, 8, 8, 8], 8, 8, 0, 10),
create2DPart(texture, [40, 8, 8, 8], 8, 8, 0, 10, 1.12),
create2DPart(texture, [20, 20, 8, 12], 8, 12, 0, 0),
create2DPart(texture, [44, 20, 4, 12], 4, 12, -6, 0),
create2DPart(texture, isLegacy ? [44, 20, 4, 12] : [36, 52, 4, 12], 4, 12, 6, 0),
create2DPart(texture, [4, 20, 4, 12], 4, 12, -2, -12),
create2DPart(texture, isLegacy ? [4, 20, 4, 12] : [20, 52, 4, 12], 4, 12, 2, -12)
];
if (!isLegacy) {
frontParts.push(
create2DPart(texture, [20, 36, 8, 12], 8, 12, 0, 0, 1.05),
create2DPart(texture, [44, 36, 4, 12], 4, 12, -6, 0, 1.05),
create2DPart(texture, [52, 52, 4, 12], 4, 12, 6, 0, 1.05),
create2DPart(texture, [4, 36, 4, 12], 4, 12, -2, -12, 1.05),
create2DPart(texture, [4, 52, 4, 12], 4, 12, 2, -12, 1.05)
);
}
frontParts.forEach((part) => targetGroup.add(part));
targetGroup.rotation.y = 0;
return;
}
// Head
const headUvs = { top: [8, 0, 8, 8], bottom: [16, 0, 8, 8], right: [0, 8, 8, 8], left: [16, 8, 8, 8], front: [8, 8, 8, 8], back: [24, 8, 8, 8] };
const head = createBodyPart(8, 8, 8, texture, headUvs);
head.position.y = 10;
targetGroup.add(head);
// Hat
const hatUvs = { top: [40, 0, 8, 8], bottom: [48, 0, 8, 8], right: [32, 8, 8, 8], left: [48, 8, 8, 8], front: [40, 8, 8, 8], back: [56, 8, 8, 8] };
const hat = createBodyPart(8, 8, 8, texture, hatUvs, 0.12);
hat.position.y = 10;
targetGroup.add(hat);
// Torso
const torsoUvs = { top: [20, 16, 8, 4], bottom: [28, 16, 8, 4], right: [16, 20, 4, 12], left: [28, 20, 4, 12], front: [20, 20, 8, 12], back: [32, 20, 8, 12] };
targetGroup.add(createBodyPart(8, 12, 4, texture, torsoUvs));
// Jacket (non-legacy only)
if (!isLegacy) {
const jacketUvs = { top: [20, 32, 8, 4], bottom: [28, 32, 8, 4], right: [16, 36, 4, 12], left: [28, 36, 4, 12], front: [20, 36, 8, 12], back: [32, 36, 8, 12] };
targetGroup.add(createBodyPart(8, 12, 4, texture, jacketUvs, 0.05));
}
// Limbs
const limbs = [
{ pos: [-6, 0, 0], uv: limbUv(40, 16), layerUv: limbUv(40, 32) },
{ pos: [6, 0, 0], uv: isLegacy ? limbUv(40, 16) : limbUv(32, 48), layerUv: limbUv(48, 48) },
{ pos: [-2, -12, 0], uv: limbUv(0, 16), layerUv: limbUv(0, 32) },
{ pos: [2, -12, 0], uv: isLegacy ? limbUv(0, 16) : limbUv(16, 48), layerUv: limbUv(0, 48) }
];
limbs.forEach(l => {
const base = createBodyPart(4, 12, 4, texture, l.uv);
base.position.set(...l.pos);
targetGroup.add(base);
if (!isLegacy) {
const layer = createBodyPart(4, 12, 4, texture, l.layerUv, 0.05);
layer.position.set(...l.pos);
targetGroup.add(layer);
}
});
targetGroup.rotation.y = 0;
});
}
function openSkinManager() {
const modal = document.getElementById('skin-modal');
modal.style.display = 'flex';
modal.style.opacity = '1';
// Clear previous state in modal
const previewContainer = document.getElementById('preview-container');
if (previewContainer) previewContainer.classList.add('hidden');
const sysMsg = document.getElementById('sys-message');
if (sysMsg) sysMsg.classList.add('hidden');
// Load current skin into preview (optional, maybe we only want to see uploaded ones)
// loadCurrentSkinToPreview();
}
function closeSkinManager() {
const modal = document.getElementById('skin-modal');
modal.style.opacity = '0';
setTimeout(() => {
modal.style.display = 'none';
// Reset state
const prompt = document.getElementById('upload-prompt');
if (prompt) prompt.style.display = 'block';
const previewContainer = document.getElementById('preview-container');
if (previewContainer) previewContainer.classList.add('hidden');
processedSkinDataUrl = null;
}, 300);
}
function handleSkinFile(file) {
if (!file || !file.type.includes('png')) return window.showToast("Only PNG files are supported!");
const prompt = document.getElementById('upload-prompt');
if (prompt) prompt.style.display = 'none';
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
processSkinImage(img, e.target.result);
};
img.onerror = () => {
window.showToast("Failed to load image");
if(prompt) prompt.style.display = 'block';
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
function processSkinImage(img, srcUrl, isInitialLoad = false) {
const canvas = document.getElementById('skin-canvas');
const ctx = canvas.getContext('2d');
const formatLabel = document.getElementById('format-label');
const statusMessage = document.getElementById('status-message');
const previewContainer = document.getElementById('preview-container');
const saveBtn = document.getElementById('save-skin-btn');
const prompt = document.getElementById('upload-prompt');
if (img.width !== 64) {
if(prompt) prompt.style.display = 'block';
if(previewContainer) previewContainer.classList.add('hidden');
return window.showToast("Invalid skin width. Must be 64px.");
}
if(prompt) prompt.style.display = 'none';
const isLegacy = img.height === 32;
ctx.clearRect(0, 0, 64, 32);
ctx.drawImage(img, 0, 0, 64, 32, 0, 0, 64, 32);
processedSkinDataUrl = canvas.toDataURL('image/png');
previewContainer.classList.remove('hidden');
if (isInitialLoad) {
if(formatLabel) formatLabel.textContent = "Current Skin";
if(statusMessage) statusMessage.innerHTML = "<span class='text-blue-400 font-black' style='color: #60a5fa;'>LOADED FROM DISK</span>";
if(saveBtn) {
saveBtn.textContent = "SAVED";
saveBtn.classList.add('disabled');
}
} else {
if(formatLabel) formatLabel.textContent = isLegacy ? "64x32 (Legacy)" : "64x64 (Modern)";
if(statusMessage) statusMessage.innerHTML = isLegacy ? "<span class='text-green-400 font-black' style='color: #4ade80;'>LEGACY READY</span>" : "<span class='text-yellow-400 font-black' style='color: #facc15;'>CONVERTED TO 64x32</span>";
if(saveBtn) {
saveBtn.textContent = "SAVE SKIN";
saveBtn.classList.remove('disabled');
}
}
if (!skinScene) initPreviewEngine();
updateSkinModel(srcUrl, isLegacy, skinPlayerGroup, '3d');
}
function initPreviewEngine() {
const container = document.getElementById('skin-viewer-container');
if (!container) return;
skinScene = new THREE.Scene();
skinCamera = new THREE.PerspectiveCamera(35, container.offsetWidth / container.offsetHeight, 0.1, 1000);
skinCamera.position.set(0, 5, 70);
skinRenderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });
skinRenderer.setSize(container.offsetWidth, container.offsetHeight);
skinRenderer.setPixelRatio(window.devicePixelRatio);
skinRenderer.outputEncoding = THREE.sRGBEncoding;
container.appendChild(skinRenderer.domElement);
skinScene.add(new THREE.AmbientLight(0xffffff, 0.9));
const dl = new THREE.DirectionalLight(0xffffff, 0.35);
dl.position.set(10, 20, 15);
skinScene.add(dl);
skinPlayerGroup = new THREE.Group();
skinScene.add(skinPlayerGroup);
container.addEventListener('mousedown', () => isSkinDragging = true);
window.addEventListener('mouseup', () => isSkinDragging = false);
window.addEventListener('mousemove', (e) => {
if (isSkinDragging && skinPlayerGroup) {
skinPlayerGroup.rotation.y += (e.movementX) * 0.01;
}
});
function animate() {
requestAnimationFrame(animate);
if (!isSkinDragging && skinPlayerGroup) skinPlayerGroup.rotation.y += 0.008;
if (skinRenderer && skinScene && skinCamera) skinRenderer.render(skinScene, skinCamera);
}
animate();
}
async function saveSkinToDisk() {
if (!processedSkinDataUrl) return;
try {
const installDir = await window.getInstallDir();
const savePath = path.join(installDir, 'Common', 'res', 'mob', 'char.png');
const dir = path.dirname(savePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const base64Data = processedSkinDataUrl.replace(/^data:image\/png;base64,/, "");
fs.writeFileSync(savePath, base64Data, 'base64');
window.showToast("Skin Saved Successfully!");
const saveBtn = document.getElementById('save-skin-btn');
if(saveBtn) {
saveBtn.textContent = "SAVED!";
saveBtn.classList.add('disabled');
}
// Refresh main menu skin
loadMainMenuSkin();
// Close modal after short delay?
setTimeout(closeSkinManager, 1000);
} catch (e) {
window.showToast("Error Saving Skin: " + e.message);
console.error(e);
}
}
// Global Export
window.openSkinManager = openSkinManager;
window.initMainMenuSkinViewer = initMainMenuSkinViewer;
window.loadMainMenuSkin = loadMainMenuSkin;
window.toggleMainSkinRenderMode = toggleMainSkinRenderMode;

483
strings.txt Normal file
View file

@ -0,0 +1,483 @@
Happy birthday, ez!
Happy birthday, Notch!
Let me tell you about Homestuck!
Let me tell you about Homestuck!
Let me tell you about Homestuck!
Merry X-mas!
Happy New Year!
As seen on TV!
A young man stands in his bedroom!
A young man stands in his bedroom!
A young woman stands in her bedroom!
A young woman stands in her bedroom!
Awesome!
100% pure!
Better than Prey!
More polygons!
Get the cruxite dowel!
Get the cruxite dowel!
Get the cruxite dowel!
Flashing letters!
Made by Notch!
It's here!
Best in class!
It's finished!
Kind of dragon free!
Excitement!
Where doing it man, where making this hapen!
Where doing it man, where making this hapen!
One of a kind!
Heaps of hits on YouTube!
Indev!
Spiders everywhere!
Check it out!
Holy cow, man!
It's a game!
Made in Sweden!
Uses LWJGL!
Reticulating splines!
Minecraft!
Yaaay!
413!
413!
413!
413!
Singleplayer!
Keyboard compatible!
Undocumented!
Ingots!
Exploding creepers!
That's no moon!
l33t!
Create!
Survive!
Dungeon!
I WARNED YOU ABOUT STAIRS BRO!!!!
I WARNED YOU ABOUT STAIRS BRO!!!!
I WARNED YOU ABOUT STAIRS BRO!!!!
Exclusive!
The bee's knees!
Closed source!
Classy!
Wow!
Not on steam!
Oh man!
Awesome community!
Pixels!
Teetsuuuuoooo!
Kaaneeeedaaaa!
Now with difficulty!
Enhanced!
90% bug free!
Pretty!
12 herbs and spices!
Fat free!
Absolutely no memes!
Free dental!
Cloud computing!
Hard to label!
Technically good!
Bringing home the bacon!
Indie!
GOTY!
Ceci n'est pas une title screen!
Euclidian!
Now in 3D!
Inspirational!
Herregud!
Complex cellular automata!
Yes, sir!
Played by cowboys!
Thousands of colors!
Try it!
Age of Wonders is better!
Try the mushroom stew!
Sensational!
Hot tamale, hot hot tamale!
Play him off, keyboard cat!
Guaranteed!
Alchemize ALL the things!
Alchemize ALL the things!
Macroscopic!
Bring it on!
Random splash!
Call your mother!
Monster infighting!
Loved by millions!
Ultimate edition!
Freaky!
Water proof!
Uninflammable!
Whoa, dude!
Roxy is best hacker!
Roxy is best hacker!
Roxy is best hacker!
All inclusive!
Tell your friends!
NP is not in P!
Notch <3 ez!
Music by C418!
Livestreamed!
Haunted!
Polynomial!
Terrestrial!
All is full of love!
Full of stars!
Scientific!
Cooler than Spock!
Collaborate and listen!
Never dig down!
Take frequent breaks!
Not linear!
Han shot first!
Nice to meet you!
Buckets of lava!
Ride the pig!
Larger than Earth!
sqrt(-1) love you!
Phobos anomaly!
Punching wood!
Falling off cliffs!
0% sugar!
150% hyperbole!
Synecdoche!
Let's danec!
Reference implementation!
Lewd with two dudes with food!
Kiss the sky!
20 GOTO 10!
Verlet intregration!
Peter Griffin!
Do not distribute!
Cogito ergo sum!
4815162342 lines of code!
A skeleton popped out!
The Work of Notch!
The sum of its parts!
BTAF used to be good!
I miss ADOM!
umop-apisdn!
OICU812!
Bring me Ray Cokes!
Finger-licking!
Thematic!
Pneumatic!
Sublime!
Octagonal!
Une baguette!
Gargamel plays it!
Rita is the new top dog!
SWM forever!
Representing Edsbyn!
Matt Damon!
Supercalifragilisticexpialidocious!
Consummate V's!
Cow Tools!
Double buffered!
Fan fiction!
Flaxkikare!
Jason! Jason! Jason!
Hotter than the sun!
Internet enabled!
Autonomous!
Engage!
Fantasy!
DRR! DRR! DRR!
Kick it root down!
Regional resources!
Woo, facepunch!
Woo, somethingawful!
Woo, /v/!
Woo, tigsource!
Woo, minecraftforum!
Woo, worldofminecraft!
Woo, reddit!
Woo, 2pp!
Google anlyticsed!
Now supports åäö!
Give us Gordon!
Tip your waiter!
Very fun!
12345 is a bad password!
Vote for net neutrality!
Lives in a pineapple under the sea!
MAP11 has two names!
Omnipotent!
Gasp!
...!
Bees, bees, bees, bees!
Jag känner en bot!
This text is hard to read if you play the game at the default resolution, but at 1080p it's fine!
Haha, LOL!
Hampsterdance!
Switches and ores!
Menger sponge!
idspispopd!
Eple (original edit)!
So fresh, so clean!
Slow acting portals!
Try the Nether!
Don't look directly at the bugs!
Oh, ok, Pigmen!
Finally with ladders!
Scary!
Play Minecraft, Watch Topgear, Get Pig!
Twittered about!
Jump up, jump up, and get down!
Joel is neat!
A riddle, wrapped in a mystery!
Huge tracts of land!
Welcome to your Doom!
Stay a while, stay forever!
Stay a while and listen!
Treatment for your rash!
"Autological" is!
Information wants to be free!
"Almost never" is an interesting concept!
Lots of truthiness!
The creeper is a spy!
Turing complete!
It's groundbreaking!
Let our battle's begin!
The sky is the limit!
Jeb has amazing hair!
Casual gaming!
Undefeated!
Kinda like Lemmings!
Follow the train, CJ!
Leveraging synergy!
This message will never appear on the splash screen, isn't that weird?
DungeonQuest is unfair!
110813!
90210!
Check out the far lands!
Tyrion would love it!
Also try VVVVVV!
Also try Super Meat Boy!
Also try Terraria!
Also try Mount And Blade!
Also try Project Zomboid!
Also try World of Goo!
Also try Limbo!
Also try Pixeljunk Shooter!
Also try Braid!
Also try Sburb!
Also try Sburb!
Also try Sburb!
That's super!
Bread is pain!
Read more books!
Khaaaaaaaaan!
Less addictive than TV Tropes!
More addictive than lemonade!
Bigger than a bread box!
Millions of peaches!
Fnord!
This is my true form!
Totally forgot about Dre!
Don't bother with the clones!
Pumpkinhead!
Hobo humping slobo babe!
Made by Jeb!
Has an ending!
Finally complete!
Feature packed!
Boots with the fur!
Stop, hammertime!
Testificates!
Conventional!
Homeomorphic to a 3-sphere!
Doesn't avoid double negatives!
Place ALL the blocks!
Does barrel rolls!
Meeting expectations!
PC gaming since 1873!
Ghoughpteighbteau tchoghs!
Déjà vu!
IT KEEPS HAPPENING!
IT KEEPS HAPPENING!
IT KEEPS HAPPENING!
I TOLD YOU DOG!
I TOLD YOU DOG!
Déjà vu!
Got your nose!
Afraid of the big, black bat!
Doesn't use the U-word!
Child's play!
See you next Friday or so!
From the streets of Södermalm!
150 bpm for 400000 minutes!
Technologic!
Funk soul brother!
Pumpa kungen!
Fetchstuck compatible!
Sylladex full!
Sylladex full!
Honk. HONK.
Honk. HONK.
You can't fight the Homestuck!
You can't fight the Homestuck!
==> Enter.
==> Enter.
STRIFE!
STRIFE!
8^y
8^y
Sweet Bro and Hella Jeff!
What pumpkin?
What pumpkin?
ABSCOND!
ABSCOND!
Check your Strife Specibus!
Ascend to God Tier!
Ascend to God Tier!
Got tiger!
Got tiger!
Motherfucking miracles!
Welcome to Skaia!
Welcome to Skaia!
Prospit or Derse?
Upgrading the Alchemiter!
Build Grist!
Build Grist!
Now with 100% more Faygo!
Sopor slime pie!
Grimdark!
Trickster mode engaged!
How HIGH do you even have to BE just to DO something like that........
Mayor of Can Town!
Spades Slick did nothing wrong!
It's hard and nobody understands.
Shenanigans!
The Scratch is happening!
612!
111111!
Jegus!
All the luck!
All the luck!
Dunkass!
Captchalogue this!
Captchalogue this!
Echeladder rung attained!
Megalovania originally played here!
Megalovania originally played here!
Music by Toby Fox!
Don't ask about the buckets!
Don't ask about the buckets!
Candy corn horns!
Your name is JOHN EGBERT.
Let me guess, your zodiac sign is a troll?
Put the bunny back in the box!
Do the windy thing!
Do the windy thing!
Betty Crocker is evil!
Betty Crocker is evil!
Beware the Batterwitch!
He is already here.
He is already here.
Doc Scratch is watching.
Con Air is a masterpiece!
Karkalicious!
SGRUB!
Sburb Beta!
Sburb Beta!
Knight of Time!
Seer of Light!
Heir of Breath!
Witch of Space!
Flipping the frog switch!
Forge the Bilious Sliver!
Midnight Crew!
We have the cue ball!
th1s 1s r3d1culous.
wweh...
glub glub...
DAVE: this is stupid
WHAT THE FUCK IS A CREEPER.
I AM GOING TO THROW A TANTRUM.
H3H3H3! 1 C4N SM3LL TH3 D14MOND 0R3!
T4ST3S L1K3 CH3RRY!
uHH, pLEASE DONT MINE THAT,,, iTS MINE.
ii jju2t want two play a game.
two much iinformatiion.
:33 < ac goes on a purrfect mining trip!
:33 < furrealsies!
I Cannot Believe You Just Mined That Dirt.
Please Stop Being Ridiculous.
All 8 of my 8lue diamonds! ::::::::)
You're w8ing for something?
D --> That block is insufficiently STRONG
D --> e%cuse me
tHaT sHiT iS mOtHeRfUcKiNg MiRaCuLoUs.
mY mOtHeRfUcKiNg BrOtHeR.
wwhats the point of minin anywway.
38) This is so GLUBBING exciting!
the dead p0pulati0n is b0thering me.
0k. i am t0tally fine with this.
ROXY: wonk ;)
ROXY: obfuscation!!!
ROXY: i am the best hacker
ROXY: meow
ROXY: dirk u there
ROXY: i am going to save u
ROXY: wtf is this block
ROXY: where r the fucking cats
ROXY: im kinda drunk
ROXY: the magic is real
ROXY: sweet catch!
ROXY: frigglish!
ROXY: where are all the wizards
ROXY: im putting on my god tier pjs
ROXY: pumpkins r legally outlawed
ROXY: mutieeeee
ROXY: cant wake up
ROXY: sup
ROXY: wonk ;)
JOHN: wow, okay.
JOHN: wow, okay.
JOHN: i am a true prankster!
JOHN: what are you even talking about?
JOHN: let's go do some ghostbusting!
JOHN: i'm not a homosexual!
JOHN: i am a true prankster!
Prankster's Gambit!
Prankster's Gambit!
Ectobiology!
Ectobiology!
JADE: hooray!!!
JADE: <3 <3 <3
JADE: beep beep meow
JADE: im going to build a tall tower!
JADE: hooray!!!
JADE: <3 <3 <3
Becquerel is a good boy!
Becquerel is a good boy!
Good dog. Best friend.
Good dog. Best friend.
Squiddleknit!
Squiddles!
DAVE: i am not a hero
DAVE: ironically of course
DAVE: time shenanigans
DAVE: lets drop it like its hot
DAVE: apple juice
DAVE: sweet catch
ROSE: Let the record state that I am not a fan of this.
ROSE: I will tear this game apart.
ROSE: The plot thickens.
ROSE: We are playing a game.
Pony Pals!
DIRK: You can't escape the miles.
DIRK: Sup.
DIRK: im going to build a robot.
Auto-Responder!
JAKE: Tally ho!
JAKE: Pip pip!
JAKE: By jove!
JAKE: Egads!
JAKE: Jolly good show
Jade Harley!!!
Jade Harley!!!
Jade Harley!!!
Jade Harley!!!
Jade Harley!!!

1424
style.css Normal file

File diff suppressed because it is too large Load diff