UNPKG

dashtools

Version:

Library for interacting with Geometry Dash servers

898 lines (859 loc) 33.6 kB
import * as constants from "../constants.js" import * as utils from "../utils.js" import * as xml from "xml-js" export function decodeSaveFile(save, type) { if (save.startsWith("<?xml")) return save.toString() if (!type) return utils.tryUnzip(utils.base64DecodeBuffer(utils.xor(save, constants.KEYS.SAVE_DATA))) } export function parseSave(save) { let raw = xml.xml2js(save) if (!raw.elements[0] || raw.elements[0].name != "plist") throw new Error("<plist> tag not detected") if (!raw.elements[0].elements[0] || raw.elements[0].elements[0].name != "dict") throw new Error("<dict> tag not detected") let decoded = parseDict(raw.elements[0].elements[0].elements) return decoded } export let enums = { "textureQuality": { "auto": 0, "low": 1, "medium": 2, "high": 3, 0: "auto", 1: "low", 2: "medium", 3: "high" }, "resolution": { 1: "640x480", 2: "720x480", 3: "720x576", 4: "800x600", 5: "1024x768", 6: "1152x864", 7: "1176x664", 8: "1280x720", 9: "1280x768", 10: "1280x800", 11: "1280x960", 12: "1280x1024", 13: "1360x768", 14: "1366x768", 15: "1440x900", 16: "1600x900", 17: "1600x1024", 18: "1600x1200", 19: "1600x1050", 20: "1768x992", 21: "1920x1080", 22: "1920x1200", 23: "1920x1440", 24: "2048x1536", 25: "2560x1440", 26: "2560x1600", 27: "3840x2160", "640x480": 1, "720x480": 2, "720x576": 3, "800x600": 4, "1024x768": 5, "1152x864": 6, "1176x664": 7, "1280x720": 8, "1280x768": 9, "1280x800": 10, "1280x960": 11, "1280x1024": 12, "1360x768": 13, "1366x768": 14, "1440x900": 15, "1600x900": 16, "1600x1024": 17, "1600x1200": 18, "1600x1050": 19, "1768x992": 20, "1920x1080": 21, "1920x1200": 22, "1920x1440": 23, "2048x1536": 24, "2560x1440": 25, "2560x1600": 26, "3840x2160": 27 } } const keyNames = { "GJA_001": "username", "GJA_002": "password", "GJA_003": "accountID", "GJA_004": "sessionID", "GJA_005": "hashedPassword", "texQuality": "textureQuality", "customObjectDict": "customObjects", "GLM_01": "officialLevels", "GLM_02": "localLevels", "GLM_03": "savedLevels", "GLM_04": "ratesLikesOld", "GLM_06": "followedCreators", "GLM_07": "lastSessionPlayedLevels", "GLM_08": "searchFilters", "GLM_09": "enabledSearchFilters", "GLM_10": "dailyLevels", "GLM_11": "dailyID", "GLM_12": "likes", "GLM_13": "ratings", "GLM_14": "reportedLevels", "GLM_15": "demonRatings", "GLM_16": "gauntletLevels", "GLM_17": "weeklyID", "GLM_18": "onlineLevelFolders", "GLM_19": "localLevelFolders", "GLM_20": "smartTemplates", "GLM_22": "favoritedLists", "GS_value": "stats", "GS_completed": "completedLevels", "GS_26": "activePath", "GS_27": "listRewards", "GS_29": "attemptedToFixPathBug", "MDLM_001": "songMetadata" } const gv = { "0001": ["bool", "followPlayer", 1], "0002": ["bool", "playMusic", 1], "0003": ["bool", "swipe", 1], "0004": ["bool", "freeMove", 1], "0005": ["int", "deleteFilter", 1], "0006": ["int", "deleteFilterObjectID", 1], "0007": ["bool", "rotate", 1], "0008": ["bool", "snap", 1], "0009": ["bool", "ignoreDamage", 1], "0010": ["bool", "flip2PlayerControls"], "0011": ["bool", "alwaysLimitControls"], "0012": ["bool", "showedCommentRules", 3], "0013": ["bool", "increaseMaxUndo", 1], "0014": ["bool", "disableExplosionShake"], "0015": ["bool", "flipPauseButton"], "0016": ["bool", "showedNewgroundsTOS", 3], "0018": ["bool", "noSongLimit"], "0019": ["bool", "songsInMemory"], "0022": ["bool", "higherAudioQuality"], "0023": ["bool", "smoothFix"], "0024": ["bool", "showCursor"], "0025": ["bool", "windowed"], "0026": ["bool", "autoRetry"], "0027": ["bool", "autoCheckpoints"], "0028": ["bool", "disableThumbstick"], "0029": ["bool", "showedOptionsText", 3], "0030": ["bool", "vsync"], "0031": ["bool", "callGLFinish"], "0032": ["bool", "forceTimerEnabled"], "0033": ["bool", "changeSongPath"], "0034": ["bool", "gameCenterEnabled"], "0036": ["bool", "previewMode", 1], "0037": ["bool", "showGround", 1], "0038": ["bool", "showGrid", 1], "0039": ["bool", "gridOnTop", 1], "0040": ["bool", "showPercentage"], "0041": ["bool", "showObjectInfo", 1], "0042": ["bool", "increaseMaxLevels"], "0043": ["bool", "effectLinesEnabled", 1], "0044": ["bool", "drawTriggerBoxes", 1], "0045": ["bool", "showHitboxes", 1], "0046": ["bool", "hideUIOnTest", 1], "0047": ["bool", "showedProfileText", 3], "0048": ["bool", "viewedOwnProfile", 3], "0049": ["int", "buttonsPerRow", 1], "0050": ["int", "buttonRows", 1], "0051": ["bool", "showedEditorNewgroundsMessage", 3], "0052": ["bool", "enableFasterReset"], "0053": ["bool", "freeGamesPopup"], "0055": ["bool", "tryCheckIfServerOnline"], "0056": ["bool", "disableObjectAlert"], "0057": ["bool", "holdToSwipe", 1], "0058": ["bool", "durationLinesEnabled", 1], "0059": ["bool", "swipeCycleMode", 1], "0060": ["bool", "defaultMiniIcon"], "0061": ["bool", "switchSpiderTeleportColor"], "0062": ["bool", "switchDashFireColor"], "0063": ["bool", "showedUnverifiedCoinsMessage", 3], "0064": ["bool", "selectFilter", 1], "0065": ["bool", "enableMoveOptimization"], "0066": ["bool", "highCapacityMode"], "0067": ["bool", "highStartPosAccuracy"], "0068": ["bool", "quickCheckpointMode"], "0069": ["bool", "hideDescriptionCommentMode"], "0070": ["bool", "showedUnlistedLevelMessage", 3], "0071": ["bool", "hidePracticeButtons"], "0072": ["bool", "disableGravityEffect"], "0073": ["bool", "newCompletedFilter"], "0074": ["bool", "showRestartButton"], "0075": ["bool", "disableComments", 2], "0076": ["bool", "disableAccountComments", 2], "0077": ["bool", "onlyShowFeaturedLevels", 2], "0078": ["bool", "hideBackground", 1], "0079": ["bool", "hideGridOnPlay", 1], "0081": ["bool", "disableShakeEffectsOld"], "0082": ["bool", "disableHighObjectAlert"], "0083": ["bool", "disableSongAlert"], "0084": ["bool", "manualLevelOrder"], "0088": ["bool", "smallCommentMode"], "0089": ["bool", "extendedInfoMode"], "0090": ["bool", "autoLoadComments"], "0091": ["int", "myLevelsFolderNumber"], "0092": ["int", "onlineLevelsFolderNumber"], "0093": ["bool", "increaseLocalLevelsPerPage"], "0094": ["bool", "moreCommentsMode"], "0095": ["bool", "justDont"], "0096": ["bool", "switchWaveTrailColor"], "0097": ["bool", "enableLinkControls", 1], "0098": ["int", "levelLeaderboardType"], "0099": ["bool", "showLeaderboardPercentage"], "0100": ["bool", "practiceDeathEffect"], "0101": ["bool", "forceSmoothFix"], "0102": ["bool", "smoothFixInEditor", 1], "0103": ["bool", "layerLocking", 1], "0104": ["bool", "recordOrder", 1], "0105": ["bool", "startPosPlayback", 1], "0106": ["bool", "showMeltdownPromo"], "0108": ["bool", "autoLowDetail"], "0109": ["bool", "levelInfoLabel"], "0110": ["bool", "fastEditorPreview", 1], "0112": ["bool", "increaseScaleLimit", 1], "0113": ["bool", "flipPlatformerControls"], "0115": ["bool", "showFPS"], "0116": ["bool", "useCustomFPS"], "0117": ["bool", "previewParticles", 1], "0118": ["bool", "previewAnimation", 1], "0119": ["bool", "dontSaveLevels"], "0121": ["bool", "hideInvisible", 1], "0122": ["bool", "disableMenuMusic"], "0125": ["bool", "unlockPracticeMusic", 1], "0126": ["bool", "decimalPercentage"], "0127": ["bool", "saveGauntletLevels"], "0128": ["bool", "lockCursorInGame"], "0129": ["bool", "disablePortalLabels"], "0130": ["bool", "enableOrbLabels"], "0131": ["bool", "useNearbyAsReferenceSmartTemplate", 1], "0132": ["bool", "dontDeleteSmartTemplate", 1], "0133": ["int", "groupIDFilter", 1], "0134": ["bool", "hideAttempts"], "0135": ["bool", "hideAttemptsInPracticeMode"], "0136": ["bool", "enableExtraLDM"], "0137": ["bool", "hideParticleIcons", 1], "0139": ["int", "colorChannelFilter", 1], "0140": ["bool", "disableOrbScale"], "0141": ["bool", "disableTriggerOrbPulse"], "0142": ["bool", "reduceAudioQuality"], "0144": ["bool", "audioVisualizer"], "0145": ["bool", "showTime"], "0146": ["bool", "disableCheckpoints"], "0147": ["bool", "searchFolderNameInSfxLibrary", 1], "0148": ["bool", "compactSfxLibraryMode", 1], "0149": ["bool", "showClicks", 1], "0150": ["bool", "autoPauseOnTest", 1], "0151": ["bool", "startOptimization", 1], "0152": ["bool", "hidePath", 1], // "0153": ["bool", "explodePlayerOnDeath"], "0155": ["bool", "disableShaderAntiAliasing"], "0156": ["bool", "disablePasteStateGroups", 1], "0157": ["bool", "showedLevelUploadGuidelines", 3], "0158": ["bool", "previewShaders", 1], "0159": ["bool", "audioFix01"], "0163": ["bool", "enableQuickKeys"], "0164": ["int", "levelLeaderboardMode"], "0166": ["bool", "showHitboxesInPracticeMode"], "0167": ["bool", "confirmExit"], "0168": ["bool", "fastMenu"], "0169": ["bool", "smallWarpButtons", 1], "0170": ["bool", "borderlessFullscreen"], "0171": ["bool", "disablePlayerHitboxInPracticeMode"], "0172": ["bool", "disableShake"], "0173": ["bool", "ignoreDamageWhenPlaytestingOutsideOfEditor", 1], "0174": ["bool", "hidePlaytestText"] } const ugv = { "1": "unlockedTheChallenge", "2": "glubfubHint1", "3": "glubfubHint2", "4": "completedTheChallenge", "5": "unlockedTreasureRoom", "6": "chamberOfTimeUnlocked", "7": "chamberOfTimeDiscovered", "8": "shownMasterEmblem", "9": "gatekeeperDialogue", "10": "scratchDialogue", "11": "scratchShopUnlocked", "12": "spokenToDemonGuardian", "13": "demonFreed", "14": "hasDemonKey1", "15": "hasDemonKey2", "16": "hasDemonKey3", "17": "shopkeeper500OrbsDialogue", "18": "onlineLevelsUnlockedInWorld", "19": "demonDiscoveredInDemonRoom", "20": "communityShopUnlocked", "21": "potborDialogue", "22": "youtubeChestOpened", "23": "facebookChestOpened", "24": "twitterChestOpened", "25": "explorersUnlocked", "26": "twitchChestOpened", "27": "discordChestOpened", "28": "clickedTheTower", "29": "enteredTheTower", "30": "acceptedTOS", "31": "zolgurothEncountered", "32": "redditChestOpened", "33": "theTowerFloor1Completed", "34": "diamondShopUnlocked", "35": "mechanicUnlocked", "36": "mechanicDialogue", "37": "diamondShopkeeperDialogue" } const GS_value = { "1": "jumps", "2": "attempts", "3": "officialLevelsCompleted", "4": "onlineLevelsCompleted", "5": "demons", "6": "stars", "7": "mapPacksCompleted", "8": "secretCoinsCollected", "9": "playersDestroyed", "10": "totalLikedOrDislikedLevels", "11": "totalRatedLevels", "12": "userCoinsCollected", "13": "totalDiamonds", "14": "orbs", "15": "dailyLevelsCompleted", "21": "demonKeys", "22": "totalOrbs", "28": "moons", "29": "diamondShards", "40": "gauntletsCompleted", "41": "listRewards" } const GS_value_shards = { "16": "fire", "17": "ice", "18": "poison", "19": "shadow", "20": "lava", "23": "earth", "24": "blood", "25": "metal", "26": "light", "27": "soul" } const GS_value_paths = { "30": "fire", "31": "ice", "32": "poison", "33": "shadow", "34": "lava", "35": "earth", "36": "blood", "37": "metal", "38": "light", "39": "soul" } export function translateSave(save) { let translated = { iconKit: { primaryColor: save.playerColor || 1, secondaryColor: save.playerColor2 || 3, cube: save.playerFrame || 1, cubes: [1, 2, 3, 4], primaryColors: [1, 2, 3, 4], secondaryColors: [1, 2, 3, 4] }, settings: {shownMessages: {}, editor: {}}, user: {}, stats: {}, completedLevels: {officialLevels: {}, customLevels: {}} } if (save.binaryVersion >= 4) { // 1.3 translated.completedLevels.demons = {} translated.completedLevels.stars = {} } if (save.binaryVersion >= 5) { // 1.4 translated.iconKit.ships = [1] translated.iconKit.ship = save.playerShip || 1 } if (save.binaryVersion >= 7) { // 1.6 translated.iconKit.balls = [1] translated.iconKit.ball = save.playerBall || 1 translated.iconKit.ufos = [1] translated.iconKit.ufo = save.playerBird || 1 translated.stats.secretCoins = {} } if (save.binaryVersion >= 10) { // 1.7 translated.iconKit.trails = [1] translated.iconKit.trail = save.playerStreak || 1 } if (save.binaryVersion >= 27) { // 2.0 translated.iconKit.waves = [1] translated.iconKit.wave = save.playerDart || 1 translated.iconKit.robots = [1] translated.iconKit.robot = save.playerRobot || 1 translated.stats.secretCoins.mainLevelPage = false translated.stats.secretCoins.sparky = false } if (save.binaryVersion >= 33) { // 2.1 translated.iconKit.spiders = [1] translated.iconKit.spider = save.playerSpider || 1 translated.iconKit.deathEffects = [1] translated.iconKit.deathEffect = save.playerDeathEffect || 1 translated.completedLevels.dailies = {} translated.completedLevels.gauntletLevels = {} translated.completedLevels.dailyIDs = {} translated.completedLevels.gauntletStars = {} translated.completedLevels.weeklyIDs = {} translated.completedLevels.gauntletDemons = {} translated.settings.parental = {} translated.storyProgression = {} translated.stats.shards = {} translated.stats.secretCoins.glubfub = false } if (save.binaryVersion >= 37) { // 2.2 translated.iconKit.swings = [1] translated.iconKit.jetpacks = [1] translated.iconKit.shipFires = [1] translated.iconKit.enabledItems = {} translated.stats.pathProgression = {} translated.listRewards = {} translated.iconKit.explode = false translated.iconKit.swing = save.playerSwing || 1 translated.iconKit.jetpack = save.playerJerpack || 1 translated.iconKit.shipFire = save.playerShipStreak || 1 translated.iconKit.glowColor = save.playerColor3 || save.playerColor2 || 3 } let gvType = {1: "editor", 2: "parental", 3: "shownMessages"} if (save.valueKeeper) { if (save.binaryVersion >= 12) { for (let i of Object.values(gv)) { let value = "" if (i[0] == "bool") value = false else if (i[0] == "int") value = 0 if (i[2] && translated.settings[gvType[i[2]]]) { translated.settings[gvType[i[2]]][i[1]] = value } else translated.settings[i[1]] = value } } for (let i of Object.entries(save.valueKeeper)) { let components = i[0].split("_") let componentToType = { "c0": "primaryColors", "c1": "secondaryColors", "i": "cubes", "ship": "ships", "ball": "balls", "bird": "ufos", "dart": "waves", "special": "trails", "robot": "robots", "shipstreak": "shipFires", "death": "deathEffects", "spider": "spiders", "swing": "swings", "jetpack": "jetpacks" } if (components[0] == "gv") { if (!translated.settings) translated.settings = {} if (components[1] == "0153") { translated.iconKit.explode = true continue } if (gv[components[1]]) { let value = i[1] if (gv[components[1]][0] == "bool") value = !!parseInt(i[1]) else if (gv[components[1]][0] == "int") value = parseInt(i[1]) if (gv[components[1]][2]) { translated.settings[gvType[gv[components[1]][2]]][gv[components[1]][1]] = value } else translated.settings[gv[components[1]][1]] = value } else translated.settings[components[1]] = i[1] } else if (parseInt(i[1])) { let iconType = componentToType[components[0]] || components[0] if (!translated.iconKit[iconType]) translated.iconKit[iconType] = [] translated.iconKit[iconType].push(parseInt(components[1])) } } } if (save.unlockValueKeeper) { for (let i of Object.values(ugv)) { translated.storyProgression[i] = false } for (let i of Object.entries(save.unlockValueKeeper)) { let components = i[0].split("_") if (components[0] == "ugv") { if (ugv[components[1]]) { translated.storyProgression[ugv[components[1]]] = !!parseInt(i[1]) } else translated.storyProgression[components[1]] = i[1] } else throw new Error("Stuff went down at unlockValueKeeper") } } if (save.GS_value) { for (let i of Object.entries(save.GS_value)) { if (i[0].startsWith("unique")) { let components = i[0].split("_") if (components.length == 3) { if (!translated.stats.secretCoins[components[1]]) translated.stats.secretCoins[components[1]] = [false, false, false] translated.stats.secretCoins[components[1]][Number(components[2]) - 1] = !!parseInt(i[1]) } else { if (components[1] == "secretB03") translated.stats.secretCoins.glubfub = !!parseInt(i[1]) else if (components[1] == "secret04") translated.stats.secretCoins.mainLevelPage = !!parseInt(i[1]) else if (components[1] == "secret06") translated.stats.secretCoins.sparky = !!parseInt(i[1]) } } else { if (GS_value[i[0]]) { translated.stats[GS_value[i[0]]] = parseInt(i[1]) } else if (GS_value_shards[i[0]]) { translated.stats.shards[GS_value_shards[i[0]]] = parseInt(i[1]) } else if (GS_value_paths[i[0]]) { translated.stats.pathProgression[GS_value_paths[i[0]]] = parseInt(i[1]) } else translated.stats[i[0]] = i[1] } } } if (save.GS_completed) { for (let i of Object.entries(save.GS_completed)) { let components = i[0].split("_") switch (components[0]) { case "n": translated.completedLevels.officialLevels[parseInt(components[1])] = !!parseInt(i[1]) break case "c": translated.completedLevels.customLevels[parseInt(components[1])] = !!parseInt(i[1]) break case "demon": translated.completedLevels.demons[parseInt(components[1])] = !!parseInt(i[1]) break case "star": translated.completedLevels.stars[parseInt(components[1])] = !!parseInt(i[1]) break case "d": translated.completedLevels.dailies[parseInt(components[1])] = !!parseInt(i[1]) break case "g": translated.completedLevels.gauntletLevels[parseInt(components[1])] = !!parseInt(i[1]) break case "dstar": translated.completedLevels.dailyIDs[parseInt(components[1])] = !!parseInt(i[1]) break case "gstar": translated.completedLevels.gauntletStars[parseInt(components[1])] = !!parseInt(i[1]) break case "ddemon": translated.completedLevels.weeklyIDs[parseInt(components[1])] = !!parseInt(i[1]) break case "gdemon": translated.completedLevels.gauntletDemons[parseInt(components[1])] = !!parseInt(i[1]) break } } } if (save.GS_27) { for (let i of Object.entries(save.GS_27)) { let components = i[0].split("_") if (components[0] != "lr") throw new Error("Stuff went down at GS_27 (listRewards)") let listID = parseInt(components[1]) translated.listRewards[listID] = parseInt(i[1]) } } translated.user.udid = save.playerUDID if (save.playerUserID) translated.user.playerID = save.playerUserID translated.user.username = save.playerName || null if (save.GJA_001) { translated.user.account = { username: save.GJA_001, accountID: save.GJA_003 } if (save.GJA_002) translated.user.account.password = save.GJA_002 if (save.GJA_004) translated.user.account.sessionID = save.GJA_004 if (save.GJA_005) translated.user.account.hashedPassword = save.GJA_005 } if (save.autoCheckpoints) {translated.settings.autoCheckpoints = save.autoCheckpoints; delete save.autoCheckpoints} if (save.texQuality) {translated.settings.textureQuality = enums.textureQuality[save.texQuality]; delete save.texQuality} else translated.settings.textureQuality = enums.textureQuality[0] if (save.resolution) {translated.settings.resolution = enums.resolution[save.resolution]; delete save.resolution} if (save.customFPSTarget) {translated.settings.customFPSTarget = save.customFPSTarget; delete save.customFPSTarget} if (save.showProgressBar != undefined) {translated.settings.showProgressBar = save.showProgressBar; delete save.showProgressBar} if (save.GS_26) {translated.settings.activePath = GS_value_paths[save.GS_26]; delete save.GS_26} if (save.clickedGarage != undefined) {translated.settings.clickedIconKit = save.clickedGarage; delete save.clickedGarage} if (save.clickedEditor != undefined) {translated.settings.clickedEditor = save.clickedEditor; delete save.clickedPractice} if (save.clickedPractice != undefined) {translated.settings.clickedPractice = save.clickedEditor; delete save.clickedPractice} if (save.clickedName != undefined) {translated.settings.clickedName = save.clickedName; delete save.clickedName} // legacy if (save.gameCenterEnabled != undefined) {translated.settings.gameCenterEnabled = save.gameCenterEnabled; delete save.gameCenterEnabled} // legacy if (save.musicEnabled != undefined) {translated.settings.musicVolume = Number(save.musicEnabled); delete save.musicEnabled} // legacy if (save.fxEnabled != undefined) {translated.settings.sfxVolume = Number(save.fxEnabled); delete save.fxEnabled} // legacy if (save.bgVolume != undefined) {translated.settings.musicVolume = save.bgVolume; delete save.bgVolume} if (save.sfxVolume != undefined) {translated.settings.sfxVolume = save.sfxVolume; delete save.sfxVolume} if (save.hasNewGames != undefined) {translated.settings.hasNewGames = save.hasNewGames; delete save.hasNewGames} // legacy if (save.moreGamesString) {translated.settings.moreGamesString = save.moreGamesString; delete save.moreGamesString} // legacy if (save.lastDay != undefined) {translated.settings.lastDay = save.lastDay; delete save.lastDay} // legacy if (save.lastMonth != undefined) {translated.settings.lastMonth = save.lastMonth; delete save.lastMonth} // legacy delete save.valueKeeper delete save.unlockValueKeeper delete save.GS_value delete save.GS_completed delete save.playerFrame delete save.playerColor delete save.playerColor2 delete save.playerShip delete save.playerBall delete save.playerBird delete save.playerStreak delete save.playerDart delete save.playerRobot delete save.playerSpider delete save.playerDeathEffect delete save.playerSwing delete save.playerJetpack delete save.playerShipStreak delete save.playerColor3 delete save.playerUDID delete save.playerName delete save.playerUserID delete save.GJA_001 delete save.GJA_002 delete save.GJA_003 delete save.GJA_004 delete save.GJA_005 delete save.GS_value delete save.GS_27 for (let i of Object.entries(save)) { if (keyNames[i[0]]) translated[keyNames[i[0]]] = i[1] else translated[i[0]] = i[1] } for (let i of Object.entries(save.reportedAchievements)) { translated.reportedAchievements[i[0]] = parseInt(i[1]) } function translatekCEKs(dict) { let newDict = dict for (let i of Object.entries(dict)) { if (i[1] == undefined) console.log(i) if (i[1].constructor == Object) { // if (i[1].kCEK && ![4, 6, 7, 8, 9, 10, 11, 12].includes(i[1].kCEK)) console.log(i[1]) if (i[1].kCEK == 4) newDict[i[0]] = parseGJGameLevel(i[1]) else if (i[1].kCEK == 6) newDict[i[0]] = parseSongInfoObject(i[1]) else newDict[i[0]] = translatekCEKs(i[1]) } else if (i[1].constructor == Array) { newDict[i[0]] = translatekCEKsArray(i[1]) } } return newDict } function translatekCEKsArray(arr) { let newArr = arr let index = 0 for (let i of arr) { if (i.constructor == Object) { if (i.kCEK == 4) newArr[index] = parseGJGameLevel(i) else if (i.kCEK == 6) newArr[index] = parseSongInfoObject(i) else newArr[index] = translatekCEKs(i) } else if (i.constructor == Array) { newArr[index] = translatekCEKsArray(i) } index++ } return newArr } translated = translatekCEKs(translated) return translated } function parseDict(dict) { let decoded = {} for (let i = 0; i < dict.length; i += 2) { if (dict[i].name != "k" && dict[i].name != "key") throw new Error("Save file is broken") let key = dict[i].elements[0].text switch (dict[i+1].name) { case "r": decoded[key] = parseFloat(dict[i+1].elements[0].text) break case "i": case "integer": decoded[key] = parseInt(dict[i+1].elements[0].text) break case "s": case "string": if (!dict[i+1].elements) decoded[key] = "" else decoded[key] = dict[i+1].elements[0].text break case "t": case "true": decoded[key] = true break case "f": case "false": decoded[key] = false break case "d": case "dict": if (!dict[i+1].elements) decoded[key] = {} else if (dict[i+1].elements[0].elements[0].text == "_isArr" && (dict[i+1].elements[1].name == "t" || dict[i+1].elements[1].name == "true")) decoded[key] = parseArray(dict[i+1].elements) else decoded[key] = parseDict(dict[i+1].elements) break } } return decoded } function parseArray(elements) { let decoded = [] for (let i = 0; i < elements.length; i += 2) { if (elements[i].elements[0].text == "_isArr") continue if (!elements[i].elements[0].text.match(/^k_\d+$/g)) throw new Error("Array in save file is broken") let key = elements[i].elements[0].text.split("_")[1] switch (elements[i+1].name) { case "r": decoded[key] = parseFloat(elements[i+1].elements[0]) break case "i": case "integer": decoded[key] = parseInt(elements[i+1].elements[0]) break case "s": case "string": decoded[key] = elements[i+1].elements[0] break case "t": case "true": decoded[key] = true break case "f": case "false": decoded[key] = false break case "d": case "dict": if (elements[i+1].elements[0].elements[0].text == "_isArr" && (elements[i+1].elements[1].name == "t" || elements[i+1].elements[1].name == "true")) decoded[key] = parseArray(elements[i+1].elements) else decoded[key] = parseDict(elements[i+1].elements) break } } return decoded } function parseGJGameLevel(data) { let level = {} let values = { "k1": "id", "k2": "name", "k3": "description", "k4": "levelString", "k5": "username", "k6": "userID", "k7": "difficulty", "k8": "officialSongID", "k9": "rating", "k10": "ratingSum", "k11": "downloads", "k12": "completions", "k13": "isEditable", "k14": "verified", "k15": "uploaded", "k16": "levelVersion", "k17": "gameVersion", "k18": "attempts", "k19": "normalPercentage", "k20": "practicePercentage", "k21": "levelType", "k22": "likes", "k23": "length", "k24": "dislikes", "k25": "isDemon", "k26": "stars", "k27": "featureScore", "k33": "isAuto", "k34": "replayData", "k35": "isPlayable", "k36": "jumps", "k37": "requiredSecretCoins", "k38": "isUnlocked", "k39": "levelSize", "k40": "buildVersion", "k41": "password", "k42": "originalID", "k43": "twoPlayerMode", "k45": "customSongID", "k46": "levelRevision", "k47": "hasBeenExternallyModified", "k48": "objectCount", "k50": "binaryVersion", "k51": "capacity1", "k52": "capacity2", "k53": "capacity3", "k54": "capacity4", "k60": "accountID", "k61": "firstCoinVerified", "k62": "secondCoinVerified", "k63": "thirdCoinVerified", "k64": "userCoins", "k65": "verifiedCoins", "k66": "requestedStars", "k67": "capacityString", "k68": "triggeredAntiCheat", "k69": "highObjectCount", "k71": "manaOrbPercentage", "k72": "hasLowDetailMode", "k73": "enabledLowDetailMode", "k74": "timelyID", "k75": "epicRating", "k76": "demonType", "k77": "isGauntlet", "k78": "isFreeGame", "k79": "unlisted", "k80": "editorTimeSeconds", "k81": "editorTimeCopiesSeconds", "k83": "levelOrder", "k85": "bestAttemptClicks", "k86": "bestAttemptTime", "k87": "classicLevelSeed", "k88": "personalBestsRaw", "k89": "isLevelCompleted", "k90": "leaderboardPercentage", "k95": "verificationTimeFrames", "k104": "songIDs", "k105": "sfxIDs", "k106": "k106_unk_server_key_54", "k107": "bestTime", "k108": "bestPoints", "k109": "localBestTimes", "k110": "localBestPoints", "k111": "platformerSeed", "k112": "disableShake" } for (let i of Object.entries(data)) { if (values[i[0]]) { level[values[i[0]]] = i[1] } else level[i[0]] = i[1] } if (data.k88) { let pbsRaw = data.k88.split(",") level.personalBests = [parseInt(pbsRaw[0])] pbsRaw.shift() for (let i of pbsRaw) { level.personalBests.push(level.personalBests[level.personalBests.length - 1] + parseInt(i)) } } if (data.k104) level.songIDs = data.k104.split(",") if (data.k105) level.sfxIDs = data.k105.split(",") if (data.k109) level.localBestTimes = data.k109.split(",") if (data.k110) level.localBestPoints = data.k110.split(",") return level } function parseSongInfoObject(data) { let song = {} let values = { "1": "id", "2": "name", "3": "artistID", "4": "artistName", "5": "size", "6": "videoID", "7": "artistYouTubeURL", "8": "isVerified", "9": "rating", "10": "link", "11": "nongType", "12": "extraArtistIDs", "13": "new", "14": "newColor", "15": "extraArtistNames", } for (let i of Object.entries(data)) { if (values[i[0]]) { song[values[i[0]]] = i[1] } else song[i[0]] = i[1] } if (data["12"]) song.extraArtistIDs = data["12"].split(".").map(e => Number(e)) if (data["15"]) song.extraArtists = utils.robTopSplitDict(data["15"], ",") return song }