MCPackDecrypt/packDecrypter.js

198 lines
6.0 KiB
JavaScript

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);
}