|
|
@ -1,30 +1,39 @@
|
|
|
|
/* eslint-disable quotes,no-undef */
|
|
|
|
/* eslint-disable quotes,no-undef */
|
|
|
|
|
|
|
|
|
|
|
|
const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell } = require("electron");
|
|
|
|
const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell, dialog, session } = require("electron");
|
|
|
|
|
|
|
|
|
|
|
|
app.commandLine.appendSwitch("in-process-gpu");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const path = require("path");
|
|
|
|
const path = require("path");
|
|
|
|
const url = require("url");
|
|
|
|
const url = require("url");
|
|
|
|
const fs = require("fs");
|
|
|
|
const fs = require("fs");
|
|
|
|
const wegame = require("./wegame");
|
|
|
|
const wegame = require("./wegame");
|
|
|
|
const asyncLock = require("async-lock");
|
|
|
|
const asyncLock = require("async-lock");
|
|
|
|
|
|
|
|
const windowStateKeeper = require("electron-window-state");
|
|
|
|
|
|
|
|
|
|
|
|
const isDev = process.argv.indexOf("--dev") >= 0;
|
|
|
|
// Disable hardware key handling, i.e. being able to pause/resume the game music
|
|
|
|
const isLocal = process.argv.indexOf("--local") >= 0;
|
|
|
|
// with hardware keys
|
|
|
|
|
|
|
|
app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const isDev = app.commandLine.hasSwitch("dev");
|
|
|
|
|
|
|
|
const isLocal = app.commandLine.hasSwitch("local");
|
|
|
|
|
|
|
|
const safeMode = app.commandLine.hasSwitch("safe-mode");
|
|
|
|
|
|
|
|
const externalMod = app.commandLine.getSwitchValue("load-mod");
|
|
|
|
|
|
|
|
|
|
|
|
const roamingFolder =
|
|
|
|
const roamingFolder =
|
|
|
|
process.env.APPDATA ||
|
|
|
|
process.env.APPDATA ||
|
|
|
|
(process.platform == "darwin"
|
|
|
|
(process.platform == "darwin"
|
|
|
|
? process.env.HOME + "/Library/Preferences"
|
|
|
|
? process.env.HOME + "/Library/Preferences"
|
|
|
|
: process.env.HOME + "/.local/share");
|
|
|
|
: process.env.HOME + "/.local/share");
|
|
|
|
let storePath = path.join(roamingFolder, "shapez-wegame", "saves");
|
|
|
|
let storePath = path.join(roamingFolder, "shapez-china", "saves");
|
|
|
|
|
|
|
|
let modsPath = path.join(roamingFolder, "shapez-china", "mods");
|
|
|
|
|
|
|
|
|
|
|
|
if (!fs.existsSync(storePath)) {
|
|
|
|
if (!fs.existsSync(storePath)) {
|
|
|
|
// No try-catch by design
|
|
|
|
// No try-catch by design
|
|
|
|
fs.mkdirSync(storePath, { recursive: true });
|
|
|
|
fs.mkdirSync(storePath, { recursive: true });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!fs.existsSync(modsPath)) {
|
|
|
|
|
|
|
|
fs.mkdirSync(modsPath, { recursive: true });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {BrowserWindow} */
|
|
|
|
/** @type {BrowserWindow} */
|
|
|
|
let win = null;
|
|
|
|
let win = null;
|
|
|
|
let menu = null;
|
|
|
|
let menu = null;
|
|
|
@ -35,30 +44,44 @@ function createWindow() {
|
|
|
|
faviconExtension = ".ico";
|
|
|
|
faviconExtension = ".ico";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const mainWindowState = windowStateKeeper({
|
|
|
|
|
|
|
|
defaultWidth: 1000,
|
|
|
|
|
|
|
|
defaultHeight: 800,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
win = new BrowserWindow({
|
|
|
|
win = new BrowserWindow({
|
|
|
|
width: 1280,
|
|
|
|
x: mainWindowState.x,
|
|
|
|
height: 800,
|
|
|
|
y: mainWindowState.y,
|
|
|
|
|
|
|
|
width: mainWindowState.width,
|
|
|
|
|
|
|
|
height: mainWindowState.height,
|
|
|
|
show: false,
|
|
|
|
show: false,
|
|
|
|
backgroundColor: "#222428",
|
|
|
|
backgroundColor: "#222428",
|
|
|
|
useContentSize: true,
|
|
|
|
useContentSize: false,
|
|
|
|
minWidth: 800,
|
|
|
|
minWidth: 800,
|
|
|
|
minHeight: 600,
|
|
|
|
minHeight: 600,
|
|
|
|
title: "图形工厂",
|
|
|
|
title: "图形工厂",
|
|
|
|
transparent: false,
|
|
|
|
transparent: false,
|
|
|
|
icon: path.join(__dirname, "favicon" + faviconExtension),
|
|
|
|
icon: path.join(__dirname, "favicon" + faviconExtension),
|
|
|
|
// fullscreen: true,
|
|
|
|
// fullscreen: true,
|
|
|
|
autoHideMenuBar: true,
|
|
|
|
autoHideMenuBar: !isDev,
|
|
|
|
webPreferences: {
|
|
|
|
webPreferences: {
|
|
|
|
nodeIntegration: false,
|
|
|
|
nodeIntegration: false,
|
|
|
|
|
|
|
|
nodeIntegrationInWorker: false,
|
|
|
|
|
|
|
|
nodeIntegrationInSubFrames: false,
|
|
|
|
|
|
|
|
contextIsolation: true,
|
|
|
|
|
|
|
|
enableRemoteModule: false,
|
|
|
|
|
|
|
|
disableBlinkFeatures: "Auxclick",
|
|
|
|
|
|
|
|
|
|
|
|
webSecurity: true,
|
|
|
|
webSecurity: true,
|
|
|
|
sandbox: true,
|
|
|
|
sandbox: true,
|
|
|
|
|
|
|
|
|
|
|
|
contextIsolation: true,
|
|
|
|
|
|
|
|
preload: path.join(__dirname, "preload.js"),
|
|
|
|
preload: path.join(__dirname, "preload.js"),
|
|
|
|
|
|
|
|
experimentalFeatures: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
allowRunningInsecureContent: false,
|
|
|
|
allowRunningInsecureContent: false,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mainWindowState.manage(win);
|
|
|
|
|
|
|
|
|
|
|
|
if (isLocal) {
|
|
|
|
if (isLocal) {
|
|
|
|
win.loadURL("http://localhost:3005");
|
|
|
|
win.loadURL("http://localhost:3005");
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
@ -70,12 +93,70 @@ function createWindow() {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
win.webContents.session.clearCache(() => null);
|
|
|
|
win.webContents.session.clearCache();
|
|
|
|
win.webContents.session.clearStorageData();
|
|
|
|
win.webContents.session.clearStorageData();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////// SECURITY
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Disable permission requests
|
|
|
|
|
|
|
|
win.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
|
|
|
|
|
|
|
callback(false);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
session.fromPartition("default").setPermissionRequestHandler((webContents, permission, callback) => {
|
|
|
|
|
|
|
|
callback(false);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.on("web-contents-created", (event, contents) => {
|
|
|
|
|
|
|
|
// Disable vewbiew
|
|
|
|
|
|
|
|
contents.on("will-attach-webview", (event, webPreferences, params) => {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
// Disable navigation
|
|
|
|
|
|
|
|
contents.on("will-navigate", (event, navigationUrl) => {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
win.webContents.on("will-redirect", (contentsEvent, navigationUrl) => {
|
|
|
|
|
|
|
|
// Log and prevent the app from redirecting to a new page
|
|
|
|
|
|
|
|
console.error(
|
|
|
|
|
|
|
|
`The application tried to redirect to the following address: '${navigationUrl}'. This attempt was blocked.`
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
contentsEvent.preventDefault();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Filter loading any module via remote;
|
|
|
|
|
|
|
|
// you shouldn't be using remote at all, though
|
|
|
|
|
|
|
|
// https://electronjs.org/docs/tutorial/security#16-filter-the-remote-module
|
|
|
|
|
|
|
|
app.on("remote-require", (event, webContents, moduleName) => {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// built-ins are modules such as "app"
|
|
|
|
|
|
|
|
app.on("remote-get-builtin", (event, webContents, moduleName) => {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.on("remote-get-global", (event, webContents, globalName) => {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.on("remote-get-current-window", (event, webContents) => {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.on("remote-get-current-web-contents", (event, webContents) => {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//// END SECURITY
|
|
|
|
|
|
|
|
|
|
|
|
win.webContents.on("new-window", (event, pth) => {
|
|
|
|
win.webContents.on("new-window", (event, pth) => {
|
|
|
|
event.preventDefault();
|
|
|
|
event.preventDefault();
|
|
|
|
shell.openExternal(pth);
|
|
|
|
|
|
|
|
|
|
|
|
if (pth.startsWith("https://") || pth.startsWith("steam://")) {
|
|
|
|
|
|
|
|
shell.openExternal(pth);
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
win.on("closed", () => {
|
|
|
|
win.on("closed", () => {
|
|
|
@ -86,6 +167,8 @@ function createWindow() {
|
|
|
|
if (isDev) {
|
|
|
|
if (isDev) {
|
|
|
|
menu = new Menu();
|
|
|
|
menu = new Menu();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
win.webContents.toggleDevTools();
|
|
|
|
|
|
|
|
|
|
|
|
const mainItem = new MenuItem({
|
|
|
|
const mainItem = new MenuItem({
|
|
|
|
label: "Toggle Dev Tools",
|
|
|
|
label: "Toggle Dev Tools",
|
|
|
|
click: () => win.webContents.toggleDevTools(),
|
|
|
|
click: () => win.webContents.toggleDevTools(),
|
|
|
@ -94,7 +177,7 @@ function createWindow() {
|
|
|
|
menu.append(mainItem);
|
|
|
|
menu.append(mainItem);
|
|
|
|
|
|
|
|
|
|
|
|
const reloadItem = new MenuItem({
|
|
|
|
const reloadItem = new MenuItem({
|
|
|
|
label: "Restart",
|
|
|
|
label: "Reload",
|
|
|
|
click: () => win.reload(),
|
|
|
|
click: () => win.reload(),
|
|
|
|
accelerator: "F5",
|
|
|
|
accelerator: "F5",
|
|
|
|
});
|
|
|
|
});
|
|
|
@ -107,7 +190,15 @@ function createWindow() {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
menu.append(fullscreenItem);
|
|
|
|
menu.append(fullscreenItem);
|
|
|
|
|
|
|
|
|
|
|
|
Menu.setApplicationMenu(menu);
|
|
|
|
const mainMenu = new Menu();
|
|
|
|
|
|
|
|
mainMenu.append(
|
|
|
|
|
|
|
|
new MenuItem({
|
|
|
|
|
|
|
|
label: "shapez.io",
|
|
|
|
|
|
|
|
submenu: menu,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Menu.setApplicationMenu(mainMenu);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
Menu.setApplicationMenu(null);
|
|
|
|
Menu.setApplicationMenu(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -121,7 +212,7 @@ function createWindow() {
|
|
|
|
if (!app.requestSingleInstanceLock()) {
|
|
|
|
if (!app.requestSingleInstanceLock()) {
|
|
|
|
app.exit(0);
|
|
|
|
app.exit(0);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
app.on("second-instance", (event, commandLine, workingDirectory) => {
|
|
|
|
app.on("second-instance", () => {
|
|
|
|
// Someone tried to run a second instance, we should focus
|
|
|
|
// Someone tried to run a second instance, we should focus
|
|
|
|
if (win) {
|
|
|
|
if (win) {
|
|
|
|
if (win.isMinimized()) {
|
|
|
|
if (win.isMinimized()) {
|
|
|
@ -143,7 +234,7 @@ ipcMain.on("set-fullscreen", (event, flag) => {
|
|
|
|
win.setFullScreen(flag);
|
|
|
|
win.setFullScreen(flag);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
ipcMain.on("exit-app", (event, flag) => {
|
|
|
|
ipcMain.on("exit-app", () => {
|
|
|
|
win.close();
|
|
|
|
win.close();
|
|
|
|
app.quit();
|
|
|
|
app.quit();
|
|
|
|
});
|
|
|
|
});
|
|
|
@ -224,7 +315,7 @@ async function writeFileSafe(filename, contents) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ipcMain.handle("fs-job", async (event, job) => {
|
|
|
|
ipcMain.handle("fs-job", async (event, job) => {
|
|
|
|
const filenameSafe = job.filename.replace(/[^a-z\.\-_0-9]/i, "");
|
|
|
|
const filenameSafe = job.filename.replace(/[^a-z\.\-_0-9]/gi, "_");
|
|
|
|
const fname = path.join(storePath, filenameSafe);
|
|
|
|
const fname = path.join(storePath, filenameSafe);
|
|
|
|
switch (job.type) {
|
|
|
|
switch (job.type) {
|
|
|
|
case "read": {
|
|
|
|
case "read": {
|
|
|
@ -249,5 +340,45 @@ ipcMain.handle("fs-job", async (event, job) => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ipcMain.handle("open-mods-folder", async () => {
|
|
|
|
|
|
|
|
shell.openPath(modsPath);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log("Loading mods ...");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function loadMods() {
|
|
|
|
|
|
|
|
if (safeMode) {
|
|
|
|
|
|
|
|
console.log("Safe Mode enabled for mods, skipping mod search");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log("Loading mods from", modsPath);
|
|
|
|
|
|
|
|
let modFiles = safeMode
|
|
|
|
|
|
|
|
? []
|
|
|
|
|
|
|
|
: fs
|
|
|
|
|
|
|
|
.readdirSync(modsPath)
|
|
|
|
|
|
|
|
.filter(filename => filename.endsWith(".js"))
|
|
|
|
|
|
|
|
.map(filename => path.join(modsPath, filename));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (externalMod) {
|
|
|
|
|
|
|
|
console.log("Adding external mod source:", externalMod);
|
|
|
|
|
|
|
|
const externalModPaths = externalMod.split(",");
|
|
|
|
|
|
|
|
modFiles = modFiles.concat(externalModPaths);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return modFiles.map(filename => fs.readFileSync(filename, "utf8"));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mods = [];
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
mods = loadMods();
|
|
|
|
|
|
|
|
console.log("Loaded", mods.length, "mods");
|
|
|
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
|
|
|
console.error("Failed to load mods");
|
|
|
|
|
|
|
|
dialog.showErrorBox("Failed to load mods:", ex);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ipcMain.handle("get-mods", async () => {
|
|
|
|
|
|
|
|
return mods;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
wegame.init(isDev);
|
|
|
|
wegame.init(isDev);
|
|
|
|
wegame.listen();
|
|
|
|
wegame.listen();
|
|
|
|