Upload files to "/"
This commit is contained in:
parent
6e14ef7465
commit
e61e7ef89d
5 changed files with 1523 additions and 0 deletions
42
index.html
Normal file
42
index.html
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>ElectricGames</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<div id="sidebar">
|
||||||
|
<button class="sidebar-button" id="downloadsTab">Downloads</button>
|
||||||
|
<button class="sidebar-button" id="libraryTab">Library</button>
|
||||||
|
</div>
|
||||||
|
<main>
|
||||||
|
<h1>ElectricGames</h1>
|
||||||
|
<!-- Downloads Section -->
|
||||||
|
<div id="downloadsSection" class="section active">
|
||||||
|
<div class="browser-navigation">
|
||||||
|
<button id="backButton">Back</button>
|
||||||
|
<button id="forwardButton">Forward</button>
|
||||||
|
</div>
|
||||||
|
<webview id="gameBrowser" src="https://steamunlocked.net"></webview>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Library Section -->
|
||||||
|
<div id="librarySection" class="section">
|
||||||
|
<h2>Library</h2>
|
||||||
|
<button id="selectLibraryFolder">Select Library Folder</button>
|
||||||
|
<div id="gameList"></div>
|
||||||
|
|
||||||
|
<h3>Wine Setup</h3>
|
||||||
|
<button id="selectWineButton">Select Wine Binary</button>
|
||||||
|
<button id="runGameButton">Run Game with Wine</button>
|
||||||
|
<p id="wineStatus"></p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="renderer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
182
main.js
Normal file
182
main.js
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const AdmZip = require('adm-zip'); // For ZIP extraction
|
||||||
|
const { promisify } = require('util');
|
||||||
|
const readdir = promisify(fs.readdir);
|
||||||
|
const stat = promisify(fs.stat);
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
|
||||||
|
let mainWindow;
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: 1000,
|
||||||
|
height: 800,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: false,
|
||||||
|
webviewTag: true, // Make sure webview is enabled
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.loadFile('index.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
createWindow();
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Function to download and extract ZIP files
|
||||||
|
ipcMain.handle('download-and-extract', async (event, downloadUrl, libraryFolder, fileName) => {
|
||||||
|
try {
|
||||||
|
const downloadPath = path.join(libraryFolder, fileName);
|
||||||
|
|
||||||
|
// Start downloading the game from the URL
|
||||||
|
const response = await fetch(downloadUrl);
|
||||||
|
if (!response.ok) throw new Error('Download failed');
|
||||||
|
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
const fileBuffer = Buffer.from(buffer);
|
||||||
|
|
||||||
|
// Write the downloaded zip file to disk
|
||||||
|
fs.writeFileSync(downloadPath, fileBuffer);
|
||||||
|
|
||||||
|
// Now extract the downloaded ZIP
|
||||||
|
await extractZip(downloadPath, libraryFolder);
|
||||||
|
|
||||||
|
// Return the path to the extracted folder
|
||||||
|
return { success: true, folderPath: libraryFolder };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during download or extraction:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to extract ZIP files
|
||||||
|
async function extractZip(zipPath, extractToFolder) {
|
||||||
|
try {
|
||||||
|
const zip = new AdmZip(zipPath);
|
||||||
|
zip.extractAllTo(extractToFolder, true);
|
||||||
|
console.log(`Extracted ZIP to ${extractToFolder}`);
|
||||||
|
|
||||||
|
// Clean up the ZIP file after extraction
|
||||||
|
fs.unlinkSync(zipPath); // Delete the ZIP file
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error extracting ZIP:', error);
|
||||||
|
throw new Error('Error extracting ZIP');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Scan for .exe files in the Library folder (excluding `redist` folders)
|
||||||
|
ipcMain.handle('scan-library', async (event, libraryFolder) => {
|
||||||
|
try {
|
||||||
|
const gameExecutables = await scanForExecutables(libraryFolder);
|
||||||
|
return gameExecutables;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error scanning library:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
async function scanForExecutables(directory) {
|
||||||
|
console.log(directory.exeFiles)
|
||||||
|
return directory.exeFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { scanForExecutables };
|
||||||
|
// Handle the Wine binary selection
|
||||||
|
ipcMain.handle('select-wine-binary', async () => {
|
||||||
|
const result = await dialog.showOpenDialog({
|
||||||
|
properties: ['openFile'],
|
||||||
|
filters: [{ name: 'Executables', extensions: ['exe'] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.canceled || result.filePaths.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
winePath = result.filePaths[0]; // Store the path to the Wine binary
|
||||||
|
return winePath;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle the selection of a Library folder and get list of `.exe` files
|
||||||
|
ipcMain.handle('select-library-folder', async () => {
|
||||||
|
const result = await dialog.showOpenDialog({
|
||||||
|
properties: ['openDirectory'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.canceled || result.filePaths.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const libraryFolderPath = result.filePaths[0];
|
||||||
|
const exeFiles = await scanDirectory(libraryFolderPath); // Scan the folder for .exe files
|
||||||
|
return { libraryFolderPath, exeFiles };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recursive function to scan a directory for `.exe` files
|
||||||
|
async function scanDirectory(dirPath) {
|
||||||
|
const exeFiles = [];
|
||||||
|
const files = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const fullPath = path.join(dirPath, file.name);
|
||||||
|
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
// Recursively scan subdirectories
|
||||||
|
const subDirFiles = await scanDirectory(fullPath);
|
||||||
|
exeFiles.push(...subDirFiles);
|
||||||
|
} else if (file.isFile() && file.name.toLowerCase().endsWith('.exe')) {
|
||||||
|
// Add `.exe` files to the list
|
||||||
|
exeFiles.push(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exeFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle running the game via Wine
|
||||||
|
ipcMain.handle('run-game-with-wine', async (event, gameExePath) => {
|
||||||
|
if (!winePath) {
|
||||||
|
return { success: false, message: 'Wine binary not selected' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runWineCommand(winePath, gameExePath);
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper function to execute Wine command
|
||||||
|
function runWineCommand(wineBinary, gameExePath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const command = `"${wineBinary}" "${gameExePath}"`;
|
||||||
|
|
||||||
|
exec(command, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error(`Error running Wine command: ${stderr || stdout}`));
|
||||||
|
} else {
|
||||||
|
resolve(stdout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
1114
package-lock.json
generated
Normal file
1114
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
21
package.json
Normal file
21
package.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "electricgames",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "electron ."
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"devDependencies": {
|
||||||
|
"electron": "^33.2.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"adm-zip": "^0.5.16",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"fs-extra": "^11.2.0",
|
||||||
|
"unzipper": "^0.12.3"
|
||||||
|
}
|
||||||
|
}
|
164
renderer.js
Normal file
164
renderer.js
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
const { ipcRenderer } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Declare variables for selected Wine binary and game executable
|
||||||
|
let winePath = null;
|
||||||
|
let selectedGameExe = null;
|
||||||
|
|
||||||
|
// Tab switching logic
|
||||||
|
document.getElementById('downloadsTab').addEventListener('click', () => {
|
||||||
|
switchTab('downloads');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('libraryTab').addEventListener('click', () => {
|
||||||
|
switchTab('library');
|
||||||
|
});
|
||||||
|
|
||||||
|
function switchTab(tabName) {
|
||||||
|
const tabs = document.querySelectorAll('.sidebar-button');
|
||||||
|
const sections = document.querySelectorAll('.section');
|
||||||
|
|
||||||
|
// Reset all tabs and sections
|
||||||
|
tabs.forEach(tab => tab.classList.remove('active'));
|
||||||
|
sections.forEach(section => section.classList.remove('active'));
|
||||||
|
|
||||||
|
// Activate the selected tab and section
|
||||||
|
if (tabName === 'downloads') {
|
||||||
|
document.getElementById('downloadsTab').classList.add('active');
|
||||||
|
document.getElementById('downloadsSection').classList.add('active');
|
||||||
|
} else if (tabName === 'library') {
|
||||||
|
document.getElementById('libraryTab').classList.add('active');
|
||||||
|
document.getElementById('librarySection').classList.add('active');
|
||||||
|
loadLibrary(); // Load the library when switching to this tab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to Downloads tab active
|
||||||
|
switchTab('downloads');
|
||||||
|
|
||||||
|
// Back and Forward buttons for webview navigation
|
||||||
|
const gameBrowser = document.getElementById('gameBrowser');
|
||||||
|
document.getElementById('backButton').addEventListener('click', () => {
|
||||||
|
gameBrowser.goBack();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('forwardButton').addEventListener('click', () => {
|
||||||
|
gameBrowser.goForward();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for a "download" request from the webview
|
||||||
|
gameBrowser.addEventListener('did-start-navigation', (event) => {
|
||||||
|
const downloadUrl = event.url;
|
||||||
|
if (downloadUrl && downloadUrl.endsWith('.zip')) {
|
||||||
|
// Trigger download automatically when a .zip file is detected
|
||||||
|
handleDownload(downloadUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleDownload(downloadUrl) {
|
||||||
|
try {
|
||||||
|
// Prompt the user to choose a library folder
|
||||||
|
const libraryFolder = await ipcRenderer.invoke('select-library-folder');
|
||||||
|
if (!libraryFolder) return;
|
||||||
|
|
||||||
|
const fileName = path.basename(downloadUrl); // Use the URL to get the file name
|
||||||
|
|
||||||
|
// Download and extract the ZIP file
|
||||||
|
const result = await ipcRenderer.invoke('download-and-extract', downloadUrl, libraryFolder, fileName);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log('Game downloaded and extracted successfully!');
|
||||||
|
} else {
|
||||||
|
console.error('Error downloading or extracting the game:', result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error handling download:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the Library content (list of .exe files)
|
||||||
|
async function loadLibrary() {
|
||||||
|
const libraryFolder = await ipcRenderer.invoke('select-library-folder');
|
||||||
|
if (libraryFolder) {
|
||||||
|
const gameList = await ipcRenderer.invoke('scan-library', libraryFolder);
|
||||||
|
|
||||||
|
// Show the list of executables in the library
|
||||||
|
const gameListDiv = document.getElementById('gameList');
|
||||||
|
gameListDiv.innerHTML = ''; // Clear previous content
|
||||||
|
|
||||||
|
if (gameList.length === 0) {
|
||||||
|
gameListDiv.innerHTML = 'No games found in the library.';
|
||||||
|
} else {
|
||||||
|
gameList.forEach(game => {
|
||||||
|
const gameItem = document.createElement('div');
|
||||||
|
gameItem.className = 'game-item';
|
||||||
|
gameItem.textContent = game; // Show the game path or name
|
||||||
|
gameListDiv.appendChild(gameItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select Wine binary
|
||||||
|
document.getElementById('selectWineButton').addEventListener('click', async () => {
|
||||||
|
winePath = await ipcRenderer.invoke('select-wine-binary');
|
||||||
|
if (winePath) {
|
||||||
|
document.getElementById('wineStatus').textContent = `Wine Binary Selected: ${winePath}`;
|
||||||
|
} else {
|
||||||
|
document.getElementById('wineStatus').textContent = 'No Wine Binary Selected';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select Library Folder and list `.exe` files
|
||||||
|
document.getElementById('selectLibraryFolder').addEventListener('click', async () => {
|
||||||
|
const result = await ipcRenderer.invoke('select-library-folder');
|
||||||
|
if (result && result.libraryFolderPath) {
|
||||||
|
document.getElementById('wineStatus').textContent = `Library Folder Selected: ${result.libraryFolderPath}`;
|
||||||
|
displayGameList(result.exeFiles);
|
||||||
|
} else {
|
||||||
|
document.getElementById('wineStatus').textContent = 'No Library Folder Selected';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Display the list of `.exe` files
|
||||||
|
function displayGameList(gameList) {
|
||||||
|
const gameListContainer = document.getElementById('gameList');
|
||||||
|
gameListContainer.innerHTML = ''; // Clear the existing list
|
||||||
|
|
||||||
|
if (gameList.length === 0) {
|
||||||
|
gameListContainer.innerHTML = 'No games found in the selected library.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameList.forEach((gameExe) => {
|
||||||
|
const gameButton = document.createElement('button');
|
||||||
|
gameButton.textContent = path.basename(gameExe);
|
||||||
|
gameButton.addEventListener('click', () => {
|
||||||
|
selectedGameExe = gameExe;
|
||||||
|
document.getElementById('wineStatus').textContent = `Selected Game: ${path.basename(gameExe)}`;
|
||||||
|
});
|
||||||
|
gameListContainer.appendChild(gameButton);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run game using Wine
|
||||||
|
document.getElementById('runGameButton').addEventListener('click', async () => {
|
||||||
|
if (!winePath) {
|
||||||
|
document.getElementById('wineStatus').textContent = 'Please select a Wine binary first.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedGameExe) {
|
||||||
|
document.getElementById('wineStatus').textContent = 'Please select a game to run.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the game via Wine
|
||||||
|
const result = await ipcRenderer.invoke('run-game-with-wine', selectedGameExe);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
document.getElementById('wineStatus').textContent = 'Game is running!';
|
||||||
|
} else {
|
||||||
|
document.getElementById('wineStatus').textContent = `Error: ${result.message}`;
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in a new issue