MCPackDecrypt/ent.js

131 lines
3.8 KiB
JavaScript

const fs = require("fs");
const path = require("path");
const aescfb = require('./aes');
const skinKey = "s5s5ejuDru4uchuF2drUFuthaspAbepE";
const localStatePath = path.join(process.env.LocalAppData, "/Packages/Microsoft.MinecraftUWP_8wekyb3d8bbwe/LocalState");
const mcpePath = path.join(localStatePath, "/games/com.mojang/minecraftpe");
const keyDb = {};
getEntFile();
function getTitleAccountId() {
const optionsTxt = path.join(mcpePath, 'options.txt');
if(fs.existsSync(optionsTxt)) {
const options = fs.readFileSync(optionsTxt).toString();
const lines = options.split('\n');
for (let i = 0; i < lines.length; i++) {
const [key, value] = lines[i].split(':');
if (key === "last_title_account_id") {
return value.replace("\n", "").replace("\r", "");
}
}
}
}
function getEntKey() {
const titleAccountId = getTitleAccountId();
const entXorKey = "X(nG*ejm&E8)m+8c;-SkLTjF)*QdN6_Y";
const entKey = Buffer.alloc(32);
for (let i = 0; i < 32; i++) {
entKey[i] = titleAccountId.charCodeAt(i % titleAccountId.length) ^ entXorKey.charCodeAt(i);
}
return entKey;
}
function lookupKey(uuid) {
return keyDb[uuid] || skinKey;
}
function getEntFile() {
if(fs.existsSync(localStatePath)) {
const files = fs.readdirSync(localStatePath);
const entFileNames = files.filter(file => file.endsWith(".ent"));
const entFiles = entFileNames.map(file => fs.readFileSync(path.join(localStatePath, file)).toString().substring("Version2".length));
for (let index = 0; index < entFiles.length; index++) {
const entFile = entFiles[index];
const cipherText = Buffer.from(entFile, 'base64')
const decrypted = decryptBuffer(cipherText, getEntKey());
try {
const json = JSON.parse(decrypted.toString());
parseEnt(json)
} catch {
continue;
}
}
}
}
module.exports = lookupKey;
function parseEnt(ent) {
const mainReceipt = ent.Receipt;
parseReceipt(mainReceipt)
for (let index = 0; index < ent.Items.length; index++) {
const item = ent.Items[index];
const receipt = item.Receipt;
parseReceipt(receipt);
}
}
function parseReceipt(receipt) {
const receiptContent = receipt.split(".")[1];
const content = JSON.parse(atob(receiptContent));
const entitlements = content.Receipt.Entitlements;
const deviceId = content.Receipt.ReceiptData?.DeviceId;
const entityId = content.Receipt.EntityId;
if (!deviceId || !entityId) return;
const userKey = deriveUserKey(deviceId, entityId);
for (let index = 0; index < entitlements.length; index++) {
const element = entitlements[index];
if (!element.ContentKey) continue;
keyDb[element.FriendlyId] = deobfuscateContentKey(element.ContentKey, userKey);
}
}
function deriveUserKey(deviceId, entityId) {
const deviceIdBuffer = Buffer.from(deviceId, 'utf16le');
const entityIdBuffer = Buffer.from(entityId, 'utf16le');
let length = deviceIdBuffer.length;
if (entityIdBuffer.length < length) length = entityIdBuffer.length;
const userKey = Buffer.alloc(length);
for (let index = 0; index < userKey.length; index++) {
userKey[index] = deviceIdBuffer[index] ^ entityIdBuffer[index];
}
return userKey;
}
function deobfuscateContentKey(contentKey, userKey) {
const b64DecodedKey = Buffer.from(contentKey, 'base64');
let length = b64DecodedKey.length;
if (userKey.length < length) length = userKey.length;
const deobfuscatedKey = Buffer.alloc(length);
for (let index = 0; index < deobfuscatedKey.length; index++) {
deobfuscatedKey[index] = b64DecodedKey[index] ^ userKey[index];
}
return deobfuscatedKey.toString("utf16le");
}
function decryptBuffer(buffer, key) {
const bufferKey = Buffer.from(key, 'binary');
return aescfb(buffer, bufferKey);
}