Last active
March 2, 2026 04:57
-
-
Save GarboMuffin/5c71103133f8fd576b2752a927a03009 to your computer and use it in GitHub Desktop.
This decrypts projects from the cocrea.world projects API. Claude and I reverse engineered the website to figure out how this works, then Claude took our learnings and made a script.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // how to break the decryption | |
| const https = require('https'); | |
| const crypto = require('crypto'); | |
| const PROJECT_URL = 'https://www.cocrea.world/@RedLzer2048/SprunkiHyperShifted'; | |
| function fetch(url) { | |
| return new Promise((resolve, reject) => { | |
| https.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, res => { | |
| if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { | |
| return fetch(res.headers.location).then(resolve, reject); | |
| } | |
| const chunks = []; | |
| res.on('data', c => chunks.push(c)); | |
| res.on('end', () => resolve(Buffer.concat(chunks))); | |
| res.on('error', reject); | |
| }).on('error', reject); | |
| }); | |
| } | |
| function decrypt(base64Data, assetId) { | |
| const secret = 'KzdnFCBRvq3'; | |
| const keyBuf = Buffer.from(secret + assetId, 'base64'); | |
| const key = keyBuf.slice(0, 32); | |
| const iv = keyBuf.slice(0, 16); | |
| const ciphertext = Buffer.from(base64Data, 'base64'); | |
| const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); | |
| const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]); | |
| // Decrypted text is comma-separated byte values | |
| return Buffer.from(decrypted.toString('utf-8').split(',').map(Number)); | |
| } | |
| (async () => { | |
| // Step 1: Fetch the page and extract projectLink from __NEXT_DATA__ | |
| console.log('Fetching page...'); | |
| const html = (await fetch(PROJECT_URL)).toString('utf-8'); | |
| const nextData = JSON.parse(html.match(/<script id="__NEXT_DATA__"[^>]*>(.*?)<\/script>/)[1]); | |
| const projectLink = nextData.props.pageProps.creationData.creationReleaseResp.projectLink; | |
| console.log('Project link:', projectLink); | |
| // Step 2: Extract assetId from the URL (the filename without .sb3) | |
| const assetId = projectLink.match(/\/([a-f0-9]+)\.sb3/)[1]; | |
| console.log('Asset ID:', assetId); | |
| // Step 3: Download and decrypt | |
| console.log('Downloading encrypted sb3...'); | |
| const encrypted = await fetch(projectLink); | |
| const first8 = Array.from(encrypted.slice(0, 8)); | |
| let sb3; | |
| if (first8[0] === 80 && first8[1] === 75) { | |
| // Already a valid zip | |
| sb3 = encrypted; | |
| } else { | |
| console.log('Decrypting...'); | |
| sb3 = decrypt(encrypted.toString('utf-8'), assetId); | |
| } | |
| console.log('Decrypted:', sb3.length, 'bytes, starts with', Array.from(sb3.slice(0, 4))); | |
| const fs = require('fs'); | |
| fs.writeFileSync('/tmp/project.sb3', sb3); | |
| console.log('Wrote /tmp/project.sb3'); | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // paste this into your JavaScript console to download the project as an sb3 (project must already be loaded) | |
| function getVM() { | |
| const root = document.querySelector('#__next'); | |
| const key = Object.keys(root).find(k => k.startsWith('__reactContainer$') || k.startsWith('__reactFiber$')); | |
| function search(obj, d) { | |
| if (!obj || typeof obj !== 'object' || d > 5) return; | |
| const seen = new WeakSet(); | |
| const stack = [{ o: obj, d }]; | |
| while (stack.length) { | |
| const { o, d } = stack.pop(); | |
| if (!o || typeof o !== 'object' || d > 5 || seen.has(o)) continue; | |
| seen.add(o); | |
| if (typeof o.start === 'function' && typeof o.greenFlag === 'function') return o; | |
| for (const k of Object.keys(o)) { | |
| try { if (o[k] && typeof o[k] === 'object') stack.push({ o: o[k], d: d + 1 }); } catch(e) {} | |
| } | |
| } | |
| } | |
| const fiberSeen = new WeakSet(); | |
| function walk(node, depth) { | |
| if (!node || depth > 100 || fiberSeen.has(node)) return; | |
| fiberSeen.add(node); | |
| for (const k of ['memoizedProps', 'memoizedState']) | |
| { const r = search(node[k], 0); if (r) return r; } | |
| if (node.stateNode && typeof node.stateNode === 'object') | |
| for (const k of ['props', 'state']) | |
| { const r = search(node.stateNode[k], 0); if (r) return r; } | |
| return walk(node.child, depth + 1) || walk(node.sibling, depth + 1); | |
| } | |
| return walk(root[key], 0); | |
| } | |
| const vm = getVM(); | |
| vm.saveProjectSb3().then(blob => { | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = document.title.replace(/[^a-zA-Z0-9 ]/g, '').trim() + '.sb3'; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| console.log('Download started:', a.download, (blob.size / 1024 / 1024).toFixed(1) + ' MB'); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment