mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
ELECTRON: Import correct cloud file when multiple exist (#2599)
This commit is contained in:
10
.eslintrc.js
10
.eslintrc.js
@@ -55,5 +55,15 @@ module.exports = {
|
|||||||
"@typescript-eslint/prefer-literal-enum-member": ["off"],
|
"@typescript-eslint/prefer-literal-enum-member": ["off"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* TypeScript requires the "var" keyword within "declare global" to correctly merge variables into the global
|
||||||
|
* namespace.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
files: ["**/*.d.ts"],
|
||||||
|
rules: {
|
||||||
|
"no-var": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
23
electron/global.d.ts
vendored
Normal file
23
electron/global.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { BrowserWindow } from "electron";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
var steamworksError: Error | undefined;
|
||||||
|
var app_handlers: {
|
||||||
|
stopProcess: (window: BrowserWindow) => void;
|
||||||
|
};
|
||||||
|
namespace Electron {
|
||||||
|
interface BrowserWindow {
|
||||||
|
gameInfo?: {
|
||||||
|
player?: {
|
||||||
|
identifier: string;
|
||||||
|
playtime: number;
|
||||||
|
lastSave: number;
|
||||||
|
};
|
||||||
|
game?: {
|
||||||
|
version: string;
|
||||||
|
hash: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -357,13 +357,12 @@ function getMenu(window) {
|
|||||||
label: "Delete Steam Cloud Data",
|
label: "Delete Steam Cloud Data",
|
||||||
enabled: steamworksClient !== undefined,
|
enabled: steamworksClient !== undefined,
|
||||||
click: () => {
|
click: () => {
|
||||||
if (steamworksClient.cloud.listFiles().length === 0) {
|
if (steamworksClient === undefined || steamworksClient.cloud.listFiles().length === 0) {
|
||||||
|
log.info("There is no Steam cloud file");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!storage.deleteCloudFile()) {
|
storage.deleteCloudFiles();
|
||||||
log.warn("Cannot delete Steam Cloud data");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
|
|||||||
8
electron/package-lock.json
generated
8
electron/package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "bitburner",
|
"name": "bitburner",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@catloversg/steamworks.js": "0.0.2",
|
"@catloversg/steamworks.js": "0.0.3",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
"electron-log": "^4.4.8",
|
"electron-log": "^4.4.8",
|
||||||
"electron-store": "^8.1.0",
|
"electron-store": "^8.1.0",
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@catloversg/steamworks.js": {
|
"node_modules/@catloversg/steamworks.js": {
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@catloversg/steamworks.js/-/steamworks.js-0.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@catloversg/steamworks.js/-/steamworks.js-0.0.3.tgz",
|
||||||
"integrity": "sha512-EPB7vQFZa0zGw+Ft4SHiHIDZ7UcuM/XUiyzPo5a9Pf+g5XmcvjIEjo8wKwk7Ox0DOjmSJ+s8GmOD/TDDQW5jgg==",
|
"integrity": "sha512-fQhWQ0FNuFgO7zC1t009+jiPUTBL0VeH6pxYDMbEaauRw64o6wy4lGG9gJ9Rq+Rp/wXKs/BqNMFPLpdxRXprRQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"buildResources": "public"
|
"buildResources": "public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@catloversg/steamworks.js": "0.0.2",
|
"@catloversg/steamworks.js": "0.0.3",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
"electron-log": "^4.4.8",
|
"electron-log": "^4.4.8",
|
||||||
"electron-store": "^8.1.0",
|
"electron-store": "^8.1.0",
|
||||||
|
|||||||
8
electron/saveDataBinaryFormat.d.ts
vendored
8
electron/saveDataBinaryFormat.d.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
export declare const encodeBytesToBase64String: (bytes: Uint8Array<ArrayBuffer>) => string;
|
export declare const encodeBytesToBase64String: (bytes: Uint8Array<ArrayBufferLike>) => string;
|
||||||
export declare const decodeBase64BytesToBytes: (bytes: Uint8Array<ArrayBuffer>) => Uint8Array<ArrayBuffer>;
|
export declare const decodeBase64BytesToBytes: (bytes: Uint8Array<ArrayBufferLike>) => Uint8Array<ArrayBuffer>;
|
||||||
export declare const isBinaryFormat: (saveData: string | Uint8Array<ArrayBuffer>) => boolean;
|
export declare const isBinaryFormat: (saveData: string | Uint8Array<ArrayBufferLike>) => boolean;
|
||||||
export declare const isSteamCloudFormat: (saveData: string | Uint8Array<ArrayBuffer>) => boolean;
|
export declare const isSteamCloudFormat: (saveData: string | Uint8Array<ArrayBufferLike>) => boolean;
|
||||||
|
|||||||
@@ -2,17 +2,21 @@
|
|||||||
const steamworks = require("@catloversg/steamworks.js");
|
const steamworks = require("@catloversg/steamworks.js");
|
||||||
const log = require("electron-log");
|
const log = require("electron-log");
|
||||||
|
|
||||||
let steamworksClient;
|
/** @type {ReturnType<typeof import("@catloversg/steamworks.js").init> | undefined} */
|
||||||
|
let steamworksClient = undefined;
|
||||||
try {
|
try {
|
||||||
// 1812820 is our Steam App ID.
|
// 1812820 is our Steam App ID.
|
||||||
steamworksClient = steamworks.init(1812820);
|
steamworksClient = steamworks.init(1812820);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message?.includes("Steam is probably not running")) {
|
if (error instanceof Error) {
|
||||||
log.warn(error.message);
|
log.warn(error.message);
|
||||||
|
global.steamworksError = error;
|
||||||
} else {
|
} else {
|
||||||
log.warn(error);
|
// This should never happen.
|
||||||
|
log.error("steamworks.js threw an error that is not an instance of Error");
|
||||||
|
log.error(error);
|
||||||
|
global.steamworksError = new Error(typeof error === "string" ? error : String(error), { cause: error });
|
||||||
}
|
}
|
||||||
global.steamworksError = error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/** @import { BrowserWindow } from "electron" */
|
||||||
|
/** @typedef {string | Uint8Array} SaveData */
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const { app, ipcMain } = require("electron");
|
const { app, ipcMain } = require("electron");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
@@ -10,6 +12,7 @@ const { decodeBase64BytesToBytes, isBinaryFormat, isSteamCloudFormat } = require
|
|||||||
const store = new Store();
|
const store = new Store();
|
||||||
const { steamworksClient } = require("./steamworksUtils");
|
const { steamworksClient } = require("./steamworksUtils");
|
||||||
|
|
||||||
|
/** @param {string} directory */
|
||||||
// https://stackoverflow.com/a/69418940
|
// https://stackoverflow.com/a/69418940
|
||||||
const dirSize = async (directory) => {
|
const dirSize = async (directory) => {
|
||||||
const files = await fs.readdir(directory);
|
const files = await fs.readdir(directory);
|
||||||
@@ -17,6 +20,7 @@ const dirSize = async (directory) => {
|
|||||||
return (await Promise.all(stats)).reduce((accumulator, { size }) => accumulator + size, 0);
|
return (await Promise.all(stats)).reduce((accumulator, { size }) => accumulator + size, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @param {string} directory */
|
||||||
const getDirFileStats = async (directory) => {
|
const getDirFileStats = async (directory) => {
|
||||||
const files = await fs.readdir(directory);
|
const files = await fs.readdir(directory);
|
||||||
const stats = files.map((f) => {
|
const stats = files.map((f) => {
|
||||||
@@ -27,11 +31,13 @@ const getDirFileStats = async (directory) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @param {string} directory */
|
||||||
const getNewestFile = async (directory) => {
|
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];
|
return data.sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime())[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @param {BrowserWindow} window */
|
||||||
const getAllSaves = async (window) => {
|
const getAllSaves = async (window) => {
|
||||||
const rootDirectory = getSaveFolder(window, true);
|
const rootDirectory = getSaveFolder(window, true);
|
||||||
const data = await fs.readdir(rootDirectory, { withFileTypes: true });
|
const data = await fs.readdir(rootDirectory, { withFileTypes: true });
|
||||||
@@ -44,6 +50,7 @@ const getAllSaves = async (window) => {
|
|||||||
return flat;
|
return flat;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @param {BrowserWindow} window */
|
||||||
async function prepareSaveFolders(window) {
|
async function prepareSaveFolders(window) {
|
||||||
const rootFolder = getSaveFolder(window, true);
|
const rootFolder = getSaveFolder(window, true);
|
||||||
const currentFolder = getSaveFolder(window);
|
const currentFolder = getSaveFolder(window);
|
||||||
@@ -51,12 +58,15 @@ async function prepareSaveFolders(window) {
|
|||||||
await prepareFolders(rootFolder, currentFolder, backupsFolder);
|
await prepareFolders(rootFolder, currentFolder, backupsFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {...string} folders */
|
||||||
async function prepareFolders(...folders) {
|
async function prepareFolders(...folders) {
|
||||||
for (const folder of folders) {
|
for (const folder of folders) {
|
||||||
try {
|
try {
|
||||||
// Making sure the folder exists
|
// Making sure the folder exists
|
||||||
await fs.stat(folder);
|
await fs.stat(folder);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// @ts-expect-error - Node.js guarantees that errors thrown by its APIs are instances of the standard Error class
|
||||||
|
// and include an error.code property.
|
||||||
if (error.code === "ENOENT") {
|
if (error.code === "ENOENT") {
|
||||||
log.warn(`'${folder}' not found, creating it...`);
|
log.warn(`'${folder}' not found, creating it...`);
|
||||||
await fs.mkdir(folder);
|
await fs.mkdir(folder);
|
||||||
@@ -67,6 +77,9 @@ async function prepareFolders(...folders) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Check callers of this function. They currently don't properly handle the case where this function returns
|
||||||
|
// undefined due to errors when reading folder stats.
|
||||||
|
/** @param {string} saveFolder */
|
||||||
async function getFolderSizeInBytes(saveFolder) {
|
async function getFolderSizeInBytes(saveFolder) {
|
||||||
try {
|
try {
|
||||||
return await dirSize(saveFolder);
|
return await dirSize(saveFolder);
|
||||||
@@ -75,6 +88,7 @@ async function getFolderSizeInBytes(saveFolder) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {boolean} value */
|
||||||
function setAutosaveConfig(value) {
|
function setAutosaveConfig(value) {
|
||||||
store.set("autosave-enabled", value);
|
store.set("autosave-enabled", value);
|
||||||
}
|
}
|
||||||
@@ -83,6 +97,7 @@ function isAutosaveEnabled() {
|
|||||||
return store.get("autosave-enabled", true);
|
return store.get("autosave-enabled", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {boolean} value */
|
||||||
function setCloudEnabledConfig(value) {
|
function setCloudEnabledConfig(value) {
|
||||||
store.set("cloud-enabled", value);
|
store.set("cloud-enabled", value);
|
||||||
}
|
}
|
||||||
@@ -91,14 +106,20 @@ function isMenuHideEnabled() {
|
|||||||
return store.get("autoHideMenuBar", false);
|
return store.get("autoHideMenuBar", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {boolean} value */
|
||||||
function setMenuHideConfig(value) {
|
function setMenuHideConfig(value) {
|
||||||
return store.set("autoHideMenuBar", value);
|
return store.set("autoHideMenuBar", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {BrowserWindow} window
|
||||||
|
* @param {boolean} [root]
|
||||||
|
*/
|
||||||
function getSaveFolder(window, root = false) {
|
function getSaveFolder(window, root = false) {
|
||||||
if (root) {
|
if (root) {
|
||||||
return path.join(app.getPath("userData"), "/saves");
|
return path.join(app.getPath("userData"), "/saves");
|
||||||
}
|
}
|
||||||
|
// TODO: check undefined gameInfo case
|
||||||
const identifier = window.gameInfo?.player?.identifier ?? "";
|
const identifier = window.gameInfo?.player?.identifier ?? "";
|
||||||
return path.join(app.getPath("userData"), "/saves", `/${identifier}`);
|
return path.join(app.getPath("userData"), "/saves", `/${identifier}`);
|
||||||
}
|
}
|
||||||
@@ -126,30 +147,93 @@ function isCloudEnabled() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string} content
|
||||||
|
*/
|
||||||
function saveCloudFile(name, content) {
|
function saveCloudFile(name, content) {
|
||||||
steamworksClient.cloud.writeFile(name, content);
|
if (!steamworksClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = steamworksClient.cloud.writeFile(name, content);
|
||||||
|
if (!result) {
|
||||||
|
log.warn(`Cannot write Steam Cloud save file: ${name}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFilenameOfFirstCloudFile() {
|
/** @param {import("@catloversg/steamworks.js/client").cloud.FileInfo[]} files */
|
||||||
|
function logCloudFiles(files) {
|
||||||
|
for (const file of files) {
|
||||||
|
log.debug(
|
||||||
|
`Name: ${file.name}. Size: ${file.size}. ` +
|
||||||
|
`isFilePersisted: ${steamworksClient?.cloud.isFilePersisted(file.name)}. ` +
|
||||||
|
`timestamp: ${steamworksClient?.cloud.fileTimestamp(file.name)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilenameOfMostRecentlyPersistedCloudFile() {
|
||||||
|
if (!steamworksClient) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const files = steamworksClient.cloud.listFiles();
|
const files = steamworksClient.cloud.listFiles();
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
throw new Error("No files in cloud");
|
return null;
|
||||||
}
|
}
|
||||||
const file = files[0];
|
const filteredFiles = files
|
||||||
log.silly(`Found ${files.length} files.`);
|
// @ts-expect-error - https://github.com/microsoft/TypeScript/issues/9998
|
||||||
log.silly(`First File: ${file.name} (${file.size} bytes)`);
|
.filter((file) => steamworksClient.cloud.isFilePersisted(file.name))
|
||||||
|
.map((file) => ({
|
||||||
|
file,
|
||||||
|
// @ts-expect-error - https://github.com/microsoft/TypeScript/issues/9998
|
||||||
|
timestamp: steamworksClient.cloud.fileTimestamp(file.name),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.timestamp - a.timestamp)
|
||||||
|
.map((item) => item.file);
|
||||||
|
if (filteredFiles.length === 0) {
|
||||||
|
log.warn("Found cloud file(s) but none are persisted");
|
||||||
|
logCloudFiles(files);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (filteredFiles.length > 1) {
|
||||||
|
log.warn("Found more than 1 persisted cloud file");
|
||||||
|
logCloudFiles(files);
|
||||||
|
}
|
||||||
|
const file = filteredFiles[0];
|
||||||
|
log.debug(`Found ${filteredFiles.length} files.`);
|
||||||
|
log.debug(`First File: ${file.name} (${file.size} bytes)`);
|
||||||
return file.name;
|
return file.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCloudFile() {
|
function getCloudFile() {
|
||||||
return steamworksClient.cloud.readFile(getFilenameOfFirstCloudFile());
|
if (!steamworksClient) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const filename = getFilenameOfMostRecentlyPersistedCloudFile();
|
||||||
|
if (filename === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return steamworksClient.cloud.readFile(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteCloudFile() {
|
function deleteCloudFiles() {
|
||||||
return steamworksClient.cloud.deleteFile(getFilenameOfFirstCloudFile());
|
if (!steamworksClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const file of steamworksClient.cloud.listFiles()) {
|
||||||
|
if (steamworksClient.cloud.deleteFile(file.name)) {
|
||||||
|
log.info(`Deleted Steam cloud file: ${file.name}`);
|
||||||
|
} else {
|
||||||
|
log.warn(`Cannot delete Steam cloud file: ${file.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {string} currentPlayerId */
|
||||||
async function backupSteamDataToDisk(currentPlayerId) {
|
async function backupSteamDataToDisk(currentPlayerId) {
|
||||||
|
if (!steamworksClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const files = steamworksClient.cloud.listFiles();
|
const files = steamworksClient.cloud.listFiles();
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -169,16 +253,24 @@ async function backupSteamDataToDisk(currentPlayerId) {
|
|||||||
* The name of save file is `${currentPlayerId}.json.gz`. The content of save file is weird: it's a base64 string of the
|
* The name of save file is `${currentPlayerId}.json.gz`. The content of save file is weird: it's a base64 string of the
|
||||||
* binary data of compressed json save string. It's weird because the extension is .json.gz while the content is a
|
* binary data of compressed json save string. It's weird because the extension is .json.gz while the content is a
|
||||||
* base64 string. Check the comments in the implementation to see why it is like that.
|
* base64 string. Check the comments in the implementation to see why it is like that.
|
||||||
|
*
|
||||||
|
* @param {SaveData} saveData
|
||||||
|
* @param {string} currentPlayerId
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
async function pushSaveDataToSteamCloud(saveData, currentPlayerId) {
|
async function pushSaveDataToSteamCloud(saveData, currentPlayerId) {
|
||||||
|
// TODO: Check whether we really need to throw an error here or if we can log and return.
|
||||||
if (!isCloudEnabled()) {
|
if (!isCloudEnabled()) {
|
||||||
return Promise.reject("Steam Cloud is not Enabled");
|
throw new Error("Steam Cloud is not enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Refactor this function and backupSteamDataToDisk. backupSteamDataToDisk is not really useful (it tries to
|
||||||
|
// back up the first cloud file). In the case of having multiple cloud files, calling backupSteamDataToDisk and
|
||||||
|
// saveCloudFile like this is useless.
|
||||||
try {
|
try {
|
||||||
await backupSteamDataToDisk(currentPlayerId);
|
await backupSteamDataToDisk(currentPlayerId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error("Cannot back up Steam data to disk", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const steamSaveName = `${currentPlayerId}.json.gz`;
|
const steamSaveName = `${currentPlayerId}.json.gz`;
|
||||||
@@ -202,7 +294,7 @@ async function pushSaveDataToSteamCloud(saveData, currentPlayerId) {
|
|||||||
try {
|
try {
|
||||||
saveCloudFile(steamSaveName, content);
|
saveCloudFile(steamSaveName, content);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error("Cannot save cloud file", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,16 +303,25 @@ async function pushSaveDataToSteamCloud(saveData, currentPlayerId) {
|
|||||||
*/
|
*/
|
||||||
async function getSteamCloudSaveData() {
|
async function getSteamCloudSaveData() {
|
||||||
if (!isCloudEnabled()) {
|
if (!isCloudEnabled()) {
|
||||||
return Promise.reject("Steam Cloud is not Enabled");
|
throw new Error("Steam Cloud is not enabled");
|
||||||
}
|
}
|
||||||
log.debug(`Fetching Save in Steam Cloud`);
|
log.debug("Fetching Save in Steam Cloud");
|
||||||
const cloudString = getCloudFile();
|
const cloudString = getCloudFile();
|
||||||
|
// TODO: Refactor this function and its callers. Not having a cloud file is a valid case.
|
||||||
|
if (cloudString === null) {
|
||||||
|
throw new Error("Cannot get cloud file");
|
||||||
|
}
|
||||||
// Decode cloudString to get save data back.
|
// Decode cloudString to get save data back.
|
||||||
const saveData = Buffer.from(cloudString, "base64");
|
const saveData = Buffer.from(cloudString, "base64");
|
||||||
log.debug(`SaveData: ${saveData.length} bytes`);
|
log.debug(`SaveData: ${saveData.length} bytes`);
|
||||||
return saveData;
|
return saveData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {BrowserWindow} window
|
||||||
|
* @param {{save: SaveData, fileName: string}} electronGameData
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async function saveGameToDisk(window, electronGameData) {
|
async function saveGameToDisk(window, electronGameData) {
|
||||||
const currentFolder = getSaveFolder(window);
|
const currentFolder = getSaveFolder(window);
|
||||||
let saveFolderSizeBytes = await getFolderSizeInBytes(currentFolder);
|
let saveFolderSizeBytes = await getFolderSizeInBytes(currentFolder);
|
||||||
@@ -269,6 +370,7 @@ async function saveGameToDisk(window, electronGameData) {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {BrowserWindow} window */
|
||||||
async function loadLastFromDisk(window) {
|
async function loadLastFromDisk(window) {
|
||||||
const folder = getSaveFolder(window);
|
const folder = getSaveFolder(window);
|
||||||
const last = await getNewestFile(folder);
|
const last = await getNewestFile(folder);
|
||||||
@@ -276,6 +378,7 @@ async function loadLastFromDisk(window) {
|
|||||||
return loadFileFromDisk(last.file);
|
return loadFileFromDisk(last.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {string} path */
|
||||||
async function loadFileFromDisk(path) {
|
async function loadFileFromDisk(path) {
|
||||||
const buffer = await fs.readFile(path);
|
const buffer = await fs.readFile(path);
|
||||||
let content;
|
let content;
|
||||||
@@ -293,47 +396,60 @@ async function loadFileFromDisk(path) {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {BrowserWindow} window
|
||||||
|
* @param {SaveData} save
|
||||||
|
*/
|
||||||
function getSaveInformation(window, save) {
|
function getSaveInformation(window, save) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
ipcMain.once("get-save-info-response", (event, data) => {
|
ipcMain.once("get-save-info-response", (__event, data) => {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
});
|
});
|
||||||
window.webContents.send("get-save-info-request", save);
|
window.webContents.send("get-save-info-request", save);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {BrowserWindow} window */
|
||||||
function getCurrentSave(window) {
|
function getCurrentSave(window) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
ipcMain.once("get-save-data-response", (event, data) => {
|
ipcMain.once("get-save-data-response", (__event, data) => {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
});
|
});
|
||||||
window.webContents.send("get-save-data-request");
|
window.webContents.send("get-save-data-request");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {BrowserWindow} window
|
||||||
|
* @param {SaveData} save
|
||||||
|
* @param {boolean} automatic
|
||||||
|
*/
|
||||||
function pushSaveGameForImport(window, save, automatic) {
|
function pushSaveGameForImport(window, save, automatic) {
|
||||||
ipcMain.once("push-import-result", (event, arg) => {
|
ipcMain.once("push-import-result", (__event, arg) => {
|
||||||
log.debug(`Was save imported? ${arg.wasImported ? "Yes" : "No"}`);
|
log.debug(`Was save imported? ${arg.wasImported ? "Yes" : "No"}`);
|
||||||
});
|
});
|
||||||
window.webContents.send("push-save-request", { save, automatic });
|
window.webContents.send("push-save-request", { save, automatic });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {BrowserWindow} window */
|
||||||
async function restoreIfNewerExists(window) {
|
async function restoreIfNewerExists(window) {
|
||||||
const currentSave = await getCurrentSave(window);
|
const currentSave = await getCurrentSave(window);
|
||||||
const currentData = await getSaveInformation(window, currentSave.save);
|
const currentData = await getSaveInformation(window, currentSave.save);
|
||||||
const steam = {};
|
const steam = {};
|
||||||
const disk = {};
|
const disk = {};
|
||||||
|
|
||||||
try {
|
if (isCloudEnabled()) {
|
||||||
steam.save = await getSteamCloudSaveData();
|
// TODO: Check if we can refactor to avoid using a try-catch block.
|
||||||
steam.data = await getSaveInformation(window, steam.save);
|
try {
|
||||||
} catch (error) {
|
steam.save = await getSteamCloudSaveData();
|
||||||
log.error("Could not retrieve steam file");
|
steam.data = await getSaveInformation(window, steam.save);
|
||||||
log.debug(error);
|
} catch (error) {
|
||||||
|
log.error("Could not retrieve Steam cloud file", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const saves = (await getAllSaves()).sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime());
|
const saves = (await getAllSaves(window)).sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime());
|
||||||
if (saves.length > 0) {
|
if (saves.length > 0) {
|
||||||
disk.save = await loadFileFromDisk(saves[0].file);
|
disk.save = await loadFileFromDisk(saves[0].file);
|
||||||
disk.data = await getSaveInformation(window, disk.save);
|
disk.data = await getSaveInformation(window, disk.save);
|
||||||
@@ -388,7 +504,7 @@ module.exports = {
|
|||||||
pushSaveGameForImport,
|
pushSaveGameForImport,
|
||||||
pushSaveDataToSteamCloud,
|
pushSaveDataToSteamCloud,
|
||||||
getSteamCloudSaveData,
|
getSteamCloudSaveData,
|
||||||
deleteCloudFile,
|
deleteCloudFiles,
|
||||||
saveGameToDisk,
|
saveGameToDisk,
|
||||||
loadLastFromDisk,
|
loadLastFromDisk,
|
||||||
loadFileFromDisk,
|
loadFileFromDisk,
|
||||||
|
|||||||
Reference in New Issue
Block a user