Greasy Fork

Papa's Save Manager

Allows you to backup your save data for the Papa's series of games on coolmathgames.com

目前为 2023-09-01 提交的版本。查看 最新版本

// ==UserScript==
// @name        Papa's Save Manager
// @namespace   vaminta
// @match       https://www.coolmathgames.com/0-papas-bakeria
// @match       https://www.coolmathgames.com/0-papas-freezeria
// @grant       none
// @version     0.2.0
// @author      Vaminta
// @run-at      document-idle
// @description Allows you to backup your save data for the Papa's series of games on coolmathgames.com
// @homepageURL https://github.com/Vaminta/papas-save-manager
// ==/UserScript==

//31/07/2023, 17:49:11
//01/09/2023

psm = new Object();

/*
USER OPTIONS:

saveTxtExt: (bool) export outputs the file with .txt extension rather than .psm - same contents
forceImport: (bool) bypass validation checks (not recommended)
preventGameLoad: (bool) attempts to prevent game loading, useful for internal testing

 */
psm.userOptions = {
    saveTxtExt: false,
    forceImport: false,
    preventGameLoad: false
}

// --------------

psm.version = "0.2.0";
psm.saveVersion = "001";
psm.savePrefix = "PSMS"; //PSM save
psm.saveExt = "psm";
psm.gameList = Object.freeze([
    {
        name:"Papa's Bakeria",
        pathname: "/0-papas-bakeria",
        saveName: "papasbakeria_save",
        saveIdentifier: "08",
        lsKeys: ["//papasbakeria1","//papasbakeria2","//papasbakeria3"]
    },
    {
        name:"Papa's Freezeria",
        pathname: "/0-papas-freezeria",
        saveName: "papasfreezeria_save",
        saveIdentifier: "09",
        lsKeys: ["//papasfreezeria_1","//papasfreezeria_2","//papasfreezeria_3"]
    }
]);

psm.game = null;

const pbsmCSS = `
.pbsm-imp-button, .pbsm-exp-button{
    background-color: #1d3752;
    color: #ffffff;
    border: 1px #06203b solid;
    border-radius: 5px;
    width: 70px;
    height: 30px;
}

.pbsm-imp-button:hover, .pbsm-exp-button:hover{
    background-color: #2d4762;
}
`;

function injectCSS(css){
    let styleE = document.createElement("style");
    styleE.innerText = css;
    document.head.appendChild(styleE);
}

function slotHasSave(slot){
    let result = false;
    const data = localStorage.getItem(psm.game.lsKeys[slot]);
    if(data!=null&&data.length>20) result = true;
    return result;
}

//Generic download function
function download(filename, text) {
    let element = document.createElement('a');
    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
    element.setAttribute('download', filename);

    element.style.display = 'none';
    document.body.appendChild(element);

    element.click();

    document.body.removeChild(element);
}

function exportSave(slot){
    if(!slotHasSave(slot)){
        alert("No save in slot " + (slot+1) + " detected!");
        return;
    }
    const data = psm.savePrefix + psm.saveVersion + psm.game.saveIdentifier + localStorage.getItem(psm.game.lsKeys[slot]);
    const filename = psm.game.saveName + "." + psm.saveExt;
    download(filename,data);
}

//Checks content of PSM for validity and returning results as object containing breakdown, useful for providing user feedback
function isValidSave(data,expGameID){
    if(!data)data = "";
    if(!expGameID) expGameID = psm.game.saveIdentifier;
    let result = {
        isNotEmpty: data.length>0 ? true : false,
        isPSMS: data.slice(0,4)==psm.savePrefix ? true : false,
        isNotTimeTraveller: parseInt(data.slice(4,7)) <= parseInt(psm.saveVersion) ? true : false, //save isn't from future version of psm
        isCorrectGame: (expGameID=="*" || data.slice(7,9) == psm.game.saveIdentifier) ? true : false,
        isEncoded: true, //implement later
        conclusion: false
    };
    result.conclusion = (result.isNotEmpty && result.isPSMS && result.isNotTimeTraveller && result.isCorrectGame && result.isEncoded) ? true : false;
    return result;
}

function processImport(slot,data){
    //const expectedSaveID = gsm.game.saveIdentifier;
    //const saveVersion = parseInt(psm.saveVersion);
    const fileValidity = isValidSave(data);
    const key = psm.game.lsKeys[slot];
    const forceLoad = psm.userOptions.forceImport;
    console.log(fileValidity);
    if(fileValidity.conclusion || forceLoad){ //continue to load
        const importData = data.slice(9);
        localStorage.setItem(key,importData);
    }
    else{ // do not load -> show error
        let errorMsg = "PSM Import Error: \n\n";
        if(!fileValidity.isNotEmpty) errorMsg += " - File is empty\n";
        else if(!fileValidity.isPSMS) errorMsg += " - File is unrecognised format\n";
        else{
            if(!fileValidity.isNotTimeTraveller) errorMsg += " - File was exported from a future version of PSM, please update\n";
            if(!fileValidity.isCorrectGame) errorMsg += " - File appears to be for a different Papa's series game\n";
        }
        errorMsg += "\nFurther information and help may be found on GitHub";
        alert(errorMsg);
    }
}

// Setup file reader then send output to processImport
function importSave(slot){
    if(slotHasSave(slot)){
        let overwrite = confirm("There is already data in slot " + (slot+1) + ". Are you sure you want to overwrite?");
        if(!overwrite) return;
    }
    const file = document.getElementById("file-picker").files[0];
    const reader = new FileReader();
    reader.addEventListener("load",function(){
        processImport(slot,reader.result);
    },false,);
    if(file) reader.readAsText(file);
}

/*
Handles button clicks from the import and export buttons
*/
function handleButtonClick(e,func){
    let targetSave = e.parentElement.getAttribute("data-ss")-1;
    if(func=="import"){
        document.getElementById("file-picker").onchange = function(){importSave(targetSave)};
        document.getElementById("file-picker").click();
    }
    else if(func=="export"){
        exportSave(targetSave);
    }
}

function genTableHTML(){
    const impButtHTML = '<button class="pbsm-imp-button">Import</button>';
    const expButtHTML = '<button class="pbsm-exp-button">Export</button>';
    let table = "<table>";
    table += "<tr><th>Slot 1</th><th>Slot 2</th><th>Slot 3</th></tr>";
    table += "<tr><td data-ss='1' >" + impButtHTML + "</td><td data-ss='2' >"+ impButtHTML + "</td><td data-ss='3' >"+impButtHTML+"</td></tr>"; //data-saveslot
    table += "<tr><td data-ss='1'>" + expButtHTML + "</td><td data-ss='2' >"+ expButtHTML + "</td><td data-ss='3' >"+expButtHTML+"</td></tr>";
    table += "</table>";
    return table;
}

function addOnclicks(){
    let importButtons = document.getElementsByClassName("pbsm-imp-button");
    let exportButtons = document.getElementsByClassName("pbsm-exp-button");
    for(let i=0;i<importButtons.length;i++){
        importButtons[i].onclick = function(){handleButtonClick(this,"import")};
    }
    for(let i=0;i<exportButtons.length;i++){
        exportButtons[i].onclick = function(){handleButtonClick(this,"export")};
    }
}

function generateHTML(){
    let div = document.createElement("div");
    div.id = "pbsm-cont"; //papas save manager container
    div.className = "game-meta-body";
    div.style = "padding-bottom: 1%;";
    let genHTML = "<h2>Save Manager</h2><p>This save manager allows you to import and export saves to ensure they are never lost. Saves can also be moved between devices.</p>";
    genHTML += genTableHTML();
    div.innerHTML = genHTML;

    let filePicker = document.createElement("input");
    filePicker.setAttribute("type","file");
    filePicker.id = "file-picker";
    filePicker.style.display = "none";
    div.appendChild(filePicker);

    let footerP = document.createElement("p");
    footerP.style = "font-size:10px; margin: 2% 0% 0% 0%;";
    footerP.innerHTML = "Version: " + psm.version + " ・ Running game: " + psm.game.name + " ・ Software provided without warranty ・ More info on <a href='https://github.com/Vaminta/papas-bakeria-save-manager' target='_blank' >GitHub</a>"
    div.appendChild(footerP);

    let parentNode = document.getElementsByClassName("game-meta-body")[0];
    document.getElementsByClassName("node__content clearfix field-item")[0].insertBefore(div,parentNode);
    addOnclicks();
}

// returns singular game object by given key + matching value
function getGame(key,value){
    let game = null;
    for(let i=0;i<psm.gameList.length;i++){
        if(psm.gameList[i][key]==value){
            game = psm.gameList[i];
            break;
        }
    }
    return game;
}

function detectGame(){
    psm.game = getGame("pathname", window.location.pathname);
    console.log(psm.game);
}

//attempt to prevent the game from loading
function blockGame(){
    let wrapper = document.getElementById("swfgamewrapper");
    wrapper.innerHTML = "";
}

function initialise(){
    detectGame();
    if(psm.userOptions.saveTxtExt) psm.saveExt = "txt";
    if(psm.userOptions.preventGameLoad) blockGame();
    injectCSS(pbsmCSS);
    generateHTML();
}

initialise();