const {app, BrowserWindow, ipcMain, dialog} = require("electron"); const path = require("path"); const fs = require("fs"); const PackDecryptor = require("./packDecrypter"); const minecraftFolderPath = path.join(process.env.LocalAppData, "/Packages/Microsoft.MinecraftUWP_8wekyb3d8bbwe"); const localStatePath = path.join(minecraftFolderPath, "LocalState") const premiumCachePath = path.join(localStatePath, "premium_cache"); const worldsPath = path.join(localStatePath, "/games/com.mojang/minecraftWorlds"); function checkWorldEncrypted(worldPath){ const worldDbFolder = path.join(worldPath, "db"); const dbFiles = fs.readdirSync(worldDbFolder); for(let index = 0; index < dbFiles.length; index++) { if(path.extname(dbFiles[index]).toLowerCase() === ".ldb") { return PackDecryptor.isContentFileEncrypted(path.join(worldDbFolder, dbFiles[index])) } } return false; } function getWorlds() { const worlds = []; if(fs.existsSync(worldsPath)) { const files = fs.readdirSync(worldsPath); for (let index = 0; index < files.length; index++) { const isEncrypted = checkWorldEncrypted(path.join(worldsPath, files[index])); if (!isEncrypted) continue; const name = fs.readFileSync(path.join(worldsPath, files[index], 'levelname.txt'), 'utf8'); const packIcon = getPackIcon(path.join(worldsPath, files[index])); worlds.push({ name: replaceName(name), packPath: path.join(worldsPath, files[index]), packIcon, }) } } return worlds; } function getPremiumCache() { const packTypes = {}; if(fs.existsSync(premiumCachePath)) { const files = fs.readdirSync(premiumCachePath); for (let index = 0; index < files.length; index++) { const dirname = files[index]; packTypes[dirname] = []; const packs = getPacks(path.join(premiumCachePath, dirname)) if (packs.length === 0) { delete packTypes[dirname]; continue; } packTypes[dirname] = packs; } } return packTypes } function getPacks(dirPath) { const packList = fs.readdirSync(dirPath); return packList.map(packDir => { const packPath = path.join(dirPath, packDir); const packName = getPackName(packPath) return { name: replaceName(packName), packPath: packPath, packIcon: getPackIcon(packPath), } }) } function replaceName(name) { return name .replaceAll("#", "") .replaceAll("?", "") .replaceAll("*", "") .replaceAll("<", "") .replaceAll(">", "") .replaceAll("|", "") .replaceAll(":", "") .replaceAll("\\", "") .replaceAll("/", "") .trim() } function getPackIcon(packPath) { const packIconNames = ["pack_icon.png", "pack_icon.jpeg" ,"world_icon.jpeg", "world_icon.png"] for (let index = 0; index < packIconNames.length; index++) { const packIconName = packIconNames[index]; const iconPath = path.join(packPath, packIconName); if (fs.existsSync(iconPath)) { return fs.readFileSync(iconPath, 'base64') } } return null; } function getPackName(packPath) { const langFile = fs.readFileSync(path.join(packPath, "texts", "en_US.lang"), 'utf8'); return langFile.split("\n")[0].split("=").at(-1).replace("\n", "").replace("\r", ""); } app.whenReady().then(() => { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegrationInWorker: true, } }); win.removeMenu() win.loadFile("./renderer/index.html") //win.webContents.openDevTools({mode: 'detach'}) ipcMain.handle("get-packs", (event) => { packs = {worlds: getWorlds(), ...getPremiumCache()}; if(packs["worlds"].length == 0) delete packs["worlds"]; return packs; }) ipcMain.handle("pick-path", async (event, {path, type, name}) => { const filter = {}; if (type === "world_templates") { filter.name = "World Template"; filter.extensions = ["mctemplate"] } if (type === "resource_packs") { filter.name = "Resource Pack"; filter.extensions = ["mcpack"] } if (type === "skin_packs") { filter.name = "Skin Pack"; filter.extensions = ["mcpack"] } if (type === "persona") { filter.name = "Persona Peice"; filter.extensions = ["mcpersona"] } if (type === "worlds") { filter.name = "World"; filter.extensions = ["mcworld"] } const dialogReturnValue = await dialog.showSaveDialog({ defaultPath: name, filters: [filter] }) if (dialogReturnValue.canceled) return; return dialogReturnValue.filePath; }) })