diff --git a/game.v2025-07-31.bundle.js b/game.v2025-07-31.bundle.js
index 806d0f3..0b334b7 100644
--- a/game.v2025-07-31.bundle.js
+++ b/game.v2025-07-31.bundle.js
@@ -1056,6 +1056,10 @@
!e.hasOwnProperty("primaryMapData") || !e.hasOwnProperty("subMapData")
);
}
+ static async sha256_hex(str) {
+ const buf = await window.crypto.subtle.digest("SHA-256", new TextEncoder().encode(str));
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, "0")).join("");
+ }
static compressJSObjectToString(e) {
return btoa(
n.arraybufferToString(
@@ -5625,7 +5629,7 @@
() => {
E.exportSavegamePrivateBin();
},
- )}"> ${e.T("settings-share-save-button", "Export save without file")}\n
\n --\x3e\n \n \n \n `;
+ )}"> ${e.T("settings-share-save-button", "Export save without file")}\n \n --\x3e\n \n \n \n `;
}
static mapManagementFormView() {
const e = s.getInstance();
@@ -5875,16 +5879,21 @@
const orig_compress = n.getSetting("compressExports");
n.setSetting("completeExports", true);
n.setSetting("compressExports", true);
+ const password = $("#named-save-password").val();
try {
const data = await _.getSavegameData(error_obj);
const compressed = n.compressJSObjectToString(data);
const blob = new Blob([compressed], { type: "application/octet-stream" });
+ const headers = { "Content-Type": "application/octet-stream" };
+ if (password) headers["X-Password-Hash"] = await n.sha256_hex(password);
await fetch(`https://pu-saves.burrson.de/saves/${encodeURIComponent(save_name)}`, {
method: "POST",
mode: "cors",
+ headers,
body: blob,
});
- n.showToast("Save stored", `"${save_name}" was saved to the server.`, "success");
+ const label = password ? " (protected)" : "";
+ n.showToast("Save stored", `"${save_name}"${label} was saved to the server.`, "success");
} catch (e) {
n.showToast("Save failed", "Could not store the save on the server.", "danger");
}
@@ -5894,14 +5903,17 @@
static async populateServerSaves() {
try {
const res = await fetch("https://pu-saves.burrson.de/saves", { mode: "cors" });
- const saves = await res.json();
+ const data = await res.json();
const select = $("#server-saves-select");
if (!select.length) return;
select.empty();
- if (saves.length === 0) {
+ if (data.saves.length === 0) {
select.append('');
} else {
- saves.forEach(name => select.append(``));
+ data.saves.forEach(name => {
+ const is_protected = data.protected.includes(name);
+ select.append(``);
+ });
}
} catch (e) {
$("#server-saves-select").empty().append('');
@@ -5910,13 +5922,21 @@
static async importNamedSave() {
const save_name = $("#server-saves-select").val();
if (!save_name) return;
+ const password = $("#named-load-password").val();
try {
- const res = await fetch(`https://pu-saves.burrson.de/saves/${encodeURIComponent(save_name)}`, { mode: "cors" });
+ const headers = {};
+ if (password) headers["X-Password-Hash"] = await n.sha256_hex(password);
+ const res = await fetch(`https://pu-saves.burrson.de/saves/${encodeURIComponent(save_name)}`, { mode: "cors", headers });
+ if (res.status === 401) {
+ const msg = await res.text();
+ n.showToast("Access denied", msg.includes("required") ? "This save is password protected." : "Wrong password.", "danger");
+ return;
+ }
if (!res.ok) throw "Not found";
const text = await res.text();
if (n.hasScript(text)) throw "Script found!";
- const data = JSON.parse(n.decompressJsonFromString(text));
- E.importSaveGame(data);
+ const parsed = JSON.parse(n.decompressJsonFromString(text));
+ E.importSaveGame(parsed);
} catch (e) {
n.showToast("Load failed", "Could not load the save from the server.", "danger");
}