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 - ? "" - : ` - ` - } +
diff --git a/src/js/states/wegame_splash.js b/src/js/states/wegame_splash.js index a5483112..ee0b07e5 100644 --- a/src/js/states/wegame_splash.js +++ b/src/js/states/wegame_splash.js @@ -19,6 +19,7 @@ export class WegameSplashState extends GameState { onEnter() { setTimeout( () => { + document.querySelector("body > .wrapper").remove(); this.app.stateMgr.moveToState("PreloadState"); }, G_IS_DEV ? 1 : 6000 diff --git a/translations/base-zh-CN-ISBN.yaml b/translations/base-zh-CN-ISBN.yaml index fa1550c1..d3d2164c 100644 --- a/translations/base-zh-CN-ISBN.yaml +++ b/translations/base-zh-CN-ISBN.yaml @@ -1,6 +1,5 @@ steamPage: - shortText: “唯一能限制您的,只有您的想象力!” 《图形工厂》 - 是一款在无限拓展的地图上,通过建造各类工厂设施,来自动化生产与组合出愈加复杂图形的游戏。 + shortText: “唯一能限制您的,只有您的想象力!” 《图形工厂》 是一款在无限拓展的地图上,通过建造各类工厂设施,来自动化生产与组合出愈加复杂图形的游戏。 discordLinkShort: 官方讨论区 intro: |- “奇形怪状,放飞想象!” @@ -154,15 +153,13 @@ dialogs: desc: 您还没有解锁蓝图功能!通过第12关的挑战后可解锁蓝图。 keybindingsIntroduction: title: 实用快捷键 - desc: - "这个游戏有很多有用的快捷键设定。 以下是其中的一些介绍,记得在按键设置中查看其他按键设定!

+ desc: "这个游戏有很多有用的快捷键设定。 以下是其中的一些介绍,记得在按键设置中查看其他按键设定!

CTRL键 + 拖动:选择区域以复制或删除。
SHIFT键: 按住以放置多个同一种设施。
ALT键: 反向放置传送带。
" createMarker: title: 创建地图标记 - desc: - 填写一个有意义的名称, 还可以同时包含一个形状的 短代码 (您可以 点击这里 + desc: 填写一个有意义的名称, 还可以同时包含一个形状的 短代码 (您可以 点击这里 生成短代码) titleEdit: 编辑地图标记 markerDemoLimit: @@ -193,27 +190,27 @@ dialogs: title: 设置项目 puzzleLoadFailed: title: 谜题载入失败 - desc: "谜题未能载入!" + desc: 谜题未能载入! submitPuzzle: title: 提交谜题 - descName: "为您的谜题命名!" - descIcon: "请输入唯一的短代码,它将作为您的谜题图标显示(您可以在这里生成,或者从以下随机推荐的图形中选择一个):" + descName: 为您的谜题命名! + descIcon: 请输入唯一的短代码,它将作为您的谜题图标显示(您可以在这里生成,或者从以下随机推荐的图形中选择一个): placeholderName: 谜题标题 puzzleResizeBadBuildings: title: 无法重新定义尺寸 desc: 由于某些设施将会超出区域范围,因此您无法将区域变得更小。 puzzleLoadError: title: 谜题出错! - desc: "谜题未能载入!" + desc: 谜题未能载入! offlineMode: title: 离线模式 desc: 无法访问服务器,所以游戏以离线模式进行。请确认您的互联网访问正常。 puzzleDownloadError: title: 下载出错! - desc: "无法下载谜题!" + desc: 无法下载谜题! puzzleSubmitError: title: 提交出错! - desc: "无法提交谜题!" + desc: 无法提交谜题! puzzleSubmitOk: title: 谜题成功发布! desc: 恭喜!您的谜题已经成功发布,其他玩家已经可以玩到。您可以在“我的谜题”中找到自己已发布的谜题。 @@ -237,7 +234,7 @@ dialogs: desc: 此谜已被标记! puzzleReportError: title: 上报失败 - desc: "无法处理您的上报!" + desc: 无法处理您的上报! puzzleLoadShortKey: title: 输入短代码 desc: 输入谜题的短代码并载入。 @@ -358,22 +355,18 @@ ingame: interactiveTutorial: title: 新手教程 hints: - 1_1_extractor: - 亲爱的玩家,欢迎来到《图形工厂》!在这里你可以通过创造各种图形设施与传送带模拟流水线生产,尽情发挥创造力,创办属于自己的工厂!

+ 1_1_extractor: 亲爱的玩家,欢迎来到《图形工厂》!在这里你可以通过创造各种图形设施与传送带模拟流水线生产,尽情发挥创造力,创办属于自己的工厂!

圆形上放置一个开采器来获取圆形!

提示:按下鼠标左键选中开采器 1_2_conveyor: 用传送带将您的开采器连接到中心基地上!

提示:选中传送带按下鼠标左键可拖动布置传送带! - 1_3_expand: - 您可以放置更多的开采器传送带来更有效率地完成关卡目标。

+ 1_3_expand: 您可以放置更多的开采器传送带来更有效率地完成关卡目标。

提示:按住 SHIFT 键可放置多个开采器,注意用R 键可旋转开采器的出口方向,确保开采的图形可以顺利传送。 2_1_place_cutter: 现在放置一个切割器,这个设施可把圆形切成两半!

注意:无论如何放置,切割机总是从上到下切割。 - 2_2_place_trash: - 使用切割机后产生的废弃图形会导致堵塞

注意使用垃圾桶清除当前 + 2_2_place_trash: 使用切割机后产生的废弃图形会导致堵塞

注意使用垃圾桶清除当前 (!) 不需要的废物。 2_3_more_cutters: 干的好!现在放置2个以上的切割机来加快当前缓慢的过程!

提示:用快捷键0-9可以快速选择各项设施! - 3_1_rectangles: - 现在让我们开采一些矩形!找到矩形地带放置4个开采器并将它们用传送带连接到中心基地。

+ 3_1_rectangles: 现在让我们开采一些矩形!找到矩形地带放置4个开采器并将它们用传送带连接到中心基地。

提示:选中传送带后按住SHIFT键可快速准确地规划传送带路线! 21_1_place_quad_painter: 放置四口上色器并且获取一些圆形白色红色! 21_2_switch_to_wires: 按 E 键选择电线层

@@ -670,8 +663,7 @@ storyRewards: desc: 恭喜!您解锁了旋转机。它会顺时针将输入的图形旋转90度。 reward_painter: title: 上色 - desc: - 恭喜!您解锁了上色器。开采一些颜色 (就像您开采图形一样),将其在上色器中与图形结合来将图形上色! + desc: 恭喜!您解锁了上色器。开采一些颜色 (就像您开采图形一样),将其在上色器中与图形结合来将图形上色!
注意:如果您不幸患有色盲,可以在设置中启用色盲模式 reward_mixer: title: 混合颜色 @@ -688,13 +680,11 @@ storyRewards: desc: 恭喜!您解锁了隧道。它可放置在传送带设施下方以运送物品。 reward_rotater_ccw: title: 逆时针旋转 - desc: - 恭喜!您解锁了旋转机逆时针变体。它可以逆时针旋转图形。 + desc: 恭喜!您解锁了旋转机逆时针变体。它可以逆时针旋转图形
选择旋转机然后按"T"键来选取这个变体。 reward_miner_chainable: title: 链式开采器 - desc: - 您已经解锁了链式开采器!它能转发资源给其他的开采器,这样您就能更有效率的开采各类资源了!

+ desc: 您已经解锁了链式开采器!它能转发资源给其他的开采器,这样您就能更有效率的开采各类资源了!

注意:新的开采器已替换了工具栏里旧的开采器! reward_underground_belt_tier_2: title: 二级隧道 @@ -711,14 +701,12 @@ storyRewards:
优先从左边输出,这样您就可以用它做一个溢流门了! reward_freeplay: title: 自由模式 - desc: - 成功了!您解锁了自由模式!挑战升级!这意味着现在将随机生成图形! + desc: 成功了!您解锁了自由模式!挑战升级!这意味着现在将随机生成图形! 从现在起,中心基地最为需要的是产量,我强烈建议您去制造一台能够自动交付所需图形的机器!

基地会在电线层输出需要的图形,您需要去分析图形并在此基础上自动配置您的工厂。 reward_blueprints: title: 蓝图 - desc: - 您现在可以复制粘贴您的工厂的一部分了!按住 CTRL键并拖动鼠标来选择一块区域,然后按C键复制。 + desc: 您现在可以复制粘贴您的工厂的一部分了!按住 CTRL键并拖动鼠标来选择一块区域,然后按C键复制。

粘贴并不是免费的,您需要制造蓝图图形来负担。蓝图图形是您刚刚交付的图形。 no_reward: title: 下一关 @@ -746,8 +734,7 @@ storyRewards:
注意:您注意到传送读取器存储器输出的他们最后读取的物品了吗?试着在显示屏上展示一下!" reward_constant_signal: title: 恒定信号 - desc: - 恭喜!您解锁了生成于电线层之上的恒定信号,把它连接到过滤器时非常有用。 + desc: 恭喜!您解锁了生成于电线层之上的恒定信号,把它连接到过滤器时非常有用。
比如,它能发出图形、颜色、开关值(1 / 0)的固定信号。 reward_logic_gates: title: 逻辑门 @@ -766,8 +753,7 @@ storyRewards: 提示:可在设置中打开电线层教程!" reward_filter: title: 物品过滤器 - desc: - 恭喜!您解锁了物品过滤器!它会根据在电线层上输入的信号决定是从上面还是右边输出物品。

+ desc: 恭喜!您解锁了物品过滤器!它会根据在电线层上输入的信号决定是从上面还是右边输出物品。

您也可以输入开关值(1 / 0)信号来激活或者禁用它。 reward_demo_end: title: 试玩结束 @@ -995,13 +981,10 @@ keybindings: showShapeTooltip: 显示图形输出提示。 about: title: 关于游戏 - body: >- + body: |- 本游戏由托比亚斯开发,并且已经开源。

- 这个游戏的开发获得了热情玩家的巨大支持。非常感谢!

- 本游戏的音乐由佩普森制作——他是个很棒的伙伴。

- 最后,我想感谢我最好的朋友尼可拉斯——如果没有他的《异星工厂》带给我的体验和启发,《图形工厂》将不会存在。 changelog: title: 版本日志