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