Compare commits
9 Commits
Author | SHA1 | Date |
---|---|---|
tobspr | b50ec8e3b8 | |
tobspr | 728c8118e1 | |
tobspr | 3f4ed3143f | |
tobspr | 138d2e15fb | |
tobspr | 9c1bac5afe | |
tobspr | 34754964d3 | |
tobspr | 6b10a79a10 | |
tobspr | cd086f7fd0 | |
tobspr | 74ab44df3a |
|
@ -0,0 +1 @@
|
|||
mods/*.js
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"files.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/typedefs_gen": true
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 105 KiB |
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
|
@ -0,0 +1,344 @@
|
|||
/* eslint-disable quotes,no-undef */
|
||||
|
||||
const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell, dialog, session } = require("electron");
|
||||
const path = require("path");
|
||||
const url = require("url");
|
||||
const fs = require("fs");
|
||||
const steam = require("./steam");
|
||||
const asyncLock = require("async-lock");
|
||||
const windowStateKeeper = require("electron-window-state");
|
||||
|
||||
// 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-china", "saves");
|
||||
|
||||
if (!fs.existsSync(storePath)) {
|
||||
// No try-catch by design
|
||||
fs.mkdirSync(storePath, { recursive: true });
|
||||
}
|
||||
|
||||
/** @type {BrowserWindow} */
|
||||
let win = null;
|
||||
let menu = null;
|
||||
|
||||
function createWindow() {
|
||||
let faviconExtension = ".png";
|
||||
if (process.platform === "win32") {
|
||||
faviconExtension = ".ico";
|
||||
}
|
||||
|
||||
const mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 1000,
|
||||
defaultHeight: 800,
|
||||
});
|
||||
|
||||
win = new BrowserWindow({
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
height: mainWindowState.height,
|
||||
show: false,
|
||||
backgroundColor: "#222428",
|
||||
useContentSize: false,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
title: "图形工厂",
|
||||
transparent: false,
|
||||
icon: path.join(__dirname, "favicon" + faviconExtension),
|
||||
// fullscreen: true,
|
||||
autoHideMenuBar: !isDev,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
nodeIntegrationInWorker: false,
|
||||
nodeIntegrationInSubFrames: false,
|
||||
contextIsolation: true,
|
||||
enableRemoteModule: false,
|
||||
disableBlinkFeatures: "Auxclick",
|
||||
|
||||
webSecurity: true,
|
||||
sandbox: true,
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
experimentalFeatures: false,
|
||||
},
|
||||
allowRunningInsecureContent: false,
|
||||
});
|
||||
|
||||
mainWindowState.manage(win);
|
||||
|
||||
if (isLocal) {
|
||||
win.loadURL("http://localhost:3005");
|
||||
} else {
|
||||
win.loadURL(
|
||||
url.format({
|
||||
pathname: path.join(__dirname, "index.html"),
|
||||
protocol: "file:",
|
||||
slashes: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
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();
|
||||
|
||||
if (pth.startsWith("https://") || pth.startsWith("steam://")) {
|
||||
shell.openExternal(pth);
|
||||
}
|
||||
});
|
||||
|
||||
win.on("closed", () => {
|
||||
console.log("Window closed");
|
||||
win = null;
|
||||
});
|
||||
|
||||
if (isDev) {
|
||||
menu = new Menu();
|
||||
|
||||
win.webContents.toggleDevTools();
|
||||
|
||||
const mainItem = new MenuItem({
|
||||
label: "Toggle Dev Tools",
|
||||
click: () => win.webContents.toggleDevTools(),
|
||||
accelerator: "F12",
|
||||
});
|
||||
menu.append(mainItem);
|
||||
|
||||
const reloadItem = new MenuItem({
|
||||
label: "Reload",
|
||||
click: () => win.reload(),
|
||||
accelerator: "F5",
|
||||
});
|
||||
menu.append(reloadItem);
|
||||
|
||||
const fullscreenItem = new MenuItem({
|
||||
label: "Fullscreen",
|
||||
click: () => win.setFullScreen(!win.isFullScreen()),
|
||||
accelerator: "F11",
|
||||
});
|
||||
menu.append(fullscreenItem);
|
||||
|
||||
const mainMenu = new Menu();
|
||||
mainMenu.append(
|
||||
new MenuItem({
|
||||
label: "shapez.io",
|
||||
submenu: menu,
|
||||
})
|
||||
);
|
||||
|
||||
Menu.setApplicationMenu(mainMenu);
|
||||
} else {
|
||||
Menu.setApplicationMenu(null);
|
||||
}
|
||||
|
||||
win.once("ready-to-show", () => {
|
||||
win.show();
|
||||
win.focus();
|
||||
});
|
||||
}
|
||||
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.exit(0);
|
||||
} else {
|
||||
app.on("second-instance", () => {
|
||||
// Someone tried to run a second instance, we should focus
|
||||
if (win) {
|
||||
if (win.isMinimized()) {
|
||||
win.restore();
|
||||
}
|
||||
win.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.on("ready", createWindow);
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
console.log("All windows closed");
|
||||
app.quit();
|
||||
});
|
||||
|
||||
ipcMain.on("set-fullscreen", (event, flag) => {
|
||||
win.setFullScreen(flag);
|
||||
});
|
||||
|
||||
ipcMain.on("exit-app", () => {
|
||||
win.close();
|
||||
app.quit();
|
||||
});
|
||||
|
||||
let renameCounter = 1;
|
||||
|
||||
const fileLock = new asyncLock({
|
||||
timeout: 30000,
|
||||
maxPending: 1000,
|
||||
});
|
||||
|
||||
function niceFileName(filename) {
|
||||
return filename.replace(storePath, "@");
|
||||
}
|
||||
|
||||
async function writeFileSafe(filename, contents) {
|
||||
++renameCounter;
|
||||
const prefix = "[ " + renameCounter + ":" + niceFileName(filename) + " ] ";
|
||||
const transactionId = String(new Date().getTime()) + "." + renameCounter;
|
||||
|
||||
if (fileLock.isBusy()) {
|
||||
console.warn(prefix, "Concurrent write process on", filename);
|
||||
}
|
||||
|
||||
fileLock.acquire(filename, async () => {
|
||||
console.log(prefix, "Starting write on", niceFileName(filename), "in transaction", transactionId);
|
||||
|
||||
if (!fs.existsSync(filename)) {
|
||||
// this one is easy
|
||||
console.log(prefix, "Writing file instantly because it does not exist:", niceFileName(filename));
|
||||
await fs.promises.writeFile(filename, contents, "utf8");
|
||||
return;
|
||||
}
|
||||
|
||||
// first, write a temporary file (.tmp-XXX)
|
||||
const tempName = filename + ".tmp-" + transactionId;
|
||||
console.log(prefix, "Writing temporary file", niceFileName(tempName));
|
||||
await fs.promises.writeFile(tempName, contents, "utf8");
|
||||
|
||||
// now, rename the original file to (.backup-XXX)
|
||||
const oldTemporaryName = filename + ".backup-" + transactionId;
|
||||
console.log(
|
||||
prefix,
|
||||
"Renaming old file",
|
||||
niceFileName(filename),
|
||||
"to",
|
||||
niceFileName(oldTemporaryName)
|
||||
);
|
||||
await fs.promises.rename(filename, oldTemporaryName);
|
||||
|
||||
// now, rename the temporary file (.tmp-XXX) to the target
|
||||
console.log(
|
||||
prefix,
|
||||
"Renaming the temporary file",
|
||||
niceFileName(tempName),
|
||||
"to the original",
|
||||
niceFileName(filename)
|
||||
);
|
||||
await fs.promises.rename(tempName, filename);
|
||||
|
||||
// we are done now, try to create a backup, but don't fail if the backup fails
|
||||
try {
|
||||
// check if there is an old backup file
|
||||
const backupFileName = filename + ".backup";
|
||||
if (fs.existsSync(backupFileName)) {
|
||||
console.log(prefix, "Deleting old backup file", niceFileName(backupFileName));
|
||||
// delete the old backup
|
||||
await fs.promises.unlink(backupFileName);
|
||||
}
|
||||
|
||||
// rename the old file to the new backup file
|
||||
console.log(prefix, "Moving", niceFileName(oldTemporaryName), "to the backup file location");
|
||||
await fs.promises.rename(oldTemporaryName, backupFileName);
|
||||
} catch (ex) {
|
||||
console.error(prefix, "Failed to switch backup files:", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ipcMain.handle("fs-job", async (event, job) => {
|
||||
const filenameSafe = job.filename.replace(/[^a-z\.\-_0-9]/gi, "_");
|
||||
const fname = path.join(storePath, filenameSafe);
|
||||
switch (job.type) {
|
||||
case "read": {
|
||||
if (!fs.existsSync(fname)) {
|
||||
// Special FILE_NOT_FOUND error code
|
||||
return { error: "file_not_found" };
|
||||
}
|
||||
return await fs.promises.readFile(fname, "utf8");
|
||||
}
|
||||
case "write": {
|
||||
await writeFileSafe(fname, job.contents);
|
||||
return job.contents;
|
||||
}
|
||||
|
||||
case "delete": {
|
||||
await fs.promises.unlink(fname);
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error("Unknown fs job: " + job.type);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("get-mods", async () => {
|
||||
return [];
|
||||
});
|
||||
|
||||
steam.init(isDev);
|
||||
steam.listen();
|
|
@ -0,0 +1,6 @@
|
|||
Here you can place mods. Every mod should be a single file ending with ".js".
|
||||
|
||||
--- WARNING ---
|
||||
Mods can potentially access to your filesystem.
|
||||
Please only install mods from trusted sources and developers.
|
||||
--- WARNING ---
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "electron",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"startDev": "electron --disable-direct-composition --in-process-gpu . --dev --local",
|
||||
"startDevGpu": "electron --enable-gpu-rasterization --enable-accelerated-2d-canvas --num-raster-threads=8 --enable-zero-copy . --dev --local",
|
||||
"start": "electron --disable-direct-composition --in-process-gpu ."
|
||||
},
|
||||
"devDependencies": {},
|
||||
"optionalDependencies": {
|
||||
"shapez.io-private-artifacts": "github:tobspr/shapez.io-private-artifacts#abi-v99"
|
||||
},
|
||||
"dependencies": {
|
||||
"async-lock": "^1.2.8",
|
||||
"electron": "16.2.8",
|
||||
"electron-window-state": "^5.0.3"
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
});
|
|
@ -0,0 +1,112 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { ipcMain } = require("electron");
|
||||
|
||||
let greenworks = null;
|
||||
let appId = null;
|
||||
let initialized = false;
|
||||
|
||||
try {
|
||||
greenworks = require("shapez.io-private-artifacts/steam/greenworks");
|
||||
appId = parseInt(fs.readFileSync(path.join(__dirname, "steam_appid.txt"), "utf8"));
|
||||
} catch (err) {
|
||||
// greenworks is not installed
|
||||
console.warn("Failed to load steam api:", err);
|
||||
}
|
||||
|
||||
console.log("App ID:", appId);
|
||||
|
||||
function init(isDev) {
|
||||
if (!greenworks) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDev) {
|
||||
if (greenworks.restartAppIfNecessary(appId)) {
|
||||
console.log("Restarting ...");
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!greenworks.init()) {
|
||||
console.log("Failed to initialize greenworks");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
function listen() {
|
||||
ipcMain.handle("steam:is-initialized", isInitialized);
|
||||
|
||||
if (!initialized) {
|
||||
console.warn("Steam not initialized, won't be able to listen");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!greenworks) {
|
||||
console.warn("Greenworks not loaded, won't be able to listen");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Adding listeners");
|
||||
|
||||
ipcMain.handle("steam:get-achievement-names", getAchievementNames);
|
||||
ipcMain.handle("steam:activate-achievement", activateAchievement);
|
||||
|
||||
function bufferToHex(buffer) {
|
||||
return Array.from(new Uint8Array(buffer))
|
||||
.map(b => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
}
|
||||
|
||||
ipcMain.handle("steam:get-ticket", (event, arg) => {
|
||||
console.log("Requested steam ticket ...");
|
||||
return new Promise((resolve, reject) => {
|
||||
greenworks.getAuthSessionTicket(
|
||||
success => {
|
||||
const ticketHex = bufferToHex(success.ticket);
|
||||
resolve(ticketHex);
|
||||
},
|
||||
error => {
|
||||
console.error("Failed to get steam ticket:", error);
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle("steam:check-app-ownership", (event, appId) => {
|
||||
return Promise.resolve(greenworks.isDLCInstalled(appId));
|
||||
});
|
||||
}
|
||||
|
||||
function isInitialized(event) {
|
||||
return Promise.resolve(initialized);
|
||||
}
|
||||
|
||||
function getAchievementNames(event) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const achievements = greenworks.getAchievementNames();
|
||||
resolve(achievements);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function activateAchievement(event, id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
greenworks.activateAchievement(
|
||||
id,
|
||||
() => resolve(),
|
||||
err => reject(err)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
listen,
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
1318690
|
|
@ -0,0 +1,584 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@electron/get@^1.13.0":
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.1.tgz#42a0aa62fd1189638bd966e23effaebb16108368"
|
||||
integrity sha512-U5vkXDZ9DwXtkPqlB45tfYnnYBN8PePp1z/XDCupnSpdrxT8/ThCv9WCwPLf9oqiSGZTkH6dx2jDUPuoXpjkcA==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
env-paths "^2.2.0"
|
||||
fs-extra "^8.1.0"
|
||||
got "^9.6.0"
|
||||
progress "^2.0.3"
|
||||
semver "^6.2.0"
|
||||
sumchecker "^3.0.1"
|
||||
optionalDependencies:
|
||||
global-agent "^3.0.0"
|
||||
global-tunnel-ng "^2.7.1"
|
||||
|
||||
"@sindresorhus/is@^0.14.0":
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
||||
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
|
||||
|
||||
"@szmarczak/http-timer@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
|
||||
integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==
|
||||
dependencies:
|
||||
defer-to-connect "^1.0.1"
|
||||
|
||||
"@types/node@^14.6.2":
|
||||
version "14.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.20.tgz#268f028b36eaf51181c3300252f605488c4f0650"
|
||||
integrity sha512-Q8KKwm9YqEmUBRsqJ2GWJDtXltBDxTdC4m5vTdXBolu2PeQh8LX+f6BTwU+OuXPu37fLxoN6gidqBmnky36FXA==
|
||||
|
||||
async-lock@^1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.2.8.tgz#7b02bdfa2de603c0713acecd11184cf97bbc7c4c"
|
||||
integrity sha512-G+26B2jc0Gw0EG/WN2M6IczuGepBsfR1+DtqLnyFSH4p2C668qkOCtEkGNVEaaNAVlYwEMazy1+/jnLxltBkIQ==
|
||||
|
||||
boolean@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.2.tgz#df1baa18b6a2b0e70840475e1d93ec8fe75b2570"
|
||||
integrity sha512-RwywHlpCRc3/Wh81MiCKun4ydaIFyW5Ea6JbL6sRCVx5q5irDw7pMXBUFYF/jArQ6YrG36q0kpovc9P/Kd3I4g==
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
cacheable-request@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
|
||||
integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==
|
||||
dependencies:
|
||||
clone-response "^1.0.2"
|
||||
get-stream "^5.1.0"
|
||||
http-cache-semantics "^4.0.0"
|
||||
keyv "^3.0.0"
|
||||
lowercase-keys "^2.0.0"
|
||||
normalize-url "^4.1.0"
|
||||
responselike "^1.0.2"
|
||||
|
||||
clone-response@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
||||
integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
concat-stream@^1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
config-chain@^1.1.11:
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
|
||||
integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==
|
||||
dependencies:
|
||||
ini "^1.3.4"
|
||||
proto-list "~1.2.1"
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||
|
||||
debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^4.1.0, debug@^4.1.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
decompress-response@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
|
||||
integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
defer-to-connect@^1.0.1:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
|
||||
integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
|
||||
|
||||
define-properties@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
||||
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
|
||||
dependencies:
|
||||
object-keys "^1.0.12"
|
||||
|
||||
detect-node@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
|
||||
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
|
||||
|
||||
duplexer3@^0.1.4:
|
||||
version "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@16.2.8:
|
||||
version "16.2.8"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-16.2.8.tgz#b7f2bd1184701e54a1bc902839d5a3ec95bb8982"
|
||||
integrity sha512-KSOytY6SPLsh3iCziztqa/WgJyfDOKzCvNqku9gGIqSdT8CqtV66dTU1SOrKZQjRFLxHaF8LbyxUL1vwe4taqw==
|
||||
dependencies:
|
||||
"@electron/get" "^1.13.0"
|
||||
"@types/node" "^14.6.2"
|
||||
extract-zip "^1.0.3"
|
||||
|
||||
encodeurl@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||
|
||||
end-of-stream@^1.1.0:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
env-paths@^2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
|
||||
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
|
||||
|
||||
es6-error@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
|
||||
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
|
||||
|
||||
escape-string-regexp@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||
|
||||
extract-zip@^1.0.3:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
|
||||
integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
|
||||
dependencies:
|
||||
concat-stream "^1.6.2"
|
||||
debug "^2.6.9"
|
||||
mkdirp "^0.5.4"
|
||||
yauzl "^2.10.0"
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
fs-extra@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
||||
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
get-stream@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
|
||||
integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-stream@^5.1.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
|
||||
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
global-agent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6"
|
||||
integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==
|
||||
dependencies:
|
||||
boolean "^3.0.1"
|
||||
es6-error "^4.1.1"
|
||||
matcher "^3.0.0"
|
||||
roarr "^2.15.3"
|
||||
semver "^7.3.2"
|
||||
serialize-error "^7.0.1"
|
||||
|
||||
global-tunnel-ng@^2.7.1:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f"
|
||||
integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==
|
||||
dependencies:
|
||||
encodeurl "^1.0.2"
|
||||
lodash "^4.17.10"
|
||||
npm-conf "^1.1.3"
|
||||
tunnel "^0.0.6"
|
||||
|
||||
globalthis@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b"
|
||||
integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
|
||||
got@^9.6.0:
|
||||
version "9.6.0"
|
||||
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
|
||||
integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==
|
||||
dependencies:
|
||||
"@sindresorhus/is" "^0.14.0"
|
||||
"@szmarczak/http-timer" "^1.1.2"
|
||||
cacheable-request "^6.0.0"
|
||||
decompress-response "^3.3.0"
|
||||
duplexer3 "^0.1.4"
|
||||
get-stream "^4.1.0"
|
||||
lowercase-keys "^1.0.1"
|
||||
mimic-response "^1.0.1"
|
||||
p-cancelable "^1.0.0"
|
||||
to-readable-stream "^1.0.0"
|
||||
url-parse-lax "^3.0.0"
|
||||
|
||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||
version "4.2.6"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
|
||||
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
|
||||
|
||||
http-cache-semantics@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
|
||||
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
|
||||
|
||||
inherits@^2.0.3, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ini@^1.3.4:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
json-buffer@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
|
||||
integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
|
||||
|
||||
json-stringify-safe@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||
|
||||
jsonfile@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
keyv@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
|
||||
integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
|
||||
dependencies:
|
||||
json-buffer "3.0.0"
|
||||
|
||||
lodash@^4.17.10:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
|
||||
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
|
||||
|
||||
lowercase-keys@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
|
||||
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
matcher@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
|
||||
integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==
|
||||
dependencies:
|
||||
escape-string-regexp "^4.0.0"
|
||||
|
||||
mimic-response@^1.0.0, mimic-response@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
|
||||
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
|
||||
|
||||
minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
mkdirp@^0.5.1, mkdirp@^0.5.4:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
normalize-url@^4.1.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
|
||||
integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
|
||||
|
||||
npm-conf@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9"
|
||||
integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==
|
||||
dependencies:
|
||||
config-chain "^1.1.11"
|
||||
pify "^3.0.0"
|
||||
|
||||
object-keys@^1.0.12:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
|
||||
|
||||
once@^1.3.1, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
p-cancelable@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
|
||||
integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
||||
|
||||
pify@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
|
||||
|
||||
prepend-http@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
progress@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||
|
||||
proto-list@~1.2.1:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||
integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
|
||||
|
||||
pump@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
|
||||
dependencies:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
readable-stream@^2.2.2:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
responselike@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
|
||||
integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
|
||||
dependencies:
|
||||
lowercase-keys "^1.0.0"
|
||||
|
||||
roarr@^2.15.3:
|
||||
version "2.15.4"
|
||||
resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd"
|
||||
integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==
|
||||
dependencies:
|
||||
boolean "^3.0.1"
|
||||
detect-node "^2.0.4"
|
||||
globalthis "^1.0.1"
|
||||
json-stringify-safe "^5.0.1"
|
||||
semver-compare "^1.0.0"
|
||||
sprintf-js "^1.1.2"
|
||||
|
||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
|
||||
|
||||
semver@^6.2.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@^7.3.2:
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
|
||||
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
serialize-error@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
|
||||
integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==
|
||||
dependencies:
|
||||
type-fest "^0.13.1"
|
||||
|
||||
"shapez.io-private-artifacts@github:tobspr/shapez.io-private-artifacts#abi-v99":
|
||||
version "0.1.0"
|
||||
resolved "git+ssh://git@github.com/tobspr/shapez.io-private-artifacts.git#3293b20be26060fd36e9f00ded9ab5d0bdf57338"
|
||||
|
||||
sprintf-js@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
|
||||
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
sumchecker@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42"
|
||||
integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==
|
||||
dependencies:
|
||||
debug "^4.1.0"
|
||||
|
||||
to-readable-stream@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
|
||||
integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
|
||||
|
||||
tunnel@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||
|
||||
type-fest@^0.13.1:
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
|
||||
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
universalify@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||
|
||||
url-parse-lax@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
|
||||
integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
|
||||
dependencies:
|
||||
prepend-http "^2.0.0"
|
||||
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
|
@ -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.io", "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();
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
});
|
|
@ -7,7 +7,7 @@ function init(isDev) {
|
|||
try {
|
||||
console.log("Step 2: Calling need restart app");
|
||||
const need_restart = railsdk.RailNeedRestartAppForCheckingEnvironment(
|
||||
2001639,
|
||||
2002030,
|
||||
[`--rail_render_pid=${process.pid}`] //,"--rail_debug_mode",
|
||||
);
|
||||
console.log("Step 3: Needs restart =", need_restart);
|
||||
|
@ -58,6 +58,22 @@ function listen() {
|
|||
|
||||
return data;
|
||||
});
|
||||
|
||||
ipcMain.handle("wegame:activate-achievement", async (event, data) => {
|
||||
console.log("Unlock wegame achievement", data);
|
||||
|
||||
var manager = railsdk.RailAchievementHelper.CreatePlayerAchievement("0");
|
||||
var result = manager.MakeAchievement(data);
|
||||
if (result != railsdk.RailResult.kSuccess) {
|
||||
console.error("Unlock wegame achievement", data, "failed with code", result);
|
||||
return false;
|
||||
}
|
||||
manager.AsyncStoreAchievement().then(
|
||||
() => console.log("Achievements stored successfully."),
|
||||
err => console.error("Failed to unlock achievement async:", err)
|
||||
);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { init, listen };
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
* chineseVersion?: boolean,
|
||||
* wegameVersion?: boolean,
|
||||
* steamDemo?: boolean,
|
||||
* steamIsbnVersion?: boolean,
|
||||
* gogVersion?: boolean
|
||||
* }}>}
|
||||
*/
|
||||
|
@ -63,6 +64,14 @@ const BUILD_VARIANTS = {
|
|||
wegameVersion: true,
|
||||
},
|
||||
},
|
||||
"standalone-steam-isbn": {
|
||||
standalone: true,
|
||||
steamAppId: 1318690,
|
||||
electronBaseDir: "electron_steam_isbn",
|
||||
buildArgs: {
|
||||
steamIsbnVersion: true,
|
||||
},
|
||||
},
|
||||
"standalone-gog": {
|
||||
standalone: true,
|
||||
electronBaseDir: "electron_gog",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -10,6 +10,7 @@ module.exports = ({
|
|||
standalone = false,
|
||||
chineseVersion = false,
|
||||
wegameVersion = false,
|
||||
steamIsbnVersion = false,
|
||||
steamDemo = false,
|
||||
gogVersion = false,
|
||||
}) => {
|
||||
|
@ -39,6 +40,8 @@ module.exports = ({
|
|||
G_APP_ENVIRONMENT: JSON.stringify("dev"),
|
||||
G_CHINA_VERSION: JSON.stringify(chineseVersion),
|
||||
G_WEGAME_VERSION: JSON.stringify(wegameVersion),
|
||||
G_ISBN_VERSION: JSON.stringify(wegameVersion || steamIsbnVersion),
|
||||
G_STEAM_ISBN_VERSION: JSON.stringify(steamIsbnVersion),
|
||||
G_GOG_VERSION: JSON.stringify(gogVersion),
|
||||
G_IS_DEV: "true",
|
||||
G_IS_RELEASE: "false",
|
||||
|
|
|
@ -17,6 +17,7 @@ module.exports = ({
|
|||
|
||||
chineseVersion = false,
|
||||
wegameVersion = false,
|
||||
steamIsbnVersion = false,
|
||||
steamDemo = false,
|
||||
gogVersion = false,
|
||||
}) => {
|
||||
|
@ -28,6 +29,8 @@ module.exports = ({
|
|||
|
||||
G_CHINA_VERSION: JSON.stringify(chineseVersion),
|
||||
G_WEGAME_VERSION: JSON.stringify(wegameVersion),
|
||||
G_ISBN_VERSION: JSON.stringify(wegameVersion || steamIsbnVersion),
|
||||
G_STEAM_ISBN_VERSION: JSON.stringify(steamIsbnVersion),
|
||||
G_GOG_VERSION: JSON.stringify(gogVersion),
|
||||
G_IS_RELEASE: environment === "prod" ? "true" : "false",
|
||||
G_IS_STANDALONE: standalone ? "true" : "false",
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
|
@ -1191,11 +1191,9 @@
|
|||
background: green;
|
||||
cursor: pointer !important;
|
||||
pointer-events: all;
|
||||
@include S(border-radius, 4px);
|
||||
overflow: hidden;
|
||||
|
||||
& {
|
||||
background: #fff uiResource("wegame_isbn_rating.jpg") center center / contain no-repeat;
|
||||
background: uiResource("wegame_isbn_rating.png") center center / contain no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" style="--ui-scale: 1.33;">
|
||||
<head>
|
||||
<title>shapez Demo - Factory Automation Game</title>
|
||||
<title>图形工厂</title>
|
||||
|
||||
<!-- mobile stuff -->
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>shapez</title>
|
||||
<title>图形工厂</title>
|
||||
|
||||
<!-- mobile stuff -->
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
|
|
|
@ -153,7 +153,7 @@ export class Application {
|
|||
|
||||
Loader.linkAppAfterBoot(this);
|
||||
|
||||
if (G_WEGAME_VERSION) {
|
||||
if (G_ISBN_VERSION) {
|
||||
this.stateMgr.moveToState("WegameSplashState");
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ export const BUILD_OPTIONS = {
|
|||
APP_ENVIRONMENT: G_APP_ENVIRONMENT,
|
||||
CHINA_VERSION: G_CHINA_VERSION,
|
||||
WEGAME_VERSION: G_WEGAME_VERSION,
|
||||
ISBN_VERSION: G_ISBN_VERSION,
|
||||
STEAM_ISBN_VERSION: G_STEAM_ISBN_VERSION,
|
||||
IS_DEV: G_IS_DEV,
|
||||
IS_RELEASE: G_IS_RELEASE,
|
||||
IS_BROWSER: G_IS_BROWSER,
|
||||
|
|
|
@ -247,6 +247,16 @@ export function formatBigNumber(num, separator = T.global.decimalSeparator) {
|
|||
if (num < 1000) {
|
||||
return sign + "" + num;
|
||||
} else {
|
||||
if (G_ISBN_VERSION) {
|
||||
if (num < 1000000) {
|
||||
if (num < 10000) {
|
||||
return sign + String(num).replace(".0", "").replace(".", separator);
|
||||
} else {
|
||||
return sign + round2Digits(num / 10000.0) + T.global.suffix.thousands;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let leadingDigits = num;
|
||||
let suffix = "";
|
||||
for (let suffixIndex = 0; suffixIndex < bigNumberSuffixTranslationKeys.length; ++suffixIndex) {
|
||||
|
@ -694,7 +704,7 @@ const romanLiteralsCache = ["0"];
|
|||
* @returns {string}
|
||||
*/
|
||||
export function getRomanNumber(number) {
|
||||
if (G_WEGAME_VERSION) {
|
||||
if (G_ISBN_VERSION) {
|
||||
return String(number);
|
||||
}
|
||||
|
||||
|
@ -753,7 +763,7 @@ export function getRomanNumber(number) {
|
|||
* Returns the appropriate logo sprite path
|
||||
*/
|
||||
export function getLogoSprite() {
|
||||
if (G_WEGAME_VERSION) {
|
||||
if (G_ISBN_VERSION) {
|
||||
return "logo_wegame.png";
|
||||
}
|
||||
|
||||
|
|
|
@ -188,9 +188,7 @@ export class HUDInteractiveTutorial extends BaseHUDPart {
|
|||
onHintChanged(hintId) {
|
||||
this.elementDescription.innerHTML = T.ingame.interactiveTutorial.hints[hintId];
|
||||
document.documentElement.setAttribute("data-tutorial-step", hintId);
|
||||
const folder = G_WEGAME_VERSION
|
||||
? "interactive_tutorial.cn.noinline"
|
||||
: "interactive_tutorial.noinline";
|
||||
const folder = G_ISBN_VERSION ? "interactive_tutorial.cn.noinline" : "interactive_tutorial.noinline";
|
||||
|
||||
this.elementGif.style.backgroundImage =
|
||||
"url('" + cachebust("res/ui/" + folder + "/" + hintId + ".gif") + "')";
|
||||
|
|
|
@ -233,7 +233,7 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
|||
|
||||
// Show small info icon
|
||||
let infoDetector;
|
||||
if (!G_WEGAME_VERSION) {
|
||||
if (!G_ISBN_VERSION) {
|
||||
const infoButton = document.createElement("button");
|
||||
infoButton.classList.add("infoButton");
|
||||
element.appendChild(infoButton);
|
||||
|
|
|
@ -4,7 +4,7 @@ import { BaseHUDPart } from "../base_hud_part";
|
|||
export class HUDPuzzleDLCLogo extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_PuzzleDLCLogo");
|
||||
this.element.classList.toggle("china", G_CHINA_VERSION || G_WEGAME_VERSION);
|
||||
this.element.classList.toggle("china", G_CHINA_VERSION || G_ISBN_VERSION);
|
||||
parent.appendChild(this.element);
|
||||
}
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ export class HUDShop extends BaseHUDPart {
|
|||
container.appendChild(pinButton);
|
||||
|
||||
let infoDetector;
|
||||
if (!G_WEGAME_VERSION) {
|
||||
if (!G_ISBN_VERSION) {
|
||||
const viewInfoButton = document.createElement("button");
|
||||
viewInfoButton.classList.add("showInfo");
|
||||
container.appendChild(viewInfoButton);
|
||||
|
|
|
@ -45,7 +45,7 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||
*/
|
||||
createElements(parent) {
|
||||
// Create the helper box on the lower right when zooming out
|
||||
if (this.root.app.settings.getAllSettings().offerHints && !G_WEGAME_VERSION) {
|
||||
if (this.root.app.settings.getAllSettings().offerHints && !G_ISBN_VERSION) {
|
||||
this.hintElement = makeDiv(
|
||||
parent,
|
||||
"ingame_HUD_Waypoints_Hint",
|
||||
|
@ -121,7 +121,7 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||
}
|
||||
|
||||
// Catch mouse and key events
|
||||
if (!G_WEGAME_VERSION) {
|
||||
if (!G_ISBN_VERSION) {
|
||||
this.root.camera.downPreHandler.add(this.onMouseDown, this);
|
||||
this.root.keyMapper
|
||||
.getBinding(KEYMAPPINGS.navigation.createMarker)
|
||||
|
|
|
@ -5,7 +5,7 @@ import { WEB_STEAM_SSO_AUTHENTICATED } from "../../core/steam_sso";
|
|||
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||
|
||||
export const finalGameShape = "RuCw--Cw:----Ru--";
|
||||
const chinaShapes = G_WEGAME_VERSION || G_CHINA_VERSION;
|
||||
const chinaShapes = G_ISBN_VERSION || G_CHINA_VERSION;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -65,7 +65,7 @@ const preparementShape = "CpRpCp--:SwSwSwSw";
|
|||
// Tiers need % of the previous tier as requirement too
|
||||
const tierGrowth = 2.5;
|
||||
|
||||
const chinaShapes = G_WEGAME_VERSION || G_CHINA_VERSION;
|
||||
const chinaShapes = G_ISBN_VERSION || G_CHINA_VERSION;
|
||||
|
||||
const upgradesCache = {};
|
||||
|
||||
|
@ -362,7 +362,7 @@ export class RegularGameMode extends GameMode {
|
|||
}
|
||||
|
||||
if (this.root.app.settings.getAllSettings().offerHints) {
|
||||
if (!G_WEGAME_VERSION) {
|
||||
if (!G_ISBN_VERSION) {
|
||||
this.additionalHudParts.tutorialHints = HUDPartTutorialHints;
|
||||
}
|
||||
this.additionalHudParts.interactiveTutorial = HUDInteractiveTutorial;
|
||||
|
|
|
@ -20,6 +20,8 @@ declare const G_IS_RELEASE: boolean;
|
|||
|
||||
declare const G_CHINA_VERSION: boolean;
|
||||
declare const G_WEGAME_VERSION: boolean;
|
||||
declare const G_ISBN_VERSION: boolean;
|
||||
declare const G_STEAM_ISBN_VERSION: boolean;
|
||||
declare const G_GOG_VERSION: boolean;
|
||||
|
||||
declare const shapez: any;
|
||||
|
|
|
@ -12,7 +12,7 @@ export const LANGUAGES = {
|
|||
"zh-CN": {
|
||||
// simplified chinese
|
||||
name: "简体中文",
|
||||
data: G_WEGAME_VERSION
|
||||
data: G_ISBN_VERSION
|
||||
? require("./built-temp/base-zh-CN-ISBN.json")
|
||||
: require("./built-temp/base-zh-CN.json"),
|
||||
code: "zh",
|
||||
|
|
|
@ -114,7 +114,7 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
|
|||
initialize() {
|
||||
this.syncKey = null;
|
||||
|
||||
if (G_WEGAME_VERSION) {
|
||||
if (G_ISBN_VERSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -221,7 +221,7 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
|
|||
* @returns {Promise<any>}
|
||||
*/
|
||||
sendToApi(endpoint, data) {
|
||||
if (G_WEGAME_VERSION) {
|
||||
if (G_ISBN_VERSION) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -263,7 +263,7 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
|
|||
* @param {string} value
|
||||
*/
|
||||
sendGameEvent(category, value) {
|
||||
if (G_WEGAME_VERSION) {
|
||||
if (G_ISBN_VERSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,9 +33,9 @@ export class StorageImplBrowser extends StorageInterface {
|
|||
window.localStorage.setItem("storage_availability_test", "1");
|
||||
window.localStorage.removeItem("storage_availability_test");
|
||||
} catch (e) {
|
||||
alert(
|
||||
"It seems we don't have permission to write to local storage! Please update your browsers settings or use a different browser!"
|
||||
);
|
||||
// alert(
|
||||
// "It seems we don't have permission to write to local storage! Please update your browsers settings or use a different browser!"
|
||||
// );
|
||||
reject(LOCAL_STORAGE_NO_WRITE_PERMISSION);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/* typehints:start */
|
||||
import { Application } from "../../application";
|
||||
import { GameRoot } from "../../game/root";
|
||||
/* typehints:end */
|
||||
|
||||
import { createLogger } from "../../core/logging";
|
||||
import { ACHIEVEMENTS, AchievementCollection, AchievementProviderInterface } from "../achievement_provider";
|
||||
|
||||
const logger = createLogger("achievements/wegame");
|
||||
|
||||
const ACHIEVEMENT_IDS = {
|
||||
[ACHIEVEMENTS.belt500Tiles]: "belt_500_tiles",
|
||||
[ACHIEVEMENTS.blueprint100k]: "blueprint_100k",
|
||||
[ACHIEVEMENTS.blueprint1m]: "blueprint_1m",
|
||||
[ACHIEVEMENTS.completeLvl26]: "complete_lvl_26",
|
||||
[ACHIEVEMENTS.cutShape]: "cut_shape",
|
||||
[ACHIEVEMENTS.darkMode]: "dark_mode",
|
||||
[ACHIEVEMENTS.destroy1000]: "destroy_1000",
|
||||
[ACHIEVEMENTS.irrelevantShape]: "irrelevant_shape",
|
||||
[ACHIEVEMENTS.level100]: "level_100",
|
||||
[ACHIEVEMENTS.level50]: "level_50",
|
||||
[ACHIEVEMENTS.logoBefore18]: "logo_before_18",
|
||||
[ACHIEVEMENTS.mam]: "mam",
|
||||
[ACHIEVEMENTS.mapMarkers15]: "map_markers_15",
|
||||
[ACHIEVEMENTS.openWires]: "open_wires",
|
||||
[ACHIEVEMENTS.oldLevel17]: "old_level_17",
|
||||
[ACHIEVEMENTS.noBeltUpgradesUntilBp]: "no_belt_upgrades_until_bp",
|
||||
[ACHIEVEMENTS.noInverseRotater]: "no_inverse_rotator", // [sic]
|
||||
[ACHIEVEMENTS.paintShape]: "paint_shape",
|
||||
[ACHIEVEMENTS.place5000Wires]: "place_5000_wires",
|
||||
[ACHIEVEMENTS.placeBlueprint]: "place_blueprint",
|
||||
[ACHIEVEMENTS.placeBp1000]: "place_bp_1000",
|
||||
[ACHIEVEMENTS.play1h]: "play_1h",
|
||||
[ACHIEVEMENTS.play10h]: "play_10h",
|
||||
[ACHIEVEMENTS.play20h]: "play_20h",
|
||||
[ACHIEVEMENTS.produceLogo]: "produce_logo",
|
||||
[ACHIEVEMENTS.produceMsLogo]: "produce_ms_logo",
|
||||
[ACHIEVEMENTS.produceRocket]: "produce_rocket",
|
||||
[ACHIEVEMENTS.rotateShape]: "rotate_shape",
|
||||
[ACHIEVEMENTS.speedrunBp30]: "speedrun_bp_30",
|
||||
[ACHIEVEMENTS.speedrunBp60]: "speedrun_bp_60",
|
||||
[ACHIEVEMENTS.speedrunBp120]: "speedrun_bp_120",
|
||||
[ACHIEVEMENTS.stack4Layers]: "stack_4_layers",
|
||||
[ACHIEVEMENTS.stackShape]: "stack_shape",
|
||||
[ACHIEVEMENTS.store100Unique]: "store_100_unique",
|
||||
[ACHIEVEMENTS.storeShape]: "store_shape",
|
||||
[ACHIEVEMENTS.throughputBp25]: "throughput_bp_25",
|
||||
[ACHIEVEMENTS.throughputBp50]: "throughput_bp_50",
|
||||
[ACHIEVEMENTS.throughputLogo25]: "throughput_logo_25",
|
||||
[ACHIEVEMENTS.throughputLogo50]: "throughput_logo_50",
|
||||
[ACHIEVEMENTS.throughputRocket10]: "throughput_rocket_10",
|
||||
[ACHIEVEMENTS.throughputRocket20]: "throughput_rocket_20",
|
||||
[ACHIEVEMENTS.trash1000]: "trash_1000",
|
||||
[ACHIEVEMENTS.unlockWires]: "unlock_wires",
|
||||
[ACHIEVEMENTS.upgradesTier5]: "upgrades_tier_5",
|
||||
[ACHIEVEMENTS.upgradesTier8]: "upgrades_tier_8",
|
||||
};
|
||||
|
||||
export class WegameAchievementProvider extends AchievementProviderInterface {
|
||||
/** @param {Application} app */
|
||||
constructor(app) {
|
||||
super(app);
|
||||
|
||||
this.initialized = false;
|
||||
this.collection = new AchievementCollection(this.activate.bind(this));
|
||||
|
||||
if (G_IS_DEV) {
|
||||
for (let key in ACHIEVEMENT_IDS) {
|
||||
assert(this.collection.map.has(key), "Key not found in collection: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
logger.log("Collection created with", this.collection.map.size, "achievements");
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
hasAchievements() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GameRoot} root
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
onLoad(root) {
|
||||
this.root = root;
|
||||
|
||||
try {
|
||||
this.collection = new AchievementCollection(this.activate.bind(this));
|
||||
this.collection.initialize(root);
|
||||
|
||||
logger.log("Initialized", this.collection.map.size, "relevant achievements");
|
||||
return Promise.resolve();
|
||||
} catch (err) {
|
||||
logger.error("Failed to initialize the collection");
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {Promise<void>} */
|
||||
initialize() {
|
||||
if (!G_IS_STANDALONE) {
|
||||
logger.warn("Wegame unavailable. Achievements won't sync.");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!G_WEGAME_VERSION) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
activate(key) {
|
||||
let promise;
|
||||
|
||||
if (!G_WEGAME_VERSION) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!this.initialized) {
|
||||
promise = Promise.resolve();
|
||||
} else {
|
||||
promise = ipcRenderer.invoke("wegame:activate-achievement", ACHIEVEMENT_IDS[key]);
|
||||
}
|
||||
|
||||
return promise
|
||||
.then(() => {
|
||||
logger.log("Achievement activated:", key);
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error("Failed to activate achievement:", key, err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import { createLogger } from "../../core/logging";
|
|||
import { StorageImplElectron } from "./storage";
|
||||
import { SteamAchievementProvider } from "./steam_achievement_provider";
|
||||
import { PlatformWrapperInterface } from "../wrapper";
|
||||
import { WegameAchievementProvider } from "./wegame_achievement_provider";
|
||||
|
||||
const logger = createLogger("electron-wrapper");
|
||||
|
||||
|
@ -24,7 +25,10 @@ export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
|
|||
this.app.ticker.frameEmitted.add(this.steamOverlayFixRedrawCanvas, this);
|
||||
|
||||
this.app.storage = new StorageImplElectron(this);
|
||||
this.app.achievementProvider = new SteamAchievementProvider(this.app);
|
||||
|
||||
this.app.achievementProvider = G_WEGAME_VERSION
|
||||
? new WegameAchievementProvider(this.app)
|
||||
: new SteamAchievementProvider(this.app);
|
||||
|
||||
return this.initializeAchievementProvider()
|
||||
.then(() => this.initializeDlcStatus())
|
||||
|
@ -70,7 +74,7 @@ export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
|
|||
}
|
||||
|
||||
initializeDlcStatus() {
|
||||
if (G_WEGAME_VERSION) {
|
||||
if (G_ISBN_VERSION) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ function initializeSettings() {
|
|||
options: Object.keys(LANGUAGES),
|
||||
valueGetter: key => key,
|
||||
textGetter: key => LANGUAGES[key].name,
|
||||
category: G_CHINA_VERSION || G_WEGAME_VERSION ? null : enumCategories.general,
|
||||
category: G_CHINA_VERSION || G_ISBN_VERSION ? null : enumCategories.general,
|
||||
restartRequired: true,
|
||||
changeCb: (app, id) => null,
|
||||
magicValue: "auto-detect",
|
||||
|
|
|
@ -39,21 +39,21 @@ export class MainMenuState extends GameState {
|
|||
}
|
||||
|
||||
getInnerHTML() {
|
||||
const showLanguageIcon = !G_CHINA_VERSION && !G_WEGAME_VERSION;
|
||||
const showLanguageIcon = !G_CHINA_VERSION && !G_ISBN_VERSION;
|
||||
const showExitAppButton = G_IS_STANDALONE;
|
||||
const showPuzzleDLC =
|
||||
!G_WEGAME_VERSION &&
|
||||
!G_ISBN_VERSION &&
|
||||
(G_IS_STANDALONE || WEB_STEAM_SSO_AUTHENTICATED) &&
|
||||
!G_IS_STEAM_DEMO &&
|
||||
!G_GOG_VERSION;
|
||||
const showWegameFooter = G_WEGAME_VERSION;
|
||||
const showWegameFooter = G_ISBN_VERSION;
|
||||
const hasMods = MODS.anyModsActive();
|
||||
const hasSteamBridge = !G_GOG_VERSION && !G_IS_STEAM_DEMO;
|
||||
const hasSteamBridge = !G_GOG_VERSION && !G_IS_STEAM_DEMO && !G_ISBN_VERSION;
|
||||
|
||||
let showExternalLinks = true;
|
||||
|
||||
if (G_IS_STANDALONE) {
|
||||
if (G_WEGAME_VERSION || G_CHINA_VERSION) {
|
||||
if (G_ISBN_VERSION || G_CHINA_VERSION) {
|
||||
showExternalLinks = false;
|
||||
}
|
||||
} else {
|
||||
|
@ -262,8 +262,8 @@ export class MainMenuState extends GameState {
|
|||
抵制不良游戏,拒绝盗版游戏。注意自我保护,谨防受骗上当。<br>
|
||||
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。
|
||||
</div>
|
||||
|
||||
<div class="rating"></div>
|
||||
|
||||
</div>
|
||||
`
|
||||
: `
|
||||
|
@ -382,7 +382,7 @@ export class MainMenuState extends GameState {
|
|||
closeLoader();
|
||||
this.dialogs.showWarning(
|
||||
T.dialogs.importSavegameError.title,
|
||||
T.dialogs.importSavegameError.text + "<br><br>" + err
|
||||
T.dialogs.importSavegameError.text
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -402,7 +402,7 @@ export class MainMenuState extends GameState {
|
|||
closeLoader();
|
||||
this.dialogs.showWarning(
|
||||
T.dialogs.importSavegameError.title,
|
||||
T.dialogs.importSavegameError.text + ":<br><br>" + err
|
||||
T.dialogs.importSavegameError.text
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -410,7 +410,7 @@ export class MainMenuState extends GameState {
|
|||
reader.addEventListener("error", error => {
|
||||
this.dialogs.showWarning(
|
||||
T.dialogs.importSavegameError.title,
|
||||
T.dialogs.importSavegameError.text + ":<br><br>" + error
|
||||
T.dialogs.importSavegameError.text
|
||||
);
|
||||
});
|
||||
reader.readAsText(file, "utf-8");
|
||||
|
@ -433,10 +433,7 @@ export class MainMenuState extends GameState {
|
|||
this.dialogs.initializeToElement(dialogsElement);
|
||||
|
||||
if (payload.loadError) {
|
||||
this.dialogs.showWarning(
|
||||
T.dialogs.gameLoadFailure.title,
|
||||
T.dialogs.gameLoadFailure.text + "<br><br>" + payload.loadError
|
||||
);
|
||||
this.dialogs.showWarning(T.dialogs.gameLoadFailure.title, T.dialogs.gameLoadFailure.text);
|
||||
}
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
|
||||
|
@ -540,10 +537,12 @@ export class MainMenuState extends GameState {
|
|||
.setAttribute("data-savegames", String(this.savedGames.length));
|
||||
|
||||
// Mods
|
||||
this.trackClicks(
|
||||
makeButton(outerDiv, ["modsButton", "styledButton"], T.mods.title),
|
||||
this.onModsClicked
|
||||
);
|
||||
if (!G_STEAM_ISBN_VERSION) {
|
||||
this.trackClicks(
|
||||
makeButton(outerDiv, ["modsButton", "styledButton"], T.mods.title),
|
||||
this.onModsClicked
|
||||
);
|
||||
}
|
||||
|
||||
buttonContainer.appendChild(outerDiv);
|
||||
}
|
||||
|
@ -715,7 +714,7 @@ export class MainMenuState extends GameState {
|
|||
downloadButton.setAttribute("aria-label", "Download");
|
||||
elem.appendChild(downloadButton);
|
||||
|
||||
if (!G_WEGAME_VERSION) {
|
||||
if (!G_ISBN_VERSION) {
|
||||
const renameButton = document.createElement("button");
|
||||
renameButton.classList.add("styledButton", "renameGame");
|
||||
renameButton.setAttribute("aria-label", "Rename Savegame");
|
||||
|
@ -788,10 +787,7 @@ export class MainMenuState extends GameState {
|
|||
})
|
||||
|
||||
.catch(err => {
|
||||
this.dialogs.showWarning(
|
||||
T.dialogs.gameLoadFailure.title,
|
||||
T.dialogs.gameLoadFailure.text + "<br><br>" + err
|
||||
);
|
||||
this.dialogs.showWarning(T.dialogs.gameLoadFailure.title, T.dialogs.gameLoadFailure.text);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -868,7 +864,7 @@ export class MainMenuState extends GameState {
|
|||
err => {
|
||||
this.dialogs.showWarning(
|
||||
T.dialogs.savegameDeletionError.title,
|
||||
T.dialogs.savegameDeletionError.text + "<br><br>" + err
|
||||
T.dialogs.savegameDeletionError.text
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
|
@ -158,7 +155,7 @@ export class PreloadState extends GameState {
|
|||
|
||||
.then(() => this.setStatus("Initializing language", 25))
|
||||
.then(() => {
|
||||
if (G_CHINA_VERSION || G_WEGAME_VERSION) {
|
||||
if (G_CHINA_VERSION || G_ISBN_VERSION) {
|
||||
return this.app.settings.updateLanguage("zh-CN");
|
||||
}
|
||||
|
||||
|
@ -222,7 +219,7 @@ export class PreloadState extends GameState {
|
|||
return;
|
||||
}
|
||||
|
||||
if (G_CHINA_VERSION || G_WEGAME_VERSION) {
|
||||
if (G_CHINA_VERSION || G_ISBN_VERSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -289,7 +286,7 @@ export class PreloadState extends GameState {
|
|||
}
|
||||
|
||||
update() {
|
||||
if (G_CHINA_VERSION || G_WEGAME_VERSION) {
|
||||
if (G_CHINA_VERSION || G_ISBN_VERSION) {
|
||||
return;
|
||||
}
|
||||
const now = performance.now();
|
||||
|
@ -323,7 +320,7 @@ export class PreloadState extends GameState {
|
|||
setStatus(text, progress) {
|
||||
logger.log("✅ " + text);
|
||||
|
||||
if (G_CHINA_VERSION || G_WEGAME_VERSION) {
|
||||
if (G_CHINA_VERSION || G_ISBN_VERSION) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.currentStatus = text;
|
||||
|
@ -346,15 +343,11 @@ export class PreloadState extends GameState {
|
|||
</div>
|
||||
<div class="failureInner">
|
||||
<div class="errorHeader">
|
||||
Failed to initialize application!
|
||||
</div>
|
||||
<div class="errorMessage">
|
||||
${this.currentStatus} failed:<br/>
|
||||
${text}
|
||||
应用初始化失败!
|
||||
</div>
|
||||
|
||||
<div class="lower">
|
||||
<button class="resetApp styledButton">Reset App</button>
|
||||
<i>Build ${G_BUILD_VERSION} @ ${G_BUILD_COMMIT_HASH}</i>
|
||||
<i>建造。${G_BUILD_VERSION} @ ${G_BUILD_COMMIT_HASH}</i>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -362,9 +355,6 @@ export class PreloadState extends GameState {
|
|||
this.htmlElement.classList.add("failure");
|
||||
this.htmlElement.appendChild(subElement);
|
||||
|
||||
const resetBtn = subElement.querySelector("button.resetApp");
|
||||
this.trackClicks(resetBtn, this.showResetConfirm);
|
||||
|
||||
this.hintsText.remove();
|
||||
}
|
||||
|
||||
|
|
|
@ -31,19 +31,20 @@ export class SettingsState extends TextualGameState {
|
|||
}
|
||||
|
||||
${
|
||||
G_WEGAME_VERSION
|
||||
// Disable mods for steam china
|
||||
G_STEAM_ISBN_VERSION
|
||||
? ""
|
||||
: `
|
||||
<button class="styledButton categoryButton manageMods">${T.mods.title}
|
||||
<span class="newBadge">${T.settings.newBadge}</span>
|
||||
</button>`
|
||||
<button class="styledButton categoryButton manageMods">${T.mods.title}
|
||||
<span class="newBadge">${T.settings.newBadge}</span>
|
||||
</button>`
|
||||
}
|
||||
|
||||
|
||||
<div class="other ${G_CHINA_VERSION || G_WEGAME_VERSION ? "noabout" : ""}">
|
||||
<div class="other ${G_CHINA_VERSION || G_ISBN_VERSION ? "noabout" : ""}">
|
||||
|
||||
${
|
||||
G_CHINA_VERSION || G_WEGAME_VERSION
|
||||
G_CHINA_VERSION || G_ISBN_VERSION
|
||||
? ""
|
||||
: `
|
||||
<button class="styledButton about">${T.about.title}</button>
|
||||
|
@ -52,7 +53,7 @@ export class SettingsState extends TextualGameState {
|
|||
`
|
||||
}
|
||||
<div class="versionbar">
|
||||
${G_WEGAME_VERSION ? "" : `<div class="buildVersion">${T.global.loading} ...</div>`}
|
||||
${G_ISBN_VERSION ? "" : `<div class="buildVersion">${T.global.loading} ...</div>`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -122,7 +123,7 @@ export class SettingsState extends TextualGameState {
|
|||
onEnter(payload) {
|
||||
this.renderBuildText();
|
||||
|
||||
if (!G_CHINA_VERSION && !G_WEGAME_VERSION) {
|
||||
if (!G_CHINA_VERSION && !G_ISBN_VERSION) {
|
||||
this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, {
|
||||
preventDefault: false,
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
steamPage:
|
||||
shortText: “唯一能限制您的,只有您的想象力!” 《异形工厂》(Shapez.io)
|
||||
是一款在无限拓展的地图上,通过建造各类工厂设施,来自动化生产与组合出愈加复杂图形的游戏。
|
||||
discordLinkShort: 官方 Discord 服务器
|
||||
shortText: “唯一能限制您的,只有您的想象力!” 《图形工厂》 是一款在无限拓展的地图上,通过建造各类工厂设施,来自动化生产与组合出愈加复杂图形的游戏。
|
||||
discordLinkShort: 官方讨论区
|
||||
intro: |-
|
||||
“奇形怪状,放飞想象!”
|
||||
“自动生产,尽情创造!”
|
||||
《异形工厂》(Shapez.io)是一款能让您尽情发挥创造力,充分享受思维乐趣的IO游戏。
|
||||
《图形工厂》是一款能让您尽情发挥创造力,充分享受思维乐趣的益智游戏。
|
||||
游戏很轻松,只需建造工厂,布好设施,无需操作即能自动创造出各种各样的几何图形。
|
||||
挑战很烧脑,随着等级提升,需要创造的图形将会越来越复杂,同时您还需要在无限扩展的地图中持续扩建优化您的工厂。
|
||||
以为这就是全部了吗? 不!图形的生产需求将会指数性增长,持续的扩大规模和熵增带来的无序,将会是令人头痛的问题!
|
||||
|
@ -13,8 +12,8 @@ steamPage:
|
|||
然后,还有吗? 当然,唯有思维,方能无限。
|
||||
|
||||
欢迎免费体验试玩版:“让您的想象力插上翅膀!”
|
||||
和最聪明的玩家一起挑战,请访问 Steam 游戏商城购买《异形工厂》(Shapez.io)的完整版,
|
||||
what_others_say: 来看看玩家们对《异形工厂》(Shapez.io)的评价
|
||||
和最聪明的玩家一起挑战,请购买《图形工厂》的完整版,
|
||||
what_others_say: 来看看玩家们对《图形工厂》的评价
|
||||
nothernlion_comment: 非常棒的有游戏,我的游戏过程充满乐趣,不觉时间飞逝。
|
||||
notch_comment: 哦,天哪!我真得该去睡了!但我想我刚刚搞定如何在游戏里面制造一台电脑出来。
|
||||
steam_review_comment: 这是一个不知不觉偷走你时间,但你并不会想要追回的游戏。非常烧脑的挑战,让我这样的完美主义者停不下来,总是希望可以再高效一些。
|
||||
|
@ -24,7 +23,7 @@ global:
|
|||
thousandsDivider: ","
|
||||
decimalSeparator: .
|
||||
suffix:
|
||||
thousands: 千
|
||||
thousands: 万
|
||||
millions: 百万
|
||||
billions: 亿万
|
||||
trillions: 兆
|
||||
|
@ -50,24 +49,24 @@ global:
|
|||
shift: SHIFT键
|
||||
space: 空格键
|
||||
loggingIn: 登录
|
||||
loadingResources: Downloading additional resources (<percentage> %)
|
||||
loadingResources: 正在下载资源(<percentage> %)
|
||||
discount: -<percentage>%
|
||||
discountSummerSale: SPECIAL PROMOTION! Offer ends 7 July
|
||||
discountSummerSale: 特别促销!优惠将于7月7日结束!
|
||||
demoBanners:
|
||||
title: 试玩版
|
||||
intro: 购买完整版以解锁所有游戏内容!
|
||||
playtimeDisclaimer: The full version contains more than <strong>20 hours of content</strong>.
|
||||
playerCount: <playerCount> players like you are currently playing shapez on Steam
|
||||
untilEndOfDemo: Until end of demo
|
||||
playtimeDisclaimerDownload: You can continue your savegame in the full version!
|
||||
Click <strong>here</strong> to download your savegame.
|
||||
titleV2: "Play the full version now for:"
|
||||
playtimeDisclaimer: 完整版本包括超过<strong>20小时游戏内容</strong>。
|
||||
playerCount: 目前有超过<playerCount>玩家正在一起玩图形工厂
|
||||
untilEndOfDemo: 直到DEMO结束
|
||||
playtimeDisclaimerDownload: 你可以在完成版中继续游戏进度!
|
||||
点击<strong>这里</strong>下载你的进度。
|
||||
titleV2: "游玩完整版本:"
|
||||
mainMenu:
|
||||
play: 开始游戏
|
||||
changelog: 更新日志
|
||||
importSavegame: 读取存档
|
||||
openSourceHint: 本游戏已开源!
|
||||
discordLink: 官方Discord服务器
|
||||
discordLink: 官方讨论区
|
||||
helpTranslate: 帮助我们翻译!
|
||||
browserWarning: 很抱歉, 本游戏在当前浏览器上可能运行缓慢! 使用 谷歌浏览器 或者购买完整版以得到更好的体验。
|
||||
savegameLevel: 第<x>关
|
||||
|
@ -75,22 +74,21 @@ mainMenu:
|
|||
continue: 继续游戏
|
||||
newGame: 新游戏
|
||||
madeBy: 作者:<author-link>
|
||||
subreddit: Reddit
|
||||
subreddit: 讨论区
|
||||
savegameUnnamed: 存档未命名
|
||||
puzzleMode: 谜题模式
|
||||
back: 返回
|
||||
puzzleDlcText: 新增谜题模式将带给您更多的游戏乐趣!
|
||||
puzzleDlcWishlist: 添加心愿单!
|
||||
puzzleDlcViewNow: View Dlc
|
||||
puzzleDlcViewNow: 查看资料片!
|
||||
mods:
|
||||
title: Active Mods
|
||||
warningPuzzleDLC: Playing the Puzzle DLC is not possible with mods. Please
|
||||
disable all mods to play the DLC.
|
||||
playingFullVersion: You are now playing the full version!
|
||||
logout: Logout
|
||||
noActiveSavegames: No active savegames found - Click play to start a new game!
|
||||
playFullVersionV2: Bough shapez on Steam? Play the full version in your Browser!
|
||||
playFullVersionStandalone: You can now also play the full version in your Browser!
|
||||
title: 激活模组
|
||||
warningPuzzleDLC: DLC谜题挑战者与模组不兼容。请屏蔽所有模组再游玩DLC。
|
||||
playingFullVersion: 你现在正在游玩完整版本游戏!
|
||||
logout: 登出
|
||||
noActiveSavegames: 未找到游戏存档 - 点击开始游戏开始一局新游戏!
|
||||
playFullVersionV2: 在浏览器中也能游玩完整版本!
|
||||
playFullVersionStandalone: 在浏览器中也能游玩完整版本!
|
||||
dialogs:
|
||||
buttons:
|
||||
ok: 确认
|
||||
|
@ -99,7 +97,7 @@ dialogs:
|
|||
later: 以后
|
||||
restart: 重新开始
|
||||
reset: 重置
|
||||
getStandalone: 获取完整版
|
||||
getStandalone: 获取完整版!
|
||||
deleteGame: 我没疯!我知道我在做什么!
|
||||
viewUpdate: 查看更新
|
||||
showUpgrades: 显示设施升级
|
||||
|
@ -109,19 +107,19 @@ dialogs:
|
|||
playOffline: 离线游戏
|
||||
importSavegameError:
|
||||
title: 读取错误
|
||||
text: 未能读取您的存档:
|
||||
text: 未能读取您的存档!
|
||||
importSavegameSuccess:
|
||||
title: 读取成功
|
||||
text: 存档被成功读取
|
||||
text: 存档被成功读取!
|
||||
gameLoadFailure:
|
||||
title: 存档损坏
|
||||
text: 未能读取您的存档:
|
||||
text: 未能读取您的存档!
|
||||
confirmSavegameDelete:
|
||||
title: 确认删除
|
||||
text: 您确定要删除这个游戏吗?<br><br> '<savegameName>' 等级 <savegameLevel><br><br> 该操作无法回退!
|
||||
text: 您确定要删除这个游戏吗?<br><br> "<savegameName>" 等级 <savegameLevel><br><br> 该操作无法回退!
|
||||
savegameDeletionError:
|
||||
title: 删除失败
|
||||
text: 未能删除您的存档
|
||||
text: 未能删除您的存档!
|
||||
restartRequired:
|
||||
title: 需要重启游戏
|
||||
text: 您需要重启游戏以应用变更的设置。
|
||||
|
@ -179,24 +177,24 @@ dialogs:
|
|||
editSignal:
|
||||
title: 设置信号
|
||||
descItems: "选择一个预定义的项目:"
|
||||
descShortKey: ... 或者输入图形的 <strong>短代码</strong> (您可以 <link>点击这里</link> 生成短代码)
|
||||
descShortKey: ... 或者输入图形的 <strong>短代码</strong> (您可以 <link>点击这里</link> 生成短代码!)
|
||||
renameSavegame:
|
||||
title: 重命名游戏存档
|
||||
desc: 您可以在此重命名游戏存档。
|
||||
tutorialVideoAvailable:
|
||||
title: 教程
|
||||
desc: 这个关卡有视频攻略! 您想查看这个视频攻略?
|
||||
desc: 这个关卡有视频攻略! 您想查看这个视频攻略吗?
|
||||
tutorialVideoAvailableForeignLanguage:
|
||||
title: 教程
|
||||
desc: 这个关卡有英语版本的视频攻略! 您想查看这个视频攻略吗??
|
||||
desc: 这个关卡有英语版本的视频攻略! 您想查看这个视频攻略吗?
|
||||
editConstantProducer:
|
||||
title: 设置项目
|
||||
puzzleLoadFailed:
|
||||
title: 谜题载入失败
|
||||
desc: 谜题未能载入:
|
||||
desc: 谜题未能载入!
|
||||
submitPuzzle:
|
||||
title: 提交谜题
|
||||
descName: 为您的谜题命名:
|
||||
descName: 为您的谜题命名!
|
||||
descIcon: 请输入唯一的短代码,它将作为您的谜题图标显示(您可以在<link>这里</link>生成,或者从以下随机推荐的图形中选择一个):
|
||||
placeholderName: 谜题标题
|
||||
puzzleResizeBadBuildings:
|
||||
|
@ -204,16 +202,16 @@ dialogs:
|
|||
desc: 由于某些设施将会超出区域范围,因此您无法将区域变得更小。
|
||||
puzzleLoadError:
|
||||
title: 谜题出错!
|
||||
desc: 谜题未能载入:
|
||||
desc: 谜题未能载入!
|
||||
offlineMode:
|
||||
title: 离线模式
|
||||
desc: 无法访问服务器,所以游戏以离线模式进行。请确认您的互联网访问正常。
|
||||
puzzleDownloadError:
|
||||
title: 下载出错!
|
||||
desc: 无法下载谜题:
|
||||
desc: 无法下载谜题!
|
||||
puzzleSubmitError:
|
||||
title: 提交出错!
|
||||
desc: 无法提交谜题:
|
||||
desc: 无法提交谜题!
|
||||
puzzleSubmitOk:
|
||||
title: 谜题成功发布!
|
||||
desc: 恭喜!您的谜题已经成功发布,其他玩家已经可以玩到。您可以在“我的谜题”中找到自己已发布的谜题。
|
||||
|
@ -237,7 +235,7 @@ dialogs:
|
|||
desc: 此谜已被标记!
|
||||
puzzleReportError:
|
||||
title: 上报失败
|
||||
desc: 无法处理您的上报:
|
||||
desc: 无法处理您的上报!
|
||||
puzzleLoadShortKey:
|
||||
title: 输入短代码
|
||||
desc: 输入谜题的短代码并载入。
|
||||
|
@ -245,25 +243,19 @@ dialogs:
|
|||
title: 删除谜题吗?
|
||||
desc: 您是否确认删除 '<title>'?删除谜题后将无法恢复!
|
||||
modsDifference:
|
||||
title: Mod Warning
|
||||
desc: The currently installed mods differ from the mods the savegame was created
|
||||
with. This might cause the savegame to break or not load at all. Are
|
||||
you sure you want to continue?
|
||||
missingMods: Missing Mods
|
||||
newMods: Newly installed Mods
|
||||
title: 模组警告
|
||||
desc: 当前安装的模组与存档中的模组不同。这可能会导致存档无法读取或损坏。您确定要继续吗?
|
||||
missingMods: 模组缺失
|
||||
newMods: 最新安装的模组
|
||||
resourceLoadFailed:
|
||||
title: Resources failed to load
|
||||
demoLinkText: shapez demo on Steam
|
||||
descWeb: "One ore more resources could not be loaded. Make sure you have a
|
||||
stable internet connection and try again. If this still doesn't
|
||||
work, make sure to also disable any browser extensions (including
|
||||
adblockers).<br><br> As an alternative, you can also play the
|
||||
<demoOnSteamLinkText>. <br><br> Error Message:"
|
||||
descSteamDemo: "One ore more resources could not be loaded. Try restarting the
|
||||
game - If that does not help, try reinstalling and verifying the
|
||||
game files via Steam. <br><br> Error Message:"
|
||||
title: 资源加载错误
|
||||
demoLinkText: DEMO
|
||||
descWeb:
|
||||
"一个或更多资源无法加载。请确保您的网络处于正常连接状态。如果仍旧出现问题,请确保已关闭所有浏览器插件(包括屏蔽广告插件)。<br><br>
|
||||
作为一种选择,您也可以玩<demoOnSteamLinkText>。 <br><br>错误信息:"
|
||||
descSteamDemo: "一个或更多资源无法加载。可以试着重启游戏 - 如果依旧不行,请试下重新安装或验证完整性。 <br><br>错误信息:"
|
||||
steamSsoError:
|
||||
title: Full Version Logout
|
||||
title: 登出完成版本
|
||||
desc: You have been logged out from the Full Browser Version since either your
|
||||
network connection is unstable or you are playing on another
|
||||
device.<br><br> Please make sure you don't have shapez open in any
|
||||
|
@ -358,7 +350,9 @@ ingame:
|
|||
interactiveTutorial:
|
||||
title: 新手教程
|
||||
hints:
|
||||
1_1_extractor: 在<strong>圆形</strong>上放置一个<strong>开采器</strong>来获取圆形!<br><br>提示:<strong>按下鼠标左键</strong>选中<strong>开采器</strong>
|
||||
1_1_extractor:
|
||||
亲爱的玩家,欢迎来到<strong>《图形工厂》<strong>!在这里你可以通过创造各种图形设施与传送带模拟流水线生产,尽情发挥创造力,创办属于自己的工厂!<br><br>
|
||||
在<strong>圆形<strong>上放置一个<strong>开采器</strong>来获取圆形!<br><br>提示:<strong>按下鼠标左键</strong>选中<strong>开采器</strong>
|
||||
1_2_conveyor: 用<strong>传送带</strong>将您的开采器连接到中心基地上!<br><br>提示:选中<strong>传送带</strong>后<strong>按下鼠标左键可拖动</strong>布置传送带!
|
||||
1_3_expand:
|
||||
您可以放置更多的<strong>开采器</strong>和<strong>传送带</strong>来更有效率地完成关卡目标。<br><br>
|
||||
|
@ -400,7 +394,7 @@ ingame:
|
|||
watermark:
|
||||
title: 试玩版
|
||||
desc: 点击这里了解完整版内容
|
||||
get_on_steam: 在Steam商城购买
|
||||
get_on_steam: 购买完整版!
|
||||
standaloneAdvantages:
|
||||
no_thanks: 不需要,谢谢
|
||||
points:
|
||||
|
@ -426,11 +420,11 @@ ingame:
|
|||
title: 成就
|
||||
desc: 挑战全成就解锁!
|
||||
mods:
|
||||
title: Modding support!
|
||||
desc: Over 80 mods available!
|
||||
titleV2: "Get the full version now on Steam to unlock:"
|
||||
titleExpiredV2: Demo completed!
|
||||
titleEnjoyingDemo: Enjoy the demo?
|
||||
title: 支持模组!
|
||||
desc: 超过80个可用模组!
|
||||
titleV2: "获取完整版解锁:"
|
||||
titleExpiredV2: DEMO已通关!
|
||||
titleEnjoyingDemo: DEMO是否让您满意?
|
||||
puzzleEditorSettings:
|
||||
zoneTitle: 区域
|
||||
zoneWidth: 宽度
|
||||
|
@ -452,7 +446,7 @@ ingame:
|
|||
- 6.谜题发布后,<strong>所有设施都将被拆除</strong>,除了<strong>常量生成器</strong>和<strong>目标接收器</strong>。然后,等着其他玩家对您创造的谜题发起挑战吧!
|
||||
puzzleCompletion:
|
||||
title: 谜题挑战成功!
|
||||
titleLike: 喜欢此谜题的话,请为它点赞:
|
||||
titleLike: 喜欢此谜题的话,请为它点赞。
|
||||
titleRating: 您觉得此谜题难度如何?
|
||||
titleRatingDesc: 您的评分将帮助作者在未来创作出更好的谜题!
|
||||
continueBtn: 继续游戏
|
||||
|
@ -542,16 +536,16 @@ buildings:
|
|||
deliver: 交付
|
||||
toUnlock: 解锁
|
||||
levelShortcut: 关卡
|
||||
endOfDemo: 试玩版结束
|
||||
endOfDemo: 试玩版结束!
|
||||
wire:
|
||||
default:
|
||||
name: 电线
|
||||
description: 可用来传输<strong>信号<strong>,信号可以是物品,颜色或者开关值(0或1)。
|
||||
不同颜色的<strong>电线</strong>不会互相连接
|
||||
不同颜色的<strong>电线</strong>不会互相连接。
|
||||
second:
|
||||
name: 电线
|
||||
description: 可用来传输<strong>信号<strong>,信号可以是物品,颜色或者开关值(0或1)。
|
||||
不同颜色的<strong>电线</strong>不会互相连接
|
||||
不同颜色的<strong>电线</strong>不会互相连接。
|
||||
balancer:
|
||||
default:
|
||||
name: 平衡器
|
||||
|
@ -640,7 +634,7 @@ buildings:
|
|||
description: 模拟将右侧<strong>图形</strong>叠在左侧<strong>图形</strong>上。
|
||||
painter:
|
||||
name: 模拟上色器
|
||||
description: 模拟使用右侧输入的<strong>颜色</strong>给底部输入的<strong>图形</strong>上色
|
||||
description: 模拟使用右侧输入的<strong>颜色</strong>给底部输入的<strong>图形</strong>上色。
|
||||
item_producer:
|
||||
default:
|
||||
name: 物品生成器
|
||||
|
@ -750,7 +744,7 @@ storyRewards:
|
|||
reward_logic_gates:
|
||||
title: 逻辑门
|
||||
desc: 您解锁了<strong>逻辑门</strong>!它们是个好东西!<br>
|
||||
您可以用它们来进行'与,或,非,异或'操作。<br><br>作为奖励,我还给您解锁了<strong>晶体管</strong>!
|
||||
您可以用它们来进行"与,或,非,异或"操作。<br><br>作为奖励,我还给您解锁了<strong>晶体管</strong>!
|
||||
reward_virtual_processing:
|
||||
title: 模拟处理器
|
||||
desc: 我刚刚给了一大堆新设施,让您可以<strong>模拟形状的处理过程</strong>!<br>
|
||||
|
@ -769,7 +763,7 @@ storyRewards:
|
|||
您也可以输入开关值(1 / 0)信号来激活或者禁用它。
|
||||
reward_demo_end:
|
||||
title: 试玩结束
|
||||
desc: 恭喜!您已经通关了试玩版本! <br>更多挑战,请至Steam商城购买完整版!谢谢支持!
|
||||
desc: 恭喜!您已经通关了试玩版本! <br>更多挑战,请购买完整版!谢谢支持!
|
||||
settings:
|
||||
title: 设置
|
||||
categories:
|
||||
|
@ -870,10 +864,10 @@ settings:
|
|||
description: 每一类设施都会记住各自上一次的旋转方向。如果您经常在不同设施类型之间切换,这个设置会让游戏操控更加便捷。
|
||||
soundVolume:
|
||||
title: 音效音量
|
||||
description: 设置音效的音量
|
||||
description: 设置音效的音量。
|
||||
musicVolume:
|
||||
title: 音乐音量
|
||||
description: 设置音乐的音量
|
||||
description: 设置音乐的音量。
|
||||
lowQualityMapResources:
|
||||
title: 低质量地图资源
|
||||
description: 放大时简化地图上资源的渲染以提高性能。开启甚至会让画面看起来更干净,低配置电脑玩家建议开启!
|
||||
|
@ -921,7 +915,7 @@ keybindings:
|
|||
massSelect: 批量选择
|
||||
buildings: 设施快捷键
|
||||
placementModifiers: 放置设施修饰键
|
||||
mods: Provided by Mods
|
||||
mods: 由模组提供
|
||||
mappings:
|
||||
confirm: 确认
|
||||
back: 返回
|
||||
|
@ -947,7 +941,7 @@ keybindings:
|
|||
painter: 上色器
|
||||
trash: 垃圾桶
|
||||
rotateWhilePlacing: 顺时针旋转
|
||||
rotateInverseModifier: "修饰键: 改为逆时针旋转"
|
||||
rotateInverseModifier: "修饰键: 改为逆时针旋转。"
|
||||
cycleBuildingVariants: 切换所选择设施变体
|
||||
confirmMassDelete: 确认批量删除
|
||||
cycleBuildings: 切换所选择设施
|
||||
|
@ -962,7 +956,7 @@ keybindings:
|
|||
exportScreenshot: 导出截图
|
||||
mapMoveFaster: 快速移动
|
||||
lockBeltDirection: 启用传送带规划
|
||||
switchDirectionLockSide: 规划器:换边
|
||||
switchDirectionLockSide: 规划器:换边。
|
||||
pipette: 吸取器
|
||||
menuClose: 关闭菜单
|
||||
switchLayers: 切换层
|
||||
|
@ -981,7 +975,7 @@ keybindings:
|
|||
analyzer: 图形分析器
|
||||
comparator: 比较器
|
||||
item_producer: 物品生产器 (沙盒模式)
|
||||
copyWireValue: 电线:复制指定电线上的值
|
||||
copyWireValue: 电线:复制指定电线上的值。
|
||||
rotateToUp: 向上旋转
|
||||
rotateToDown: 向下旋转
|
||||
rotateToRight: 向右旋转
|
||||
|
@ -990,20 +984,14 @@ keybindings:
|
|||
goal_acceptor: 目标接收器
|
||||
block: 方块
|
||||
massSelectClear: 清除传送带
|
||||
showShapeTooltip: 显示图形输出提示
|
||||
showShapeTooltip: 显示图形输出提示。
|
||||
about:
|
||||
title: 关于游戏
|
||||
body: >-
|
||||
本游戏由 <a href="https://github.com/tobspr" target="_blank">Tobias
|
||||
Springer</a>(我)开发,并且已经开源。<br><br>
|
||||
|
||||
如果您想参与开发,请查看 <a href="<githublink>" target="_blank">shapez.io on github</a>。<br><br>
|
||||
|
||||
这个游戏的开发获得了 Discord 社区内热情玩家的巨大支持。诚挚邀请您加入我们的 <a href="<discordlink>" target="_blank">Discord 服务器</a>!<br><br>
|
||||
|
||||
本游戏的音乐由 <a href="https://soundcloud.com/pettersumelius" target="_blank">Peppsen</a> 制作——他是个很棒的伙伴。<br><br>
|
||||
|
||||
最后,我想感谢我最好的朋友 <a href="https://github.com/niklas-dahl" target="_blank">Niklas</a> ——如果没有他的《异星工厂》(factorio)带给我的体验和启发,《异形工厂》(shapez.io)将不会存在。
|
||||
body: |-
|
||||
本游戏由托比亚斯开发,并且已经开源。<br><br>
|
||||
这个游戏的开发获得了热情玩家的巨大支持。非常感谢!<br><br>
|
||||
本游戏的音乐由佩普森制作——他是个很棒的伙伴。<br><br>
|
||||
最后,我想感谢我最好的朋友尼可拉斯——如果没有他的《异星工厂》带给我的体验和启发,《图形工厂》将不会存在。
|
||||
changelog:
|
||||
title: 版本日志
|
||||
demo:
|
||||
|
@ -1067,7 +1055,7 @@ tips:
|
|||
- 这个游戏有很多设置可以提高游戏效率,请一定要了解一下!
|
||||
- 中心基地有个指向它所在方向的小指南指针!
|
||||
- 想清理传送带,可剪切那块区域然后将其在相同位置粘贴。
|
||||
- 按F4显示FPS。
|
||||
- 按F4显示帧数。
|
||||
- 按两次F4显示您鼠标和镜头所在的块。
|
||||
- 您可以点击被固定在屏幕左侧的图形来解除固定。
|
||||
- 您可以点击被固定在屏幕左侧的图形来解除固定。
|
||||
|
@ -1081,7 +1069,7 @@ puzzleMenu:
|
|||
validatingPuzzle: 验证谜题
|
||||
submittingPuzzle: 提交谜题
|
||||
noPuzzles: 暂无满足此部分条件的谜题。
|
||||
dlcHint: 如已购买DLC,请在您的Steam库中右键点击异形工厂,然后选择属性-DLC。
|
||||
dlcHint: 如已购买资料片,请在您的游戏库中右键点击图形工厂,然后选择属性-资料片。
|
||||
categories:
|
||||
levels: 关卡
|
||||
new: 最新
|
||||
|
@ -1127,7 +1115,7 @@ puzzleMenu:
|
|||
autoComplete: 您的谜题已自动完成!请确认您的常量生成器没有直接向您的目标接收器进行传送。
|
||||
backendErrors:
|
||||
ratelimit: 您的操作太频繁了。请稍等。
|
||||
invalid-api-key: 与后台通信失败,请尝试更新或重新启动游戏(无效的Api密钥)。
|
||||
invalid-api-key: 与后台通信失败,请尝试更新或重新启动游戏(无效的密钥)。
|
||||
unauthorized: 与后台通信失败,请尝试更新或重新启动游戏(未经授权)。
|
||||
bad-token: 与后台通信失败,请尝试更新或重新启动游戏(令牌错误)。
|
||||
bad-id: 谜题标识符无效。
|
||||
|
@ -1145,24 +1133,19 @@ backendErrors:
|
|||
bad-payload: 此请求包含无效数据。
|
||||
bad-building-placement: 您的谜题包含放置错误的设施。
|
||||
timeout: 请求超时。
|
||||
too-many-likes-already: 您的谜题已经得到了许多玩家的赞赏。如果您仍然希望删除它,请联系support@shapez.io!
|
||||
too-many-likes-already: 您的谜题已经得到了许多玩家的赞赏。如果您仍然希望删除它,请联系客服!
|
||||
no-permission: 您没有执行此操作的权限。
|
||||
mods:
|
||||
title: Mods
|
||||
author: Author
|
||||
version: Version
|
||||
modWebsite: Website
|
||||
openFolder: Open Mods Folder
|
||||
folderOnlyStandalone: Opening the mod folder is only possible when running the standalone.
|
||||
browseMods: Browse Mods
|
||||
modsInfo: To install and manage mods, copy them to the mods folder within the
|
||||
game directory. You can also use the 'Open Mods Folder' button on the
|
||||
top right.
|
||||
noModSupport: You need the standalone version on Steam to install mods.
|
||||
title: 模组
|
||||
author: 作者
|
||||
version: 版本
|
||||
modWebsite: 网站
|
||||
openFolder: 打开模组文件夹
|
||||
folderOnlyStandalone: 只有在运行完整版时才能打开模组文件夹
|
||||
browseMods: 浏览模组
|
||||
modsInfo: 要安装和管理模组,请把模组文件复制到游戏模组文件夹中。使用右上角的“打开模组文件夹”按钮可快速。
|
||||
noModSupport: 你需要完整版才能安装模组。
|
||||
togglingComingSoon:
|
||||
title: Coming Soon
|
||||
description: Enabling or disabling mods is currently only possible by copying
|
||||
the mod file from or to the mods/ folder. However, being able to
|
||||
toggle them here is planned for a future update!
|
||||
browserNoSupport: Due to browser restrictions it is currently only possible to
|
||||
install mods in the Steam version - Sorry!
|
||||
title: 即将开放
|
||||
description: 当前只能通过复制、移除模组文件才能启用/安装模组。不久的将来,可以通过界面来启用/安装模组,敬请期待!
|
||||
browserNoSupport: 由于浏览器限制,目前网页版无法使用模组 - 非常抱歉!
|
||||
|
|
Loading…
Reference in New Issue