UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

584 lines (583 loc) 26.3 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const Log_1 = __importDefault(require("../core/Log")); const Utilities_1 = __importDefault(require("../core/Utilities")); const LevelDb_1 = __importDefault(require("../minecraft/LevelDb")); const MCWorld_1 = __importDefault(require("../minecraft/MCWorld")); const WorldLevelDat_1 = __importDefault(require("../minecraft/WorldLevelDat")); const StorageUtilities_1 = __importStar(require("../storage/StorageUtilities")); const IProjectData_1 = require("./IProjectData"); const IProjectItemData_1 = require("./IProjectItemData"); const minecraftIndicatorFolderNames = [ "development_behavior_packs", "development_resource_packs", "development_resource_packs", "behavior_packs", "resource_packs", "behavior_pack", "resource_pack", "world_templates", "com.mojang", "minecraftworlds", "worlds", "world", ]; class ProjectIntegrator { candidateFiles = {}; worlds = []; isInErrorState; errorMessages; _pushError(message, contextIn) { this.isInErrorState = true; if (this.errorMessages === undefined) { this.errorMessages = []; } message = message + contextIn ? " " + contextIn : ""; Log_1.default.error(message); this.errorMessages.push({ message: message, context: contextIn, }); return message; } static async createProjectFromFolder(creatorTools, newProjectName, folder, operationDescriptor, log) { let newProject = await creatorTools.createNewProject(newProjectName, undefined, undefined, undefined, IProjectData_1.ProjectFocus.general, true, IProjectData_1.ProjectScriptLanguage.typeScript); await this.extendProjectFromCollectionFolder(creatorTools, newProject, folder, operationDescriptor, log); await creatorTools.save(); return newProject; } static async extendProjectFromCollectionFolder(creatorTools, project, folder, operationDescriptor, log) { // assume everything else is "loose files" that need to be reconstructed const pi = new ProjectIntegrator(); await pi.addFromFolder(project, folder, true, log); await pi.processResults(project, operationDescriptor, log); await project.inferProjectItemsFromFiles(true); } async attachWorldDataToWorlds() { for (const world of this.worlds) { if (world.currentManifest) { if (world.manifestFiles.length > 0) { await this.addToWorldByFileIds(world); } } } for (const world of this.worlds) { if (world.currentManifest) { if (world.manifestFiles.length > 0) { await this.findLevelDat(world); } } } } async processResults(project, operationDescriptor, log) { for (const world of this.worlds) { // find our MANIFEST if (world.currentManifest) { for (const fileName in this.candidateFiles) { const tokenToLookFor = world.currentManifest.substring(2).trim(); const file = this.candidateFiles[fileName]; if (file && !file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDbManifest && fileName.indexOf(tokenToLookFor) >= 0) { file.isUsed = true; world.manifestFiles.push(file); break; } } if (world.manifestFiles.length > 0) { await this.addToWorldByManifestData(world); } } } await this.attachWorldDataToWorlds(); // now that we've attached as many LDBs/LOGs to the "good" manifests connected to CURRENTs, // let's see if we can make more worlds out of 'leftover' MANIFEST files (those without CURRENTs) // in some cases their CURRENT might be missing, or in other cases they are older versions of "good" manifests for (const fileName in this.candidateFiles) { const file = this.candidateFiles[fileName]; if (file && !file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDbManifest) { file.isUsed = true; const world = { manifestFiles: [file], ldbFiles: [], logFiles: [], }; if (world.manifestFiles.length > 0) { await this.addToWorldByManifestData(world); } this.worlds.push(world); } } await this.attachWorldDataToWorlds(); const leftoverWorld = { manifestFiles: [], ldbFiles: [], logFiles: [], }; for (const fileName in this.candidateFiles) { const file = this.candidateFiles[fileName]; if (file && !file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDbLdb) { file.isUsed = true; leftoverWorld.ldbFiles.push(file); } if (file && !file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDbLog) { file.isUsed = true; leftoverWorld.logFiles.push(file); } if (file && !file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDat && leftoverWorld.levelDatFile === undefined) { file.isUsed = true; leftoverWorld.levelDatFile = file; } } if (leftoverWorld.ldbFiles.length > 0 || leftoverWorld.logFiles.length > 0) { this.worlds.push(leftoverWorld); } await project.ensureLoadedProjectFolder(); if (project.projectFolder) { let worldsFolder = await project.ensureWorldContainer(); if (worldsFolder) { let index = 0; for (const world of this.worlds) { await this.copyFilesToWorld(world, project, worldsFolder, index); index++; } } } project.appendErrors(this, operationDescriptor); } async addToWorldByManifestData(world) { let keySets = []; let logIndices = []; let ldbIndices = []; for (const manifestFile of world.manifestFiles) { if (manifestFile.levelDbSubset && manifestFile.levelDbSubset.newFileNumber && manifestFile.levelDbSubset.newFileSmallest && manifestFile.levelDbSubset.newFileLargest) { for (let i = 0; i < manifestFile.levelDbSubset.newFileSmallest.length; i++) { const smallest = manifestFile.levelDbSubset.newFileSmallest[i]; const largest = manifestFile.levelDbSubset.newFileLargest[i]; if (smallest && largest) { keySets.push({ smallest: smallest, largest: largest }); } } for (let i = 0; i < manifestFile.levelDbSubset.newFileNumber.length; i++) { const index = manifestFile.levelDbSubset.newFileNumber[i]; if (!manifestFile.levelDbSubset.deletedFileNumber || !manifestFile.levelDbSubset.deletedFileNumber.includes(index)) { ldbIndices.push(index); } } if (manifestFile.levelDbSubset.logNumber !== undefined) { logIndices.push(manifestFile.levelDbSubset.logNumber); } } } for (const fileName in this.candidateFiles) { const file = this.candidateFiles[fileName]; if (!file.isUsed && (file.type === IProjectItemData_1.ProjectItemType.levelDbLdb || file.type === IProjectItemData_1.ProjectItemType.levelDbLog) && file.levelDbSubset) { for (const keySet of keySets) { if (file.levelDbSubset.keys.get(keySet.largest) !== undefined && file.levelDbSubset.keys.get(keySet.smallest) !== undefined) { if (file.type === IProjectItemData_1.ProjectItemType.levelDbLog) { file.isUsed = true; world.logFiles.push(file); break; } else if (file.type === IProjectItemData_1.ProjectItemType.levelDbLdb) { file.isUsed = true; world.ldbFiles.push(file); break; } } } } } } async addToWorldByFileIds(world) { let logIndices = []; let ldbIndices = []; for (const manifestFile of world.manifestFiles) { if (manifestFile.levelDbSubset && manifestFile.levelDbSubset.newFileNumber) { for (let i = 0; i < manifestFile.levelDbSubset.newFileNumber.length; i++) { const index = manifestFile.levelDbSubset.newFileNumber[i]; if (!manifestFile.levelDbSubset.deletedFileNumber || !manifestFile.levelDbSubset.deletedFileNumber.includes(index)) { ldbIndices.push(index); } } if (manifestFile.levelDbSubset.logNumber !== undefined) { logIndices.push(manifestFile.levelDbSubset.logNumber); } } } for (const fileName in this.candidateFiles) { const file = this.candidateFiles[fileName]; if (!file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDbLdb && file.levelDbSubset && file.index !== undefined && ldbIndices.includes(file.index)) { file.isUsed = true; world.ldbFiles.push(file); } else if (!file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDbLog && file.levelDbSubset && file.index !== undefined && logIndices.includes(file.index)) { file.isUsed = true; world.logFiles.push(file); } } } async findLevelDat(world) { if ((world.ldbFiles.length > 0 || world.logFiles.length > 0) && world.manifestFiles.length > 0) { for (const file of world.ldbFiles) { if (file && file.levelDbSubset) { const mcworld = new MCWorld_1.default(); await mcworld.loadFromLevelDb(file.levelDbSubset); if (mcworld.generationSeed) { world.seedFromLevelDb = mcworld.generationSeed; break; } } } if (!world.seedFromLevelDb) { for (const file of world.logFiles) { if (file && file.levelDbSubset) { const mcworld = new MCWorld_1.default(); await mcworld.loadFromLevelDb(file.levelDbSubset); if (mcworld.generationSeed) { world.seedFromLevelDb = mcworld.generationSeed; break; } } } } if (world.seedFromLevelDb) { for (const fileName in this.candidateFiles) { const file = this.candidateFiles[fileName]; if (!file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDat && file.levelDat && file.levelDat.randomSeed === world.seedFromLevelDb) { file.isUsed = true; world.levelDatFile = file; } } } } } async copyFilesToWorld(world, project, worldsFolder, index) { if (world.ldbFiles.length > 0 || world.logFiles.length > 0) { // && world.manifestFiles.length > 0) { let name = "world" + index; if (world.levelDatFile && world.levelDatFile.levelDat?.levelName) { name = world.levelDatFile.levelDat?.levelName; let worldIndex = 0; // ensure folder name is unique while (worldsFolder.folders[name] !== undefined) { worldIndex++; name = world.levelDatFile.levelDat?.levelName + " " + worldIndex; } } const worldFolder = worldsFolder.ensureFolder(name); const newLevelDataFile = worldFolder.ensureFile("level.dat"); const newLevelDat = new WorldLevelDat_1.default(); newLevelDat.ensureDefaults(); newLevelDat.levelName = name; if (world.levelDatFile && world.levelDatFile.file.content && world.levelDatFile.file.content instanceof Uint8Array) { // we could probably just newLevelDat = world.levelDatFile.levelDat // but we're going to modify it so use the new copy. newLevelDat.loadFromNbtBytes(world.levelDatFile.file.content); } // note that we are re-writing level.dat rather than just copying it. // add experiments flag newLevelDat.experimentsEverUsed = true; newLevelDat.savedWithToggledExperiments = true; newLevelDat.persist(); const levelDatBytes = newLevelDat.getBytes(); if (levelDatBytes !== undefined) { newLevelDataFile.setContent(levelDatBytes); } else if (world.levelDatFile && world.levelDatFile.file.content && world.levelDatFile.file.content instanceof Uint8Array) { newLevelDataFile.setContent(world.levelDatFile.file.content); } const levelNameFile = worldFolder.ensureFile("levelname.txt"); levelNameFile.setContent(name); let dbFolder = worldFolder.ensureFolder("db"); for (const file of world.ldbFiles) { if (file && file.index && file.file.content) { const newFileName = Utilities_1.default.frontPadToLength(file.index, 6, "0"); const newLdbFile = dbFolder.ensureFile(newFileName + ".ldb"); newLdbFile.setContent(file.file.content); } } for (const file of world.logFiles) { if (file && file.index && file.file.content) { const newFileName = Utilities_1.default.frontPadToLength(file.index, 6, "0"); const newLogFile = dbFolder.ensureFile(newFileName + ".log"); newLogFile.setContent(file.file.content); } } let manifestFileName = world.currentManifest; for (const file of world.manifestFiles) { if (file && file.index && file.file.content) { const newFileName = "MANIFEST-" + Utilities_1.default.frontPadToLength(file.index, 6, "0"); if (!manifestFileName) { manifestFileName = newFileName; } const newManifestFile = dbFolder.ensureFile(newFileName); newManifestFile.setContent(file.file.content); } } const currentFile = dbFolder.ensureFile("CURRENT"); currentFile.setContent(manifestFileName + "\n"); } } async addFromFolder(project, sourceFolder, processSubFolders, log) { await sourceFolder.load(); const files = []; for (const filePath in sourceFolder.files) { const file = sourceFolder.files[filePath]; if (file) { files.push(file); } } await this.addLooseFiles(project, files, log); if (processSubFolders) { // first pass: see if the root folder contains any structured 'known' Minecraft Folders for (const folderName in sourceFolder.folders) { const folderNameToLower = folderName.toLowerCase().trim(); const childFolder = sourceFolder.folders[folderName]; if (childFolder) { if (minecraftIndicatorFolderNames.includes(folderNameToLower)) { // handle what looks like special minecraft folders in some way } } } for (const folderName in sourceFolder.folders) { const folderNameToLower = folderName.toLowerCase().trim(); const childFolder = sourceFolder.folders[folderName]; if (childFolder) { if (!minecraftIndicatorFolderNames.includes(folderNameToLower)) { // treat non-Minecraft indicated folders as loose files if (childFolder) { await this.addFromFolder(project, childFolder, true); } } } } } } async addLooseFiles(project, files, log) { for (const file of files) { const extension = StorageUtilities_1.default.getTypeFromName(file.name); if (file.name.indexOf("evel") >= 0 && extension === "dat") { if (!file.isContentLoaded) { await file.loadContent(); } if (file.content && file.content instanceof Uint8Array) { const levelDat = new WorldLevelDat_1.default(); try { levelDat.loadFromNbtBytes(file.content); if (!levelDat.isInErrorState) { this.candidateFiles[file.storageRelativePath] = { file: file, levelDat: levelDat, type: IProjectItemData_1.ProjectItemType.levelDat, }; } } catch (e) { if (log) { log("Error processing " + file.name + " (" + e.toString() + ")"); } } } } else if (file.name.indexOf("RRENT") >= 0) { await this.considerCURRENTFile(file, log); } else if (file.name.indexOf("NIFEST") >= 0) { await this.considerMANIFESTFile(file, log); } else { switch (extension) { case "ldb": if (!file.isContentLoaded) { await file.loadContent(); } if (file.isBinary) { const levelDb = new LevelDb_1.default([file], [], [], file.name); try { await levelDb.init(log); Utilities_1.default.appendErrors(this, levelDb); if (!levelDb.isInErrorState) { this.candidateFiles[file.storageRelativePath] = { file: file, index: ProjectIntegrator.getIndexFromString(file.name), levelDbSubset: levelDb, type: IProjectItemData_1.ProjectItemType.levelDbLdb, }; } } catch (e) { const error = this._pushError("Error processing ldb file " + file.name + " (" + e.toString() + ")"); if (log) { log(error); } } } break; case "log": await file.loadContent(false, StorageUtilities_1.EncodingType.ByteBuffer); if (file.isBinary) { const levelDb = new LevelDb_1.default([], [file], [], file.name); try { await levelDb.init(log); Utilities_1.default.appendErrors(this, levelDb); if (!levelDb.isInErrorState) { this.candidateFiles[file.storageRelativePath] = { file: file, index: ProjectIntegrator.getIndexFromString(file.name), levelDbSubset: levelDb, type: IProjectItemData_1.ProjectItemType.levelDbLog, }; } } catch (e) { const error = this._pushError("Error processing log file " + file.name + " (" + e.toString() + ")"); if (log) { log(error); } } } break; } } } } async considerCURRENTFile(file, log) { await file.loadContent(false, StorageUtilities_1.EncodingType.Utf8String); if (file.content && typeof file.content === "string") { if (file.content.startsWith("MANIFEST")) { this.worlds.push({ currentFile: file, currentManifest: file.content.trim(), manifestFiles: [], ldbFiles: [], logFiles: [], }); } } } static getIndexFromString(fileName) { fileName = StorageUtilities_1.default.getBaseFromName(fileName.trim()); const lastDash = fileName.lastIndexOf("-"); if (lastDash >= 0) { fileName = fileName.substring(lastDash + 1); } let zeroStart = -1; let adjustStr = ""; let index = 0; for (const char of fileName) { if (char >= "1" && char <= "9") { if (zeroStart >= 0) { adjustStr += char; } } else if (char === "0") { zeroStart = index; adjustStr += char; } else if (zeroStart >= 0) { break; } index++; } if (adjustStr.length > 4) { fileName = adjustStr; } try { const num = parseInt(fileName); if (isNaN(num)) { return undefined; } return num; } catch (e) { } return undefined; } async considerMANIFESTFile(file, log) { await file.loadContent(false, StorageUtilities_1.EncodingType.ByteBuffer); if (file.isBinary) { const levelDb = new LevelDb_1.default([], [], [file], file.name); try { await levelDb.init(log); Utilities_1.default.appendErrors(this, levelDb); if (!levelDb.isInErrorState) { this.candidateFiles[file.storageRelativePath] = { file: file, index: ProjectIntegrator.getIndexFromString(file.name), levelDbSubset: levelDb, type: IProjectItemData_1.ProjectItemType.levelDbManifest, }; } } catch (e) { const error = this._pushError("Error processing MANIFEST file " + file.name + " (" + e.toString() + ")"); if (log) { log(error); } } } } } exports.default = ProjectIntegrator;