├── directory.txt ├── assets.json ├── readme.txt └── shuffle.js /directory.txt: -------------------------------------------------------------------------------- 1 | C:/Program Files (x86)/Steam/steamapps/common/Geometry Dash/Resources -------------------------------------------------------------------------------- /assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": { 3 | "FireSheet_01-uhd": 40, 4 | "GauntletSheet-uhd": 50, 5 | "GJ_GameSheet-uhd": 30, 6 | "GJ_GameSheet02-uhd": 40, 7 | "GJ_GameSheet03-uhd": 30, 8 | "GJ_GameSheet04-uhd": 25, 9 | "GJ_GameSheetGlow-uhd": 30, 10 | "GJ_LaunchSheet-uhd": 150, 11 | "GJ_ShopSheet-uhd": 60, 12 | "SecretSheet-uhd": 150 13 | }, 14 | 15 | "sprites": [ 16 | "dialogIcon_#", 17 | "game_bg_#_001", 18 | "GJ_button_#", 19 | "GJ_square#", 20 | "groundSquare_#_2_001||*", 21 | "tutorial_#", 22 | "groundSquare_#_001||g1", 23 | "groundSquare_#_001||g2" 24 | ], 25 | 26 | "forms": [ 27 | "player_ball", "player", "ship", "bird", "dart", "robot", "spider" 28 | ] 29 | } -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | BACK UP YOUR GD RESOURCES FOLDER BEFORE RUNNING THIS PROGRAM! 2 | C:\Program Files (x86)\Steam\steamapps\common\Geometry Dash\Resources 3 | (just make a copy of the folder or something) 4 | 5 | === 6 | 7 | In a nutshell, this program randomizes all the GD textures. 8 | To keep things tidy, textures will only be replaced with similar-sized ones. 9 | All icons are also randomized. 10 | It's also compatible with texture packs! 11 | 12 | === 13 | 14 | HOW TO USE: 15 | - Run the "shuffle.exe" file. 16 | - When it finishes, drag the files in the "pack" folder into your GD resources folder. 17 | - When asked to overwrite files, click yes. 18 | 19 | === 20 | 21 | If the program closes as soon as you open it, that means it crashed. 22 | A file called crash_log.txt will be created - send it to Colon and he'll hopefully get around to fixing it. 23 | 24 | === 25 | 26 | Mac/Linux/non-Windows users, or if the game is installed in a nonstandard location: 27 | Replace the directory in directory.txt with whereever the game's Resources folder is located on your system, or simply pass it as a command-line argument. 28 | -------------------------------------------------------------------------------- /shuffle.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const plist = require('plist'); 3 | const assets = require('./assets.json') 4 | 5 | try { // god-tier crash prevention system 6 | 7 | Array.prototype.shuffle = function() { 8 | let length = this.length; let unshuffled = this; let shuffled = []; 9 | while (shuffled.length !== length) { 10 | let index = Math.floor(Math.random() * unshuffled.length); 11 | shuffled.push(unshuffled[index]); 12 | unshuffled = unshuffled.filter((x, y) => y !== (index))} 13 | return shuffled; 14 | } 15 | 16 | function plistToJson(file) { 17 | let data = plist.parse(file) 18 | for (let key in data.frames) { 19 | let fileData = data.frames[key]; 20 | for (let innerKey in fileData) { 21 | if (typeof fileData[innerKey] == 'string') { 22 | if (!fileData[innerKey].length) delete fileData[innerKey] 23 | else fileData[innerKey] = JSON.parse(fileData[innerKey].replace(/{/g, '[').replace(/}/g, ']')); 24 | } 25 | }} 26 | return data.frames 27 | } 28 | 29 | if (!fs.existsSync('./pack')) fs.mkdirSync('./pack'); 30 | 31 | function glow(name) { return name.replace("_001.png", "_glow_001.png") } 32 | function undupe (arr) { return arr.filter((x, y) => arr.indexOf(x) == y) } 33 | //function spriteRegex(name) { return new RegExp(`(${name.replace(".", "\\.")}<\/key>\\s*)((.|\\n)+?<\\/dict>)`) } 34 | let iconRegex = /^.+?_(\d+?)_.+/ 35 | 36 | let forms = assets.forms 37 | let sheetList = Object.keys(assets.sheets) 38 | let glowName = sheetList.filter(x => x.startsWith('GJ_GameSheetGlow')) 39 | 40 | // newlines/CRs are usually present in text files, strip them out so they aren't part of the pathname 41 | let gdPath = process.argv[2] ?? fs.readFileSync('directory.txt', 'utf8').replace(/[\n\r]/g, '') 42 | 43 | if (!fs.existsSync(gdPath)) throw "Couldn't find your GD directory! Make sure to enter the correct file path in directory.txt" 44 | let glowPlist = fs.readFileSync(`${gdPath}/${glowName[0]}.plist`, 'utf8') 45 | let sheetNames = sheetList.filter(x => !glowName.includes(x)) 46 | let resources = fs.readdirSync(gdPath) 47 | 48 | let plists = [] 49 | let sheets = [] 50 | let glowBackups = [] 51 | let glowSheet = plistToJson(glowPlist) 52 | 53 | resources.forEach(x => { 54 | if (x.startsWith('PlayerExplosion_') && x.endsWith('-uhd.plist')) sheetNames.push(x.slice(0, -6)) 55 | }) 56 | 57 | sheetNames.forEach(x => { 58 | let file = fs.readFileSync(`${gdPath}/${x}.plist`, 'utf8') 59 | plists.push(file) 60 | try { sheets.push(plistToJson(file)) } 61 | catch(e) { throw `Error parsing ${x}.plist - ${e.message}` } 62 | }) 63 | 64 | sheets.forEach((gameSheet, sheetNum) => { 65 | let plist = plists[sheetNum] 66 | let name = sheetNames[sheetNum] 67 | if (!name.startsWith('PlayerExplosion_')) console.log("Shuffling " + name) 68 | else if (name == "PlayerExplosion_01-uhd") console.log("Shuffling death effects") 69 | 70 | let sizes = {} 71 | Object.keys(gameSheet).forEach(x => { 72 | let obj = gameSheet[x] 73 | obj.name = x 74 | if (sheetNum == sheetNames.findIndex(y => y.startsWith('GJ_GameSheet02')) && forms.some(y => x.startsWith(y))) { 75 | let form = forms.find(y => x.startsWith(y)) 76 | if (!sizes[form]) sizes[form] = [obj] 77 | else sizes[form].push(obj) 78 | } 79 | else { 80 | let sizeDiff = assets.sheets[name] || 30 81 | let size = obj.textureRect[1].map(x => Math.round(x / sizeDiff) * sizeDiff).join() 82 | if (name.startsWith('PlayerExplosion')) size = "deatheffect" 83 | if (!sizes[size]) sizes[size] = [obj] 84 | else sizes[size].push(obj) 85 | } 86 | }) 87 | 88 | Object.keys(sizes).forEach(obj => { 89 | let objects = sizes[obj] 90 | if (objects.length == 1) return delete sizes[obj] 91 | let iconMode = forms.includes(obj) 92 | let oldNames = objects.map(x => x.name) 93 | if (iconMode) oldNames = undupe(oldNames.map(x => x.replace(iconRegex, "$1"))) 94 | let newNames = oldNames.shuffle() 95 | if (iconMode) { 96 | let iconList = {} 97 | oldNames.forEach((x, y) => iconList[x] = newNames[y]) 98 | newNames = iconList 99 | } 100 | 101 | oldNames.forEach((x, y) => { 102 | let newName = newNames[iconMode ? x : y] 103 | if (iconMode) { 104 | plist = plist.replace(new RegExp(`${obj}_${x}_`, "g"), `###${obj}_${newName}_`) 105 | glowPlist = glowPlist.replace(`${obj}_${x}_`, `###${obj}_${newName}_`) 106 | } 107 | else { 108 | plist = plist.replace(`${x}`, `###${newName}`) 109 | if (glowSheet[glow(x)]) { 110 | glowBackups.push(glow(x)) 111 | glowPlist = glowPlist.replace(`${glow(x)}`, `###${glow(newName)}`) 112 | } 113 | } 114 | }) 115 | }) 116 | plist = plist.replace(/###/g, "") 117 | fs.writeFileSync('./pack/' + sheetNames[sheetNum] + '.plist', plist, 'utf8') 118 | }) 119 | 120 | console.log("Shuffling misc textures") 121 | let specialGrounds = [] 122 | assets.sprites.forEach(img => { 123 | let spriteMatch = img.split("|") 124 | let foundTextures = resources.filter(x => x.match(new RegExp(`^${spriteMatch[0].replace("#", "\\d+?")}-uhd\\.${spriteMatch[1] || "png"}`))) 125 | 126 | if (spriteMatch[2] == "*") specialGrounds = specialGrounds.concat(foundTextures.map(x => x.slice(0, 15))) 127 | if (spriteMatch[2] == "g1") foundTextures = foundTextures.filter(x => !specialGrounds.some(y => x.startsWith(y))) 128 | if (spriteMatch[2] == "g2") foundTextures = foundTextures.filter(x => specialGrounds.some(y => x.startsWith(y))) 129 | 130 | let shuffledTextures = foundTextures.shuffle() 131 | foundTextures.forEach((x, y) => fs.copyFileSync(`${gdPath}/${x}`, `./pack/${shuffledTextures[y]}`)) 132 | }) 133 | 134 | let emptyDict = glowPlist.match(/\s*aliases<\/key>(.|\n)+?<\/dict>/)[0].replace(/{\d+,\d+}/g, "{0, 0}") 135 | let mappedBackups = glowBackups.reverse().map(x => `${x}${emptyDict}`).join("") 136 | glowPlist = fs.writeFileSync('./pack/GJ_GameSheetGlow-uhd.plist', glowPlist.replace(/###/g, "").replace(/\s*frames<\/key>\s*/g, "$&" + mappedBackups), 'utf8') 137 | console.log("Randomization complete!") 138 | 139 | } 140 | 141 | catch(e) { console.log(e); fs.writeFileSync('crash_log.txt', e.stack ? `Something went wrong! Send this error to Colon and he'll get around to fixing it at some point.\n\n${e.stack}` : e, 'utf8') } 142 | --------------------------------------------------------------------------------