Comparing version 0.1.1 to 1.0.0
206
index.d.ts
@@ -1,205 +0,3 @@ | ||
/// <reference types="node" /> | ||
import ByteBuffer from "bytebuffer"; | ||
import Long from "long"; | ||
import { Optional } from "typescript-optional"; | ||
export declare namespace NBT { | ||
type TagType = TagTypePrimitive | typeof TagType.List | typeof TagType.Compound; | ||
type TagTypePrimitive = typeof TagType.End | typeof TagType.Byte | typeof TagType.Short | typeof TagType.Int | typeof TagType.Long | typeof TagType.Float | typeof TagType.Double | typeof TagType.ByteArray | typeof TagType.String | typeof TagType.IntArray | typeof TagType.LongArray; | ||
namespace TagType { | ||
const End: 0; | ||
const Byte: 1; | ||
const Short: 2; | ||
const Int: 3; | ||
const Long: 4; | ||
const Float: 5; | ||
const Double: 6; | ||
const ByteArray: 7; | ||
const String: 8; | ||
const List: 9; | ||
const Compound: 10; | ||
const IntArray: 11; | ||
const LongArray: 12; | ||
function name(tagType: TagType): string; | ||
} | ||
interface Tag<T extends TagType, V> { | ||
readonly type: T; | ||
readonly value: V; | ||
} | ||
type TagEnd = Tag<typeof TagType.End, null>; | ||
type TagByte = Tag<typeof TagType.Byte, number>; | ||
type TagShort = Tag<typeof TagType.Short, number>; | ||
type TagInt = Tag<typeof TagType.Int, number>; | ||
type TagLong = Tag<typeof TagType.Long, Long>; | ||
type TagFloat = Tag<typeof TagType.Float, number>; | ||
type TagDouble = Tag<typeof TagType.Double, number>; | ||
type TagByteArray = Tag<typeof TagType.ByteArray, Uint8Array>; | ||
type TagString = Tag<typeof TagType.String, string>; | ||
type TagIntArray = Tag<typeof TagType.IntArray, Int32Array>; | ||
type TagLongArray = Tag<typeof TagType.LongArray, Long[]>; | ||
type Tags = TagPrimitive | TagCompound | TagLists; | ||
type TagPrimitive = TagEnd | TagByte | TagShort | TagInt | TagLong | TagFloat | TagDouble | TagByteArray | TagString | TagIntArray | TagLongArray; | ||
type TagLists = TagList<TagByte> | TagList<TagShort> | TagList<TagInt> | TagList<TagLong> | TagList<TagFloat> | TagList<TagDouble> | TagList<TagByteArray> | TagList<TagString> | TagList<TagIntArray> | TagList<TagLongArray> | TagList<TagCompound> | TagListList | TagListTag; | ||
interface TagListList extends TagList<TagLists> { | ||
} | ||
interface TagListTag extends TagList<Tags> { | ||
} | ||
interface TagList<T extends Tags> extends Tag<typeof TagType.List, void>, Array<T> { | ||
elementType: T["type"]; | ||
} | ||
interface TagCompound<T = any> extends Tag<typeof TagType.Compound, { | ||
[P in keyof T]: Tags; | ||
}> { | ||
get(key: string): Tags; | ||
set(key: string, tag: Tags): boolean; | ||
has(key: string): boolean; | ||
setByte(key: string, value: number): this; | ||
setShort(key: string, value: number): this; | ||
setInt(key: string, value: number): this; | ||
setLong(key: string, value: Long): this; | ||
setFloat(key: string, value: number): this; | ||
setDouble(key: string, value: number): this; | ||
setByteArray(key: string, value: Uint8Array): this; | ||
setString(key: string, value: string): this; | ||
setIntArray(key: string, value: Int32Array): this; | ||
setLongArray(key: string, value: Long[]): this; | ||
getByte(key: string): Optional<number>; | ||
getShort(key: string): Optional<number>; | ||
getInt(key: string): Optional<number>; | ||
getLong(key: string): Optional<Long>; | ||
getFloat(key: string): Optional<number>; | ||
getDouble(key: string): Optional<number>; | ||
getByteArray(key: string): Optional<Uint8Array>; | ||
getString(key: string): Optional<string>; | ||
getIntArray(key: string): Optional<Int32Array>; | ||
getLongArray(key: string): Optional<Long[]>; | ||
} | ||
function tagPrimitive(type: TagTypePrimitive, value: any): TagPrimitive; | ||
function tagCompound<T extends { | ||
[key: string]: Tags; | ||
}>(v: T): TagCompound<T>; | ||
function tagList<T extends Tags>(elementType: T["type"], items: T[]): TagList<T>; | ||
function tagByte(value: number): TagByte; | ||
function tagShort(value: number): TagShort; | ||
function tagInt(value: number): TagInt; | ||
function tagLong(value: Long): TagLong; | ||
function tagFloat(value: number): TagFloat; | ||
function tagDouble(value: number): TagDouble; | ||
function tagByteArray(value: Uint8Array): TagByteArray; | ||
function tagString(value: string): TagString; | ||
function tagIntArray(value: Int32Array): TagIntArray; | ||
function tagLongArray(value: Long[]): TagLongArray; | ||
namespace Persistence { | ||
interface TypedObject { | ||
readonly __nbtPrototype__: CompoundSchema; | ||
[key: string]: any; | ||
} | ||
type Schema = ListSchema | CompoundSchema; | ||
interface CompoundSchema { | ||
[key: string]: TagType | string | Schema; | ||
} | ||
interface ListSchema extends Array<TagType | string | Schema> { | ||
} | ||
type TypeIdentity = Schema | string | TagType; | ||
export class ReadContext { | ||
private scopeType; | ||
private reg; | ||
constructor(scopeType: TypeIdentity, reg: { | ||
[name: string]: string; | ||
}); | ||
type: TypeIdentity; | ||
findSchemaId(schema: Schema): string | undefined; | ||
fork(tagType: number): ReadContext; | ||
} | ||
export class WriteContext { | ||
readonly type: Schema; | ||
private reg; | ||
constructor(type: Schema, reg: { | ||
[id: string]: Schema; | ||
}); | ||
findSchema(id: string): Schema | undefined; | ||
fork(type?: Schema): WriteContext; | ||
} | ||
export interface IO { | ||
read(buf: ByteBuffer, context: ReadContext): any; | ||
write(buf: ByteBuffer, value: any, context: WriteContext): void; | ||
} | ||
export interface SerializationOption { | ||
compressed?: boolean; | ||
/** | ||
* IO override for serialization | ||
*/ | ||
io?: { | ||
[tagType: number]: IO; | ||
}; | ||
} | ||
/** | ||
* Serialzie an nbt typed json object into NBT binary | ||
* @param object The json | ||
* @param compressed Should we compress it | ||
*/ | ||
export function serialize(object: TypedObject, option?: SerializationOption): Promise<Buffer>; | ||
/** | ||
* Deserialize the nbt binary into json | ||
* @param fileData The nbt binary | ||
* @param compressed Should we compress it | ||
*/ | ||
export function deserialize(fileData: Buffer, option?: SerializationOption): Promise<TypedObject>; | ||
/** | ||
* Serialzie an nbt typed json object into NBT binary | ||
* @param object The json | ||
* @param compressed Should we compress it | ||
*/ | ||
export function serializeSync(object: TypedObject, option?: SerializationOption): Buffer; | ||
/** | ||
* Deserialize the nbt binary into json | ||
* @param fileData The nbt binary | ||
* @param compressed Should we compress it | ||
*/ | ||
export function deserializeSync(fileData: Buffer, option?: SerializationOption): TypedObject; | ||
export function createSerializer(): Serializer; | ||
export class Serializer { | ||
private registry; | ||
private reversedRegistry; | ||
/** | ||
* Register a new type nbt schema to the serializer | ||
* @param type The type name | ||
* @param schema The schema | ||
*/ | ||
register(type: string, schema: CompoundSchema): this; | ||
/** | ||
* Serialize the object into the specific type | ||
* @param object The json object | ||
* @param type The registered nbt type | ||
* @param option The serialize option | ||
*/ | ||
serialize(object: object, type: string, option?: SerializationOption): Promise<Buffer>; | ||
/** | ||
* Serialize the object into the specific type | ||
* @param object The json object | ||
* @param type The registered nbt type | ||
* @param compressed Should compress this nbt | ||
*/ | ||
serializeSync(object: object, type: string, option?: SerializationOption): any; | ||
/** | ||
* Deserialize the nbt to json object directly | ||
* @param fileData The nbt data | ||
* @param compressed Does the data compressed | ||
*/ | ||
deserialize(fileData: Buffer, option?: SerializationOption): Promise<{ | ||
value: any; | ||
type: any | string; | ||
}>; | ||
/** | ||
* Deserialize the nbt to json object directly | ||
* @param fileData The nbt data | ||
* @param compressed Does the data compressed | ||
*/ | ||
deserializeSync(fileData: Buffer, option?: SerializationOption): { | ||
value: any; | ||
type: any | string; | ||
}; | ||
} | ||
export {}; | ||
} | ||
} | ||
import NBT from "./nbt"; | ||
export { NBT }; | ||
export default NBT; |
626
index.js
@@ -1,612 +0,16 @@ | ||
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const bytebuffer_1 = __importDefault(require("bytebuffer")); | ||
const file_type_1 = __importDefault(require("file-type")); | ||
const long_1 = __importDefault(require("long")); | ||
const typescript_optional_1 = require("typescript-optional"); | ||
const utils_1 = require("./utils"); | ||
var NBT; | ||
(function (NBT) { | ||
let TagType; | ||
(function (TagType) { | ||
TagType.End = 0; | ||
TagType.Byte = 1; | ||
TagType.Short = 2; | ||
TagType.Int = 3; | ||
TagType.Long = 4; | ||
TagType.Float = 5; | ||
TagType.Double = 6; | ||
TagType.ByteArray = 7; | ||
TagType.String = 8; | ||
TagType.List = 9; | ||
TagType.Compound = 10; | ||
TagType.IntArray = 11; | ||
TagType.LongArray = 12; | ||
function name(tagType) { | ||
return [ | ||
"End", | ||
"Byte", | ||
"Short", | ||
"Int", | ||
"Long", | ||
"Float", | ||
"Double", | ||
"ByteArray", | ||
"String", | ||
"List", | ||
"Compound", | ||
"IntArray", | ||
"LongArray", | ||
][tagType]; | ||
} | ||
TagType.name = name; | ||
})(TagType = NBT.TagType || (NBT.TagType = {})); | ||
function badTag(tag) { | ||
if (typeof tag.type !== "number") { | ||
return true; | ||
} | ||
if (!Number.isInteger(tag.type) || tag.type > 12 || tag.type < 0) { | ||
return true; | ||
} | ||
const tagType = tag.type; | ||
const value = tag.value; | ||
switch (tagType) { | ||
case TagType.Byte: | ||
return typeof value !== "number" || !Number.isInteger(value) || value < -0x80 || value > 0x7F; | ||
case TagType.Short: | ||
return typeof value !== "number" || !Number.isInteger(value) || value < -0x8000 || value > 0x7FFF; | ||
case TagType.Int: | ||
return typeof value !== "number" || !Number.isInteger(value) || value < -0x80000000 || value > 0x7FFFFFFF; | ||
case TagType.Long: | ||
return typeof value !== "object" || !(value instanceof long_1.default) || value.unsigned; | ||
case TagType.Float: | ||
case TagType.Double: | ||
return typeof value !== "number"; | ||
case TagType.ByteArray: | ||
return typeof value !== "object" || !(value instanceof Uint8Array); | ||
case TagType.String: | ||
return typeof value !== "string"; | ||
case TagType.IntArray: | ||
return typeof value !== "object" || !(value instanceof Int32Array); | ||
case TagType.LongArray: | ||
return typeof value !== "object" || !(value instanceof Array); | ||
} | ||
return true; | ||
} | ||
function badElementTag(v, etype) { | ||
if (badTag(v)) { | ||
return true; | ||
} | ||
if (v.type !== etype) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
class TagListImpl extends Array { | ||
constructor(elementType, items) { | ||
super(...items); | ||
this.elementType = elementType; | ||
this.type = 9; | ||
return new Proxy(this, { | ||
set(target, k, v) { | ||
if (badElementTag(v, target.elementType)) { | ||
return false; | ||
} | ||
return Reflect.set(target, k, v); | ||
}, | ||
}); | ||
} | ||
push(...items) { | ||
if (items.some((v) => badElementTag(v, this.elementType))) { | ||
return this.length; | ||
} | ||
return super.push(...items); | ||
} | ||
unshift(...items) { | ||
if (items.some((v) => badElementTag(v, this.elementType))) { | ||
return this.length; | ||
} | ||
return super.unshift(...items); | ||
} | ||
} | ||
class TagCompoundImpl { | ||
constructor(value) { | ||
this.type = 10; | ||
this.value = new Proxy(value, { | ||
set(target, k, v) { | ||
if (badTag(v)) { | ||
return false; | ||
} | ||
Reflect.set(target, k, v); | ||
return true; | ||
}, | ||
}); | ||
} | ||
getByte(key) { | ||
return typescript_optional_1.Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Byte) | ||
.map((v) => v.value); | ||
} | ||
getShort(key) { | ||
return typescript_optional_1.Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Short) | ||
.map((v) => v.value); | ||
} | ||
getInt(key) { | ||
return typescript_optional_1.Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Int) | ||
.map((v) => v.value); | ||
} | ||
getLong(key) { | ||
return typescript_optional_1.Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Long) | ||
.map((v) => v.value); | ||
} | ||
getFloat(key) { | ||
return typescript_optional_1.Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Float) | ||
.map((v) => v.value); | ||
} | ||
getDouble(key) { | ||
return typescript_optional_1.Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Double) | ||
.map((v) => v.value); | ||
} | ||
getByteArray(key) { | ||
return typescript_optional_1.Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.ByteArray) | ||
.map((v) => v.value); | ||
} | ||
getString(key) { | ||
return typescript_optional_1.Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.String) | ||
.map((v) => v.value); | ||
} | ||
getIntArray(key) { | ||
return typescript_optional_1.Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.IntArray) | ||
.map((v) => v.value); | ||
} | ||
getLongArray(key) { | ||
return typescript_optional_1.Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.LongArray) | ||
.map((v) => v.value); | ||
} | ||
get(key) { return Reflect.get(this.value, key); } | ||
set(key, tag) { return Reflect.set(this.value, key, tag); } | ||
has(key) { return Reflect.has(this.value, key); } | ||
setByte(key, value) { this.set(key, tagByte(value)); return this; } | ||
setShort(key, value) { this.set(key, tagShort(value)); return this; } | ||
setInt(key, value) { this.set(key, tagInt(value)); return this; } | ||
setLong(key, value) { this.set(key, tagLong(value)); return this; } | ||
setFloat(key, value) { this.set(key, tagFloat(value)); return this; } | ||
setDouble(key, value) { this.set(key, tagDouble(value)); return this; } | ||
setByteArray(key, value) { this.set(key, tagByteArray(value)); return this; } | ||
setString(key, value) { this.set(key, tagString(value)); return this; } | ||
setIntArray(key, value) { this.set(key, tagIntArray(value)); return this; } | ||
setLongArray(key, value) { this.set(key, tagLongArray(value)); return this; } | ||
} | ||
function tagPrimitive(type, value) { | ||
return { type, value }; | ||
} | ||
NBT.tagPrimitive = tagPrimitive; | ||
function tagCompound(v) { return new TagCompoundImpl(v); } | ||
NBT.tagCompound = tagCompound; | ||
function tagList(elementType, items) { return new TagListImpl(elementType, items); } | ||
NBT.tagList = tagList; | ||
function tagByte(value) { return { type: 1, value }; } | ||
NBT.tagByte = tagByte; | ||
function tagShort(value) { return { type: 2, value }; } | ||
NBT.tagShort = tagShort; | ||
function tagInt(value) { return { type: 3, value }; } | ||
NBT.tagInt = tagInt; | ||
function tagLong(value) { return { type: 4, value }; } | ||
NBT.tagLong = tagLong; | ||
function tagFloat(value) { return { type: 5, value }; } | ||
NBT.tagFloat = tagFloat; | ||
function tagDouble(value) { return { type: 6, value }; } | ||
NBT.tagDouble = tagDouble; | ||
function tagByteArray(value) { return { type: 7, value }; } | ||
NBT.tagByteArray = tagByteArray; | ||
function tagString(value) { return { type: 8, value }; } | ||
NBT.tagString = tagString; | ||
function tagIntArray(value) { return { type: 11, value }; } | ||
NBT.tagIntArray = tagIntArray; | ||
function tagLongArray(value) { return { type: 12, value }; } | ||
NBT.tagLongArray = tagLongArray; | ||
let Persistence; | ||
(function (Persistence) { | ||
class ReadContext { | ||
constructor(scopeType, reg) { | ||
this.scopeType = scopeType; | ||
this.reg = reg; | ||
} | ||
set type(s) { this.scopeType = s; } | ||
get type() { return this.scopeType; } | ||
findSchemaId(schema) { return this.reg[JSON.stringify(schema)]; } | ||
fork(tagType) { | ||
if (tagType < TagType.End || tagType > TagType.LongArray) { | ||
throw new Error(`Illegal Tag Type ${tagType}`); | ||
} | ||
return new ReadContext(tagType, this.reg); | ||
} | ||
} | ||
Persistence.ReadContext = ReadContext; | ||
class WriteContext { | ||
constructor(type, reg) { | ||
this.type = type; | ||
this.reg = reg; | ||
} | ||
findSchema(id) { return this.reg[id]; } | ||
fork(type = {}) { return new WriteContext(type, this.reg); } | ||
} | ||
Persistence.WriteContext = WriteContext; | ||
const handlers = [ | ||
{ read: (buf) => undefined, write(buf, v) { } }, | ||
{ read: (buf) => buf.readByte(), write(buf, v) { buf.writeByte(v ? v : 0); } }, | ||
{ read: (buf) => buf.readShort(), write(buf, v) { buf.writeShort(v ? v : 0); } }, | ||
{ read: (buf) => buf.readInt(), write(buf, v) { buf.writeInt(v ? v : 0); } }, | ||
{ read: (buf) => buf.readInt64(), write(buf, v) { buf.writeInt64(v ? v : 0); } }, | ||
{ read: (buf) => buf.readFloat(), write(buf, v) { buf.writeFloat(v ? v : 0); } }, | ||
{ read: (buf) => buf.readDouble(), write(buf, v) { buf.writeDouble(v ? v : 0); } }, | ||
{ | ||
read(buf) { | ||
const arr = new Array(buf.readInt()); | ||
for (let i = 0; i < arr.length; i++) { | ||
arr[i] = buf.readByte(); | ||
} | ||
return arr; | ||
}, | ||
write(buf, arr = []) { | ||
buf.writeInt(arr.length); | ||
for (let i = 0; i < arr.length; i++) { | ||
buf.writeByte(arr[i]); | ||
} | ||
}, | ||
}, | ||
{ read: (buf) => utils_1.readUTF8(buf), write: (buf, v) => utils_1.writeUTF8(buf, v ? v : "") }, | ||
{ | ||
read(buf, context) { | ||
const listType = buf.readByte(); | ||
const len = buf.readInt(); | ||
const list = new Array(len); | ||
const child = context.fork(listType); | ||
for (let i = 0; i < len; i++) { | ||
const value = handlers[listType].read(buf, child); | ||
list[i] = value; | ||
} | ||
context.type = [child.type]; | ||
return list; | ||
}, | ||
write(buf, value = [], context) { | ||
const type = context.type[0]; | ||
switch (typeof type) { | ||
case "number": // type enum | ||
buf.writeByte(type); | ||
buf.writeInt(value.length); | ||
for (const v of value) { | ||
handlers[type].write(buf, v, context); | ||
} | ||
break; | ||
case "string": // custom registered type | ||
const customScope = context.findSchema(type); | ||
if (!customScope) { | ||
throw new Error(`Unknown custom type [${type}]`); | ||
} | ||
buf.writeByte(customScope instanceof Array ? TagType.List : TagType.Compound); | ||
buf.writeInt(value.length); | ||
value.forEach((v) => handlers[TagType.Compound].write(buf, v, context.fork(customScope))); | ||
break; | ||
case "object": // custom type | ||
buf.writeByte(TagType.Compound); | ||
buf.writeInt(value.length); | ||
value.forEach((v) => handlers[TagType.Compound].write(buf, v, context.fork(type))); | ||
break; | ||
default: | ||
if (value.length !== 0) { | ||
throw new Error(`Unknown list type [${type}].`); | ||
} | ||
buf.writeByte(TagType.End); | ||
buf.writeInt(0); | ||
} | ||
}, | ||
}, | ||
{ | ||
read(buf, context) { | ||
const object = {}; | ||
const scope = {}; | ||
for (let tag = 0; (tag = buf.readByte()) !== TagType.End;) { | ||
const name = utils_1.readUTF8(buf); | ||
const visitor = handlers[tag]; | ||
if (!visitor) { | ||
throw new Error("No such tag id: " + tag); | ||
} | ||
const child = context.fork(tag); | ||
const value = visitor.read(buf, child); | ||
object[name] = value; | ||
scope[name] = child.type; | ||
} | ||
const existedType = context.findSchemaId(scope); | ||
context.type = existedType ? existedType : scope; | ||
return object; | ||
}, | ||
write(buf, object = {}, context) { | ||
for (const [key, value] of Object.entries(object)) { | ||
if (key === "___nbtPrototype___") { | ||
continue; | ||
} | ||
const type = context.type[key]; | ||
let tagType; | ||
let nextScope; | ||
if (typeof type === "number") { // common enum type | ||
tagType = type; | ||
} | ||
else if (type instanceof Array) { // array type | ||
tagType = TagType.List; | ||
nextScope = type; | ||
} | ||
else if (typeof type === "string") { // custom type | ||
tagType = TagType.Compound; | ||
nextScope = context.findSchema(type); | ||
if (!nextScope) { | ||
throw new Error(`Unknown custom type [${type}]`); | ||
} | ||
} | ||
else if (typeof type === "object") { // tagged compund type | ||
tagType = TagType.Compound; | ||
nextScope = type; | ||
} | ||
else { | ||
continue; // just ignore it if it's not on definition | ||
} | ||
const writer = handlers[tagType]; | ||
if (!writer) { | ||
throw new Error("Unknown type " + type); | ||
} | ||
buf.writeByte(tagType); | ||
utils_1.writeUTF8(buf, key); | ||
try { | ||
writer.write(buf, value, context.fork(nextScope)); | ||
} | ||
catch (e) { | ||
if (e instanceof TypeError) { | ||
throw { | ||
type: "IllegalInputType", | ||
message: `Require ${TagType.name(tagType)} but found ${typeof value}`, | ||
}; | ||
} | ||
} | ||
} | ||
buf.writeByte(TagType.End); | ||
}, | ||
}, | ||
{ | ||
read(buf) { | ||
const arr = new Array(buf.readInt()); | ||
for (let i = 0; i < arr.length; i++) { | ||
arr[i] = buf.readInt(); | ||
} | ||
return arr; | ||
}, | ||
write(buf, v = []) { | ||
buf.writeInt(v.length); | ||
for (let i = 0; i < v.length; i++) { | ||
buf.writeInt(v[i]); | ||
} | ||
}, | ||
}, | ||
{ | ||
read(buf) { | ||
const len = buf.readInt(); | ||
const arr = new Array(len); | ||
for (let i = 0; i < len; i++) { | ||
arr[i] = buf.readInt64(); | ||
} | ||
return arr; | ||
}, | ||
write(buf, v = []) { | ||
buf.writeInt(v.length); | ||
for (let i = 0; i < v.length; i++) { | ||
buf.writeInt64(v[i]); | ||
} | ||
}, | ||
}, | ||
]; | ||
/** | ||
* Serialzie an nbt typed json object into NBT binary | ||
* @param object The json | ||
* @param compressed Should we compress it | ||
*/ | ||
async function serialize(object, option = {}) { | ||
const buff = writeRootTag(object, object.__nbtPrototype__, {}, "", Object.assign({}, handlers, option.io)); | ||
if (!option.compressed) { | ||
return buff; | ||
} | ||
return utils_1.zlib.gzip(buff); | ||
} | ||
Persistence.serialize = serialize; | ||
/** | ||
* Deserialize the nbt binary into json | ||
* @param fileData The nbt binary | ||
* @param compressed Should we compress it | ||
*/ | ||
async function deserialize(fileData, option = {}) { | ||
const doUnzip = shouldUnzip(fileData, option.compressed); | ||
const bb = bytebuffer_1.default.wrap(doUnzip ? await utils_1.zlib.unzip(fileData) : fileData); | ||
const { value, type } = readRootTag(bb, undefined, Object.assign({}, handlers, option.io)); | ||
deepFreeze(type); | ||
Object.defineProperty(value, "__nbtPrototype__", { value: type }); | ||
return value; | ||
} | ||
Persistence.deserialize = deserialize; | ||
/** | ||
* Serialzie an nbt typed json object into NBT binary | ||
* @param object The json | ||
* @param compressed Should we compress it | ||
*/ | ||
function serializeSync(object, option = {}) { | ||
const buff = writeRootTag(object, object.__nbtPrototype__, {}, "", Object.assign({}, handlers, option.io)); | ||
if (!option.compressed) { | ||
return buff; | ||
} | ||
return utils_1.zlib.gzipSync(buff); | ||
} | ||
Persistence.serializeSync = serializeSync; | ||
/** | ||
* Deserialize the nbt binary into json | ||
* @param fileData The nbt binary | ||
* @param compressed Should we compress it | ||
*/ | ||
function deserializeSync(fileData, option = {}) { | ||
const doUnzip = shouldUnzip(fileData, option.compressed); | ||
const bb = bytebuffer_1.default.wrap(doUnzip ? utils_1.zlib.unzipSync(fileData) : fileData); | ||
const { value, type } = readRootTag(bb, undefined, Object.assign({}, handlers, option.io)); | ||
deepFreeze(type); | ||
Object.defineProperty(value, "__nbtPrototype__", { value: type }); | ||
return value; | ||
} | ||
Persistence.deserializeSync = deserializeSync; | ||
function shouldUnzip(fileData, compressed) { | ||
let doUnzip; | ||
if (typeof compressed === "undefined") { | ||
const ft = file_type_1.default(fileData); | ||
doUnzip = ft !== undefined && ft.ext === "gz"; | ||
} | ||
else { | ||
doUnzip = compressed; | ||
} | ||
return doUnzip; | ||
} | ||
function readRootTag(buffer, reg = {}, io = handlers) { | ||
const rootType = buffer.readByte(); | ||
if (rootType === TagType.End) { | ||
throw new Error("NBTEnd"); | ||
} | ||
if (rootType !== TagType.Compound) { | ||
throw new Error("Root tag must be a named compound tag. " + rootType); | ||
} | ||
const name = utils_1.readUTF8(buffer); // I think this is the nameProperty of the file... | ||
const context = new ReadContext(TagType.Compound, reg); | ||
const value = io[TagType.Compound].read(buffer, context); | ||
return { type: context.type, value, name }; | ||
} | ||
function writeRootTag(value, type, reg, filename, io) { | ||
const buffer = new bytebuffer_1.default(); | ||
buffer.writeByte(NBT.TagType.Compound); | ||
utils_1.writeUTF8(buffer, filename || ""); | ||
const context = new WriteContext(type, reg); | ||
io[NBT.TagType.Compound].write(buffer, value, context); | ||
return buffer.flip().buffer.slice(0, buffer.limit); | ||
} | ||
function createSerializer() { | ||
return new Serializer(); | ||
} | ||
Persistence.createSerializer = createSerializer; | ||
class Serializer { | ||
constructor() { | ||
this.registry = {}; | ||
this.reversedRegistry = {}; | ||
} | ||
/** | ||
* Register a new type nbt schema to the serializer | ||
* @param type The type name | ||
* @param schema The schema | ||
*/ | ||
register(type, schema) { | ||
if (typeof schema !== "object" || schema === null) { | ||
throw new Error(); | ||
} | ||
this.registry[type] = schema; | ||
this.reversedRegistry[JSON.stringify(schema)] = type; | ||
return this; | ||
} | ||
/** | ||
* Serialize the object into the specific type | ||
* @param object The json object | ||
* @param type The registered nbt type | ||
* @param option The serialize option | ||
*/ | ||
async serialize(object, type, option = {}) { | ||
const schema = this.registry[type]; | ||
if (!schema) { | ||
throw new Error(`Unknown type [${schema}]`); | ||
} | ||
const buff = writeRootTag(object, schema, this.registry, "", Object.assign({}, handlers, option.io)); | ||
if (!option.compressed) { | ||
return buff; | ||
} | ||
return utils_1.zlib.gzip(buff); | ||
} | ||
/** | ||
* Serialize the object into the specific type | ||
* @param object The json object | ||
* @param type The registered nbt type | ||
* @param compressed Should compress this nbt | ||
*/ | ||
serializeSync(object, type, option = {}) { | ||
const schema = this.registry[type]; | ||
if (!schema) { | ||
throw new Error(`Unknown type [${schema}]`); | ||
} | ||
const buff = writeRootTag(object, schema, this.registry, "", Object.assign({}, handlers, option.io)); | ||
if (!option.compressed) { | ||
return buff; | ||
} | ||
return utils_1.zlib.gzipSync(buff); | ||
} | ||
/** | ||
* Deserialize the nbt to json object directly | ||
* @param fileData The nbt data | ||
* @param compressed Does the data compressed | ||
*/ | ||
async deserialize(fileData, option = {}) { | ||
const doUnzip = shouldUnzip(fileData, option.compressed); | ||
let bytebuffer; | ||
if (doUnzip) { | ||
bytebuffer = bytebuffer_1.default.wrap(await utils_1.zlib.unzip(fileData)); | ||
} | ||
else { | ||
bytebuffer = bytebuffer_1.default.wrap(fileData); | ||
} | ||
return readRootTag(bytebuffer, this.reversedRegistry, Object.assign({}, handlers, option.io)); | ||
} | ||
/** | ||
* Deserialize the nbt to json object directly | ||
* @param fileData The nbt data | ||
* @param compressed Does the data compressed | ||
*/ | ||
deserializeSync(fileData, option = {}) { | ||
const doUnzip = shouldUnzip(fileData, option.compressed); | ||
let bytebuffer; | ||
if (doUnzip) { | ||
bytebuffer = bytebuffer_1.default.wrap(utils_1.zlib.unzipSync(fileData)); | ||
} | ||
else { | ||
bytebuffer = bytebuffer_1.default.wrap(fileData); | ||
} | ||
return readRootTag(bytebuffer, this.reversedRegistry, Object.assign({}, handlers, option.io)); | ||
} | ||
} | ||
Persistence.Serializer = Serializer; | ||
})(Persistence = NBT.Persistence || (NBT.Persistence = {})); | ||
})(NBT = exports.NBT || (exports.NBT = {})); | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze | ||
function deepFreeze(obj) { | ||
// Retrieve the property names defined on obj | ||
const propNames = Object.getOwnPropertyNames(obj); | ||
// Freeze properties before freezing self | ||
propNames.forEach((name) => { | ||
const prop = obj[name]; | ||
// Freeze prop if it is an object | ||
if (typeof prop === "object" && prop !== null) { | ||
deepFreeze(prop); | ||
} | ||
}); | ||
// Freeze self (no-op if already frozen) | ||
return Object.freeze(obj); | ||
} | ||
exports.default = NBT; | ||
import { deflate, deflateSync, gunzip, gunzipSync, gzip, gzipSync, inflate, inflateSync } from "zlib"; | ||
import NBT, { setZlib } from "./nbt"; | ||
import { promisify } from "util"; | ||
setZlib({ | ||
gzip: promisify(gzip), | ||
ungzip: promisify(gunzip), | ||
inflate: promisify(inflate), | ||
deflate: promisify(deflate), | ||
gzipSync, | ||
gunzipSync, | ||
inflateSync, | ||
deflateSync, | ||
}); | ||
export { NBT }; | ||
export default NBT; | ||
//# sourceMappingURL=index.js.map |
681
index.ts
@@ -1,671 +0,18 @@ | ||
import ByteBuffer from "bytebuffer"; | ||
import fileType from "file-type"; | ||
import Long from "long"; | ||
import { Optional } from "typescript-optional"; | ||
import { readUTF8, writeUTF8, zlib } from "./utils"; | ||
import { deflate, deflateSync, gunzip, gunzipSync, gzip, gzipSync, inflate, inflateSync } from "zlib"; | ||
import NBT, { setZlib } from "./nbt"; | ||
import { promisify } from "util"; | ||
export namespace NBT { | ||
setZlib({ | ||
gzip: promisify(gzip), | ||
ungzip: promisify(gunzip), | ||
inflate: promisify(inflate), | ||
deflate: promisify(deflate), | ||
gzipSync, | ||
gunzipSync, | ||
inflateSync, | ||
deflateSync, | ||
}); | ||
export type TagType = | ||
TagTypePrimitive | | ||
typeof TagType.List | | ||
typeof TagType.Compound; | ||
export { NBT }; | ||
export type TagTypePrimitive = | ||
typeof TagType.End | | ||
typeof TagType.Byte | | ||
typeof TagType.Short | | ||
typeof TagType.Int | | ||
typeof TagType.Long | | ||
typeof TagType.Float | | ||
typeof TagType.Double | | ||
typeof TagType.ByteArray | | ||
typeof TagType.String | | ||
typeof TagType.IntArray | | ||
typeof TagType.LongArray; | ||
export namespace TagType { | ||
export const End = 0 as const; | ||
export const Byte = 1 as const; | ||
export const Short = 2 as const; | ||
export const Int = 3 as const; | ||
export const Long = 4 as const; | ||
export const Float = 5 as const; | ||
export const Double = 6 as const; | ||
export const ByteArray = 7 as const; | ||
export const String = 8 as const; | ||
export const List = 9 as const; | ||
export const Compound = 10 as const; | ||
export const IntArray = 11 as const; | ||
export const LongArray = 12 as const; | ||
export function name(tagType: TagType) { | ||
return [ | ||
"End", | ||
"Byte", | ||
"Short", | ||
"Int", | ||
"Long", | ||
"Float", | ||
"Double", | ||
"ByteArray", | ||
"String", | ||
"List", | ||
"Compound", | ||
"IntArray", | ||
"LongArray", | ||
][tagType]; | ||
} | ||
} | ||
export interface Tag<T extends TagType, V> { | ||
readonly type: T; | ||
readonly value: V; | ||
} | ||
export type TagEnd = Tag<typeof TagType.End, null>; | ||
export type TagByte = Tag<typeof TagType.Byte, number>; | ||
export type TagShort = Tag<typeof TagType.Short, number>; | ||
export type TagInt = Tag<typeof TagType.Int, number>; | ||
export type TagLong = Tag<typeof TagType.Long, Long>; | ||
export type TagFloat = Tag<typeof TagType.Float, number>; | ||
export type TagDouble = Tag<typeof TagType.Double, number>; | ||
export type TagByteArray = Tag<typeof TagType.ByteArray, Uint8Array>; | ||
export type TagString = Tag<typeof TagType.String, string>; | ||
export type TagIntArray = Tag<typeof TagType.IntArray, Int32Array>; | ||
export type TagLongArray = Tag<typeof TagType.LongArray, Long[]>; | ||
export type Tags = | ||
TagPrimitive | | ||
TagCompound | | ||
TagLists; | ||
export type TagPrimitive = | ||
TagEnd | | ||
TagByte | | ||
TagShort | | ||
TagInt | | ||
TagLong | | ||
TagFloat | | ||
TagDouble | | ||
TagByteArray | | ||
TagString | | ||
TagIntArray | | ||
TagLongArray; | ||
export type TagLists = | ||
TagList<TagByte> | | ||
TagList<TagShort> | | ||
TagList<TagInt> | | ||
TagList<TagLong> | | ||
TagList<TagFloat> | | ||
TagList<TagDouble> | | ||
TagList<TagByteArray> | | ||
TagList<TagString> | | ||
TagList<TagIntArray> | | ||
TagList<TagLongArray> | | ||
TagList<TagCompound> | | ||
TagListList | | ||
TagListTag; | ||
export interface TagListList extends TagList<TagLists> { } | ||
export interface TagListTag extends TagList<Tags> { } | ||
export interface TagList<T extends Tags> extends Tag<typeof TagType.List, void>, Array<T> { | ||
elementType: T["type"]; | ||
} | ||
export interface TagCompound<T = any> extends Tag<typeof TagType.Compound, { [P in keyof T]: Tags }> { | ||
get(key: string): Tags; | ||
set(key: string, tag: Tags): boolean; | ||
has(key: string): boolean; | ||
setByte(key: string, value: number): this; | ||
setShort(key: string, value: number): this; | ||
setInt(key: string, value: number): this; | ||
setLong(key: string, value: Long): this; | ||
setFloat(key: string, value: number): this; | ||
setDouble(key: string, value: number): this; | ||
setByteArray(key: string, value: Uint8Array): this; | ||
setString(key: string, value: string): this; | ||
setIntArray(key: string, value: Int32Array): this; | ||
setLongArray(key: string, value: Long[]): this; | ||
getByte(key: string): Optional<number>; | ||
getShort(key: string): Optional<number>; | ||
getInt(key: string): Optional<number>; | ||
getLong(key: string): Optional<Long>; | ||
getFloat(key: string): Optional<number>; | ||
getDouble(key: string): Optional<number>; | ||
getByteArray(key: string): Optional<Uint8Array>; | ||
getString(key: string): Optional<string>; | ||
getIntArray(key: string): Optional<Int32Array>; | ||
getLongArray(key: string): Optional<Long[]>; | ||
} | ||
function badTag(tag: any): boolean { | ||
if (typeof tag.type !== "number") { return true; } | ||
if (!Number.isInteger(tag.type) || tag.type > 12 || tag.type < 0) { return true; } | ||
const tagType = tag.type; | ||
const value = tag.value; | ||
switch (tagType) { | ||
case TagType.Byte: | ||
return typeof value !== "number" || !Number.isInteger(value) || value < -0x80 || value > 0x7F; | ||
case TagType.Short: | ||
return typeof value !== "number" || !Number.isInteger(value) || value < -0x8000 || value > 0x7FFF; | ||
case TagType.Int: | ||
return typeof value !== "number" || !Number.isInteger(value) || value < -0x80000000 || value > 0x7FFFFFFF; | ||
case TagType.Long: | ||
return typeof value !== "object" || !(value instanceof Long) || value.unsigned; | ||
case TagType.Float: | ||
case TagType.Double: | ||
return typeof value !== "number"; | ||
case TagType.ByteArray: | ||
return typeof value !== "object" || !(value instanceof Uint8Array); | ||
case TagType.String: | ||
return typeof value !== "string"; | ||
case TagType.IntArray: | ||
return typeof value !== "object" || !(value instanceof Int32Array); | ||
case TagType.LongArray: | ||
return typeof value !== "object" || !(value instanceof Array); | ||
} | ||
return true; | ||
} | ||
function badElementTag(v: any, etype: TagType) { | ||
if (badTag(v)) { return true; } | ||
if (v.type !== etype) { return true; } | ||
return false; | ||
} | ||
class TagListImpl<T extends Tags> extends Array<T> implements TagList<T> { | ||
type: 9 = 9; | ||
value: void; | ||
constructor(readonly elementType: T["type"], items: T[]) { | ||
super(...items); | ||
return new Proxy(this, { | ||
set(target, k, v) { | ||
if (badElementTag(v, target.elementType)) { return false; } | ||
return Reflect.set(target, k, v); | ||
}, | ||
}); | ||
} | ||
push(...items: T[]) { | ||
if (items.some((v) => badElementTag(v, this.elementType))) { return this.length; } | ||
return super.push(...items); | ||
} | ||
unshift(...items: T[]) { | ||
if (items.some((v) => badElementTag(v, this.elementType))) { return this.length; } | ||
return super.unshift(...items); | ||
} | ||
} | ||
class TagCompoundImpl<T extends { [key: string]: Tags; }> implements TagCompound<T> { | ||
type: 10 = 10; | ||
readonly value: T; | ||
constructor(value: T) { | ||
this.value = new Proxy(value, { | ||
set(target, k, v) { | ||
if (badTag(v)) { return false; } | ||
Reflect.set(target, k, v); | ||
return true; | ||
}, | ||
}); | ||
} | ||
getByte(key: string): Optional<number> { | ||
return Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Byte) | ||
.map((v) => v.value as number); | ||
} | ||
getShort(key: string): Optional<number> { | ||
return Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Short) | ||
.map((v) => v.value as number); | ||
} | ||
getInt(key: string): Optional<number> { | ||
return Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Int) | ||
.map((v) => v.value as number); | ||
} | ||
getLong(key: string): Optional<Long> { | ||
return Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Long) | ||
.map((v) => v.value as Long); | ||
} | ||
getFloat(key: string): Optional<number> { | ||
return Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Float) | ||
.map((v) => v.value as number); | ||
} | ||
getDouble(key: string): Optional<number> { | ||
return Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.Double) | ||
.map((v) => v.value as number); | ||
} | ||
getByteArray(key: string): Optional<Uint8Array> { | ||
return Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.ByteArray) | ||
.map((v) => v.value as Uint8Array); | ||
} | ||
getString(key: string): Optional<string> { | ||
return Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.String) | ||
.map((v) => v.value as string); | ||
} | ||
getIntArray(key: string): Optional<Int32Array> { | ||
return Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.IntArray) | ||
.map((v) => v.value as Int32Array); | ||
} | ||
getLongArray(key: string): Optional<Long[]> { | ||
return Optional.ofNullable(this.value[key]) | ||
.filter((v) => v.type === TagType.LongArray) | ||
.map((v) => v.value as Long[]); | ||
} | ||
get(key: string): Tags { return Reflect.get(this.value, key); } | ||
set(key: string, tag: Tags): boolean { return Reflect.set(this.value, key, tag); } | ||
has(key: string): boolean { return Reflect.has(this.value, key); } | ||
setByte(key: string, value: number): this { this.set(key, tagByte(value)); return this; } | ||
setShort(key: string, value: number): this { this.set(key, tagShort(value)); return this; } | ||
setInt(key: string, value: number): this { this.set(key, tagInt(value)); return this; } | ||
setLong(key: string, value: Long): this { this.set(key, tagLong(value)); return this; } | ||
setFloat(key: string, value: number): this { this.set(key, tagFloat(value)); return this; } | ||
setDouble(key: string, value: number): this { this.set(key, tagDouble(value)); return this; } | ||
setByteArray(key: string, value: Uint8Array): this { this.set(key, tagByteArray(value)); return this; } | ||
setString(key: string, value: string): this { this.set(key, tagString(value)); return this; } | ||
setIntArray(key: string, value: Int32Array): this { this.set(key, tagIntArray(value)); return this; } | ||
setLongArray(key: string, value: Long[]): this { this.set(key, tagLongArray(value)); return this; } | ||
} | ||
export function tagPrimitive(type: TagTypePrimitive, value: any): TagPrimitive { | ||
return { type, value } as any; | ||
} | ||
export function tagCompound<T extends { [key: string]: Tags; }>(v: T): TagCompound<T> { return new TagCompoundImpl<T>(v); } | ||
export function tagList<T extends Tags>(elementType: T["type"], items: T[]): TagList<T> { return new TagListImpl<T>(elementType, items); } | ||
export function tagByte(value: number): TagByte { return { type: 1, value }; } | ||
export function tagShort(value: number): TagShort { return { type: 2, value }; } | ||
export function tagInt(value: number): TagInt { return { type: 3, value }; } | ||
export function tagLong(value: Long): TagLong { return { type: 4, value }; } | ||
export function tagFloat(value: number): TagFloat { return { type: 5, value }; } | ||
export function tagDouble(value: number): TagDouble { return { type: 6, value }; } | ||
export function tagByteArray(value: Uint8Array): TagByteArray { return { type: 7, value }; } | ||
export function tagString(value: string): TagString { return { type: 8, value }; } | ||
export function tagIntArray(value: Int32Array): TagIntArray { return { type: 11, value }; } | ||
export function tagLongArray(value: Long[]): TagLongArray { return { type: 12, value }; } | ||
export namespace Persistence { | ||
interface TypedObject { | ||
readonly __nbtPrototype__: CompoundSchema; | ||
[key: string]: any; | ||
} | ||
type Schema = ListSchema | CompoundSchema; | ||
interface CompoundSchema { [key: string]: TagType | string | Schema; } | ||
interface ListSchema extends Array<TagType | string | Schema> { } | ||
type TypeIdentity = Schema | string | TagType; | ||
export class ReadContext { | ||
constructor(private scopeType: TypeIdentity, private reg: { [name: string]: string }) { } | ||
set type(s: TypeIdentity) { this.scopeType = s; } | ||
get type(): TypeIdentity { return this.scopeType; } | ||
findSchemaId(schema: Schema): string | undefined { return this.reg[JSON.stringify(schema)]; } | ||
fork(tagType: number): ReadContext { | ||
if (tagType < TagType.End || tagType > TagType.LongArray) { throw new Error(`Illegal Tag Type ${tagType}`); } | ||
return new ReadContext(tagType as TagType, this.reg); | ||
} | ||
} | ||
export class WriteContext { | ||
constructor(readonly type: Schema, private reg: { [id: string]: Schema }) { } | ||
findSchema(id: string): Schema | undefined { return this.reg[id]; } | ||
fork(type: Schema = {}): WriteContext { return new WriteContext(type, this.reg); } | ||
} | ||
export interface IO { | ||
read(buf: ByteBuffer, context: ReadContext): any; | ||
write(buf: ByteBuffer, value: any, context: WriteContext): void; | ||
// readTag?(buf: ByteBuffer): Tags; | ||
// writeTag?(buf: ByteBuffer, tags: Tags): void; | ||
} | ||
const handlers: IO[] = [ | ||
{ read: (buf) => undefined, write(buf, v) { } }, // end | ||
{ read: (buf) => buf.readByte(), write(buf, v) { buf.writeByte(v ? v : 0); } }, // byte | ||
{ read: (buf) => buf.readShort(), write(buf, v) { buf.writeShort(v ? v : 0); } }, // short | ||
{ read: (buf) => buf.readInt(), write(buf, v) { buf.writeInt(v ? v : 0); } }, // int | ||
{ read: (buf) => buf.readInt64(), write(buf, v) { buf.writeInt64(v ? v : 0); } }, // long | ||
{ read: (buf) => buf.readFloat(), write(buf, v) { buf.writeFloat(v ? v : 0); } }, // float | ||
{ read: (buf) => buf.readDouble(), write(buf, v) { buf.writeDouble(v ? v : 0); } }, // double | ||
{ // byte array | ||
read(buf) { | ||
const arr = new Array(buf.readInt()); | ||
for (let i = 0; i < arr.length; i++) { arr[i] = buf.readByte(); } | ||
return arr; | ||
}, | ||
write(buf, arr = []) { | ||
buf.writeInt(arr.length); | ||
for (let i = 0; i < arr.length; i++) { buf.writeByte(arr[i]); } | ||
}, | ||
}, | ||
{ read: (buf) => readUTF8(buf), write: (buf, v) => writeUTF8(buf, v ? v : "") }, // string | ||
{ // list | ||
read(buf, context) { | ||
const listType = buf.readByte(); | ||
const len = buf.readInt(); | ||
const list = new Array(len); | ||
const child = context.fork(listType); | ||
for (let i = 0; i < len; i++) { | ||
const value = handlers[listType].read(buf, child); | ||
list[i] = value; | ||
} | ||
context.type = [child.type]; | ||
return list; | ||
}, | ||
write(buf, value: any[] = [], context) { | ||
const type = (context.type as ListSchema)[0]; | ||
switch (typeof type) { | ||
case "number": // type enum | ||
buf.writeByte(type); | ||
buf.writeInt(value.length); | ||
for (const v of value) { handlers[type as number].write(buf, v, context); } | ||
break; | ||
case "string": // custom registered type | ||
const customScope = context.findSchema(type); | ||
if (!customScope) { throw new Error(`Unknown custom type [${type}]`); } | ||
buf.writeByte(customScope instanceof Array ? TagType.List : TagType.Compound); | ||
buf.writeInt(value.length); | ||
value.forEach((v) => handlers[TagType.Compound].write(buf, v, context.fork(customScope))); | ||
break; | ||
case "object": // custom type | ||
buf.writeByte(TagType.Compound); | ||
buf.writeInt(value.length); | ||
value.forEach((v) => handlers[TagType.Compound].write(buf, v, context.fork(type))); | ||
break; | ||
default: | ||
if (value.length !== 0) { throw new Error(`Unknown list type [${type}].`); } | ||
buf.writeByte(TagType.End); | ||
buf.writeInt(0); | ||
} | ||
}, | ||
}, | ||
{// tag compound | ||
read(buf, context) { | ||
const object: any = {}; | ||
const scope: CompoundSchema = {}; | ||
for (let tag = 0; (tag = buf.readByte()) !== TagType.End;) { | ||
const name = readUTF8(buf); | ||
const visitor = handlers[tag]; | ||
if (!visitor) { throw new Error("No such tag id: " + tag); } | ||
const child = context.fork(tag); | ||
const value = visitor.read(buf, child); | ||
object[name] = value; | ||
scope[name] = child.type; | ||
} | ||
const existedType = context.findSchemaId(scope); | ||
context.type = existedType ? existedType : scope; | ||
return object; | ||
}, | ||
write(buf, object = {}, context) { | ||
for (const [key, value] of Object.entries(object)) { | ||
if (key === "___nbtPrototype___") { continue; } | ||
const type = (context.type as CompoundSchema)[key]; | ||
let tagType: TagType; | ||
let nextScope: Schema | undefined; | ||
if (typeof type === "number") { // common enum type | ||
tagType = type; | ||
} else if (type instanceof Array) { // array type | ||
tagType = TagType.List; | ||
nextScope = type; | ||
} else if (typeof type === "string") { // custom type | ||
tagType = TagType.Compound; | ||
nextScope = context.findSchema(type); | ||
if (!nextScope) { throw new Error(`Unknown custom type [${type}]`); } | ||
} else if (typeof type === "object") { // tagged compund type | ||
tagType = TagType.Compound; | ||
nextScope = type; | ||
} else { | ||
continue; // just ignore it if it's not on definition | ||
} | ||
const writer = handlers[tagType]; | ||
if (!writer) { throw new Error("Unknown type " + type); } | ||
buf.writeByte(tagType); | ||
writeUTF8(buf, key); | ||
try { | ||
writer.write(buf, value, context.fork(nextScope)); | ||
} catch (e) { | ||
if (e instanceof TypeError) { | ||
throw { | ||
type: "IllegalInputType", | ||
message: `Require ${TagType.name(tagType)} but found ${typeof value}`, | ||
}; | ||
} | ||
} | ||
} | ||
buf.writeByte(TagType.End); | ||
}, | ||
}, | ||
{ // int array | ||
read(buf) { | ||
const arr = new Array(buf.readInt()); | ||
for (let i = 0; i < arr.length; i++) { arr[i] = buf.readInt(); } | ||
return arr; | ||
}, | ||
write(buf, v = []) { | ||
buf.writeInt(v.length); | ||
for (let i = 0; i < v.length; i++) { buf.writeInt(v[i]); } | ||
}, | ||
}, | ||
{ // long array | ||
read(buf) { | ||
const len = buf.readInt(); | ||
const arr: Long[] = new Array(len); | ||
for (let i = 0; i < len; i++) { arr[i] = buf.readInt64(); } | ||
return arr; | ||
}, | ||
write(buf, v = []) { | ||
buf.writeInt(v.length); | ||
for (let i = 0; i < v.length; i++) { buf.writeInt64(v[i]); } | ||
}, | ||
}, | ||
]; | ||
export interface SerializationOption { | ||
compressed?: boolean; | ||
/** | ||
* IO override for serialization | ||
*/ | ||
io?: { [tagType: number]: IO }; | ||
} | ||
/** | ||
* Serialzie an nbt typed json object into NBT binary | ||
* @param object The json | ||
* @param compressed Should we compress it | ||
*/ | ||
export async function serialize(object: TypedObject, option: SerializationOption = {}): Promise<Buffer> { | ||
const buff = writeRootTag(object, object.__nbtPrototype__, {}, "", Object.assign({}, handlers, option.io)); | ||
if (!option.compressed) { | ||
return buff; | ||
} | ||
return zlib.gzip(buff) as any; | ||
} | ||
/** | ||
* Deserialize the nbt binary into json | ||
* @param fileData The nbt binary | ||
* @param compressed Should we compress it | ||
*/ | ||
export async function deserialize(fileData: Buffer, option: SerializationOption = {}): Promise<TypedObject> { | ||
const doUnzip: boolean = shouldUnzip(fileData, option.compressed); | ||
const bb = ByteBuffer.wrap(doUnzip ? await zlib.unzip(fileData) : fileData); | ||
const { value, type } = readRootTag(bb, undefined, Object.assign({}, handlers, option.io)); | ||
deepFreeze(type); | ||
Object.defineProperty(value, "__nbtPrototype__", { value: type }); | ||
return value; | ||
} | ||
/** | ||
* Serialzie an nbt typed json object into NBT binary | ||
* @param object The json | ||
* @param compressed Should we compress it | ||
*/ | ||
export function serializeSync(object: TypedObject, option: SerializationOption = {}): Buffer { | ||
const buff = writeRootTag(object, object.__nbtPrototype__, {}, "", Object.assign({}, handlers, option.io)); | ||
if (!option.compressed) { | ||
return buff; | ||
} | ||
return zlib.gzipSync(buff); | ||
} | ||
/** | ||
* Deserialize the nbt binary into json | ||
* @param fileData The nbt binary | ||
* @param compressed Should we compress it | ||
*/ | ||
export function deserializeSync(fileData: Buffer, option: SerializationOption = {}): TypedObject { | ||
const doUnzip: boolean = shouldUnzip(fileData, option.compressed); | ||
const bb = ByteBuffer.wrap(doUnzip ? zlib.unzipSync(fileData) : fileData); | ||
const { value, type } = readRootTag(bb, undefined, Object.assign({}, handlers, option.io)); | ||
deepFreeze(type); | ||
Object.defineProperty(value, "__nbtPrototype__", { value: type }); | ||
return value; | ||
} | ||
function shouldUnzip(fileData: Buffer, compressed?: boolean) { | ||
let doUnzip: boolean; | ||
if (typeof compressed === "undefined") { | ||
const ft = fileType(fileData); | ||
doUnzip = ft !== undefined && ft.ext === "gz"; | ||
} else { | ||
doUnzip = compressed; | ||
} | ||
return doUnzip; | ||
} | ||
function readRootTag(buffer: ByteBuffer, reg: { [id: string]: string } = {}, io: IO[] = handlers) { | ||
const rootType = buffer.readByte(); | ||
if (rootType === TagType.End) { throw new Error("NBTEnd"); } | ||
if (rootType !== TagType.Compound) { throw new Error("Root tag must be a named compound tag. " + rootType); } | ||
const name = readUTF8(buffer); // I think this is the nameProperty of the file... | ||
const context = new ReadContext(TagType.Compound, reg); | ||
const value = io[TagType.Compound].read(buffer, context); | ||
return { type: context.type, value, name }; | ||
} | ||
function writeRootTag(value: any, type: Schema, reg: { [id: string]: Schema }, filename: string, io: IO[]): Buffer { | ||
const buffer = new ByteBuffer(); | ||
buffer.writeByte(NBT.TagType.Compound); | ||
writeUTF8(buffer, filename || ""); | ||
const context = new WriteContext(type, reg); | ||
io[NBT.TagType.Compound].write(buffer, value, context); | ||
return buffer.flip().buffer.slice(0, buffer.limit); | ||
} | ||
export function createSerializer() { | ||
return new Serializer(); | ||
} | ||
export class Serializer { | ||
private registry: { [id: string]: CompoundSchema } = {}; | ||
private reversedRegistry: { [shape: string]: string } = {}; | ||
/** | ||
* Register a new type nbt schema to the serializer | ||
* @param type The type name | ||
* @param schema The schema | ||
*/ | ||
register(type: string, schema: CompoundSchema): this { | ||
if (typeof schema !== "object" || schema === null) { throw new Error(); } | ||
this.registry[type] = schema; | ||
this.reversedRegistry[JSON.stringify(schema)] = type; | ||
return this; | ||
} | ||
/** | ||
* Serialize the object into the specific type | ||
* @param object The json object | ||
* @param type The registered nbt type | ||
* @param option The serialize option | ||
*/ | ||
async serialize(object: object, type: string, option: SerializationOption = {}) { | ||
const schema = this.registry[type]; | ||
if (!schema) { throw new Error(`Unknown type [${schema}]`); } | ||
const buff = writeRootTag(object, schema, this.registry, "", Object.assign({}, handlers, option.io)); | ||
if (!option.compressed) { | ||
return buff; | ||
} | ||
return zlib.gzip(buff) as Promise<Buffer>; | ||
} | ||
/** | ||
* Serialize the object into the specific type | ||
* @param object The json object | ||
* @param type The registered nbt type | ||
* @param compressed Should compress this nbt | ||
*/ | ||
serializeSync(object: object, type: string, option: SerializationOption = {}) { | ||
const schema = this.registry[type]; | ||
if (!schema) { throw new Error(`Unknown type [${schema}]`); } | ||
const buff = writeRootTag(object, schema, this.registry, "", Object.assign({}, handlers, option.io)); | ||
if (!option.compressed) { | ||
return buff; | ||
} | ||
return zlib.gzipSync(buff); | ||
} | ||
/** | ||
* Deserialize the nbt to json object directly | ||
* @param fileData The nbt data | ||
* @param compressed Does the data compressed | ||
*/ | ||
async deserialize(fileData: Buffer, option: SerializationOption = {}): Promise<{ value: any, type: any | string }> { | ||
const doUnzip: boolean = shouldUnzip(fileData, option.compressed); | ||
let bytebuffer: ByteBuffer; | ||
if (doUnzip) { | ||
bytebuffer = ByteBuffer.wrap(await zlib.unzip(fileData)); | ||
} else { | ||
bytebuffer = ByteBuffer.wrap(fileData); | ||
} | ||
return readRootTag(bytebuffer, this.reversedRegistry, Object.assign({}, handlers, option.io)); | ||
} | ||
/** | ||
* Deserialize the nbt to json object directly | ||
* @param fileData The nbt data | ||
* @param compressed Does the data compressed | ||
*/ | ||
deserializeSync(fileData: Buffer, option: SerializationOption = {}): { value: any, type: any | string } { | ||
const doUnzip: boolean = shouldUnzip(fileData, option.compressed); | ||
let bytebuffer: ByteBuffer; | ||
if (doUnzip) { | ||
bytebuffer = ByteBuffer.wrap(zlib.unzipSync(fileData)); | ||
} else { | ||
bytebuffer = ByteBuffer.wrap(fileData); | ||
} | ||
return readRootTag(bytebuffer, this.reversedRegistry, Object.assign({}, handlers, option.io)); | ||
} | ||
} | ||
} | ||
} | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze | ||
function deepFreeze(obj: any) { | ||
// Retrieve the property names defined on obj | ||
const propNames = Object.getOwnPropertyNames(obj); | ||
// Freeze properties before freezing self | ||
propNames.forEach((name) => { | ||
const prop = obj[name]; | ||
// Freeze prop if it is an object | ||
if (typeof prop === "object" && prop !== null) { | ||
deepFreeze(prop); | ||
} | ||
}); | ||
// Freeze self (no-op if already frozen) | ||
return Object.freeze(obj); | ||
} | ||
export default NBT; |
{ | ||
"name": "@xmcl/nbt", | ||
"version": "0.1.1", | ||
"version": "1.0.0", | ||
"main": "./index.js", | ||
"module": "./index.module.js", | ||
"description": "NBT serialization and deserialization", | ||
@@ -42,3 +43,3 @@ "engines": { | ||
"homepage": "https://github.com/Voxelum/minecraft-launcher-core-node#readme", | ||
"gitHead": "b1b29753ab0261fb9e6ca9058df3c9a6868b27b5" | ||
"gitHead": "7babb4628dd072c266a30697d9104aef38215403" | ||
} |
@@ -18,10 +18,12 @@ # Nbt Module | ||
const fileData: Buffer; | ||
const compressed: boolean; | ||
const readed: NBT.Persistence.TypedObject = await NBT.Persistence.deserialize(fileData, { compressed }); | ||
// compressed = undefined will not perform compress algorithm | ||
// compressed = true will use gzip algorithm | ||
const compressed: true | "gzip" | "deflate" | undefined; | ||
const readed: NBT.TypedObject = await NBT.deserialize(fileData, { compressed }); | ||
// NBT.Persistence.TypedObject is just a object with __nbtPrototype__ defining its nbt type | ||
// After you do the modification on it, you can serialize it back to NBT | ||
const buf: Buffer = await NBT.Persistence.serialize(readed, { compressed }); | ||
const buf: Buffer = await NBT.serialize(readed, { compressed }); | ||
// or use serializer style | ||
const serial = NBT.Persistence.createSerializer() | ||
const serial = NBT.createSerializer() | ||
.register("server", { | ||
@@ -28,0 +30,0 @@ name: NBT.TagType.String, |
{ | ||
"files": [ | ||
"index.ts", | ||
"index.module.ts", | ||
"nbt.ts", | ||
"utils.ts" | ||
@@ -5,0 +7,0 @@ ], |
@@ -1,13 +0,3 @@ | ||
/// <reference types="node" /> | ||
/// <reference types="bytebuffer" /> | ||
export declare type ZippingData = Uint8Array | number[] | string | Buffer; | ||
export declare let zlib: ZLib<any>; | ||
export declare function setZlib<BUF>(lib: ZLib<BUF>): void; | ||
export interface ZLib<BUF> { | ||
gzip(buffer: BUF): Promise<BUF>; | ||
gzipSync(buffer: BUF): BUF; | ||
unzip(buffer: BUF): Promise<BUF>; | ||
unzipSync(buffer: BUF): BUF; | ||
} | ||
export declare function writeUTF8(out: ByteBuffer, str: string): number; | ||
export declare function readUTF8(buff: ByteBuffer): string; |
47
utils.js
@@ -1,43 +0,2 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function setZlib(lib) { exports.zlib = lib; } | ||
exports.setZlib = setZlib; | ||
try { | ||
// tslint:disable-next-line: no-var-requires | ||
const lib = require("zlib"); | ||
setZlib({ | ||
gzip(buf) { | ||
return new Promise((resolve, reject) => { | ||
lib.gzip(buf, (e, r) => { | ||
if (e) { | ||
reject(e); | ||
} | ||
else { | ||
resolve(r); | ||
} | ||
}); | ||
}); | ||
}, | ||
unzip(buff) { | ||
return new Promise((resolve, reject) => { | ||
lib.gunzip(buff, (err, r) => { | ||
if (err) { | ||
reject(err); | ||
} | ||
else { | ||
resolve(r); | ||
} | ||
}); | ||
}); | ||
}, | ||
gzipSync(buff) { return lib.gzipSync(buff); }, | ||
unzipSync(buff) { return lib.unzipSync(buff); }, | ||
}); | ||
} | ||
catch (e) { | ||
console.error(e); | ||
// tslint:disable-next-line: no-var-requires | ||
require("pako"); | ||
} | ||
function writeUTF8(out, str) { | ||
export function writeUTF8(out, str) { | ||
const strlen = str.length; | ||
@@ -93,4 +52,3 @@ let utflen = 0; | ||
} | ||
exports.writeUTF8 = writeUTF8; | ||
function readUTF8(buff) { | ||
export function readUTF8(buff) { | ||
const utflen = buff.readUint16(); | ||
@@ -165,3 +123,2 @@ const bytearr = new Array(utflen); | ||
} | ||
exports.readUTF8 = readUTF8; | ||
//# sourceMappingURL=utils.js.map |
41
utils.ts
@@ -1,42 +0,1 @@ | ||
export type ZippingData = Uint8Array | number[] | string | Buffer; | ||
export let zlib: ZLib<any>; | ||
export function setZlib<BUF>(lib: ZLib<BUF>) { zlib = lib; } | ||
export interface ZLib<BUF> { | ||
gzip(buffer: BUF): Promise<BUF>; | ||
gzipSync(buffer: BUF): BUF; | ||
unzip(buffer: BUF): Promise<BUF>; | ||
unzipSync(buffer: BUF): BUF; | ||
} | ||
try { | ||
// tslint:disable-next-line: no-var-requires | ||
const lib: typeof import("zlib") = require("zlib"); | ||
setZlib({ | ||
gzip(buf) { | ||
return new Promise((resolve, reject) => { | ||
lib.gzip(buf, (e, r) => { | ||
if (e) { reject(e); } else { resolve(r); } | ||
}); | ||
}); | ||
}, | ||
unzip(buff) { | ||
return new Promise((resolve, reject) => { | ||
lib.gunzip(buff, (err, r) => { | ||
if (err) { reject(err); } else { resolve(r); } | ||
}); | ||
}); | ||
}, | ||
gzipSync(buff) { return lib.gzipSync(buff); }, | ||
unzipSync(buff) { return lib.unzipSync(buff); }, | ||
} as ZLib<Buffer>); | ||
} catch (e) { | ||
console.error(e); | ||
// tslint:disable-next-line: no-var-requires | ||
require("pako"); | ||
} | ||
export function writeUTF8(out: ByteBuffer, str: string) { | ||
@@ -43,0 +2,0 @@ const strlen = str.length; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
21
0
37
79092
1342
1