/**
 * The nbt module provides nbt  serialize and  deserialize functions.
 *
 *  packageDocumentation module xmcl import ByteBuffer from "bytebuffer" ; import fileType from "file-type" ; import Long from "long" ; import { readUTF8 , writeUTF8 } from "./utils" ; import { ungzip , inflate , gunzipSync , gzip , gzipSync , inflateSync , deflateSync , deflate } from "./zlib" ; type Constructor < T > = new ( ... args ) => T ; export const NBTPrototype = Symbol ( "NBTPrototype" ) ; export const NBTConstructor = Symbol ( "NBTConstructor" ) ; export type TagType = TagTypePrimitive | typeof TagType . List | typeof TagType . Compound ; 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 ; /**
 * Annotate the type of a field
 */ export function TagType < T > ( type ) { return ( targetClass , key ) => { let nbtPrototype = getPrototypeOf ( targetClass . constructor ) ; nbtPrototype [ key ] = type ; } ; } /**
 * Construct the object from schema or constructor.
 *
 * - If pass-in value type is a schema object, it create a prototype with constructor constructing the object with this schema as NBTPrototype.
 * - If pass-in value is a constructor, it will try to take the NBTPrototype on the constructor to create object.
 */ function constructObject ( valueType ) { if ( ! valueType ) { return { } ; } const prot = typeof valueType === "object" ? createPrototypeFrom ( valueType ) : getPrototypeOf ( valueType ) ; return prot [ NBTConstructor ] ( ) ; } function createPrototypeFrom ( schema , constructor ? ) { const proto = { ... schema , } ; Object . defineProperty ( proto , NBTConstructor , { value : ( ) => { let object ; if ( constructor ) { try { object = new constructor ( ) ; } catch { object = { } ; Object . setPrototypeOf ( object , constructor . prototype ) ; } } else { object = { } ; } Object . defineProperty ( object , NBTPrototype , { value : proto } ) ; return object ; } , } ) ; return proto ; } param object export function getPrototypeOf ( object ) { const targetObject = typeof object === "function" ? object . prototype : object ; let nbtPrototype ; if ( targetObject . hasOwnProperty ( NBTPrototype ) ) { nbtPrototype = targetObject [ NBTPrototype ] ; } else { nbtPrototype = createPrototypeFrom ( { } , typeof object === "function" ? object as Constructor < any > : undefined ) ; } const parentClass = Object . getPrototypeOf ( targetObject ) ; if ( parentClass && parentClass !== Object . getPrototypeOf ( { } ) ) { const parentNBTPrototype = getPrototypeOf ( parentClass ) ; const existedParentNBTPrototype = Object . getPrototypeOf ( nbtPrototype ) ; if ( existedParentNBTPrototype !== parentNBTPrototype ) { Object . setPrototypeOf ( nbtPrototype , parentNBTPrototype ) ; } } return new Proxy ( nbtPrototype , { set ( target , key , value ) { if ( ! object . hasOwnProperty ( NBTPrototype ) ) { setPrototypeOf ( object , target ) ; } if ( typeof key === "string" ) { if ( typeof value === "number" && ! isTagType ( value ) ) { return false ; } target [ key ] = value ; return true ; } return false ; } , } ) ; } param object param nbtPrototype export function setPrototypeOf ( object , nbtPrototype ) { const target = typeof object === "function" ? object . prototype : object ; Object . defineProperty ( target , NBTPrototype , { value : nbtPrototype } ) ; } function isTagType ( n ) { return n >= 0 && n <= 12 ; } 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 getName ( tagType ) { return [ "End" , "Byte" , "Short" , "Int" , "Long" , "Float" , "Double" , "ByteArray" , "String" , "List" , "Compound" , "IntArray" , "LongArray" , ] [ tagType ] ; } } export type Schema = ListSchema | CompoundSchema | Constructor < any > ; export type ListSchema = [ TagType | Schema ] ; export type CompoundSchema = { [ key ] : TagType | Schema ; } ; export interface NBTPrototype extends CompoundSchema { [ NBTConstructor ] : ( ) => any ; } export interface IO { read ( buf , context ) : any ; write ( buf , value , context ) : void ; } const IO = [ { read : ( buf ) => undefined , write ( buf , v ) { } } , { read : ( buf ) => buf . readByte ( ) , write ( buf , v = 0 ) { buf . writeByte ( v ) ; } } , { read : ( buf ) => buf . readShort ( ) , write ( buf , v = 0 ) { buf . writeShort ( v ) ; } } , { read : ( buf ) => buf . readInt ( ) , write ( buf , v = 0 ) { buf . writeInt ( v ) ; } } , { read : ( buf ) => buf . readInt64 ( ) , write ( buf , v = 0 ) { buf . writeInt64 ( v ) ; } } , { read : ( buf ) => buf . readFloat ( ) , write ( buf , v = 0 ) { buf . writeFloat ( v ) ; } } , { read : ( buf ) => buf . readDouble ( ) , write ( buf , v = 0 ) { buf . writeDouble ( v ) ; } } , { 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 ) } , { read ( buf , context ) { const listType = buf . readByte ( ) ; assertTag ( listType ) ; const len = buf . readInt ( ) ; const list = new Array ( len ) ; if ( context . schema ) { assertListSchema ( context . schema ) ; } if ( ! ! context . schema && typeof context . schema === "number" && listType !== context . schema ) { return list ; } const childContext = context . fork ( context . schema ? context . schema [ 0 ] : listType ) ; const shouldInspectChildType = ! childContext . schema ; for ( let i = 0 ; i < len ; i ++ ) { list [ i ] = IO [ listType ] . read ( buf , childContext ) ; } if ( shouldInspectChildType ) { context . inspect = childContext . inspect || [ childContext . tagType ] ; } return list ; } , write ( buf , value = [ ] , context ) { assertListSchema ( context . schema ) ; const valueType = context . schema [ 0 ] ; const childContext = context . fork ( valueType ) ; const tagType = childContext . tagType ; const writer = IO [ tagType ] ; assertTag ( tagType ) ; if ( ( tagType === TagType . Compound || tagType === TagType . List ) && ! childContext . schema ) { return ; } try { buf . writeByte ( tagType ) ; buf . writeInt ( value . length ) ; for ( const v of value ) { writer . write ( buf , v , childContext ) ; } } catch ( e ) { if ( e instanceof TypeError ) { throw new TypeError ( `Require ${ TagType . getName ( tagType ) } but found ${ typeof value }` ) ; } } } , } , { read ( buf , context ) { assertCompoundSchema ( context . schema ) ; const object = constructObject ( context . schema ) ; const nbtPrototype = getPrototypeOf ( object ) ; const knowingType = ! ! context . schema ; for ( let tag = 0 ; ( tag = buf . readByte ( ) ) !== TagType . End ; ) { const reader = IO [ tag ] ; const key = readUTF8 ( buf ) ; assertTag ( tag ) ; let childContext ; if ( knowingType ) { const valueType = nbtPrototype [ key ] ; if ( typeof valueType === "undefined" || ( typeof valueType === "number" && valueType !== tag ) ) { continue ; } childContext = context . fork ( valueType ) ; } else { childContext = context . fork ( tag ) ; } const shouldInspectChildType = ! childContext . schema ; object [ key ] = reader . read ( buf , childContext ) ; if ( shouldInspectChildType ) { nbtPrototype [ key ] = childContext . inspect || childContext . tagType ; } } if ( ! knowingType ) { context . inspect = nbtPrototype ; } return object ; } , write ( buf , object = { } , context ) { assertCompoundSchema ( context . schema ) ; const schema = context . schema ? context . schema instanceof Function ? getPrototypeOf ( context . schema ) : context . schema : getPrototypeOf ( object ) as CompoundSchema ; for ( const [ key , value ] of Object . entries ( object ) ) { const valueType = schema [ key ] ; if ( typeof valueType === "undefined" ) { continue ; } const childContext = context . fork ( valueType ) ; const tagType = childContext . tagType ; const writer = IO [ tagType ] ; assertTag ( tagType ) ; if ( ! childContext . schema && ( tagType === TagType . Compound || tagType === TagType . List ) ) { continue ; } try { buf . writeByte ( tagType ) ; writeUTF8 ( buf , key ) ; writer . write ( buf , value , childContext ) ; } catch ( e ) { if ( e instanceof TypeError ) { throw new TypeError ( `Required ${ TagType . getName ( 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 ] ) ; } } , } , ] ; export interface SerializationOption { compressed ? : true | "deflate" | "gzip" ; /**
     * IO override for serialization
     */ io ? : { [ tagType ] : IO ; } ; /**
     * Used for serialize function. Assign the filename for it.
     */ filename ? : string ; } export interface DeserializationOption < T > { compressed ? : true | "deflate" | "gzip" ; /**
     * IO override for serialization
     */ io ? : { [ tagType ] : IO ; } ; type ? : Constructor < T > ; } param object param compressed export async function serialize ( object , option = { } ) { const buff = writeRootTag ( object , undefined , option . filename || "" , Object . assign ( { } , IO , option . io ) ) ; return normalizeBuffer ( buff , option . compressed ) ; } param fileData export async function deserialize < T > ( fileData , option = { } ) { const doUnzip = normalizeCompress ( fileData , option . compressed ) ; const bb = ByteBuffer . wrap ( doUnzip === "none" ? fileData : doUnzip === "gzip" ? await ungzip ( fileData ) : await inflate ( fileData ) ) ; return readRootTag ( bb , Object . assign ( { } , IO , option . io ) , option . type ) ; } param object export function serializeSync ( object , option = { } ) { const buff = writeRootTag ( object , undefined , option . filename || "" , Object . assign ( { } , IO , option . io ) ) ; return normalizeBufferSync ( buff , option . compressed ) ; } param fileData param compressed export function deserializeSync < T > ( fileData , option = { } ) { const doUnzip = normalizeCompress ( fileData , option . compressed ) ; const bb = ByteBuffer . wrap ( doUnzip === "none" ? fileData : doUnzip === "gzip" ? gunzipSync ( fileData ) : inflateSync ( fileData ) ) ; return readRootTag ( bb , Object . assign ( { } , IO , option . io ) , option . type ) ; } function normalizeCompress ( fileData , compressed ? ) { let doUnzip ; if ( typeof compressed === "undefined" ) { const ft = fileType ( fileData ) ; doUnzip = ft !== undefined && ft . ext === "gz" ? "gzip" : "none" ; } else if ( typeof compressed === "boolean" && compressed ) { doUnzip = "gzip" ; } else { doUnzip = compressed ; } return doUnzip ; } function normalizeBuffer ( buff , compressed ? ) { if ( ! compressed ) { return buff ; } if ( compressed === "deflate" ) { return deflate ( buff ) ; } return gzip ( buff ) ; } function normalizeBufferSync ( buff , compressed ? ) { if ( ! compressed ) { return buff ; } if ( compressed === "deflate" ) { return deflateSync ( buff ) ; } return gzipSync ( buff ) ; } function readRootTag ( buffer , io , type ? ) { 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 ) ; const context = new ReadContext ( type , TagType . Compound ) ; const value = io [ TagType . Compound ] . read ( buffer , context ) ; return value ; } function writeRootTag ( value , type , filename , io ) { const buffer = new ByteBuffer ( ) ; buffer . writeByte ( TagType . Compound ) ; writeUTF8 ( buffer , filename || "" ) ; const context = new WriteContext ( type , 10 ) ; io [ TagType . Compound ] . write ( buffer , value , context ) ; return buffer . flip ( ) . buffer . slice ( 0 , buffer . limit ) ; } function assertListSchema < T > ( v ) { if ( ! ( v instanceof Array ) ) { throw new Error ( "IllegalState" ) ; } } function assertCompoundSchema ( v ) { if ( v instanceof Array ) { throw new Error ( "IllegalState" ) ; } } function assertTag ( v ) { if ( ! isTagType ( v ) ) { throw new Error ( "Unknown type " + v ) ; } } export class ReadContext { public inspect : Schema | undefined ; constructor ( public schema , public tagType ) { } fork ( schemaOrTagType ) { if ( typeof schemaOrTagType === "number" ) { return new ReadContext ( undefined , schemaOrTagType ) ; } return new ReadContext ( schemaOrTagType , typeof schemaOrTagType === "number" ? schemaOrTagType : schemaOrTagType instanceof Array ? TagType . List : TagType . Compound ) ; } } export class WriteContext { constructor ( readonly schema , readonly tagType ) { } fork ( schemaOrTagType ) { if ( schemaOrTagType === TagType . Compound ) { throw new Error ( "IllegalState" ) ; } return new WriteContext ( typeof schemaOrTagType === "number" ? undefined : schemaOrTagType , typeof schemaOrTagType === "number" ? schemaOrTagType : schemaOrTagType instanceof Array ? TagType . List : TagType . Compound ) ; } }