mirror of
https://github.com/araxiaonline/wow-client-patcher.git
synced 2026-06-13 03:12:24 -04:00
intial commit files
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from '../renderer/App';
|
||||
|
||||
describe('App', () => {
|
||||
it('should render', () => {
|
||||
expect(render(<App />)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
const Channels = {
|
||||
WOW_LAUNCH: 'launch-wow',
|
||||
WOW_LAUNCH_ERROR: 'launch-wow-error',
|
||||
WOW_CLIENT_EXIT: 'launch-wow-exit',
|
||||
APP_INFO: 'app-info',
|
||||
} as const;
|
||||
|
||||
const PLATFORM = {
|
||||
WINDOWS: 'win32',
|
||||
MAC: 'darwin',
|
||||
LINUX: 'linux',
|
||||
} as const
|
||||
|
||||
export default Channels;
|
||||
export { PLATFORM };
|
||||
10
src/main/libs/index.ts
Normal file
10
src/main/libs/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import * as FileManager from './FileManager';
|
||||
import * as Downloader from './Downloader';
|
||||
import * as Emitter from './Emitter';
|
||||
|
||||
export {
|
||||
FileManager,
|
||||
Downloader,
|
||||
Emitter
|
||||
};
|
||||
|
||||
6
src/main/libs/utils.ts
Normal file
6
src/main/libs/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
|
||||
// Removes double quotes on ETags from S3
|
||||
export function santizeETag(tag: string): string {
|
||||
return tag.replace(/"/g, '');
|
||||
}
|
||||
313
src/main/main.ts
313
src/main/main.ts
@@ -11,16 +11,20 @@
|
||||
import path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
import { app, BrowserWindow, shell, ipcMain, globalShortcut } from 'electron';
|
||||
// import { autoUpdater } from 'electron-updater';
|
||||
import log from 'electron-log';
|
||||
// import MenuBuilder from './menu';
|
||||
import { resolveHtmlPath } from './util';
|
||||
import FileManager from './libs';
|
||||
import { app, BrowserWindow, shell, ipcMain, globalShortcut } from 'electron';
|
||||
|
||||
// Types
|
||||
import Channels from '../constants';
|
||||
// Typings, Interfaces, and Constants
|
||||
import { LauncherServer } from 'typings';
|
||||
import Downloader from './libs/Downloader';
|
||||
import { Channels } from '../constants';
|
||||
|
||||
// import { autoUpdater } from 'electron-updater';
|
||||
import MenuBuilder from './menu';
|
||||
import { resolveHtmlPath } from './util';
|
||||
import FileManager from './libs/FileManager';
|
||||
|
||||
const showdown = require('showdown');
|
||||
|
||||
// class AppUpdater {
|
||||
// constructor() {
|
||||
@@ -32,113 +36,14 @@ import Channels from '../constants';
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
/**
|
||||
* Server Preload Events to gather information about the environment before the UI launches
|
||||
*/
|
||||
|
||||
// loads the application path where the exe is run, this is needed to lookup information about the installed patches.
|
||||
let appPath: string;
|
||||
let fileManager: FileManager;
|
||||
if (process.platform === 'win32') {
|
||||
if(process.env.PORTABLE_EXECUTABLE_DIR) {
|
||||
appPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR, 'Wow.exe')
|
||||
fileManager = new FileManager(process.env.PORTABLE_EXECUTABLE_DIR || '')
|
||||
} else {
|
||||
appPath = path.join(__dirname, '../../mockGame','Wow.exe');
|
||||
fileManager = new FileManager(path.join(__dirname, '../../mockGame'));
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
appPath = '/System/Applications/Chess.app';
|
||||
fileManager = new FileManager(path.join(__dirname, '../../dist/')); // will handle macs even though we will never be building for one.
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return all the information of the file system installed in the directory.
|
||||
*/
|
||||
ipcMain.handle(Channels.APP_INFO, async () => {
|
||||
const appInfo = {
|
||||
version: '3.3.5 (v1)',
|
||||
credits: 'ben-of-codecraft',
|
||||
lastupdate: '2023-09-03',
|
||||
execpath: process.env.PORTABLE_EXECUTABLE_DIR,
|
||||
HD: await fileManager.IsHDSetup(),
|
||||
HDExtra: await fileManager.IsHDExtraSetup(),
|
||||
Misc: await fileManager.IsMiscSetup(),
|
||||
Araxia: await fileManager.IsAraxiaSetup(),
|
||||
WoWInstalled: await fileManager.IsWoWInstalled(),
|
||||
WoWPatched: await fileManager.IsWoWPatched(),
|
||||
AIOInstalled: await fileManager.IsAIOInstalled(),
|
||||
};
|
||||
|
||||
console.log('App Info: ', appInfo);
|
||||
return appInfo;
|
||||
});
|
||||
|
||||
|
||||
ipcMain.on(Channels.WOW_LAUNCH, (event, arg) => {
|
||||
if (!fs.existsSync(appPath)) {
|
||||
event.reply(Channels.WOW_LAUNCH_ERROR, {
|
||||
type: 'FileError',
|
||||
message: `WoW application not found or permissions are not set to readable: ${appPath}`,
|
||||
} as ErrorEvent);
|
||||
}
|
||||
|
||||
let childProcess;
|
||||
if (process.platform === 'darwin') {
|
||||
childProcess = spawn('open', [appPath]);
|
||||
} else {
|
||||
childProcess = spawn(appPath);
|
||||
}
|
||||
|
||||
childProcess.on('close', (code) => {
|
||||
console.info(`WoW client exited with code: ${code}`);
|
||||
});
|
||||
|
||||
childProcess.on('exit', () => {
|
||||
console.info('WoW client exited');
|
||||
});
|
||||
|
||||
childProcess.on('exit', () => {
|
||||
console.info('Child process is launched closing window');
|
||||
// event.reply(Channels.WOW_CLIENT_EXIT);
|
||||
mainWindow?.close();
|
||||
});
|
||||
|
||||
mainWindow?.close();
|
||||
});
|
||||
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const sourceMapSupport = require('source-map-support');
|
||||
sourceMapSupport.install();
|
||||
}
|
||||
|
||||
const isDebug =
|
||||
process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';
|
||||
|
||||
// if (isDebug || true) {
|
||||
// require('electron-debug')();
|
||||
// }
|
||||
|
||||
// const installExtensions = async () => {
|
||||
// const installer = require('electron-devtools-installer');
|
||||
// const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
|
||||
// const extensions = ['REACT_DEVELOPER_TOOLS'];
|
||||
|
||||
// return installer
|
||||
// .default(
|
||||
// extensions.map((name) => installer[name]),
|
||||
// forceDownload
|
||||
// )
|
||||
// .catch(console.log);
|
||||
// };
|
||||
const isDebug = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';
|
||||
|
||||
const createWindow = async () => {
|
||||
if (isDebug) {
|
||||
// await installExtensions();
|
||||
}
|
||||
|
||||
const RESOURCES_PATH = app.isPackaged
|
||||
? path.join(process.resourcesPath, 'assets')
|
||||
@@ -157,7 +62,8 @@ const createWindow = async () => {
|
||||
preload: app.isPackaged
|
||||
? path.join(__dirname, 'preload.js')
|
||||
: path.join(__dirname, '../../.erb/dll/preload.js'),
|
||||
nodeIntegration: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: false
|
||||
},
|
||||
});
|
||||
|
||||
@@ -178,8 +84,9 @@ const createWindow = async () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
// const menuBuilder = new MenuBuilder(mainWindow);
|
||||
// menuBuilder.buildMenu();
|
||||
|
||||
const menuBuilder = new MenuBuilder(mainWindow);
|
||||
menuBuilder.buildMenu();
|
||||
|
||||
// Open urls in the user's browser
|
||||
mainWindow.webContents.setWindowOpenHandler((edata) => {
|
||||
@@ -219,3 +126,187 @@ app
|
||||
});
|
||||
})
|
||||
.catch(console.log);
|
||||
|
||||
/** ************************************************
|
||||
* Event Management for IPC
|
||||
************************************************* */
|
||||
// loads the application path where the exe is run, this is needed to lookup information about the installed patches.
|
||||
let appPath: string;
|
||||
let fileManager: FileManager;
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
if(process.env.PORTABLE_EXECUTABLE_DIR) {
|
||||
appPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR, 'Wow.exe')
|
||||
fileManager = new FileManager(process.env.PORTABLE_EXECUTABLE_DIR || '')
|
||||
} else {
|
||||
appPath = path.join(__dirname, '../../mockGame','Wow.exe');
|
||||
fileManager = new FileManager(path.join(__dirname, '../../mockGame'));
|
||||
}
|
||||
|
||||
} else {
|
||||
appPath = '/System/Applications/Chess.app';
|
||||
fileManager = new FileManager(path.join(__dirname, '../../dist/')); // will handle macs even though we will never be building for one.
|
||||
}
|
||||
/**
|
||||
* This will return all the information of the file system installed in the directory.
|
||||
*/
|
||||
ipcMain.handle(Channels.APP_INFO, async () => {
|
||||
const version = await fileManager.GetVersion();
|
||||
|
||||
// Get the news and convert it to html
|
||||
const news = await fileManager.GetLatestNews();
|
||||
const converter = new showdown.Converter();
|
||||
const newHtml = converter.makeHtml(news);
|
||||
const remoteInfo = await fileManager.GetRemoteVersion();
|
||||
const localInfo = await fileManager.GetVersion();
|
||||
|
||||
const appInfo = {
|
||||
Version: localInfo.Version,
|
||||
LastUpdate: localInfo.LastUpdate,
|
||||
ExecPath: process.env.PORTABLE_EXECUTABLE_DIR,
|
||||
HD: await fileManager.IsHDSetup(),
|
||||
Misc: await fileManager.IsMiscSetup(),
|
||||
WoWInstalled: await fileManager.IsWoWInstalled(),
|
||||
WoWPatched: await fileManager.IsWoWPatched(),
|
||||
AIOInstalled: await fileManager.IsAIOInstalled(),
|
||||
LatestNews: newHtml,
|
||||
RemoteVersion: remoteInfo[0]?.version,
|
||||
};
|
||||
return appInfo;
|
||||
});
|
||||
|
||||
(async () => {
|
||||
|
||||
// const remoteInfo = await fileManager.GetRemoteVersion();
|
||||
// const custom = await fileManager.GetCustomFiles();
|
||||
// console.log(custom);
|
||||
|
||||
})();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* We have to redirect events from the main process to the render process as they are fired this allows the
|
||||
* frontend to attach the backend event listeners, complex objects can not be handled by ipcRenderer.
|
||||
* @param downloader
|
||||
*/
|
||||
function passthrough(downloader: Downloader) {
|
||||
|
||||
downloader.on('start', (event) => {
|
||||
try {
|
||||
mainWindow?.webContents.send(Channels.DOWNLOAD_START, {
|
||||
totalBytes: event.totalBytes,
|
||||
remoteFile: event.remoteFile
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.log(error.message);
|
||||
}
|
||||
|
||||
});
|
||||
downloader.on('data', (event) => {
|
||||
mainWindow?.webContents.send(Channels.DOWNLOAD_PROGRESS, event);
|
||||
});
|
||||
downloader.on('end', (event) => {
|
||||
mainWindow?.webContents.send(Channels.DOWNLOAD_END, event);
|
||||
});
|
||||
downloader.on('batchStart', (event) => {
|
||||
mainWindow?.webContents.send(Channels.DOWNLOAD_BATCH_START, event);
|
||||
});
|
||||
downloader.on('batchData', (event) => {
|
||||
mainWindow?.webContents.send(Channels.DOWNLOAD_BATCH_DATA, event);
|
||||
});
|
||||
downloader.on('batchEnd', (event) => {
|
||||
mainWindow?.webContents.send(Channels.DOWNLOAD_BATCH_END, event);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Main Calls to backend Filemanager
|
||||
*/
|
||||
ipcMain.handle(Channels.APP_API, async (event, method: LauncherServer.Methods, ...args) => {
|
||||
|
||||
switch (method) {
|
||||
case 'GetInstalledPatches': {
|
||||
// return await fileManager.GetInstalledPatches(args[0]);
|
||||
break;
|
||||
}
|
||||
case 'InstallAIO': {
|
||||
const result = await fileManager.InstallAIO();
|
||||
|
||||
if(result instanceof Downloader) {
|
||||
passthrough(result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'PatchWow': {
|
||||
const result = await fileManager.PatchWowExe();
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'InstallHD': {
|
||||
const result = await fileManager.InstallPatches('hd');
|
||||
|
||||
if(result instanceof Downloader) {
|
||||
passthrough(result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'InstallMisc': {
|
||||
const result = await fileManager.InstallPatches('misc');
|
||||
|
||||
if(result instanceof Downloader) {
|
||||
passthrough(result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'InstallUpdates': {
|
||||
const result = await fileManager.InstallUpdates();
|
||||
|
||||
if(result instanceof Downloader) {
|
||||
passthrough(result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
log.error(`Unknown method: ${method}`);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
|
||||
ipcMain.on(Channels.WOW_LAUNCH, (event, arg) => {
|
||||
if (!fs.existsSync(appPath)) {
|
||||
event.reply(Channels.WOW_LAUNCH_ERROR, {
|
||||
type: 'FileError',
|
||||
message: `WoW application not found or permissions are not set to readable: ${appPath}`,
|
||||
} as ErrorEvent);
|
||||
console.error(`WoW application not found or permissions are not set to readable: ${appPath}`);
|
||||
}
|
||||
|
||||
if(!app.isPackaged) {
|
||||
// appPath = "C:\\Users\\benca\\Desktop\\WoW\\WorldOfWarcraft_3.3.5a-unpatched\\Wow.exe";
|
||||
}
|
||||
|
||||
console.log(`Launching WoW client: ${appPath}`);
|
||||
const childProcess = spawn(appPath, {
|
||||
detached: true
|
||||
});
|
||||
|
||||
childProcess.on('close', (code) => {
|
||||
console.info(`WoW client exited with code: ${code}`);
|
||||
});
|
||||
|
||||
childProcess.on('error', (err) => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
mainWindow?.close();
|
||||
}, 1000);
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
247
src/main/menu.ts
247
src/main/menu.ts
@@ -3,13 +3,11 @@ import {
|
||||
Menu,
|
||||
shell,
|
||||
BrowserWindow,
|
||||
MenuItemConstructorOptions,
|
||||
dialog
|
||||
} from 'electron';
|
||||
|
||||
interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
|
||||
selector?: string;
|
||||
submenu?: DarwinMenuItemConstructorOptions[] | Menu;
|
||||
}
|
||||
import path from 'path';
|
||||
import packageJson from '../../package.json';
|
||||
|
||||
export default class MenuBuilder {
|
||||
mainWindow: BrowserWindow;
|
||||
@@ -26,11 +24,7 @@ export default class MenuBuilder {
|
||||
this.setupDevelopmentEnvironment();
|
||||
}
|
||||
|
||||
const template =
|
||||
process.platform === 'darwin'
|
||||
? this.buildDarwinTemplate()
|
||||
: this.buildDefaultTemplate();
|
||||
|
||||
const template = this.buildDefaultTemplate();
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
|
||||
@@ -52,155 +46,11 @@ export default class MenuBuilder {
|
||||
});
|
||||
}
|
||||
|
||||
buildDarwinTemplate(): MenuItemConstructorOptions[] {
|
||||
const subMenuAbout: DarwinMenuItemConstructorOptions = {
|
||||
label: 'Electron',
|
||||
submenu: [
|
||||
{
|
||||
label: 'About ElectronReact',
|
||||
selector: 'orderFrontStandardAboutPanel:',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ label: 'Services', submenu: [] },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Hide ElectronReact',
|
||||
accelerator: 'Command+H',
|
||||
selector: 'hide:',
|
||||
},
|
||||
{
|
||||
label: 'Hide Others',
|
||||
accelerator: 'Command+Shift+H',
|
||||
selector: 'hideOtherApplications:',
|
||||
},
|
||||
{ label: 'Show All', selector: 'unhideAllApplications:' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'Command+Q',
|
||||
click: () => {
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const subMenuEdit: DarwinMenuItemConstructorOptions = {
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' },
|
||||
{ label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Cut', accelerator: 'Command+X', selector: 'cut:' },
|
||||
{ label: 'Copy', accelerator: 'Command+C', selector: 'copy:' },
|
||||
{ label: 'Paste', accelerator: 'Command+V', selector: 'paste:' },
|
||||
{
|
||||
label: 'Select All',
|
||||
accelerator: 'Command+A',
|
||||
selector: 'selectAll:',
|
||||
},
|
||||
],
|
||||
};
|
||||
const subMenuViewDev: MenuItemConstructorOptions = {
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'Command+R',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.reload();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle Full Screen',
|
||||
accelerator: 'Ctrl+Command+F',
|
||||
click: () => {
|
||||
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator: 'Alt+Command+I',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.toggleDevTools();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const subMenuViewProd: MenuItemConstructorOptions = {
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Toggle Full Screen',
|
||||
accelerator: 'Ctrl+Command+F',
|
||||
click: () => {
|
||||
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const subMenuWindow: DarwinMenuItemConstructorOptions = {
|
||||
label: 'Window',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Minimize',
|
||||
accelerator: 'Command+M',
|
||||
selector: 'performMiniaturize:',
|
||||
},
|
||||
{ label: 'Close', accelerator: 'Command+W', selector: 'performClose:' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Bring All to Front', selector: 'arrangeInFront:' },
|
||||
],
|
||||
};
|
||||
const subMenuHelp: MenuItemConstructorOptions = {
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click() {
|
||||
shell.openExternal('https://electronjs.org');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
click() {
|
||||
shell.openExternal(
|
||||
'https://github.com/electron/electron/tree/main/docs#readme'
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click() {
|
||||
shell.openExternal('https://www.electronjs.org/community');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click() {
|
||||
shell.openExternal('https://github.com/electron/electron/issues');
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const subMenuView =
|
||||
process.env.NODE_ENV === 'development' ||
|
||||
process.env.DEBUG_PROD === 'true'
|
||||
? subMenuViewDev
|
||||
: subMenuViewProd;
|
||||
|
||||
return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp];
|
||||
}
|
||||
|
||||
buildDefaultTemplate() {
|
||||
const templateDefault = [
|
||||
{
|
||||
label: '&File',
|
||||
submenu: [
|
||||
{
|
||||
label: '&Open',
|
||||
accelerator: 'Ctrl+O',
|
||||
},
|
||||
{
|
||||
label: '&Close',
|
||||
accelerator: 'Ctrl+W',
|
||||
@@ -212,75 +62,46 @@ export default class MenuBuilder {
|
||||
},
|
||||
{
|
||||
label: '&View',
|
||||
submenu:
|
||||
process.env.NODE_ENV === 'development' ||
|
||||
process.env.DEBUG_PROD === 'true'
|
||||
? [
|
||||
{
|
||||
label: '&Reload',
|
||||
accelerator: 'Ctrl+R',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.reload();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle &Full Screen',
|
||||
accelerator: 'F11',
|
||||
click: () => {
|
||||
this.mainWindow.setFullScreen(
|
||||
!this.mainWindow.isFullScreen()
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle &Developer Tools',
|
||||
accelerator: 'Alt+Ctrl+I',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.toggleDevTools();
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: 'Toggle &Full Screen',
|
||||
accelerator: 'F11',
|
||||
click: () => {
|
||||
this.mainWindow.setFullScreen(
|
||||
!this.mainWindow.isFullScreen()
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click() {
|
||||
shell.openExternal('https://electronjs.org');
|
||||
label: '&Reload',
|
||||
accelerator: 'Ctrl+R',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.reload();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
label: 'Toggle &Developer Tools',
|
||||
accelerator: 'Alt+Ctrl+I',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.toggleDevTools();
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'About',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Araxia Client Patcher',
|
||||
click() {
|
||||
dialog.showMessageBox({
|
||||
title: 'About',
|
||||
message: `Araxia Client Patcher v${packageJson.version}`,
|
||||
detail: "Credits:\nProgramming and Design: Ben Carter @ben-of-codecraft \nPatch Master: Eric Gates @manmadedrummer\nInfrastructure: Chris Turley @wonderboy",
|
||||
buttons: ['OK'],
|
||||
icon: path.join(__dirname, '../../assets', 'wow-icon.png')
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
click() {
|
||||
shell.openExternal(
|
||||
'https://github.com/electron/electron/tree/main/docs#readme'
|
||||
'https://github.com/araxiaonline'
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click() {
|
||||
shell.openExternal('https://www.electronjs.org/community');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click() {
|
||||
shell.openExternal('https://github.com/electron/electron/issues');
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,23 +1,86 @@
|
||||
/* eslint-disable no-console */
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
import log from 'electron-log';
|
||||
import Channels from '../constants';
|
||||
import { LauncherServer } from '../typings';
|
||||
import { Channels } from '../constants';
|
||||
import { LauncherServer, DownloadCallbacks } from '../typings';
|
||||
|
||||
function apiCaller(method: LauncherServer.Methods, ...args: any[]): Promise<any>{
|
||||
return ipcRenderer.invoke(Channels.APP_API, method, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around the ipcRenderer to handle the download events
|
||||
* Since you can not
|
||||
* ss complex object through the IPC Channel this is used
|
||||
* to relay the results of downloads happening on the server to the renderer.
|
||||
*
|
||||
* Every download that is requested by the users will track status on these channels.
|
||||
*/
|
||||
class DownloadListener {
|
||||
constructor(callbacks: DownloadCallbacks) {
|
||||
|
||||
ipcRenderer.on(Channels.DOWNLOAD_START, (event, args ) => {
|
||||
callbacks.start?.({
|
||||
totalBytes: args.total,
|
||||
remoteFile: args.files
|
||||
});
|
||||
});
|
||||
|
||||
ipcRenderer.on(Channels.DOWNLOAD_END, (event, args ) => {
|
||||
callbacks.end?.(args);
|
||||
});
|
||||
|
||||
ipcRenderer.on(Channels.DOWNLOAD_PROGRESS, (event, args ) => {
|
||||
callbacks.data?.(args);
|
||||
});
|
||||
|
||||
ipcRenderer.on(Channels.DOWNLOAD_BATCH_START, (event, args ) => {
|
||||
callbacks.batchStart?.(args);
|
||||
});
|
||||
|
||||
ipcRenderer.on(Channels.DOWNLOAD_BATCH_END, (event, args ) => {
|
||||
callbacks.batchEnd?.(args);
|
||||
});
|
||||
|
||||
ipcRenderer.on(Channels.DOWNLOAD_BATCH_DATA, (event, args ) => {
|
||||
callbacks.batchData?.(args);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const handleInstall = async (apiMethodName: LauncherServer.Methods, callbacks: DownloadCallbacks) => {
|
||||
// this registers callbacks on the object
|
||||
const downloadListener = new DownloadListener(callbacks);
|
||||
|
||||
try {
|
||||
await apiCaller(apiMethodName);
|
||||
} catch (error: any) {
|
||||
const msg = `Install Error: getting install details: ${error.message}`;
|
||||
|
||||
return {
|
||||
type: 'Install',
|
||||
message: msg,
|
||||
} as ErrorEvent;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* All the API's exposed to the renderer process
|
||||
*/
|
||||
const IPCApi: LauncherServer.Api = {
|
||||
Launch: () => {
|
||||
console.info('Launching WoW client');
|
||||
ipcRenderer.send(Channels.WOW_LAUNCH);
|
||||
},
|
||||
|
||||
OnExitApp: (handler) => {
|
||||
ipcRenderer.on(Channels.WOW_CLIENT_EXIT, handler);
|
||||
},
|
||||
|
||||
OnLaunchError: (handler) => {
|
||||
ipcRenderer.on(
|
||||
Channels.WOW_LAUNCH_ERROR,
|
||||
(event: any, error: ErrorEvent) => {
|
||||
log.info(
|
||||
console.info(
|
||||
`WoW client exited with Error: [${error.type}]: ${error.message}`
|
||||
);
|
||||
handler(event, error);
|
||||
@@ -31,7 +94,7 @@ const IPCApi: LauncherServer.Api = {
|
||||
return appInfo;
|
||||
} catch (error: any) {
|
||||
const msg = `Error getting app info: ${error}`;
|
||||
log.error(msg);
|
||||
console.error(msg);
|
||||
|
||||
return {
|
||||
type: 'Parse',
|
||||
@@ -49,13 +112,32 @@ const IPCApi: LauncherServer.Api = {
|
||||
return installDetails;
|
||||
} catch (error: any) {
|
||||
const msg = `Error getting install details: ${error}`;
|
||||
log.error(msg);
|
||||
console.error(msg);
|
||||
|
||||
return {
|
||||
type: 'Parse',
|
||||
message: msg,
|
||||
} as ErrorEvent;
|
||||
}
|
||||
},
|
||||
InstallStore: async (callbacks: DownloadCallbacks) => {
|
||||
await handleInstall('InstallAIO', callbacks);
|
||||
},
|
||||
|
||||
PatchWoW: async () => {
|
||||
await handleInstall('PatchWow', {});
|
||||
},
|
||||
|
||||
InstallHD: async (callbacks: DownloadCallbacks) => {
|
||||
await handleInstall('InstallHD', callbacks);
|
||||
},
|
||||
|
||||
InstallMisc: async (callbacks: DownloadCallbacks) => {
|
||||
handleInstall('InstallMisc', callbacks);
|
||||
},
|
||||
|
||||
InstallUpdates: async (callbacks: DownloadCallbacks) => {
|
||||
handleInstall('InstallUpdates', callbacks);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -11,3 +11,12 @@ export function resolveHtmlPath(htmlFileName: string) {
|
||||
}
|
||||
return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`;
|
||||
}
|
||||
|
||||
export function byteToMbs(bytes: number): number {
|
||||
return +(bytes/1024/1024).toFixed(2);
|
||||
}
|
||||
|
||||
// Removes double quotes on ETags from S3
|
||||
export function santizeETag(tag: string): string {
|
||||
return tag.replace(/"/g, '');
|
||||
}
|
||||
|
||||
155
src/typings/index.d.ts
vendored
155
src/typings/index.d.ts
vendored
@@ -1,4 +1,5 @@
|
||||
import { IpcRendererEvent } from 'electron';
|
||||
import { DownloadEventMap } from 'main/libs/Downloader';
|
||||
|
||||
declare global {
|
||||
/**
|
||||
@@ -10,15 +11,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to delcare file types to TypeScript that are not modules
|
||||
*/
|
||||
declare module '*.png';
|
||||
declare module '*.jpeg';
|
||||
declare module '*.mp3' {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
type Callbacks<T> = {
|
||||
[K in keyof T]?: (params: T[K]) => void;
|
||||
};
|
||||
|
||||
export type DownloadCallbacks = Callbacks<DownloadEventMap>;
|
||||
|
||||
declare namespace WowLauncher {
|
||||
/*
|
||||
@@ -28,55 +25,57 @@ declare namespace WowLauncher {
|
||||
/**
|
||||
* The current version of the application.
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* Contributors to the application and their contributions.
|
||||
*/
|
||||
credits: string;
|
||||
Version: string;
|
||||
|
||||
/**
|
||||
* The last time the application was updated.
|
||||
*/
|
||||
lastupdate: string;
|
||||
LastUpdate: string;
|
||||
/**
|
||||
* The original path to the executable of the application. This is different
|
||||
* than a potentially temporary path where the actual exe runs; this is the directory
|
||||
* where the exe is located. This is used to communicate with the external WoW exe
|
||||
* and collect information about the installed WoW client.
|
||||
*/
|
||||
execpath: string;
|
||||
/**
|
||||
* Manifest of files on host machine
|
||||
*/
|
||||
manifest: Manifest;
|
||||
|
||||
ExecPath: string;
|
||||
/**
|
||||
* Whether or not the HD patch is installed.
|
||||
*/
|
||||
HD: boolean;
|
||||
/**
|
||||
* Whether or not the HD Extra patch is installed.
|
||||
*/
|
||||
HDExtra: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the Fearure patches are installed
|
||||
*/
|
||||
Features: boolean;
|
||||
Misc: boolean;
|
||||
/**
|
||||
* Whether or not Araxia patches are installed.
|
||||
*/
|
||||
Araxia: boolean;
|
||||
}
|
||||
|
||||
interface Manifest {
|
||||
/**
|
||||
* List of files currently present on the user's machine that are not part of the vanilla installation.
|
||||
* from ChromieCraft. <https://www.chromiecraft.com/en/downloads/>
|
||||
* Whether or not the WoW client is installed.
|
||||
*/
|
||||
patchfiles: PatchFile[];
|
||||
WoWInstalled: boolean;
|
||||
|
||||
/**
|
||||
* Last time the manifest was updated.
|
||||
* Whether or not the WoW client is patched.
|
||||
*/
|
||||
lastupdate: string;
|
||||
WoWPatched: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the AIO patch is installed.
|
||||
*/
|
||||
AIOInstalled: boolean;
|
||||
|
||||
/**
|
||||
* The latest news about the application.
|
||||
*/
|
||||
LatestNews: string;
|
||||
|
||||
/**
|
||||
* The remote version of the client.
|
||||
*/
|
||||
RemoteVersion: string;
|
||||
}
|
||||
|
||||
interface PatchFile {
|
||||
@@ -120,6 +119,45 @@ declare namespace LauncherServer {
|
||||
* If not detected, the version returned is "3.3.5a-v0" (the base version of the client).
|
||||
*/
|
||||
GetAppInfo: () => Promise<WowLauncher.AppInfo | null>;
|
||||
|
||||
/**
|
||||
* Patches the server Wow.exe with the patched version if needed.
|
||||
*/
|
||||
PatchWoW: () => void;
|
||||
|
||||
/**
|
||||
* Returns the list of files that are currently installed on the user's machine.
|
||||
*/
|
||||
GetInstalledPatches(type: string): Promise<WowLauncher.PatchFile[] | null>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param callbacks Installs addon required to login to the store.
|
||||
* @returns
|
||||
*/
|
||||
InstallStore: (callbacks: any) => void;
|
||||
|
||||
/**
|
||||
* Install HD patches from server
|
||||
* @param callbacks
|
||||
* @returns
|
||||
*/
|
||||
InstallHD: (callbacks: any) => void;
|
||||
|
||||
/**
|
||||
* Installs Misc patches from server
|
||||
* @param callbacks
|
||||
* @returns
|
||||
*/
|
||||
InstallMisc: (callbacks: any) => void;
|
||||
|
||||
/**
|
||||
* Installs custom content updates from server
|
||||
* @param callbacks
|
||||
* @returns
|
||||
*/
|
||||
InstallUpdates: (callbacks: any) => void;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,35 +174,34 @@ declare namespace LauncherServer {
|
||||
type: 'File' | 'Request' | 'Generic' | 'Parse';
|
||||
message: string;
|
||||
}
|
||||
|
||||
type Methods =
|
||||
'InstallAIO' |
|
||||
'GetInstalledPatches' |
|
||||
'PatchWow' |
|
||||
'InstallHD' |
|
||||
'InstallMisc' |
|
||||
'GetRemoteVersion' |
|
||||
'InstallUpdates';
|
||||
}
|
||||
|
||||
declare namespace LauncherUI {
|
||||
/**
|
||||
* List of files and tags showing last time they were changed. Additionally, this
|
||||
* is used to determine the current version of the client.
|
||||
*/
|
||||
declare interface Manifest {
|
||||
/**
|
||||
* Server response data to server about the latest information about the client patches.
|
||||
* The version the current client with patching
|
||||
*/
|
||||
interface RemoteServer {
|
||||
/**
|
||||
* Server patch version.
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* List of changes made in the patch.
|
||||
*/
|
||||
changelog: string[];
|
||||
/**
|
||||
* Date the patch was released.
|
||||
*/
|
||||
date: string;
|
||||
/**
|
||||
* Who made the patch.
|
||||
*/
|
||||
updatedby: string;
|
||||
/**
|
||||
* List of files that were changed in the patch. That need to be downloaded
|
||||
* and replaced on the client.
|
||||
*/
|
||||
filelist: string[];
|
||||
}
|
||||
Version: string;
|
||||
/**
|
||||
* List of files currently present on the user's machine that are not part of the vanilla installation.
|
||||
*/
|
||||
Files: Record<string, any>;
|
||||
/**
|
||||
* Last time the manifest was updated.
|
||||
*/
|
||||
LastUpdate: string;
|
||||
}
|
||||
|
||||
export { LauncherServer, LauncherUI, WowLauncher };
|
||||
export { LauncherServer, WowLauncher, Manifest };
|
||||
|
||||
Reference in New Issue
Block a user