mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-17 23:08:36 +02:00
merg dev
This commit is contained in:
@@ -6,18 +6,18 @@ async function enableAchievementsInterval(window) {
|
||||
// If the Steam API could not be initialized on game start, we'll abort this.
|
||||
if (global.greenworksError) return;
|
||||
|
||||
// This is backward but the game fills in an array called `document.achievements` and we retrieve it from
|
||||
// This is backward but the game fills in an array called `document.achievements` and we retrieve it from
|
||||
// here. Hey if it works it works.
|
||||
const steamAchievements = greenworks.getAchievementNames();
|
||||
log.silly(`All Steam achievements ${JSON.stringify(steamAchievements)}`);
|
||||
const playerAchieved = (await Promise.all(steamAchievements.map(checkSteamAchievement))).filter(name => !!name);
|
||||
const playerAchieved = (await Promise.all(steamAchievements.map(checkSteamAchievement))).filter((name) => !!name);
|
||||
log.debug(`Player has Steam achievements ${JSON.stringify(playerAchieved)}`);
|
||||
const intervalID = setInterval(async () => {
|
||||
try {
|
||||
const playerAchievements = await window.webContents.executeJavaScript("document.achievements");
|
||||
for (const ach of playerAchievements) {
|
||||
if (!steamAchievements.includes(ach)) continue; // Don't try activating achievements that don't exist Steam-side
|
||||
if (playerAchieved.includes(ach)) continue; // Don't spam achievements that have already been recorded
|
||||
if (playerAchieved.includes(ach)) continue; // Don't spam achievements that have already been recorded
|
||||
log.info(`Granting Steam achievement ${ach}`);
|
||||
greenworks.activateAchievement(ach, () => undefined);
|
||||
playerAchieved.push(ach);
|
||||
@@ -26,7 +26,7 @@ async function enableAchievementsInterval(window) {
|
||||
log.error(error);
|
||||
|
||||
// The interval probably did not get cleared after a window kill
|
||||
log.warn('Clearing achievements timer');
|
||||
log.warn("Clearing achievements timer");
|
||||
clearInterval(intervalID);
|
||||
return;
|
||||
}
|
||||
@@ -36,10 +36,14 @@ async function enableAchievementsInterval(window) {
|
||||
|
||||
function checkSteamAchievement(name) {
|
||||
return new Promise((resolve) => {
|
||||
greenworks.getAchievement(name, playerHas => resolve(playerHas ? name : ""), err => {
|
||||
log.warn(`Failed to get Steam achievement ${name} status: ${err}`);
|
||||
resolve("");
|
||||
});
|
||||
greenworks.getAchievement(
|
||||
name,
|
||||
(playerHas) => resolve(playerHas ? name : ""),
|
||||
(err) => {
|
||||
log.warn(`Failed to get Steam achievement ${name} status: ${err}`);
|
||||
resolve("");
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,5 +54,6 @@ function disableAchievementsInterval(window) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
enableAchievementsInterval, disableAchievementsInterval
|
||||
}
|
||||
enableAchievementsInterval,
|
||||
disableAchievementsInterval,
|
||||
};
|
||||
|
||||
@@ -12,25 +12,27 @@ async function initialize(win) {
|
||||
window = win;
|
||||
server = http.createServer(async function (req, res) {
|
||||
let body = "";
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
req.on("data", (chunk) => {
|
||||
body += chunk.toString(); // convert Buffer to string
|
||||
});
|
||||
|
||||
req.on("end", async () => {
|
||||
const providedToken = req.headers?.authorization?.replace('Bearer ', '') ?? '';
|
||||
const providedToken = req.headers?.authorization?.replace("Bearer ", "") ?? "";
|
||||
const isValid = providedToken === getAuthenticationToken();
|
||||
if (isValid) {
|
||||
log.debug('Valid authentication token');
|
||||
log.debug("Valid authentication token");
|
||||
} else {
|
||||
log.log('Invalid authentication token');
|
||||
log.log("Invalid authentication token");
|
||||
res.writeHead(401);
|
||||
|
||||
res.end(JSON.stringify({
|
||||
success: false,
|
||||
msg: 'Invalid authentication token'
|
||||
}));
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
msg: "Invalid authentication token",
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,16 +42,18 @@ async function initialize(win) {
|
||||
} catch (error) {
|
||||
log.warn(`Invalid body data`);
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify({
|
||||
success: false,
|
||||
msg: 'Invalid body data'
|
||||
}));
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
msg: "Invalid body data",
|
||||
}),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let result;
|
||||
switch(req.method) {
|
||||
switch (req.method) {
|
||||
// Request files
|
||||
case "GET":
|
||||
result = await window.webContents.executeJavaScript(`document.getFiles()`);
|
||||
@@ -62,10 +66,12 @@ async function initialize(win) {
|
||||
if (!data) {
|
||||
log.warn(`Invalid script update request - No data`);
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify({
|
||||
success: false,
|
||||
msg: 'Invalid script update request - No data'
|
||||
}));
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
msg: "Invalid script update request - No data",
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,19 +90,20 @@ async function initialize(win) {
|
||||
log.warn(`Api Server Error`, result.msg);
|
||||
}
|
||||
|
||||
res.end(JSON.stringify({
|
||||
success: result.res,
|
||||
msg: result.msg,
|
||||
data: result.data
|
||||
}));
|
||||
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
success: result.res,
|
||||
msg: result.msg,
|
||||
data: result.data,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const autostart = config.get('autostart', false);
|
||||
const autostart = config.get("autostart", false);
|
||||
if (autostart) {
|
||||
try {
|
||||
await enable()
|
||||
await enable();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
@@ -105,15 +112,14 @@ async function initialize(win) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
||||
function enable() {
|
||||
if (isListening()) {
|
||||
log.warn('API server already listening');
|
||||
log.warn("API server already listening");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const port = config.get('port', 9990);
|
||||
const host = config.get('host', '127.0.0.1');
|
||||
const port = config.get("port", 9990);
|
||||
const host = config.get("host", "127.0.0.1");
|
||||
log.log(`Starting http server on port ${port} - listening on ${host}`);
|
||||
|
||||
// https://stackoverflow.com/a/62289870
|
||||
@@ -125,13 +131,10 @@ function enable() {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
server.once('error', (err) => {
|
||||
server.once("error", (err) => {
|
||||
if (!startFinished) {
|
||||
startFinished = true;
|
||||
console.log(
|
||||
'There was an error starting the server in the error listener:',
|
||||
err
|
||||
);
|
||||
console.log("There was an error starting the server in the error listener:", err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
@@ -140,11 +143,11 @@ function enable() {
|
||||
|
||||
function disable() {
|
||||
if (!isListening()) {
|
||||
log.warn('API server not listening');
|
||||
log.warn("API server not listening");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
log.log('Stopping http server');
|
||||
log.log("Stopping http server");
|
||||
return server.close();
|
||||
}
|
||||
|
||||
@@ -162,31 +165,35 @@ function isListening() {
|
||||
|
||||
function toggleAutostart() {
|
||||
const newValue = !isAutostart();
|
||||
config.set('autostart', newValue);
|
||||
config.set("autostart", newValue);
|
||||
log.log(`New autostart value is '${newValue}'`);
|
||||
}
|
||||
|
||||
function isAutostart() {
|
||||
return config.get('autostart');
|
||||
return config.get("autostart");
|
||||
}
|
||||
|
||||
function getAuthenticationToken() {
|
||||
const token = config.get('token');
|
||||
const token = config.get("token");
|
||||
if (token) return token;
|
||||
|
||||
const newToken = generateToken();
|
||||
config.set('token', newToken);
|
||||
config.set("token", newToken);
|
||||
return newToken;
|
||||
}
|
||||
|
||||
function generateToken() {
|
||||
const buffer = crypto.randomBytes(48);
|
||||
return buffer.toString('base64')
|
||||
return buffer.toString("base64");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initialize,
|
||||
enable, disable, toggleServer,
|
||||
toggleAutostart, isAutostart,
|
||||
getAuthenticationToken, isListening,
|
||||
}
|
||||
enable,
|
||||
disable,
|
||||
toggleServer,
|
||||
toggleAutostart,
|
||||
isAutostart,
|
||||
getAuthenticationToken,
|
||||
isListening,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta charset="utf-8" />
|
||||
<title>Bitburner</title>
|
||||
<style>
|
||||
body {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta charset="utf-8" />
|
||||
<title>Bitburner</title>
|
||||
<style>
|
||||
body {
|
||||
|
||||
@@ -15,19 +15,20 @@ const debug = process.argv.includes("--debug");
|
||||
|
||||
async function createWindow(killall) {
|
||||
const setStopProcessHandler = global.app_handlers.stopProcess;
|
||||
app.setAppUserModelId("Bitburner");
|
||||
|
||||
let icon;
|
||||
if (process.platform == 'linux') {
|
||||
icon = path.join(__dirname, 'icon.png');
|
||||
if (process.platform == "linux") {
|
||||
icon = path.join(__dirname, "icon.png");
|
||||
}
|
||||
|
||||
const tracker = windowTracker('main');
|
||||
const tracker = windowTracker("main");
|
||||
const window = new BrowserWindow({
|
||||
icon,
|
||||
show: false,
|
||||
backgroundThrottling: false,
|
||||
backgroundColor: "#000000",
|
||||
title: 'Bitburner',
|
||||
title: "Bitburner",
|
||||
x: tracker.state.x,
|
||||
y: tracker.state.y,
|
||||
width: tracker.state.width,
|
||||
@@ -36,7 +37,7 @@ async function createWindow(killall) {
|
||||
minHeight: 400,
|
||||
webPreferences: {
|
||||
nativeWindowOpen: true,
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -51,12 +52,12 @@ async function createWindow(killall) {
|
||||
|
||||
window.webContents.on("new-window", async function (e, url) {
|
||||
// Let's make sure sure we have a proper url
|
||||
let parsedUrl
|
||||
let parsedUrl;
|
||||
try {
|
||||
parsedUrl = new URL(url);
|
||||
} catch (_) {
|
||||
// This is an invalid url, let's just do nothing
|
||||
log.warn(`Invalid url found: ${url}`)
|
||||
log.warn(`Invalid url found: ${url}`);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
@@ -73,11 +74,13 @@ async function createWindow(killall) {
|
||||
|
||||
if (!isChild) {
|
||||
// If we're not relative to our app's path let's abort
|
||||
log.warn(`Requested path ${filePath.dir}${path.sep}${filePath.base} is not relative to the app: ${appPath.dir}${path.sep}${appPath.base}`)
|
||||
log.warn(
|
||||
`Requested path ${filePath.dir}${path.sep}${filePath.base} is not relative to the app: ${appPath.dir}${path.sep}${appPath.base}`,
|
||||
);
|
||||
e.preventDefault();
|
||||
} else if (!fileExists) {
|
||||
// If the file does not exist let's abort
|
||||
log.warn(`Requested path ${filePath.dir}${path.sep}${filePath.base} does not exist`)
|
||||
log.warn(`Requested path ${filePath.dir}${path.sep}${filePath.base} does not exist`);
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
@@ -89,7 +92,7 @@ async function createWindow(killall) {
|
||||
let urlToOpen = parsedUrl.toString();
|
||||
if (parsedUrl.search) {
|
||||
log.log(`Cannot open a path with parameters: ${parsedUrl.search}`);
|
||||
urlToOpen = urlToOpen.replace(parsedUrl.search, '');
|
||||
urlToOpen = urlToOpen.replace(parsedUrl.search, "");
|
||||
// It would be possible to launch an URL with parameter using this, but it would mess up the process again...
|
||||
// const escapedUri = parsedUrl.href.replace('&', '^&');
|
||||
// cp.spawn("cmd.exe", ["/c", "start", escapedUri], { detached: true, stdio: "ignore" });
|
||||
|
||||
@@ -3,32 +3,26 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
// The source code can be found in https://github.com/greenheartgames/greenworks
|
||||
var fs = require('fs');
|
||||
var fs = require("fs");
|
||||
|
||||
var greenworks;
|
||||
|
||||
if (process.platform == 'darwin') {
|
||||
if (process.arch == 'x64')
|
||||
greenworks = require('./lib/greenworks-osx64');
|
||||
else if (process.arch == 'ia32')
|
||||
greenworks = require('./lib/greenworks-osx32');
|
||||
} else if (process.platform == 'win32') {
|
||||
if (process.arch == 'x64')
|
||||
greenworks = require('./lib/greenworks-win64');
|
||||
else if (process.arch == 'ia32')
|
||||
greenworks = require('./lib/greenworks-win32');
|
||||
} else if (process.platform == 'linux') {
|
||||
if (process.arch == 'x64')
|
||||
greenworks = require('./lib/greenworks-linux64');
|
||||
else if (process.arch == 'ia32')
|
||||
greenworks = require('./lib/greenworks-linux32');
|
||||
if (process.platform == "darwin") {
|
||||
if (process.arch == "x64") greenworks = require("./lib/greenworks-osx64");
|
||||
else if (process.arch == "ia32") greenworks = require("./lib/greenworks-osx32");
|
||||
} else if (process.platform == "win32") {
|
||||
if (process.arch == "x64") greenworks = require("./lib/greenworks-win64");
|
||||
else if (process.arch == "ia32") greenworks = require("./lib/greenworks-win32");
|
||||
} else if (process.platform == "linux") {
|
||||
if (process.arch == "x64") greenworks = require("./lib/greenworks-linux64");
|
||||
else if (process.arch == "ia32") greenworks = require("./lib/greenworks-linux32");
|
||||
}
|
||||
|
||||
function error_process(err, error_callback) {
|
||||
if (err && error_callback)
|
||||
error_callback(err);
|
||||
if (err && error_callback) error_callback(err);
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (greenworks) {
|
||||
greenworks.ugcGetItems = function (options, ugc_matching_type, ugc_query_type,
|
||||
success_callback, error_callback) {
|
||||
@@ -208,12 +202,279 @@ if (greenworks) {
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
greenworks.__proto__ = EventEmitter.prototype;
|
||||
EventEmitter.call(greenworks);
|
||||
=======
|
||||
greenworks.ugcGetItems = function (options, ugc_matching_type, ugc_query_type, success_callback, error_callback) {
|
||||
if (typeof options !== "object") {
|
||||
error_callback = success_callback;
|
||||
success_callback = ugc_query_type;
|
||||
ugc_query_type = ugc_matching_type;
|
||||
ugc_matching_type = options;
|
||||
options = {
|
||||
app_id: greenworks.getAppId(),
|
||||
page_num: 1,
|
||||
};
|
||||
}
|
||||
greenworks._ugcGetItems(options, ugc_matching_type, ugc_query_type, success_callback, error_callback);
|
||||
};
|
||||
|
||||
greenworks.ugcGetUserItems = function (
|
||||
options,
|
||||
ugc_matching_type,
|
||||
ugc_list_sort_order,
|
||||
ugc_list,
|
||||
success_callback,
|
||||
error_callback,
|
||||
) {
|
||||
if (typeof options !== "object") {
|
||||
error_callback = success_callback;
|
||||
success_callback = ugc_list;
|
||||
ugc_list = ugc_list_sort_order;
|
||||
ugc_list_sort_order = ugc_matching_type;
|
||||
ugc_matching_type = options;
|
||||
options = {
|
||||
app_id: greenworks.getAppId(),
|
||||
page_num: 1,
|
||||
};
|
||||
}
|
||||
greenworks._ugcGetUserItems(
|
||||
options,
|
||||
ugc_matching_type,
|
||||
ugc_list_sort_order,
|
||||
ugc_list,
|
||||
success_callback,
|
||||
error_callback,
|
||||
);
|
||||
};
|
||||
|
||||
greenworks.ugcSynchronizeItems = function (options, sync_dir, success_callback, error_callback) {
|
||||
if (typeof options !== "object") {
|
||||
error_callback = success_callback;
|
||||
success_callback = sync_dir;
|
||||
sync_dir = options;
|
||||
options = {
|
||||
app_id: greenworks.getAppId(),
|
||||
page_num: 1,
|
||||
};
|
||||
}
|
||||
greenworks._ugcSynchronizeItems(options, sync_dir, success_callback, error_callback);
|
||||
};
|
||||
|
||||
greenworks.publishWorkshopFile = function (
|
||||
options,
|
||||
file_path,
|
||||
image_path,
|
||||
title,
|
||||
description,
|
||||
success_callback,
|
||||
error_callback,
|
||||
) {
|
||||
if (typeof options !== "object") {
|
||||
error_callback = success_callback;
|
||||
success_callback = description;
|
||||
description = title;
|
||||
title = image_path;
|
||||
image_path = file_path;
|
||||
file_path = options;
|
||||
options = {
|
||||
app_id: greenworks.getAppId(),
|
||||
tags: [],
|
||||
};
|
||||
}
|
||||
greenworks._publishWorkshopFile(options, file_path, image_path, title, description, success_callback, error_callback);
|
||||
};
|
||||
|
||||
greenworks.updatePublishedWorkshopFile = function (
|
||||
options,
|
||||
published_file_handle,
|
||||
file_path,
|
||||
image_path,
|
||||
title,
|
||||
description,
|
||||
success_callback,
|
||||
error_callback,
|
||||
) {
|
||||
if (typeof options !== "object") {
|
||||
error_callback = success_callback;
|
||||
success_callback = description;
|
||||
description = title;
|
||||
title = image_path;
|
||||
image_path = file_path;
|
||||
file_path = published_file_handle;
|
||||
published_file_handle = options;
|
||||
options = {
|
||||
tags: [], // No tags are set
|
||||
};
|
||||
}
|
||||
greenworks._updatePublishedWorkshopFile(
|
||||
options,
|
||||
published_file_handle,
|
||||
file_path,
|
||||
image_path,
|
||||
title,
|
||||
description,
|
||||
success_callback,
|
||||
error_callback,
|
||||
);
|
||||
};
|
||||
|
||||
// An utility function for publish related APIs.
|
||||
// It processes remains steps after saving files to Steam Cloud.
|
||||
function file_share_process(file_name, image_name, next_process_func, error_callback, progress_callback) {
|
||||
if (progress_callback) progress_callback("Completed on saving files on Steam Cloud.");
|
||||
greenworks.fileShare(
|
||||
file_name,
|
||||
function () {
|
||||
greenworks.fileShare(
|
||||
image_name,
|
||||
function () {
|
||||
next_process_func();
|
||||
},
|
||||
function (err) {
|
||||
error_process(err, error_callback);
|
||||
},
|
||||
);
|
||||
},
|
||||
function (err) {
|
||||
error_process(err, error_callback);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Publishing user generated content(ugc) to Steam contains following steps:
|
||||
// 1. Save file and image to Steam Cloud.
|
||||
// 2. Share the file and image.
|
||||
// 3. publish the file to workshop.
|
||||
greenworks.ugcPublish = function (
|
||||
file_name,
|
||||
title,
|
||||
description,
|
||||
image_name,
|
||||
success_callback,
|
||||
error_callback,
|
||||
progress_callback,
|
||||
) {
|
||||
var publish_file_process = function () {
|
||||
if (progress_callback) progress_callback("Completed on sharing files.");
|
||||
greenworks.publishWorkshopFile(
|
||||
file_name,
|
||||
image_name,
|
||||
title,
|
||||
description,
|
||||
function (publish_file_id) {
|
||||
success_callback(publish_file_id);
|
||||
},
|
||||
function (err) {
|
||||
error_process(err, error_callback);
|
||||
},
|
||||
);
|
||||
};
|
||||
greenworks.saveFilesToCloud(
|
||||
[file_name, image_name],
|
||||
function () {
|
||||
file_share_process(file_name, image_name, publish_file_process, error_callback, progress_callback);
|
||||
},
|
||||
function (err) {
|
||||
error_process(err, error_callback);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// Update publish ugc steps:
|
||||
// 1. Save new file and image to Steam Cloud.
|
||||
// 2. Share file and images.
|
||||
// 3. Update published file.
|
||||
greenworks.ugcPublishUpdate = function (
|
||||
published_file_id,
|
||||
file_name,
|
||||
title,
|
||||
description,
|
||||
image_name,
|
||||
success_callback,
|
||||
error_callback,
|
||||
progress_callback,
|
||||
) {
|
||||
var update_published_file_process = function () {
|
||||
if (progress_callback) progress_callback("Completed on sharing files.");
|
||||
greenworks.updatePublishedWorkshopFile(
|
||||
published_file_id,
|
||||
file_name,
|
||||
image_name,
|
||||
title,
|
||||
description,
|
||||
function () {
|
||||
success_callback();
|
||||
},
|
||||
function (err) {
|
||||
error_process(err, error_callback);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
greenworks.saveFilesToCloud(
|
||||
[file_name, image_name],
|
||||
function () {
|
||||
file_share_process(file_name, image_name, update_published_file_process, error_callback, progress_callback);
|
||||
},
|
||||
function (err) {
|
||||
error_process(err, error_callback);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// Greenworks Utils APIs implmentation.
|
||||
greenworks.Utils.move = function (source_dir, target_dir, success_callback, error_callback) {
|
||||
fs.rename(source_dir, target_dir, function (err) {
|
||||
if (err) {
|
||||
if (error_callback) error_callback(err);
|
||||
return;
|
||||
}
|
||||
if (success_callback) success_callback();
|
||||
});
|
||||
};
|
||||
|
||||
greenworks.init = function () {
|
||||
if (this.initAPI()) return true;
|
||||
if (!this.isSteamRunning()) throw new Error("Steam initialization failed. Steam is not running.");
|
||||
var appId;
|
||||
try {
|
||||
appId = fs.readFileSync("steam_appid.txt", "utf8");
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
"Steam initialization failed. Steam is running," +
|
||||
"but steam_appid.txt is missing. Expected to find it in: " +
|
||||
require("path").resolve("steam_appid.txt"),
|
||||
);
|
||||
}
|
||||
if (!/^\d+ *\r?\n?$/.test(appId)) {
|
||||
throw new Error(
|
||||
"Steam initialization failed. " +
|
||||
"steam_appid.txt appears to be invalid; " +
|
||||
"it should contain a numeric ID: " +
|
||||
appId,
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
"Steam initialization failed, but Steam is running, " +
|
||||
"and steam_appid.txt is present and valid." +
|
||||
"Maybe that's not really YOUR app ID? " +
|
||||
appId.trim(),
|
||||
);
|
||||
};
|
||||
|
||||
var EventEmitter = require("events").EventEmitter;
|
||||
greenworks.__proto__ = EventEmitter.prototype;
|
||||
EventEmitter.call(greenworks);
|
||||
>>>>>>> dev
|
||||
|
||||
greenworks._steam_events.on = function () {
|
||||
greenworks.emit.apply(greenworks, arguments);
|
||||
};
|
||||
|
||||
<<<<<<< HEAD
|
||||
process.versions['greenworks'] = greenworks._version;
|
||||
}
|
||||
=======
|
||||
process.versions["greenworks"] = greenworks._version;
|
||||
>>>>>>> dev
|
||||
|
||||
module.exports = greenworks;
|
||||
|
||||
@@ -18,7 +18,7 @@ log.transports.console.level = config.get("console-log-level", "debug");
|
||||
log.catchErrors();
|
||||
log.info(`Started app: ${JSON.stringify(process.argv)}`);
|
||||
|
||||
process.on('uncaughtException', function () {
|
||||
process.on("uncaughtException", function () {
|
||||
// The exception will already have been logged by electron-log
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -67,42 +67,43 @@ function setStopProcessHandler(app, window, enabled) {
|
||||
// So we'll alert the player to close their browser.
|
||||
if (global.app_playerOpenedExternalLink) {
|
||||
await dialog.showMessageBox({
|
||||
title: 'Bitburner',
|
||||
message: 'You may have to close your browser to properly exit the game.',
|
||||
detail: 'Steam will keep tracking Bitburner as "Running" if any process started within the game is still running.' +
|
||||
' This includes launching an external link, which opens up your browser.',
|
||||
type: 'warning', buttons: ['OK']
|
||||
title: "Bitburner",
|
||||
message: "You may have to close your browser to properly exit the game.",
|
||||
detail:
|
||||
'Steam will keep tracking Bitburner as "Running" if any process started within the game is still running.' +
|
||||
" This includes launching an external link, which opens up your browser.",
|
||||
type: "warning",
|
||||
buttons: ["OK"],
|
||||
});
|
||||
}
|
||||
// We'll try to execute javascript on the page to see if we're stuck
|
||||
let canRunJS = false;
|
||||
window.webContents.executeJavaScript('window.stop(); document.close()', true)
|
||||
.then(() => canRunJS = true);
|
||||
window.webContents.executeJavaScript("window.stop(); document.close()", true).then(() => (canRunJS = true));
|
||||
setTimeout(() => {
|
||||
// Wait a few milliseconds to prevent a race condition before loading the exit screen
|
||||
window.webContents.stop();
|
||||
window.loadFile("exit.html")
|
||||
window.loadFile("exit.html");
|
||||
}, 20);
|
||||
|
||||
// Wait 200ms, if the promise has not yet resolved, let's crash the process since we're possibly in a stuck scenario
|
||||
setTimeout(() => {
|
||||
if (!canRunJS) {
|
||||
// We're stuck, let's crash the process
|
||||
log.warn('Forcefully crashing the renderer process');
|
||||
log.warn("Forcefully crashing the renderer process");
|
||||
window.webContents.forcefullyCrashRenderer();
|
||||
}
|
||||
|
||||
log.debug('Destroying the window');
|
||||
log.debug("Destroying the window");
|
||||
window.destroy();
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
const clearWindowHandler = () => {
|
||||
window = null;
|
||||
};
|
||||
|
||||
const stopProcessHandler = () => {
|
||||
log.info('Quitting the app...');
|
||||
log.info("Quitting the app...");
|
||||
app.isQuiting = true;
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
@@ -121,12 +122,12 @@ function setStopProcessHandler(app, window, enabled) {
|
||||
const restoreNewest = config.get("onload-restore-newest", true);
|
||||
if (restoreNewest && !isRestoreDisabled) {
|
||||
try {
|
||||
await storage.restoreIfNewerExists(window)
|
||||
await storage.restoreIfNewerExists(window);
|
||||
} catch (error) {
|
||||
log.error("Could not restore newer file", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const receivedDisableRestoreHandler = async (event, arg) => {
|
||||
if (!window) {
|
||||
@@ -140,7 +141,7 @@ function setStopProcessHandler(app, window, enabled) {
|
||||
isRestoreDisabled = false;
|
||||
log.debug("Re-enabling auto-restore");
|
||||
}, arg.duration);
|
||||
}
|
||||
};
|
||||
|
||||
const receivedGameSavedHandler = async (event, arg) => {
|
||||
if (!window) {
|
||||
@@ -164,38 +165,46 @@ function setStopProcessHandler(app, window, enabled) {
|
||||
log.debug(`Auto-save to cloud disabled for save game under ${minimumPlaytime}ms (${playtime}ms)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const saveToCloud = debounce(async (save) => {
|
||||
log.debug("Saving to Steam Cloud ...")
|
||||
try {
|
||||
const playerId = window.gameInfo.player.identifier;
|
||||
await storage.pushGameSaveToSteamCloud(save, playerId);
|
||||
log.silly("Saved Game to Steam Cloud");
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
utils.writeToast(window, "Could not save to Steam Cloud.", "error", 5000);
|
||||
}
|
||||
}, config.get("cloud-save-min-time", 1000 * 60 * 15), { leading: true });
|
||||
const saveToCloud = debounce(
|
||||
async (save) => {
|
||||
log.debug("Saving to Steam Cloud ...");
|
||||
try {
|
||||
const playerId = window.gameInfo.player.identifier;
|
||||
await storage.pushGameSaveToSteamCloud(save, playerId);
|
||||
log.silly("Saved Game to Steam Cloud");
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
utils.writeToast(window, "Could not save to Steam Cloud.", "error", 5000);
|
||||
}
|
||||
},
|
||||
config.get("cloud-save-min-time", 1000 * 60 * 15),
|
||||
{ leading: true },
|
||||
);
|
||||
|
||||
const saveToDisk = debounce(async (save, fileName) => {
|
||||
log.debug("Saving to Disk ...")
|
||||
try {
|
||||
const file = await storage.saveGameToDisk(window, { save, fileName });
|
||||
log.silly(`Saved Game to '${file.replaceAll('\\', '\\\\')}'`);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
utils.writeToast(window, "Could not save to disk", "error", 5000);
|
||||
}
|
||||
}, config.get("disk-save-min-time", 1000 * 60 * 5), { leading: true });
|
||||
const saveToDisk = debounce(
|
||||
async (save, fileName) => {
|
||||
log.debug("Saving to Disk ...");
|
||||
try {
|
||||
const file = await storage.saveGameToDisk(window, { save, fileName });
|
||||
log.silly(`Saved Game to '${file.replaceAll("\\", "\\\\")}'`);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
utils.writeToast(window, "Could not save to disk", "error", 5000);
|
||||
}
|
||||
},
|
||||
config.get("disk-save-min-time", 1000 * 60 * 5),
|
||||
{ leading: true },
|
||||
);
|
||||
|
||||
if (enabled) {
|
||||
log.debug("Adding closing handlers");
|
||||
ipcMain.on("push-game-ready", receivedGameReadyHandler);
|
||||
ipcMain.on("push-game-saved", receivedGameSavedHandler);
|
||||
ipcMain.on("push-disable-restore", receivedDisableRestoreHandler)
|
||||
ipcMain.on("push-disable-restore", receivedDisableRestoreHandler);
|
||||
window.on("closed", clearWindowHandler);
|
||||
window.on("close", closingWindowHandler)
|
||||
window.on("close", closingWindowHandler);
|
||||
app.on("window-all-closed", stopProcessHandler);
|
||||
} else {
|
||||
log.debug("Removing closing handlers");
|
||||
@@ -213,7 +222,7 @@ async function startWindow(noScript) {
|
||||
global.app_handlers = {
|
||||
stopProcess: setStopProcessHandler,
|
||||
createWindow: startWindow,
|
||||
}
|
||||
};
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
log.info("Application is ready!");
|
||||
@@ -231,7 +240,8 @@ app.whenReady().then(async () => {
|
||||
title: "Bitburner",
|
||||
message: "Could not connect to Steam",
|
||||
detail: `${global.greenworksError}\n\nYou won't be able to receive achievements until this is resolved and you restart the game.`,
|
||||
type: 'warning', buttons: ['OK']
|
||||
type: "warning",
|
||||
buttons: ["OK"],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
114
electron/menu.js
114
electron/menu.js
@@ -71,7 +71,7 @@ function getMenu(window) {
|
||||
log.error(error);
|
||||
utils.writeToast(window, "Could not load last save from disk", "error", 5000);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Load From File",
|
||||
@@ -85,9 +85,7 @@ function getMenu(window) {
|
||||
{ name: "Game Saves", extensions: ["json", "json.gz", "txt"] },
|
||||
{ name: "All", extensions: ["*"] },
|
||||
],
|
||||
properties: [
|
||||
"openFile", "dontAddToRecent",
|
||||
]
|
||||
properties: ["openFile", "dontAddToRecent"],
|
||||
});
|
||||
if (result.canceled) return;
|
||||
const file = result.filePaths[0];
|
||||
@@ -99,7 +97,7 @@ function getMenu(window) {
|
||||
log.error(error);
|
||||
utils.writeToast(window, "Could not load save from disk", "error", 5000);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Load From Steam Cloud",
|
||||
@@ -112,7 +110,7 @@ function getMenu(window) {
|
||||
log.error(error);
|
||||
utils.writeToast(window, "Could not load from Steam Cloud", "error", 5000);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
@@ -123,8 +121,7 @@ function getMenu(window) {
|
||||
checked: storage.isSaveCompressionEnabled(),
|
||||
click: (menuItem) => {
|
||||
storage.setSaveCompressionConfig(menuItem.checked);
|
||||
utils.writeToast(window,
|
||||
`${menuItem.checked ? "Enabled" : "Disabled"} Save Compression`, "info", 5000);
|
||||
utils.writeToast(window, `${menuItem.checked ? "Enabled" : "Disabled"} Save Compression`, "info", 5000);
|
||||
refreshMenu(window);
|
||||
},
|
||||
},
|
||||
@@ -134,8 +131,7 @@ function getMenu(window) {
|
||||
checked: storage.isAutosaveEnabled(),
|
||||
click: (menuItem) => {
|
||||
storage.setAutosaveConfig(menuItem.checked);
|
||||
utils.writeToast(window,
|
||||
`${menuItem.checked ? "Enabled" : "Disabled"} Auto-Save to Disk`, "info", 5000);
|
||||
utils.writeToast(window, `${menuItem.checked ? "Enabled" : "Disabled"} Auto-Save to Disk`, "info", 5000);
|
||||
refreshMenu(window);
|
||||
},
|
||||
},
|
||||
@@ -146,8 +142,12 @@ function getMenu(window) {
|
||||
checked: storage.isCloudEnabled(),
|
||||
click: (menuItem) => {
|
||||
storage.setCloudEnabledConfig(menuItem.checked);
|
||||
utils.writeToast(window,
|
||||
`${menuItem.checked ? "Enabled" : "Disabled"} Auto-Save to Steam Cloud`, "info", 5000);
|
||||
utils.writeToast(
|
||||
window,
|
||||
`${menuItem.checked ? "Enabled" : "Disabled"} Auto-Save to Steam Cloud`,
|
||||
"info",
|
||||
5000,
|
||||
);
|
||||
refreshMenu(window);
|
||||
},
|
||||
},
|
||||
@@ -157,8 +157,12 @@ function getMenu(window) {
|
||||
checked: config.get("onload-restore-newest", true),
|
||||
click: (menuItem) => {
|
||||
config.set("onload-restore-newest", menuItem.checked);
|
||||
utils.writeToast(window,
|
||||
`${menuItem.checked ? "Enabled" : "Disabled"} Restore Newest on Load`, "info", 5000);
|
||||
utils.writeToast(
|
||||
window,
|
||||
`${menuItem.checked ? "Enabled" : "Disabled"} Restore Newest on Load`,
|
||||
"info",
|
||||
5000,
|
||||
);
|
||||
refreshMenu(window);
|
||||
},
|
||||
},
|
||||
@@ -187,7 +191,7 @@ function getMenu(window) {
|
||||
label: "Open Data Directory",
|
||||
click: () => shell.openPath(app.getPath("userData")),
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
@@ -196,7 +200,7 @@ function getMenu(window) {
|
||||
label: "Quit",
|
||||
click: () => app.quit(),
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Edit",
|
||||
@@ -244,29 +248,29 @@ function getMenu(window) {
|
||||
label: "API Server",
|
||||
submenu: [
|
||||
{
|
||||
label: api.isListening() ? 'Disable Server' : 'Enable Server',
|
||||
click: (async () => {
|
||||
label: api.isListening() ? "Disable Server" : "Enable Server",
|
||||
click: async () => {
|
||||
let success = false;
|
||||
try {
|
||||
await api.toggleServer();
|
||||
success = true;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
utils.showErrorBox('Error Toggling Server', error);
|
||||
utils.showErrorBox("Error Toggling Server", error);
|
||||
}
|
||||
if (success && api.isListening()) {
|
||||
utils.writeToast(window, "Started API Server", "success");
|
||||
} else if (success && !api.isListening()) {
|
||||
utils.writeToast(window, "Stopped API Server", "success");
|
||||
} else {
|
||||
utils.writeToast(window, 'Error Toggling Server', "error");
|
||||
utils.writeToast(window, "Error Toggling Server", "error");
|
||||
}
|
||||
refreshMenu(window);
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
label: api.isAutostart() ? 'Disable Autostart' : 'Enable Autostart',
|
||||
click: (async () => {
|
||||
label: api.isAutostart() ? "Disable Autostart" : "Enable Autostart",
|
||||
click: async () => {
|
||||
api.toggleAutostart();
|
||||
if (api.isAutostart()) {
|
||||
utils.writeToast(window, "Enabled API Server Autostart", "success");
|
||||
@@ -274,42 +278,45 @@ function getMenu(window) {
|
||||
utils.writeToast(window, "Disabled API Server Autostart", "success");
|
||||
}
|
||||
refreshMenu(window);
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Copy Auth Token',
|
||||
click: (async () => {
|
||||
label: "Copy Auth Token",
|
||||
click: async () => {
|
||||
const token = api.getAuthenticationToken();
|
||||
log.log('Wrote authentication token to clipboard');
|
||||
log.log("Wrote authentication token to clipboard");
|
||||
clipboard.writeText(token);
|
||||
utils.writeToast(window, "Copied Authentication Token to Clipboard", "info");
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
type: "separator",
|
||||
},
|
||||
{
|
||||
label: 'Information',
|
||||
label: "Information",
|
||||
click: () => {
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: 'Bitburner > API Server Information',
|
||||
message: 'The API Server is used to write script files to your in-game home.',
|
||||
detail: 'There is an official Visual Studio Code extension that makes use of that feature.\n\n' +
|
||||
'It allows you to write your script file in an external IDE and have them pushed over to the game automatically.\n' +
|
||||
'If you want more information, head over to: https://github.com/bitburner-official/bitburner-vscode.',
|
||||
buttons: ['Dismiss', 'Open Extension Link (GitHub)'],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
noLink: true,
|
||||
}).then(({ response }) => {
|
||||
if (response === 1) {
|
||||
utils.openExternal('https://github.com/bitburner-official/bitburner-vscode');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: "info",
|
||||
title: "Bitburner > API Server Information",
|
||||
message: "The API Server is used to write script files to your in-game home.",
|
||||
detail:
|
||||
"There is an official Visual Studio Code extension that makes use of that feature.\n\n" +
|
||||
"It allows you to write your script file in an external IDE and have them pushed over to the game automatically.\n" +
|
||||
"If you want more information, head over to: https://github.com/bitburner-official/bitburner-vscode.",
|
||||
buttons: ["Dismiss", "Open Extension Link (GitHub)"],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
noLink: true,
|
||||
})
|
||||
.then(({ response }) => {
|
||||
if (response === 1) {
|
||||
utils.openExternal("https://github.com/bitburner-official/bitburner-vscode");
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Zoom",
|
||||
@@ -374,8 +381,8 @@ function getMenu(window) {
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
@@ -386,5 +393,6 @@ function refreshMenu(window) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getMenu, refreshMenu,
|
||||
}
|
||||
getMenu,
|
||||
refreshMenu,
|
||||
};
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { ipcRenderer, contextBridge } = require('electron')
|
||||
const { ipcRenderer, contextBridge } = require("electron");
|
||||
const log = require("electron-log");
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
"electronBridge", {
|
||||
send: (channel, data) => {
|
||||
log.log("Send on channel " + channel)
|
||||
// whitelist channels
|
||||
let validChannels = [
|
||||
"get-save-data-response",
|
||||
"get-save-info-response",
|
||||
"push-game-saved",
|
||||
"push-game-ready",
|
||||
"push-import-result",
|
||||
"push-disable-restore",
|
||||
];
|
||||
if (validChannels.includes(channel)) {
|
||||
ipcRenderer.send(channel, data);
|
||||
}
|
||||
},
|
||||
receive: (channel, func) => {
|
||||
log.log("Receive on channel " + channel)
|
||||
let validChannels = [
|
||||
"get-save-data-request",
|
||||
"get-save-info-request",
|
||||
"push-save-request",
|
||||
"trigger-save",
|
||||
"trigger-game-export",
|
||||
"trigger-scripts-export",
|
||||
];
|
||||
if (validChannels.includes(channel)) {
|
||||
// Deliberately strip event as it includes `sender`
|
||||
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
||||
}
|
||||
contextBridge.exposeInMainWorld("electronBridge", {
|
||||
send: (channel, data) => {
|
||||
log.log("Send on channel " + channel);
|
||||
// whitelist channels
|
||||
let validChannels = [
|
||||
"get-save-data-response",
|
||||
"get-save-info-response",
|
||||
"push-game-saved",
|
||||
"push-game-ready",
|
||||
"push-import-result",
|
||||
"push-disable-restore",
|
||||
];
|
||||
if (validChannels.includes(channel)) {
|
||||
ipcRenderer.send(channel, data);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
receive: (channel, func) => {
|
||||
log.log("Receive on channel " + channel);
|
||||
let validChannels = [
|
||||
"get-save-data-request",
|
||||
"get-save-info-request",
|
||||
"push-save-request",
|
||||
"trigger-save",
|
||||
"trigger-game-export",
|
||||
"trigger-scripts-export",
|
||||
];
|
||||
if (validChannels.includes(channel)) {
|
||||
// Deliberately strip event as it includes `sender`
|
||||
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -16,9 +16,9 @@ const config = new Config();
|
||||
// https://stackoverflow.com/a/69418940
|
||||
const dirSize = async (directory) => {
|
||||
const files = await fs.readdir(directory);
|
||||
const stats = files.map(file => fs.stat(path.join(directory, file)));
|
||||
const stats = files.map((file) => fs.stat(path.join(directory, file)));
|
||||
return (await Promise.all(stats)).reduce((accumulator, { size }) => accumulator + size, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const getDirFileStats = async (directory) => {
|
||||
const files = await fs.readdir(directory);
|
||||
@@ -26,30 +26,31 @@ const getDirFileStats = async (directory) => {
|
||||
const file = path.join(directory, f);
|
||||
return fs.stat(file).then((stat) => ({ file, stat }));
|
||||
});
|
||||
const data = (await Promise.all(stats));
|
||||
const data = await Promise.all(stats);
|
||||
return data;
|
||||
};
|
||||
|
||||
const getNewestFile = async (directory) => {
|
||||
const data = await getDirFileStats(directory)
|
||||
const data = await getDirFileStats(directory);
|
||||
return data.sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime())[0];
|
||||
};
|
||||
|
||||
const getAllSaves = async (window) => {
|
||||
const rootDirectory = await getSaveFolder(window, true);
|
||||
const data = await fs.readdir(rootDirectory, { withFileTypes: true});
|
||||
const savesPromises = data.filter((e) => e.isDirectory()).
|
||||
map((dir) => path.join(rootDirectory, dir.name)).
|
||||
map((dir) => getDirFileStats(dir));
|
||||
const data = await fs.readdir(rootDirectory, { withFileTypes: true });
|
||||
const savesPromises = data
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((dir) => path.join(rootDirectory, dir.name))
|
||||
.map((dir) => getDirFileStats(dir));
|
||||
const saves = await Promise.all(savesPromises);
|
||||
const flat = flatten(saves);
|
||||
return flat;
|
||||
}
|
||||
};
|
||||
|
||||
async function prepareSaveFolders(window) {
|
||||
const rootFolder = await getSaveFolder(window, true);
|
||||
const currentFolder = await getSaveFolder(window);
|
||||
const backupsFolder = path.join(rootFolder, "/_backups")
|
||||
const backupsFolder = path.join(rootFolder, "/_backups");
|
||||
await prepareFolders(rootFolder, currentFolder, backupsFolder);
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ async function prepareFolders(...folders) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await fs.stat(folder);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
if (error.code === "ENOENT") {
|
||||
log.warn(`'${folder}' not found, creating it...`);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await fs.mkdir(folder);
|
||||
@@ -125,14 +126,14 @@ function isCloudEnabled() {
|
||||
function saveCloudFile(name, content) {
|
||||
return new Promise((resolve, reject) => {
|
||||
greenworks.saveTextToFile(name, content, resolve, reject);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function getFirstCloudFile() {
|
||||
const nbFiles = greenworks.getFileCount();
|
||||
if (nbFiles === 0) throw new Error('No files in cloud');
|
||||
if (nbFiles === 0) throw new Error("No files in cloud");
|
||||
const file = greenworks.getFileNameAndSize(0);
|
||||
log.silly(`Found ${nbFiles} files.`)
|
||||
log.silly(`Found ${nbFiles} files.`);
|
||||
log.silly(`First File: ${file.name} (${file.size} bytes)`);
|
||||
return file.name;
|
||||
}
|
||||
@@ -153,7 +154,7 @@ function deleteCloudFile() {
|
||||
|
||||
async function getSteamCloudQuota() {
|
||||
return new Promise((resolve, reject) => {
|
||||
greenworks.getCloudQuota(resolve, reject)
|
||||
greenworks.getCloudQuota(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -166,9 +167,9 @@ async function backupSteamDataToDisk(currentPlayerId) {
|
||||
if (previousPlayerId !== currentPlayerId) {
|
||||
const backupSave = await getSteamCloudSaveString();
|
||||
const backupFile = path.join(app.getPath("userData"), "/saves/_backups", `${previousPlayerId}.json.gz`);
|
||||
const buffer = Buffer.from(backupSave, 'base64').toString('utf8');
|
||||
const buffer = Buffer.from(backupSave, "base64").toString("utf8");
|
||||
saveContent = await gzip(buffer);
|
||||
await fs.writeFile(backupFile, saveContent, 'utf8');
|
||||
await fs.writeFile(backupFile, saveContent, "utf8");
|
||||
log.debug(`Saved backup game to '${backupFile}`);
|
||||
}
|
||||
}
|
||||
@@ -219,7 +220,9 @@ async function saveGameToDisk(window, saveData) {
|
||||
const remainingSpaceBytes = maxFolderSizeBytes - saveFolderSizeBytes;
|
||||
log.debug(`Folder Usage: ${saveFolderSizeBytes} bytes`);
|
||||
log.debug(`Folder Capacity: ${maxFolderSizeBytes} bytes`);
|
||||
log.debug(`Remaining: ${remainingSpaceBytes} bytes (${(saveFolderSizeBytes / maxFolderSizeBytes * 100).toFixed(2)}% used)`)
|
||||
log.debug(
|
||||
`Remaining: ${remainingSpaceBytes} bytes (${((saveFolderSizeBytes / maxFolderSizeBytes) * 100).toFixed(2)}% used)`,
|
||||
);
|
||||
const shouldCompress = isSaveCompressionEnabled();
|
||||
const fileName = saveData.fileName;
|
||||
const file = path.join(currentFolder, fileName + (shouldCompress ? ".gz" : ""));
|
||||
@@ -227,10 +230,10 @@ async function saveGameToDisk(window, saveData) {
|
||||
let saveContent = saveData.save;
|
||||
if (shouldCompress) {
|
||||
// Let's decode the base64 string so GZIP is more efficient.
|
||||
const buffer = Buffer.from(saveContent, 'base64').toString('utf8');
|
||||
const buffer = Buffer.from(saveContent, "base64").toString("utf8");
|
||||
saveContent = await gzip(buffer);
|
||||
}
|
||||
await fs.writeFile(file, saveContent, 'utf8');
|
||||
await fs.writeFile(file, saveContent, "utf8");
|
||||
log.debug(`Saved Game to '${file}'`);
|
||||
log.debug(`Save Size: ${saveContent.length} bytes`);
|
||||
} catch (error) {
|
||||
@@ -240,7 +243,8 @@ async function saveGameToDisk(window, saveData) {
|
||||
const fileStats = await getDirFileStats(currentFolder);
|
||||
const oldestFiles = fileStats
|
||||
.sort((a, b) => a.stat.mtime.getTime() - b.stat.mtime.getTime())
|
||||
.map(f => f.file).filter(f => f !== file);
|
||||
.map((f) => f.file)
|
||||
.filter((f) => f !== file);
|
||||
|
||||
while (saveFolderSizeBytes > maxFolderSizeBytes && oldestFiles.length > 0) {
|
||||
const fileToRemove = oldestFiles.shift();
|
||||
@@ -255,7 +259,12 @@ async function saveGameToDisk(window, saveData) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
saveFolderSizeBytes = await getFolderSizeInBytes(currentFolder);
|
||||
log.debug(`Save Folder: ${saveFolderSizeBytes} bytes`);
|
||||
log.debug(`Remaining: ${maxFolderSizeBytes - saveFolderSizeBytes} bytes (${(saveFolderSizeBytes / maxFolderSizeBytes * 100).toFixed(2)}% used)`)
|
||||
log.debug(
|
||||
`Remaining: ${maxFolderSizeBytes - saveFolderSizeBytes} bytes (${(
|
||||
(saveFolderSizeBytes / maxFolderSizeBytes) *
|
||||
100
|
||||
).toFixed(2)}% used)`,
|
||||
);
|
||||
}
|
||||
|
||||
return file;
|
||||
@@ -271,13 +280,13 @@ async function loadLastFromDisk(window) {
|
||||
async function loadFileFromDisk(path) {
|
||||
const buffer = await fs.readFile(path);
|
||||
let content;
|
||||
if (path.endsWith('.gz')) {
|
||||
if (path.endsWith(".gz")) {
|
||||
const uncompressedBuffer = await gunzip(buffer);
|
||||
content = uncompressedBuffer.toString('base64');
|
||||
content = uncompressedBuffer.toString("base64");
|
||||
log.debug(`Uncompressed file content (new size: ${content.length} bytes)`);
|
||||
} else {
|
||||
content = buffer.toString('utf8');
|
||||
log.debug(`Loaded file with ${content.length} bytes`)
|
||||
content = buffer.toString("utf8");
|
||||
log.debug(`Loaded file with ${content.length} bytes`);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
@@ -293,10 +302,10 @@ function getSaveInformation(window, save) {
|
||||
|
||||
function getCurrentSave(window) {
|
||||
return new Promise((resolve) => {
|
||||
ipcMain.once('get-save-data-response', (event, data) => {
|
||||
ipcMain.once("get-save-data-response", (event, data) => {
|
||||
resolve(data);
|
||||
});
|
||||
window.webContents.send('get-save-data-request');
|
||||
window.webContents.send("get-save-data-request");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -322,13 +331,12 @@ async function restoreIfNewerExists(window) {
|
||||
}
|
||||
|
||||
try {
|
||||
const saves = (await getAllSaves()).
|
||||
sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime());
|
||||
const saves = (await getAllSaves()).sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime());
|
||||
if (saves.length > 0) {
|
||||
disk.save = await loadFileFromDisk(saves[0].file);
|
||||
disk.data = await getSaveInformation(window, disk.save);
|
||||
}
|
||||
} catch(error) {
|
||||
} catch (error) {
|
||||
log.error("Could not retrieve disk file");
|
||||
log.debug(error);
|
||||
}
|
||||
@@ -339,18 +347,17 @@ async function restoreIfNewerExists(window) {
|
||||
log.info("No data to import");
|
||||
} else if (!steam.data) {
|
||||
// We'll just compare using the lastSave field for now.
|
||||
log.debug('Best potential save match: Disk');
|
||||
log.debug("Best potential save match: Disk");
|
||||
bestMatch = disk;
|
||||
} else if (!disk.data) {
|
||||
log.debug('Best potential save match: Steam Cloud');
|
||||
log.debug("Best potential save match: Steam Cloud");
|
||||
bestMatch = steam;
|
||||
} else if ((steam.data.lastSave >= disk.data.lastSave)
|
||||
|| (steam.data.playtime + lowPlaytime > disk.data.playtime)) {
|
||||
} else if (steam.data.lastSave >= disk.data.lastSave || steam.data.playtime + lowPlaytime > disk.data.playtime) {
|
||||
// We want to prioritze steam data if the playtime is very close
|
||||
log.debug('Best potential save match: Steam Cloud');
|
||||
log.debug("Best potential save match: Steam Cloud");
|
||||
bestMatch = steam;
|
||||
} else {
|
||||
log.debug('Best potential save match: disk');
|
||||
log.debug("Best potential save match: disk");
|
||||
bestMatch = disk;
|
||||
}
|
||||
if (bestMatch) {
|
||||
@@ -360,7 +367,7 @@ async function restoreIfNewerExists(window) {
|
||||
log.silly(bestMatch.data);
|
||||
await pushSaveGameForImport(window, bestMatch.save, true);
|
||||
return true;
|
||||
} else if(bestMatch.data.playtime > currentData.playtime && currentData.playtime < lowPlaytime) {
|
||||
} else if (bestMatch.data.playtime > currentData.playtime && currentData.playtime < lowPlaytime) {
|
||||
log.info("Found older save, but with more playtime, and current less than 15 mins played");
|
||||
log.silly(bestMatch.data);
|
||||
await pushSaveGameForImport(window, bestMatch.save, true);
|
||||
@@ -373,12 +380,24 @@ async function restoreIfNewerExists(window) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCurrentSave, getSaveInformation,
|
||||
restoreIfNewerExists, pushSaveGameForImport,
|
||||
pushGameSaveToSteamCloud, getSteamCloudSaveString, getSteamCloudQuota, deleteCloudFile,
|
||||
saveGameToDisk, loadLastFromDisk, loadFileFromDisk,
|
||||
getSaveFolder, prepareSaveFolders, getAllSaves,
|
||||
isCloudEnabled, setCloudEnabledConfig,
|
||||
isAutosaveEnabled, setAutosaveConfig,
|
||||
isSaveCompressionEnabled, setSaveCompressionConfig,
|
||||
};
|
||||
getCurrentSave,
|
||||
getSaveInformation,
|
||||
restoreIfNewerExists,
|
||||
pushSaveGameForImport,
|
||||
pushGameSaveToSteamCloud,
|
||||
getSteamCloudSaveString,
|
||||
getSteamCloudQuota,
|
||||
deleteCloudFile,
|
||||
saveGameToDisk,
|
||||
loadLastFromDisk,
|
||||
loadFileFromDisk,
|
||||
getSaveFolder,
|
||||
prepareSaveFolders,
|
||||
getAllSaves,
|
||||
isCloudEnabled,
|
||||
setCloudEnabledConfig,
|
||||
isAutosaveEnabled,
|
||||
setAutosaveConfig,
|
||||
isSaveCompressionEnabled,
|
||||
setSaveCompressionConfig,
|
||||
};
|
||||
|
||||
@@ -9,61 +9,61 @@ const Config = require("electron-config");
|
||||
const config = new Config();
|
||||
|
||||
function reloadAndKill(window, killScripts) {
|
||||
const setStopProcessHandler = global.app_handlers.stopProcess
|
||||
const setStopProcessHandler = global.app_handlers.stopProcess;
|
||||
const createWindowHandler = global.app_handlers.createWindow;
|
||||
|
||||
log.info('Reloading & Killing all scripts...');
|
||||
log.info("Reloading & Killing all scripts...");
|
||||
setStopProcessHandler(app, window, false);
|
||||
|
||||
achievements.disableAchievementsInterval(window);
|
||||
api.disable();
|
||||
|
||||
window.webContents.forcefullyCrashRenderer();
|
||||
window.on('closed', () => {
|
||||
window.on("closed", () => {
|
||||
// Wait for window to be closed before opening the new one to prevent race conditions
|
||||
log.debug('Opening new window');
|
||||
log.debug("Opening new window");
|
||||
createWindowHandler(killScripts);
|
||||
})
|
||||
});
|
||||
|
||||
window.close();
|
||||
}
|
||||
|
||||
function promptForReload(window) {
|
||||
detachUnresponsiveAppHandler(window);
|
||||
dialog.showMessageBox({
|
||||
type: 'error',
|
||||
title: 'Bitburner > Application Unresponsive',
|
||||
message: 'The application is unresponsive, possibly due to an infinite loop in your scripts.',
|
||||
detail:' Did you forget a ns.sleep(x)?\n\n' +
|
||||
'The application will be restarted for you, do you want to kill all running scripts?',
|
||||
buttons: ['Restart', 'Cancel'],
|
||||
defaultId: 0,
|
||||
checkboxLabel: 'Kill all running scripts',
|
||||
checkboxChecked: true,
|
||||
noLink: true,
|
||||
}).then(({response, checkboxChecked}) => {
|
||||
if (response === 0) {
|
||||
reloadAndKill(window, checkboxChecked);
|
||||
} else {
|
||||
attachUnresponsiveAppHandler(window);
|
||||
}
|
||||
});
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: "error",
|
||||
title: "Bitburner > Application Unresponsive",
|
||||
message: "The application is unresponsive, possibly due to an infinite loop in your scripts.",
|
||||
detail:
|
||||
" Did you forget a ns.sleep(x)?\n\n" +
|
||||
"The application will be restarted for you, do you want to kill all running scripts?",
|
||||
buttons: ["Restart", "Cancel"],
|
||||
defaultId: 0,
|
||||
checkboxLabel: "Kill all running scripts",
|
||||
checkboxChecked: true,
|
||||
noLink: true,
|
||||
})
|
||||
.then(({ response, checkboxChecked }) => {
|
||||
if (response === 0) {
|
||||
reloadAndKill(window, checkboxChecked);
|
||||
} else {
|
||||
attachUnresponsiveAppHandler(window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function attachUnresponsiveAppHandler(window) {
|
||||
window.unresponsiveHandler = () => promptForReload(window);
|
||||
window.on('unresponsive', window.unresponsiveHandler);
|
||||
window.on("unresponsive", window.unresponsiveHandler);
|
||||
}
|
||||
|
||||
function detachUnresponsiveAppHandler(window) {
|
||||
window.off('unresponsive', window.unresponsiveHandler);
|
||||
window.off("unresponsive", window.unresponsiveHandler);
|
||||
}
|
||||
|
||||
function showErrorBox(title, error) {
|
||||
dialog.showErrorBox(
|
||||
title,
|
||||
`${error.name}\n\n${error.message}`
|
||||
);
|
||||
dialog.showErrorBox(title, `${error.name}\n\n${error.message}`);
|
||||
}
|
||||
|
||||
function exportSaveFromIndexedDb() {
|
||||
@@ -71,15 +71,15 @@ function exportSaveFromIndexedDb() {
|
||||
const dbRequest = indexedDB.open("bitburnerSave");
|
||||
dbRequest.onsuccess = () => {
|
||||
const db = dbRequest.result;
|
||||
const transaction = db.transaction(['savestring'], "readonly");
|
||||
const store = transaction.objectStore('savestring');
|
||||
const request = store.get('save');
|
||||
const transaction = db.transaction(["savestring"], "readonly");
|
||||
const store = transaction.objectStore("savestring");
|
||||
const request = store.get("save");
|
||||
request.onsuccess = () => {
|
||||
const file = new Blob([request.result], {type: 'text/plain'});
|
||||
const file = new Blob([request.result], { type: "text/plain" });
|
||||
const a = document.createElement("a");
|
||||
const url = URL.createObjectURL(file);
|
||||
a.href = url;
|
||||
a.download = 'save.json';
|
||||
a.download = "save.json";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function () {
|
||||
@@ -87,24 +87,21 @@ function exportSaveFromIndexedDb() {
|
||||
window.URL.revokeObjectURL(url);
|
||||
resolve();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function exportSave(window) {
|
||||
await window.webContents
|
||||
.executeJavaScript(`${exportSaveFromIndexedDb.toString()}; exportSaveFromIndexedDb();`, true);
|
||||
await window.webContents.executeJavaScript(`${exportSaveFromIndexedDb.toString()}; exportSaveFromIndexedDb();`, true);
|
||||
}
|
||||
|
||||
async function writeTerminal(window, message, type = null) {
|
||||
await window.webContents
|
||||
.executeJavaScript(`window.appNotifier.terminal("${message}", "${type}");`, true)
|
||||
await window.webContents.executeJavaScript(`window.appNotifier.terminal("${message}", "${type}");`, true);
|
||||
}
|
||||
|
||||
async function writeToast(window, message, type = "info", duration = 2000) {
|
||||
await window.webContents
|
||||
.executeJavaScript(`window.appNotifier.toast("${message}", "${type}", ${duration});`, true)
|
||||
await window.webContents.executeJavaScript(`window.appNotifier.toast("${message}", "${type}", ${duration});`, true);
|
||||
}
|
||||
|
||||
function openExternal(url) {
|
||||
@@ -113,7 +110,7 @@ function openExternal(url) {
|
||||
}
|
||||
|
||||
function getZoomFactor() {
|
||||
const configZoom = config.get('zoom', 1);
|
||||
const configZoom = config.get("zoom", 1);
|
||||
return configZoom;
|
||||
}
|
||||
|
||||
@@ -121,14 +118,20 @@ function setZoomFactor(window, zoom = null) {
|
||||
if (zoom === null) {
|
||||
zoom = 1;
|
||||
} else {
|
||||
config.set('zoom', zoom);
|
||||
config.set("zoom", zoom);
|
||||
}
|
||||
window.webContents.setZoomFactor(zoom);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
reloadAndKill, showErrorBox, exportSave,
|
||||
attachUnresponsiveAppHandler, detachUnresponsiveAppHandler,
|
||||
openExternal, writeTerminal, writeToast,
|
||||
getZoomFactor, setZoomFactor,
|
||||
}
|
||||
reloadAndKill,
|
||||
showErrorBox,
|
||||
exportSave,
|
||||
attachUnresponsiveAppHandler,
|
||||
detachUnresponsiveAppHandler,
|
||||
openExternal,
|
||||
writeTerminal,
|
||||
writeToast,
|
||||
getZoomFactor,
|
||||
setZoomFactor,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user