mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
MISC: Support importing Steam Cloud save file manually (#2583)
This commit is contained in:
3
electron/saveDataBinaryFormat.d.ts
vendored
3
electron/saveDataBinaryFormat.d.ts
vendored
@@ -1 +1,4 @@
|
||||
export declare const encodeBytesToBase64String: (bytes: Uint8Array<ArrayBuffer>) => string;
|
||||
export declare const decodeBase64BytesToBytes: (bytes: Uint8Array<ArrayBuffer>) => Uint8Array<ArrayBuffer>;
|
||||
export declare const isBinaryFormat: (saveData: string | Uint8Array<ArrayBuffer>) => boolean;
|
||||
export declare const isSteamCloudFormat: (saveData: string | Uint8Array<ArrayBuffer>) => boolean;
|
||||
|
||||
@@ -1,13 +1,68 @@
|
||||
// The 2 magic bytes of the gzip header plus the mandatory compression type of DEFLATE
|
||||
const magicBytes = [0x1f, 0x8b, 0x08];
|
||||
const magicBytesOfDeflateGzip = new Uint8Array([0x1f, 0x8b, 0x08]);
|
||||
// Base64-encoded string of magicBytesOfDeflateGzip
|
||||
const base64EncodingOfMagicBytes = encodeBytesToBase64String(magicBytesOfDeflateGzip);
|
||||
// Convert the base64-encoded string to a byte array
|
||||
const byteArrayOfBase64EncodingOfMagicBytes = Uint8Array.from(base64EncodingOfMagicBytes, (c) => c.charCodeAt(0));
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {string}
|
||||
*/
|
||||
function encodeBytesToBase64String(bytes) {
|
||||
let binaryString = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
binaryString += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binaryString);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function decodeBase64BytesToBytes(bytes) {
|
||||
let base64String = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
base64String += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
const decodedBinaryString = atob(base64String);
|
||||
const result = new Uint8Array(decodedBinaryString.length);
|
||||
for (let i = 0; i < decodedBinaryString.length; i++) {
|
||||
result[i] = decodedBinaryString.charCodeAt(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | Uint8Array} rawData
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isBinaryFormat(rawData) {
|
||||
for (let i = 0; i < magicBytes.length; ++i) {
|
||||
if (magicBytes[i] !== rawData[i]) {
|
||||
for (let i = 0; i < magicBytesOfDeflateGzip.length; ++i) {
|
||||
if (magicBytesOfDeflateGzip[i] !== rawData[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = { isBinaryFormat };
|
||||
/**
|
||||
* The Steam Cloud save file is a base64-encoded gz file.
|
||||
*
|
||||
* @param {string | Uint8Array} rawData
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isSteamCloudFormat(rawData) {
|
||||
if (typeof rawData === "string") {
|
||||
return rawData.startsWith(base64EncodingOfMagicBytes);
|
||||
}
|
||||
for (let i = 0; i < byteArrayOfBase64EncodingOfMagicBytes.length; ++i) {
|
||||
if (byteArrayOfBase64EncodingOfMagicBytes[i] !== rawData[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = { encodeBytesToBase64String, decodeBase64BytesToBytes, isBinaryFormat, isSteamCloudFormat };
|
||||
|
||||
@@ -6,7 +6,7 @@ const fs = require("fs/promises");
|
||||
const log = require("electron-log");
|
||||
const flatten = require("lodash/flatten");
|
||||
const Store = require("electron-store");
|
||||
const { isBinaryFormat } = require("./saveDataBinaryFormat");
|
||||
const { decodeBase64BytesToBytes, isBinaryFormat, isSteamCloudFormat } = require("./saveDataBinaryFormat");
|
||||
const store = new Store();
|
||||
const { steamworksClient } = require("./steamworksUtils");
|
||||
|
||||
@@ -224,7 +224,7 @@ async function getSteamCloudSaveData() {
|
||||
async function saveGameToDisk(window, electronGameData) {
|
||||
const currentFolder = getSaveFolder(window);
|
||||
let saveFolderSizeBytes = await getFolderSizeInBytes(currentFolder);
|
||||
const maxFolderSizeBytes = store.get("autosave-quota", 1e8); // 100Mb per playerIndentifier
|
||||
const maxFolderSizeBytes = store.get("autosave-quota", 1e8); // 100Mb
|
||||
const remainingSpaceBytes = maxFolderSizeBytes - saveFolderSizeBytes;
|
||||
log.debug(`Folder Usage: ${saveFolderSizeBytes} bytes`);
|
||||
log.debug(`Folder Capacity: ${maxFolderSizeBytes} bytes`);
|
||||
@@ -282,6 +282,9 @@ async function loadFileFromDisk(path) {
|
||||
if (isBinaryFormat(buffer)) {
|
||||
// Save file is in the binary format.
|
||||
content = buffer;
|
||||
} else if (isSteamCloudFormat(buffer)) {
|
||||
// Save file is in the Steam Cloud format.
|
||||
content = decodeBase64BytesToBytes(buffer);
|
||||
} else {
|
||||
// Save file is in the base64 format.
|
||||
content = buffer.toString("utf8");
|
||||
|
||||
@@ -23,7 +23,7 @@ import { pushGameSaved, pushImportResult } from "./Electron";
|
||||
import { getGoSave, loadGo } from "./Go/SaveLoad";
|
||||
import { SaveData } from "./types";
|
||||
import { SaveDataError, canUseBinaryFormat, decodeSaveData, encodeJsonSaveString } from "./utils/SaveDataUtils";
|
||||
import { isBinaryFormat } from "../electron/saveDataBinaryFormat";
|
||||
import { decodeBase64BytesToBytes, isBinaryFormat, isSteamCloudFormat } from "../electron/saveDataBinaryFormat";
|
||||
import { downloadContentAsFile } from "./utils/FileUtils";
|
||||
import { handleGetSaveDataInfoError } from "./utils/ErrorHandler";
|
||||
import { isObject, assertObject } from "./utils/TypeAssertion";
|
||||
@@ -346,6 +346,9 @@ class BitburnerSaveObject implements BitburnerSaveObjectType {
|
||||
if (isBinaryFormat(rawData)) {
|
||||
return rawData;
|
||||
}
|
||||
if (isSteamCloudFormat(rawData)) {
|
||||
return decodeBase64BytesToBytes(rawData);
|
||||
}
|
||||
return new TextDecoder().decode(rawData);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const { isBinaryFormat } = require("../electron/saveDataBinaryFormat");
|
||||
const { decodeBase64BytesToBytes, isBinaryFormat, isSteamCloudFormat } = require("../electron/saveDataBinaryFormat");
|
||||
|
||||
async function decompress(data) {
|
||||
const decompressedReadableStream = new Blob([data]).stream().pipeThrough(new DecompressionStream("gzip"));
|
||||
return await new Response(decompressedReadableStream).text();
|
||||
}
|
||||
|
||||
async function getSave(file) {
|
||||
const data = await fs.readFile(file);
|
||||
|
||||
let jsonSaveString;
|
||||
if (isBinaryFormat(data)) {
|
||||
const decompressedReadableStream = new Blob([data]).stream().pipeThrough(new DecompressionStream("gzip"));
|
||||
jsonSaveString = await new Response(decompressedReadableStream).text();
|
||||
jsonSaveString = await decompress(data);
|
||||
} else if (isSteamCloudFormat(data)) {
|
||||
jsonSaveString = await decompress(decodeBase64BytesToBytes(data));
|
||||
} else {
|
||||
jsonSaveString = decodeURIComponent(escape(atob(data)));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user