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