diff --git a/electron_wegame/index.js b/electron_wegame/index.js index 0dc4a51b..041169ce 100644 --- a/electron_wegame/index.js +++ b/electron_wegame/index.js @@ -1,30 +1,39 @@ /* eslint-disable quotes,no-undef */ -const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell } = require("electron"); - -app.commandLine.appendSwitch("in-process-gpu"); - +const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell, dialog, session } = require("electron"); const path = require("path"); const url = require("url"); const fs = require("fs"); const wegame = require("./wegame"); const asyncLock = require("async-lock"); +const windowStateKeeper = require("electron-window-state"); -const isDev = process.argv.indexOf("--dev") >= 0; -const isLocal = process.argv.indexOf("--local") >= 0; +// Disable hardware key handling, i.e. being able to pause/resume the game music +// 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 = process.env.APPDATA || (process.platform == "darwin" ? process.env.HOME + "/Library/Preferences" : 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)) { // No try-catch by design fs.mkdirSync(storePath, { recursive: true }); } +if (!fs.existsSync(modsPath)) { + fs.mkdirSync(modsPath, { recursive: true }); +} + /** @type {BrowserWindow} */ let win = null; let menu = null; @@ -35,30 +44,44 @@ function createWindow() { faviconExtension = ".ico"; } + const mainWindowState = windowStateKeeper({ + defaultWidth: 1000, + defaultHeight: 800, + }); + win = new BrowserWindow({ - width: 1280, - height: 800, + x: mainWindowState.x, + y: mainWindowState.y, + width: mainWindowState.width, + height: mainWindowState.height, show: false, backgroundColor: "#222428", - useContentSize: true, + useContentSize: false, minWidth: 800, minHeight: 600, title: "图形工厂", transparent: false, icon: path.join(__dirname, "favicon" + faviconExtension), // fullscreen: true, - autoHideMenuBar: true, + autoHideMenuBar: !isDev, webPreferences: { nodeIntegration: false, + nodeIntegrationInWorker: false, + nodeIntegrationInSubFrames: false, + contextIsolation: true, + enableRemoteModule: false, + disableBlinkFeatures: "Auxclick", + webSecurity: true, sandbox: true, - - contextIsolation: true, preload: path.join(__dirname, "preload.js"), + experimentalFeatures: false, }, allowRunningInsecureContent: false, }); + mainWindowState.manage(win); + if (isLocal) { win.loadURL("http://localhost:3005"); } else { @@ -70,12 +93,70 @@ function createWindow() { }) ); } - win.webContents.session.clearCache(() => null); + win.webContents.session.clearCache(); 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) => { event.preventDefault(); - shell.openExternal(pth); + + if (pth.startsWith("https://") || pth.startsWith("steam://")) { + shell.openExternal(pth); + } }); win.on("closed", () => { @@ -86,6 +167,8 @@ function createWindow() { if (isDev) { menu = new Menu(); + win.webContents.toggleDevTools(); + const mainItem = new MenuItem({ label: "Toggle Dev Tools", click: () => win.webContents.toggleDevTools(), @@ -94,7 +177,7 @@ function createWindow() { menu.append(mainItem); const reloadItem = new MenuItem({ - label: "Restart", + label: "Reload", click: () => win.reload(), accelerator: "F5", }); @@ -107,7 +190,15 @@ function createWindow() { }); menu.append(fullscreenItem); - Menu.setApplicationMenu(menu); + const mainMenu = new Menu(); + mainMenu.append( + new MenuItem({ + label: "shapez.io", + submenu: menu, + }) + ); + + Menu.setApplicationMenu(mainMenu); } else { Menu.setApplicationMenu(null); } @@ -121,7 +212,7 @@ function createWindow() { if (!app.requestSingleInstanceLock()) { app.exit(0); } else { - app.on("second-instance", (event, commandLine, workingDirectory) => { + app.on("second-instance", () => { // Someone tried to run a second instance, we should focus if (win) { if (win.isMinimized()) { @@ -143,7 +234,7 @@ ipcMain.on("set-fullscreen", (event, flag) => { win.setFullScreen(flag); }); -ipcMain.on("exit-app", (event, flag) => { +ipcMain.on("exit-app", () => { win.close(); app.quit(); }); @@ -224,7 +315,7 @@ async function writeFileSafe(filename, contents) { } 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); switch (job.type) { 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.listen(); diff --git a/electron_wegame/package.json b/electron_wegame/package.json index aba5bb6a..4ae5829d 100644 --- a/electron_wegame/package.json +++ b/electron_wegame/package.json @@ -13,6 +13,7 @@ "electron": "^13.1.6" }, "dependencies": { - "async-lock": "^1.2.8" + "async-lock": "^1.2.8", + "electron-window-state": "^5.0.3" } } diff --git a/electron_wegame/preload.js b/electron_wegame/preload.js new file mode 100644 index 00000000..1fb0e94d --- /dev/null +++ b/electron_wegame/preload.js @@ -0,0 +1,7 @@ +const { contextBridge, ipcRenderer } = require("electron"); + +contextBridge.exposeInMainWorld("ipcRenderer", { + invoke: ipcRenderer.invoke.bind(ipcRenderer), + on: ipcRenderer.on.bind(ipcRenderer), + send: ipcRenderer.send.bind(ipcRenderer), +}); diff --git a/electron_wegame/yarn.lock b/electron_wegame/yarn.lock index 69c595ea..58730670 100644 --- a/electron_wegame/yarn.lock +++ b/electron_wegame/yarn.lock @@ -146,6 +146,14 @@ duplexer3@^0.1.4: resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= +electron-window-state@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/electron-window-state/-/electron-window-state-5.0.3.tgz#4f36d09e3f953d87aff103bf010f460056050aa8" + integrity sha512-1mNTwCfkolXl3kMf50yW3vE2lZj0y92P/HYWFBrb+v2S/pCka5mdwN3cagKm458A7NjndSwijynXgcLWRodsVg== + dependencies: + jsonfile "^4.0.0" + mkdirp "^0.5.1" + electron@^13.1.6: version "13.1.6" resolved "https://registry.yarnpkg.com/electron/-/electron-13.1.6.tgz#6ecaf969255d62ce82cc0b5c948bf26e7dfb489b" @@ -357,6 +365,18 @@ minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + mkdirp@^0.5.4: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" diff --git a/gulp/standalone.js b/gulp/standalone.js index b70ef04c..1329ade2 100644 --- a/gulp/standalone.js +++ b/gulp/standalone.js @@ -281,13 +281,10 @@ function gulptasksStandalone($, gulp) { ); gulp.task(taskPrefix + ".package.win64", cb => packageStandalone("win32", "x64", cb)); - gulp.task(taskPrefix + ".package.linux64", cb => packageStandalone("linux", "x64", cb)); + // gulp.task(taskPrefix + ".package.linux64", cb => packageStandalone("linux", "x64", cb)); gulp.task( taskPrefix + ".build-from-windows", - gulp.series( - taskPrefix + ".prepare", - gulp.parallel(taskPrefix + ".package.win64", taskPrefix + ".package.linux64") - ) + gulp.series(taskPrefix + ".prepare", gulp.parallel(taskPrefix + ".package.win64")) ); gulp.task( taskPrefix + ".build-from-darwin", diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index bbf3d383..e42aeed0 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -48,7 +48,7 @@ export class MainMenuState extends GameState { !G_GOG_VERSION; const showWegameFooter = G_WEGAME_VERSION; const hasMods = MODS.anyModsActive(); - const hasSteamBridge = !G_GOG_VERSION && !G_IS_STEAM_DEMO; + const hasSteamBridge = !G_GOG_VERSION && !G_IS_STEAM_DEMO && !G_WEGAME_VERSION; let showExternalLinks = true; diff --git a/src/js/states/preload.js b/src/js/states/preload.js index c984f08d..746e1769 100644 --- a/src/js/states/preload.js +++ b/src/js/states/preload.js @@ -141,9 +141,6 @@ export class PreloadState extends GameState { .then(() => this.app.analytics.initialize()) .then(() => this.app.gameAnalytics.initialize()) - .then(() => this.setStatus("Connecting to api", 15)) - .then(() => this.fetchDiscounts()) - .then(() => this.setStatus("Initializing settings", 20)) .then(() => { return this.app.settings.initialize(); diff --git a/src/js/states/settings.js b/src/js/states/settings.js index 0927fbf9..e1524329 100644 --- a/src/js/states/settings.js +++ b/src/js/states/settings.js @@ -30,14 +30,9 @@ export class SettingsState extends TextualGameState { : "" } - ${ - G_WEGAME_VERSION - ? "" - : ` - ` - } +
CTRL键
+ 拖动:选择区域以复制或删除。SHIFT键
: 按住以放置多个同一种设施。ALT键
: 反向放置传送带。