From a0071681e72f10fca4bb584e098db8c821c4e071 Mon Sep 17 00:00:00 2001 From: tobspr Date: Sat, 15 Jan 2022 10:49:58 +0100 Subject: [PATCH] Start to make mods safer --- electron/index.js | 5 ++++- electron/preload.js | 3 +++ package.json | 3 ++- src/js/core/modal_dialog_forms.js | 13 +++++------- src/js/core/utils.js | 15 ------------- src/js/game/modes/regular.js | 2 +- src/js/globals.d.ts | 2 ++ src/js/mods/modloader.js | 21 ++++++++++--------- src/js/platform/api.js | 5 +---- .../electron/steam_achievement_provider.js | 7 ++----- src/js/platform/electron/storage.js | 9 ++++---- src/js/platform/electron/wrapper.js | 9 +++----- src/js/states/main_menu.js | 9 +++----- src/js/states/mods.js | 3 +-- 14 files changed, 42 insertions(+), 64 deletions(-) create mode 100644 electron/preload.js diff --git a/electron/index.js b/electron/index.js index 81b0e360..c7b07c55 100644 --- a/electron/index.js +++ b/electron/index.js @@ -54,8 +54,11 @@ function createWindow() { // fullscreen: true, autoHideMenuBar: true, webPreferences: { - nodeIntegration: true, + nodeIntegration: false, webSecurity: false, + // sandbox: true, + contextIsolation: true, + preload: path.join(__dirname, "preload.js"), }, allowRunningInsecureContent: false, }); diff --git a/electron/preload.js b/electron/preload.js new file mode 100644 index 00000000..5729634a --- /dev/null +++ b/electron/preload.js @@ -0,0 +1,3 @@ +const { contextBridge, ipcRenderer } = require("electron"); + +contextBridge.exposeInMainWorld("ipcRenderer", ipcRenderer); diff --git a/package.json b/package.json index f3ab2f80..f1f3cfdf 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "publishStandalone": "yarn publishOnItch && yarn publishOnSteam", "publishWeb": "cd gulp && yarn main.deploy.prod", "publish": "yarn publishStandalone && yarn publishWeb", - "syncTranslations": "node sync-translations.js" + "syncTranslations": "node sync-translations.js", + "buildTypes": "tsc src/js/application.js --declaration --allowJs --emitDeclarationOnly --skipLibCheck --out types.js" }, "dependencies": { "@babel/core": "^7.5.4", diff --git a/src/js/core/modal_dialog_forms.js b/src/js/core/modal_dialog_forms.js index ccf9bfb2..a4e617f8 100644 --- a/src/js/core/modal_dialog_forms.js +++ b/src/js/core/modal_dialog_forms.js @@ -1,7 +1,6 @@ import { BaseItem } from "../game/base_item"; import { ClickDetector } from "./click_detector"; import { Signal } from "./signal"; -import { getIPCRenderer } from "./utils"; /* * *************************************************** @@ -113,13 +112,11 @@ export class FormElementInput extends FormElement { if (G_WEGAME_VERSION) { const value = String(this.element.value); - getIPCRenderer() - .invoke("profanity-check", value) - .then(newValue => { - if (value !== newValue && this.element) { - this.element.value = newValue; - } - }); + ipcRenderer.invoke("profanity-check", value).then(newValue => { + if (value !== newValue && this.element) { + this.element.value = newValue; + } + }); } } diff --git a/src/js/core/utils.js b/src/js/core/utils.js index 2c2c0ed8..35e70310 100644 --- a/src/js/core/utils.js +++ b/src/js/core/utils.js @@ -42,21 +42,6 @@ export function getPlatformName() { return "unknown"; } -/** - * Returns the IPC renderer, or null if not within the standalone - * @returns {object|null} - */ -let ipcRenderer = null; -export function getIPCRenderer() { - if (!G_IS_STANDALONE) { - return null; - } - if (!ipcRenderer) { - ipcRenderer = eval("require")("electron").ipcRenderer; - } - return ipcRenderer; -} - /** * Makes a new 2D array with undefined contents * @param {number} w diff --git a/src/js/game/modes/regular.js b/src/js/game/modes/regular.js index b52cbc89..cd14833e 100644 --- a/src/js/game/modes/regular.js +++ b/src/js/game/modes/regular.js @@ -602,7 +602,7 @@ export class RegularGameMode extends GameMode { /** @type {(typeof MetaBuilding)[]} */ this.hiddenBuildings = [MetaConstantProducerBuilding, MetaGoalAcceptorBuilding, MetaBlockBuilding]; - // @ts-expect-error + // @ts-ignore if (!(G_IS_DEV || window.sandboxMode || queryParamOptions.sandboxMode)) { this.hiddenBuildings.push(MetaItemProducerBuilding); } diff --git a/src/js/globals.d.ts b/src/js/globals.d.ts index f6983645..0b9421e1 100644 --- a/src/js/globals.d.ts +++ b/src/js/globals.d.ts @@ -24,6 +24,8 @@ declare const G_WEGAME_VERSION: boolean; declare const shapez: any; +declare const ipcRenderer: any; + // Polyfills declare interface String { replaceAll(search: string, replacement: string): string; diff --git a/src/js/mods/modloader.js b/src/js/mods/modloader.js index 7cbcc0fd..f5145207 100644 --- a/src/js/mods/modloader.js +++ b/src/js/mods/modloader.js @@ -1,10 +1,8 @@ /* typehints:start */ import { Application } from "../application"; /* typehints:end */ - import { globalConfig } from "../core/config"; import { createLogger } from "../core/logging"; -import { getIPCRenderer } from "../core/utils"; import { Mod } from "./mod"; import { ModInterface } from "./mod_interface"; import { MOD_SIGNALS } from "./mod_signals"; @@ -76,12 +74,17 @@ export class ModLoader { this.exposeExports(); + window.registerMod = mod => { + this.modLoadQueue.push(mod); + }; + if (G_IS_STANDALONE || G_IS_DEV) { try { let mods = []; if (G_IS_STANDALONE) { - mods = await getIPCRenderer().invoke("get-mods"); - } else if (G_IS_DEV && globalConfig.debug.externalModUrl) { + mods = await ipcRenderer.invoke("get-mods"); + } + if (G_IS_DEV && globalConfig.debug.externalModUrl) { const mod = await ( await fetch(globalConfig.debug.externalModUrl, { method: "GET", @@ -91,12 +94,8 @@ export class ModLoader { } mods.forEach(modCode => { - window.registerMod = mod => { - this.modLoadQueue.push(mod); - }; - // ugh - eval(modCode); - delete window.registerMod; + const func = new Function(modCode); + func(); }); } catch (ex) { alert("Failed to load mods: " + ex); @@ -111,6 +110,8 @@ export class ModLoader { }); this.modLoadQueue = []; this.signals.postInit.dispatch(); + + delete window.registerMod; } } diff --git a/src/js/platform/api.js b/src/js/platform/api.js index d518c98a..4e7a82f9 100644 --- a/src/js/platform/api.js +++ b/src/js/platform/api.js @@ -3,7 +3,6 @@ import { Application } from "../application"; /* typehints:end */ import { createLogger } from "../core/logging"; import { compressX64 } from "../core/lzstring"; -import { getIPCRenderer } from "../core/utils"; import { T } from "../translations"; const logger = createLogger("puzzle-api"); @@ -113,9 +112,7 @@ export class ClientAPI { return Promise.resolve({ token }); } - const renderer = getIPCRenderer(); - - return renderer.invoke("steam:get-ticket").then( + return ipcRenderer.invoke("steam:get-ticket").then( ticket => { logger.log("Got auth ticket:", ticket); return this._request("/v1/public/login", { diff --git a/src/js/platform/electron/steam_achievement_provider.js b/src/js/platform/electron/steam_achievement_provider.js index c0ef552c..638cdbc5 100644 --- a/src/js/platform/electron/steam_achievement_provider.js +++ b/src/js/platform/electron/steam_achievement_provider.js @@ -4,7 +4,6 @@ import { GameRoot } from "../../game/root"; /* typehints:end */ import { createLogger } from "../../core/logging"; -import { getIPCRenderer } from "../../core/utils"; import { ACHIEVEMENTS, AchievementCollection, AchievementProviderInterface } from "../achievement_provider"; const logger = createLogger("achievements/steam"); @@ -109,9 +108,7 @@ export class SteamAchievementProvider extends AchievementProviderInterface { return Promise.resolve(); } - this.ipc = getIPCRenderer(); - - return this.ipc.invoke("steam:is-initialized").then(initialized => { + return ipcRenderer.invoke("steam:is-initialized").then(initialized => { this.initialized = initialized; if (!this.initialized) { @@ -136,7 +133,7 @@ export class SteamAchievementProvider extends AchievementProviderInterface { if (!this.initialized) { promise = Promise.resolve(); } else { - promise = this.ipc.invoke("steam:activate-achievement", ACHIEVEMENT_IDS[key]); + promise = ipcRenderer.invoke("steam:activate-achievement", ACHIEVEMENT_IDS[key]); } return promise diff --git a/src/js/platform/electron/storage.js b/src/js/platform/electron/storage.js index 41ed1746..24aff500 100644 --- a/src/js/platform/electron/storage.js +++ b/src/js/platform/electron/storage.js @@ -1,5 +1,4 @@ import { StorageInterface } from "../storage"; -import { getIPCRenderer } from "../../core/utils"; import { createLogger } from "../../core/logging"; const logger = createLogger("electron-storage"); @@ -12,7 +11,7 @@ export class StorageImplElectron extends StorageInterface { this.jobs = {}; this.jobId = 0; - getIPCRenderer().on("fs-response", (event, arg) => { + ipcRenderer.on("fs-response", (event, arg) => { const id = arg.id; if (!this.jobs[id]) { logger.warn("Got unhandled FS response, job not known:", id); @@ -37,7 +36,7 @@ export class StorageImplElectron extends StorageInterface { const jobId = ++this.jobId; this.jobs[jobId] = { resolve, reject }; - getIPCRenderer().send("fs-job", { + ipcRenderer.send("fs-job", { type: "write", filename, contents, @@ -52,7 +51,7 @@ export class StorageImplElectron extends StorageInterface { const jobId = ++this.jobId; this.jobs[jobId] = { resolve, reject }; - getIPCRenderer().send("fs-job", { + ipcRenderer.send("fs-job", { type: "read", filename, id: jobId, @@ -65,7 +64,7 @@ export class StorageImplElectron extends StorageInterface { // ipcMain const jobId = ++this.jobId; this.jobs[jobId] = { resolve, reject }; - getIPCRenderer().send("fs-job", { + ipcRenderer.send("fs-job", { type: "delete", filename, id: jobId, diff --git a/src/js/platform/electron/wrapper.js b/src/js/platform/electron/wrapper.js index c1764f68..65451395 100644 --- a/src/js/platform/electron/wrapper.js +++ b/src/js/platform/electron/wrapper.js @@ -1,6 +1,5 @@ import { NoAchievementProvider } from "../browser/no_achievement_provider"; import { PlatformWrapperImplBrowser } from "../browser/wrapper"; -import { getIPCRenderer } from "../../core/utils"; import { createLogger } from "../../core/logging"; import { StorageImplElectron } from "./storage"; import { SteamAchievementProvider } from "./steam_achievement_provider"; @@ -71,15 +70,13 @@ export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser { } initializeDlcStatus() { - const renderer = getIPCRenderer(); - if (G_WEGAME_VERSION) { return Promise.resolve(); } logger.log("Checking DLC ownership ..."); // @todo: Don't hardcode the app id - return renderer.invoke("steam:check-app-ownership", 1625400).then( + return ipcRenderer.invoke("steam:check-app-ownership", 1625400).then( res => { logger.log("Got DLC ownership:", res); this.dlcs.puzzle = Boolean(res); @@ -106,7 +103,7 @@ export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser { } setFullscreen(flag) { - getIPCRenderer().send("set-fullscreen", flag); + ipcRenderer.send("set-fullscreen", flag); } getSupportsAppExit() { @@ -115,6 +112,6 @@ export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser { exitApp() { logger.log(this, "Sending app exit signal"); - getIPCRenderer().send("exit-app"); + ipcRenderer.send("exit-app"); } } diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index cbebd629..04cf8bca 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -405,12 +405,6 @@ export class MainMenuState extends GameState { makeButton(outerDiv, ["newGameButton", "styledButton"], T.mainMenu.newGame), this.onPlayButtonClicked ); - - // Mods - this.trackClicks( - makeButton(outerDiv, ["modsButton", "styledButton"], " "), - this.onModsClicked - ); } else { // New game this.trackClicks( @@ -419,6 +413,9 @@ export class MainMenuState extends GameState { ); } + // Mods + this.trackClicks(makeButton(outerDiv, ["modsButton", "styledButton"], " "), this.onModsClicked); + buttonContainer.appendChild(outerDiv); } diff --git a/src/js/states/mods.js b/src/js/states/mods.js index d746434d..a99d2e95 100644 --- a/src/js/states/mods.js +++ b/src/js/states/mods.js @@ -1,6 +1,5 @@ import { THIRDPARTY_URLS } from "../core/config"; import { TextualGameState } from "../core/textual_game_state"; -import { getIPCRenderer } from "../core/utils"; import { MODS } from "../mods/modloader"; import { T } from "../translations"; @@ -117,7 +116,7 @@ export class ModsState extends TextualGameState { this.dialogs.showWarning(T.global.error, T.mods.folderOnlyStandalone); return; } - getIPCRenderer().send("open-mods-folder"); + ipcRenderer.send("open-mods-folder"); } onSteamLinkClicked() {