dashtools
Version:
Library for interacting with Geometry Dash servers
898 lines (859 loc) • 33.6 kB
JavaScript
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
}