This commit is contained in:
Philippe 2024-12-20 23:41:58 -08:00 committed by GitHub
commit ba0a3f8395
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 158 additions and 70 deletions

View File

@ -14,7 +14,7 @@ import { GameModes, getGameMode } from "#app/game-mode";
import { BattleType } from "#app/battle";
import TrainerData from "#app/system/trainer-data";
import { trainerConfigs } from "#app/data/trainer-config";
import { resetSettings, setSetting, SettingKeys } from "#app/system/settings/settings";
import { resetSettings, setSetting, Setting, settingIndex, SettingKeys } from "#app/system/settings/settings";
import { achvs } from "#app/system/achv";
import EggData from "#app/system/egg-data";
import { Egg } from "#app/data/egg";
@ -65,16 +65,12 @@ export const defaultStarterSpecies: Species[] = [
const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary
export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): string {
export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0, username?: string): string {
switch (dataType) {
case GameDataType.SYSTEM:
return "data";
return `data_${username}`;
case GameDataType.SESSION:
let ret = "sessionData";
if (slotId) {
ret += slotId;
}
return ret;
return `sessionData${slotId || ""}_${username}`;
case GameDataType.SETTINGS:
return "settings";
case GameDataType.TUTORIALS:
@ -82,7 +78,7 @@ export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): str
case GameDataType.SEEN_DIALOGUES:
return "seenDialogues";
case GameDataType.RUN_HISTORY:
return "runHistoryData";
return `runHistoryData_${username}`;
}
}
@ -1364,9 +1360,35 @@ export class GameData {
});
}
public getDataToExport(dataType: GameDataType, slotId: integer = 0): Promise<string | null> {
return new Promise<string | null>(resolve => {
if (!bypassLogin && dataType < GameDataType.SETTINGS) {
let promise: Promise<string | null> = Promise.resolve(null);
if (dataType === GameDataType.SYSTEM) {
promise = pokerogueApi.savedata.system.get({ clientSessionId });
} else if (dataType === GameDataType.SESSION) {
promise = pokerogueApi.savedata.session.get({ slot: slotId, clientSessionId });
}
promise.then(response => {
if (!response?.length || response[0] !== "{") {
console.error(response);
resolve(null);
}
resolve(response);
});
} else {
const dataKey: string = getDataTypeKey(dataType, slotId, loggedInUser?.username);
const data = localStorage.getItem(dataKey);
resolve((!data || dataType === GameDataType.SETTINGS) ? data : decrypt(data, bypassLogin));
}
});
}
public tryExportData(dataType: GameDataType, slotId: integer = 0): Promise<boolean> {
return new Promise<boolean>(resolve => {
const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`;
const dataKey: string = getDataTypeKey(dataType, slotId, loggedInUser?.username);
const handleData = (dataStr: string) => {
switch (dataType) {
case GameDataType.SYSTEM:
@ -1381,38 +1403,56 @@ export class GameData {
link.click();
link.remove();
};
if (!bypassLogin && dataType < GameDataType.SETTINGS) {
let promise: Promise<string | null> = Promise.resolve(null);
if (dataType === GameDataType.SYSTEM) {
promise = pokerogueApi.savedata.system.get({ clientSessionId });
} else if (dataType === GameDataType.SESSION) {
promise = pokerogueApi.savedata.session.get({ slot: slotId, clientSessionId });
}
promise.then(response => {
if (!response?.length || response[0] !== "{") {
console.error(response);
resolve(false);
return;
this.getDataToExport(dataType, slotId)
.then(data => {
if (data) {
handleData(data);
}
handleData(response);
resolve(true);
resolve(!!data);
});
} else {
const data = localStorage.getItem(dataKey);
if (data) {
handleData(decrypt(data, bypassLogin));
}
resolve(!!data);
}
});
}
public importData(dataType: GameDataType, slotId: integer = 0): void {
const dataKey = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`;
public validateDataToImport(dataStr: string, dataType: GameDataType): boolean {
try {
switch (dataType) {
case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr);
const systemData = this.parseSystemData(dataStr);
return !!systemData.dexData && !!systemData.timestamp;
case GameDataType.SESSION:
const sessionData = this.parseSessionData(dataStr);
return !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp;
case GameDataType.RUN_HISTORY:
const data = JSON.parse(dataStr);
const keys = Object.keys(data);
return keys.every((key) => {
const entryKeys = Object.keys(data[key]);
return [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3;
});
case GameDataType.SETTINGS:
return Object.entries(JSON.parse(dataStr))
.every(([ k, v ]: [string, number]) => {
const index: number = settingIndex(k);
return index === -1 || Setting[index].options.length > v;
});
case GameDataType.TUTORIALS:
case GameDataType.SEEN_DIALOGUES:
return true;
}
} catch (ex) {
console.error(ex);
return false;
}
}
public setImportedData(dataStr: string, dataType: GameDataType, slotId: integer = 0) {
const dataKey = getDataTypeKey(dataType, slotId, loggedInUser?.username);
const encryptedData = (dataType === GameDataType.SETTINGS) ? dataStr : encrypt(dataStr, bypassLogin);
localStorage.setItem(dataKey, encryptedData);
}
public importData(dataType: GameDataType, slotId: integer = 0): void {
let saveFile: any = document.getElementById("saveFile");
if (saveFile) {
saveFile.remove();
@ -1429,41 +1469,13 @@ export class GameData {
reader.onload = (_ => {
return e => {
let dataName: string;
let dataStr = AES.decrypt(e.target?.result?.toString()!, saveKey).toString(enc.Utf8); // TODO: is this bang correct?
let valid = false;
try {
dataName = GameDataType[dataType].toLowerCase();
switch (dataType) {
case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr);
const systemData = this.parseSystemData(dataStr);
valid = !!systemData.dexData && !!systemData.timestamp;
break;
case GameDataType.SESSION:
const sessionData = this.parseSessionData(dataStr);
valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp;
break;
case GameDataType.RUN_HISTORY:
const data = JSON.parse(dataStr);
const keys = Object.keys(data);
dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase();
keys.forEach((key) => {
const entryKeys = Object.keys(data[key]);
valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3;
});
break;
case GameDataType.SETTINGS:
case GameDataType.TUTORIALS:
valid = true;
break;
}
} catch (ex) {
console.error(ex);
}
const dataName = (dataType === GameDataType.RUN_HISTORY)
? i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase()
: GameDataType[dataType].toLowerCase();
const dataStr = AES.decrypt(e.target?.result?.toString()!, saveKey).toString(enc.Utf8); // TODO: is this bang correct?
const valid = this.validateDataToImport(dataStr, dataType);
const displayError = (error: string) => this.scene.ui.showText(error, null, () => this.scene.ui.showText("", 0), Utils.fixedInt(1500));
dataName = dataName!; // tell TS compiler that dataName is defined!
if (!valid) {
return this.scene.ui.showText(`Your ${dataName} data could not be loaded. It may be corrupted.`, null, () => this.scene.ui.showText("", 0), Utils.fixedInt(1500));
@ -1471,7 +1483,7 @@ export class GameData {
this.scene.ui.showText(`Your ${dataName} data will be overridden and the page will reload. Proceed?`, null, () => {
this.scene.ui.setOverlayMode(Mode.CONFIRM, () => {
localStorage.setItem(dataKey, encrypt(dataStr, bypassLogin));
this.setImportedData(dataStr, dataType, slotId);
if (!bypassLogin && dataType < GameDataType.SETTINGS) {
updateUserInfo().then(success => {

View File

@ -2,6 +2,7 @@ import * as BattleScene from "#app/battle-scene";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import { SessionSaveData } from "#app/system/game-data";
import { Abilities } from "#enums/abilities";
import { GameDataType } from "#enums/game-data-type";
import { Moves } from "#enums/moves";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
@ -73,4 +74,63 @@ describe("System - Game Data", () => {
expect(account.updateUserInfo).toHaveBeenCalled();
});
});
describe("getDataToExport", () => {
it("should get default settings", async () => {
const defaultSettings = "{\"PLAYER_GENDER\":0,\"gameVersion\":\"1.0.4\"}";
localStorage.setItem("settings", defaultSettings);
const result = await game.scene.gameData.getDataToExport(GameDataType.SETTINGS);
expect(result).toEqual(defaultSettings);
});
it("should get undefined when there is no settings", async () => {
const result = await game.scene.gameData.getDataToExport(GameDataType.SETTINGS);
expect(result).toBeUndefined();
});
});
describe("setImportedData", () => {
it("should set settings in local storage", () => {
const settings = "{\"PLAYER_GENDER\":0,\"gameVersion\":\"1.0.4\"}";
game.scene.gameData.setImportedData(settings, GameDataType.SETTINGS);
expect(localStorage.getItem("settings")).toEqual(settings);
});
it("should override default settings", () => {
const defaultSettings = "{\"PLAYER_GENDER\":0,\"gameVersion\":\"1.0.4\"}";
localStorage.setItem("settings", defaultSettings);
const newSettings = "{\"PLAYER_GENDER\":1,\"gameVersion\":\"1.0.7\",\"GAME_SPEED\":7}";
game.scene.gameData.setImportedData(newSettings, GameDataType.SETTINGS);
expect(localStorage.getItem("settings")).toEqual(newSettings);
});
});
describe("validateDataToImport", () => {
it("should be true when the setting data is valid", async () => {
const settings = "{\"PLAYER_GENDER\":0,\"gameVersion\":\"1.0.4\"}";
const result = await game.scene.gameData.validateDataToImport(settings, GameDataType.SETTINGS);
expect(result).toBeTruthy();
});
it("should be false when the setting data is an invalid JSON", async () => {
const settings = "";
const result = await game.scene.gameData.validateDataToImport(settings, GameDataType.SETTINGS);
expect(result).toBeFalsy();
});
it("should be false when the setting data contains an unknow value", async () => {
const settings = "{\"PLAYER_GENDER\":0,\"gameVersion\":\"1.0.4\",\"GAME_SPEED\":999}";
const result = await game.scene.gameData.validateDataToImport(settings, GameDataType.SETTINGS);
expect(result).toBeFalsy();
});
});
});

View File

@ -246,6 +246,22 @@ export default class MenuUiHandler extends MessageUiHandler {
},
keepOpen: true
});
manageDataOptions.push({
label: i18next.t("menuUiHandler:importSettings"),
handler: () => {
this.scene.gameData.importData(GameDataType.SETTINGS);
return true;
},
keepOpen: true
});
manageDataOptions.push({
label: i18next.t("menuUiHandler:exportSettings"),
handler: () => {
this.scene.gameData.tryExportData(GameDataType.SETTINGS);
return true;
},
keepOpen: true
});
if (Utils.isLocal || Utils.isBeta) {
manageDataOptions.push({
label: i18next.t("menuUiHandler:importData"),