bedrock-development
Version:
APIs for creating and editing files related to Minecraft Bedrock development.
264 lines (263 loc) • 11.7 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { archiveDirectory, copySourceDirectory, copySourceFile, getFiles, setFiles } from "../file_manager.js";
import * as JSONC from 'comment-json';
import { chalk } from "../utils.js";
import * as fs from 'fs';
import path from "path";
import { v4 } from 'uuid';
import { execSync } from "child_process";
import { NbtFile } from "deepslate";
const APPDATA = (process.env.LOCALAPPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share")).replace(/\\/g, '/');
export const MOJANG = `${APPDATA}/Packages/Microsoft.MinecraftUWP_8wekyb3d8bbwe/LocalState/games/com.mojang`;
const DOWNLOAD = `${process.env.USERPROFILE}/Downloads`.replace(/\\/g, '/');
export function cache(target, propertyName, descriptor) {
if (descriptor.get) {
const get = descriptor.get;
target[`${propertyName}_cached`] = undefined;
descriptor.get = function () {
const obj = this;
if (obj[`${propertyName}_cached`]) {
return obj[`${propertyName}_cached`];
}
else {
const value = get.apply(obj);
obj[`${propertyName}_cached`] = value;
return value;
}
};
}
}
export class MinecraftWorld {
get BehaviorPacks() {
return this.getPacks('behavior');
}
get ResourcePacks() {
return this.getPacks('resource');
}
get Name() {
return String(fs.readFileSync(this.filePath + '/levelname.txt'));
}
get LevelDat() {
const buffer = fs.readFileSync(this.filePath + '/level.dat');
const uint8Array = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
return NbtFile.read(uint8Array, { bedrockHeader: true, littleEndian: true });
}
constructor(filePath) {
this.filePath = filePath;
}
static create(worldName, options) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const world = new MinecraftWorld(`${DOWNLOAD}/${worldName}`);
if (options.behavior_pack_manifest_path) {
world.addPack(MinecraftWorld.getPackFromManifest(options.behavior_pack_manifest_path));
}
if (options.resource_pack_manifest_path) {
world.addPack(MinecraftWorld.getPackFromManifest(options.resource_pack_manifest_path));
}
copySourceFile('world/level.dat', `${world.filePath}/level.dat`);
setFiles([{ filePath: `${world.filePath}/levelname.txt`, fileContents: worldName }]);
const levelDat = world.LevelDat.toJson();
levelDat.root.LevelName = { type: 8, value: worldName };
levelDat.root.RandomSeed = { type: 4, value: [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000)] };
levelDat.root.GameType = { type: 3, value: (_a = options.gamemode) !== null && _a !== void 0 ? _a : 0 };
if (options.flatworld) {
levelDat.root.Generator = { type: 3, value: 2 };
}
if (options.testworld) {
levelDat.root.commandsEnabled = { type: 1, value: 1 };
levelDat.root.dodaylightcycle = { type: 1, value: 0 };
levelDat.root.domobloot = { type: 1, value: 0 };
levelDat.root.domobspawning = { type: 1, value: 0 };
levelDat.root.mobgriefing = { type: 1, value: 0 };
levelDat.root.keepinventory = { type: 1, value: 1 };
levelDat.root.doweathercycle = { type: 1, value: 0 };
}
world.setLevelDat(NbtFile.fromJson(levelDat).write());
yield new Promise(resolve => {
archiveDirectory(world.filePath, `${world.filePath}.mcworld`, () => {
console.log(`${chalk.green(`Opening World`)}`);
execSync(`"${world.filePath}.mcworld"`);
fs.rmSync(`${world.filePath}.mcworld`);
resolve();
});
});
return world;
});
}
static getAllWorlds() {
try {
let path = `${MOJANG}/minecraftWorlds`;
return fs.readdirSync(path).map(dir => new MinecraftWorld(`${path}/${dir}`));
}
catch (error) {
console.log(error);
return [];
}
}
exportWorld(include_packs, type) {
const outputPath = `${DOWNLOAD}/${this.Name}`;
this.addManifest();
console.log(`${chalk.green(`Exporting ${this.filePath}`)}`);
copySourceDirectory(this.filePath, outputPath);
if (include_packs) {
try {
fs.mkdirSync(`${outputPath}/behavior_packs`);
fs.mkdirSync(`${outputPath}/resource_packs`);
}
catch (error) { }
for (const bp of this.BehaviorPacks) {
console.log(`${chalk.green(`Exporting ${bp.name}`)}`);
copySourceDirectory(bp.directory, `${outputPath}/behavior_packs/${bp.name}`);
}
for (const rp of this.ResourcePacks) {
console.log(`${chalk.green(`Exporting ${rp.name}`)}`);
copySourceDirectory(rp.directory, `${outputPath}/resource_packs/${rp.name}`);
}
}
console.log(`${chalk.green(`Packaging World`)}`);
return new Promise(resolve => {
archiveDirectory(outputPath, `${outputPath}.mc${type}`, () => {
console.log(`${chalk.green(`Cleaning Up`)}`);
fs.rmSync(outputPath, { recursive: true, force: true });
console.log(`${chalk.green(`Packaged ${this.filePath}.mc${type} To ${DOWNLOAD}`)}`);
execSync(`start %windir%\\explorer.exe "${DOWNLOAD.replace(/\//g, '\\')}"`);
resolve();
});
});
}
getPacks(type) {
if (fs.existsSync(`${this.filePath}/world_${type}_packs.json`)) {
let bp = JSONC.parse(String(fs.readFileSync(`${this.filePath}/world_${type}_packs.json`)));
return bp.map(pack => {
const directory = findPackPathWithID(pack.pack_id, type);
return {
type: type,
name: path.basename(directory),
uuid: pack.pack_id,
directory,
};
});
}
return [];
}
static getPackFromManifest(filepath) {
const manifest = JSON.parse(String(fs.readFileSync(filepath)));
const type = filepath.includes('resource') ? 'resource' : 'behavior';
const uuid = manifest.header.uuid;
const directory = path.dirname(filepath);
const name = path.basename(directory);
const pack = {
directory,
name,
type,
uuid,
};
return type === 'resource' ? pack : pack;
}
addPack(pack) {
let files = getFiles(this.filePath + `/world_${pack.type}_packs.json`);
if (!files.length) {
files.push({ filePath: this.filePath + `/world_${pack.type}_packs.json`, fileContents: '[]' });
}
files = files.filter(file => {
if (file.fileContents.includes(pack.uuid)) {
console.warn(`${chalk.yellow(`${this.Name} already contains pack ${pack.name}`)}`);
return false;
}
;
const json = JSON.parse(file.fileContents);
json.push({
pack_id: pack.uuid,
version: [1, 0, 0],
});
file.fileContents = JSON.stringify(json, null, '\t');
file.handleExisting = 'overwrite';
return true;
});
setFiles(files);
}
addManifest() {
return __awaiter(this, void 0, void 0, function* () {
const dat = this.LevelDat.toJson();
const version_array = dat.root.lastOpenedWithVersion.value.items.slice(0, 3);
console.log(version_array);
setFiles([{
filePath: `${this.filePath}/manifest.json`,
handleExisting: 'overwrite_silent',
fileContents: JSON.stringify({
format_version: 2,
header: {
base_game_version: version_array,
description: "",
lock_template_options: true,
name: this.Name,
platform_locked: false,
uuid: v4(),
version: [1, 0, 0]
},
modules: [
{
description: "",
type: "world_template",
uuid: v4(),
version: [1, 0, 0]
}
]
}, null, '/t')
}]);
});
}
setLevelDat(v) {
let stream = fs.createWriteStream(this.filePath + '/level.dat');
stream.write(v);
stream.end();
}
}
__decorate([
cache
], MinecraftWorld.prototype, "BehaviorPacks", null);
__decorate([
cache
], MinecraftWorld.prototype, "ResourcePacks", null);
__decorate([
cache
], MinecraftWorld.prototype, "Name", null);
function findPackPathWithID(id, type) {
let path = `${APPDATA}/Packages/Microsoft.MinecraftUWP_8wekyb3d8bbwe/LocalState/games/com.mojang`;
// Check dev packs first
for (const folder of fs.readdirSync(`${path}/development_${type}_packs`)) {
let subpath = `${path}/development_${type}_packs/${folder}`;
if (fs.existsSync(`${subpath}/manifest.json`)) {
let manifest = JSONC.parse(String(fs.readFileSync(`${subpath}/manifest.json`)));
if (manifest.header.uuid === id) {
return subpath;
}
}
}
// Check other packs next
for (const folder of fs.readdirSync(`${path}/${type}_packs`)) {
let subpath = `${path}/${folder}`;
if (fs.existsSync(`${subpath}/manifest.json`)) {
let manifest = JSONC.parse(String(fs.readFileSync(`${subpath}/manifest.json`)));
if (manifest.header.uuid === id) {
return subpath;
}
}
}
return '';
}