const fs = require("fs"); const path = require("path"); const aescfb = require('./aes'); const JSZip = require("jszip"); const lookupKey = require("./ent"); const Progress = require("./progress"); const { ipcMain } = require("electron"); module.exports = class PackDecryptor extends Progress { inputPath = ""; outputFilePath = ""; zip = new JSZip(); zippedContent = []; contentFiles = [] decryptDenylist = ["pack_icon.png", "pack_icon.jpeg", "world_icon.png", "world_icon.jpeg", "manifest.json"] constructor(inputPath, outputFilePath) { super(); this.inputPath = inputPath; this.outputFilePath = outputFilePath; } async start() { return new Promise(async res => { console.log("start") const dbPath = path.join(this.inputPath, "db"); this.contentFiles = recursiveReaddirrSync(this.inputPath); this._started = true; if (fs.existsSync(dbPath)) { const dbDir = recursiveReaddirrSync(dbPath); for (let index = 0; index < dbDir.length; index++) { const dbFilePath = dbDir[index]; if (fs.lstatSync(dbFilePath).isDirectory()) { continue; } const decrypted = await this.decryptContentFile(dbFilePath); this.addFile(dbFilePath, decrypted) } } for (let index = 0; index < this.contentFiles.length; index++) { const name = path.basename(this.contentFiles[index]); if (name.toLowerCase() === "contents.json") { await this.decryptContent(this.contentFiles[index]); } } for (let index = 0; index < this.contentFiles.length; index++) { const filePath = this.contentFiles[index]; if (!this.zippedContent.includes(filePath)) { this.addFile(filePath, fs.readFileSync(filePath)); } } await this.crackSkinPack() this.crackWorld(); this.zip.generateAsync({type:"arraybuffer"}).then((content) => { fs.writeFileSync(this.outputFilePath, Buffer.from(content)); console.log("done") res(0); }); }) } crackWorld() { const levelDatPath = path.join(this.inputPath, "level.dat"); if (!fs.existsSync(levelDatPath)) return; const levelDat = fs.readFileSync(levelDatPath); let offset = levelDat.indexOf("prid"); while (offset !== -1) { levelDat.writeUInt8("a".charCodeAt(0), offset); offset = levelDat.indexOf("prid"); } this.addFile(levelDatPath, levelDat) } addFile(filePath, content) { const relPath = path.relative(this.inputPath, filePath).replaceAll("\\", "/"); if (!this.zippedContent.includes(filePath)) { this.zippedContent.push(filePath); } this.zip.file(relPath, content, {binary: true}) self.postMessage(this.getPercentage()); } static isContentFileEncrypted(filePath){ const contents = fs.readFileSync(filePath); if (contents.length < 0x100) return false; const magic = contents.readUint32LE(0x4); if (magic === 2614082044) { return true; } return false; } async decryptContent(filePath) { const dirname = path.dirname(filePath); const isEncrypted = PackDecryptor.isContentFileEncrypted(filePath); const content = await this.decryptContentFile(filePath); const parsedContent = JSON.parse(content); if(isEncrypted) { for (let index = 0; index < parsedContent.content.length; index++) { const key = parsedContent.content[index].key; const filePath = parsedContent.content[index].path; const fileName = path.basename(filePath); if(this.decryptDenylist.indexOf(fileName.toLowerCase()) !== -1) continue; if (!key) continue; const joinedPath = path.join(dirname, filePath); const file = await this.decryptFile(joinedPath, key); this.addFile(joinedPath, file) } } this.addFile(filePath, content) } async decryptContentFile(filePath) { const contents = fs.readFileSync(filePath); if (contents.length < 0x100) return contents; const magic = contents.readUint32LE(0x4); if (magic === 2614082044) { const cipherText = contents.subarray(0x100); const uuidSize = contents.readUInt8(0x10) const uuid = contents.subarray(0x11, 0x11 + uuidSize) const key = lookupKey(uuid); const decrypted = decryptAes(key, cipherText) return decrypted } else { return contents; } } async decryptFile(filePath, key) { const contents = fs.readFileSync(filePath); const decrypted = decryptAes(key, contents) return decrypted; } async crackSkinPack() { const skinJsonFilePath = "skins.json"; if (!this.zip.files[skinJsonFilePath]) return; const skinsFile = await this.zip.files[skinJsonFilePath].async("string"); try{ const skins = JSON.parse(skinsFile); for (let index = 0; index < skins.skins.length; index++) { const skin = skins.skins[index]; skin.type = "free"; } this.addFile(path.join(this.inputPath, skinJsonFilePath), JSON.stringify(skins, null, 2)); } catch(Exception) {}; } } function recursiveReaddirrSync(dir) { let results = []; let list = fs.readdirSync(dir); list.forEach(function (file) { file = path.join(dir, file); let stat = fs.statSync(file); if (stat && stat.isDirectory()) { results = results.concat(recursiveReaddirrSync(file)); } else { results.push(file); } }); return results; } function decryptAes(key, buffer) { const bufferKey = Buffer.from(key, 'binary'); return aescfb(buffer, bufferKey); }