From d7cf4b58491eddd16960ac97bc1239f414506a3f Mon Sep 17 00:00:00 2001 From: SpreedBLood Date: Tue, 2 Feb 2021 02:13:17 +0100 Subject: [PATCH] Effects are done --- config.ini | 4 +- package-lock.json | 13 + package.json | 1 + src/Main.ts | 44 +- src/converters/ArchiveType.ts | 9 + .../effect/EffectAnimationXMLTypes.ts | 522 ++++++++++++++++++ src/converters/effect/EffectConverter.ts | 80 +++ src/converters/effect/EffectJsonMapper.ts | 338 ++++++++++++ .../effect/EffectManifestXMLTypes.ts | 142 +++++ src/converters/effect/EffectTypes.ts | 135 +++++ src/converters/figure/FigureConverter.ts | 17 +- .../furniture/FurnitureConverter.ts | 18 +- src/converters/util/SpriteSheetConverter.ts | 35 +- src/downloaders/EffectDownloader.ts | 68 +++ src/downloaders/FigureDownloader.ts | 2 +- src/utils/NitroBundle.ts | 38 ++ 16 files changed, 1420 insertions(+), 46 deletions(-) create mode 100644 src/converters/ArchiveType.ts create mode 100644 src/converters/effect/EffectAnimationXMLTypes.ts create mode 100644 src/converters/effect/EffectConverter.ts create mode 100644 src/converters/effect/EffectJsonMapper.ts create mode 100644 src/converters/effect/EffectManifestXMLTypes.ts create mode 100644 src/converters/effect/EffectTypes.ts create mode 100644 src/downloaders/EffectDownloader.ts create mode 100644 src/utils/NitroBundle.ts diff --git a/config.ini b/config.ini index 68569d5..44ebc9e 100644 --- a/config.ini +++ b/config.ini @@ -1,5 +1,5 @@ output.folder.furniture=/home/user/WebstormProjects/sites/assets.nitro.se/game/dcr/furniture/ -output.folder.figure=/home/user/WebstormProjects/sites/assets.nitro.se/game/gordon/figure-new1/ +output.folder.figure=/home/user/WebstormProjects/sites/assets.nitro.se/game/gordon/figure/ output.folder.effect=/home/user/WebstormProjects/sites/assets.nitro.se/game/gordon/effect/ output.folder.pet=/home/user/WebstormProjects/sites/assets.nitro.se/game/gordon/pet/ furnidata.url=http://assets.nitro.se/game/gamedata/furnidata-entry.xml @@ -8,7 +8,7 @@ effectmap.url=http://assets.nitro.se/game/gordon/PRODUCTION-201701242205-8373861 external_vars.url=http://assets.nitro.se/game/gamedata/external_variables.txt dynamic.download.url.furniture=/home/user/WebstormProjects/sites/assets.nitro.se/game/dcr/endrit/hof_furni/%className%.swf dynamic.download.url.figure=/home/user/WebstormProjects/sites/assets.nitro.se/game/gordon/PRODUCTION-201701242205-837386173/%className%.swf -dynamic.download.url.effect=http://assets.nitro.se/game/gordon/PRODUCTION-201701242205-837386173/%className%.swf +dynamic.download.url.effect=/home/user/WebstormProjects/sites/assets.nitro.se/game/gordon/PRODUCTION-201701242205-837386173/%className%.swf dynamic.download.url.pet=http://assets.nitro.se/game/gordon/PRODUCTION-201701242205-837386173/%className%.swf convert.furniture=1 convert.figure=0 diff --git a/package-lock.json b/package-lock.json index 4cd6a97..956d265 100644 --- a/package-lock.json +++ b/package-lock.json @@ -99,6 +99,14 @@ "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" }, + "bytebuffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", + "integrity": "sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=", + "requires": { + "long": "~3" + } + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -383,6 +391,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + }, "lzma-purejs": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/lzma-purejs/-/lzma-purejs-0.9.3.tgz", diff --git a/package.json b/package.json index 1a5b183..619719f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@types/node": "^14.14.22", + "bytebuffer": "^5.0.1", "free-tex-packer-core": "^0.3.2", "lodash": "^4.17.20", "node-fetch": "^2.6.1", diff --git a/src/Main.ts b/src/Main.ts index 675a1b1..9f6c5d1 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -6,6 +6,8 @@ import FigureConverter from "./converters/figure/FigureConverter"; import File from "./utils/File"; import FurnitureDownloader from "./downloaders/FurnitureDownloader"; import FurnitureConverter from "./converters/furniture/FurnitureConverter"; +import EffectConverter from "./converters/effect/EffectConverter"; +import EffectDownloader from "./downloaders/EffectDownloader"; (async () => { const config = new Configuration(); @@ -21,9 +23,15 @@ import FurnitureConverter from "./converters/furniture/FurnitureConverter"; outputFolderFurniture.mkdirs(); } + const outputFolderEffect = new File(config.getValue("output.folder.effect")); + if (!outputFolderEffect.isDirectory()) { + outputFolderEffect.mkdirs(); + } + const spriteSheetConverter = new SpriteSheetConverter(); const figureConverter = new FigureConverter(config); - const furnitureConverter= new FurnitureConverter(config); + const furnitureConverter = new FurnitureConverter(config); + const effectConverter = new EffectConverter(config); if (config.getBoolean("convert.figure")) { const figureDownloader = new FigureDownloader(config); @@ -38,29 +46,21 @@ import FurnitureConverter from "./converters/furniture/FurnitureConverter"; } catch (e) { console.log("Figure error: " + habboAssetSwf.getDocumentClass()); + console.log(e); } }); } - let count = 0; - if (config.getBoolean("convert.furniture")) { + let count = 0; const furnitureDownloader = new FurnitureDownloader(config); await furnitureDownloader.download(async function (habboAssetSwf: HabboAssetSWF, className: string) { console.log("Attempt parsing furniture: " + habboAssetSwf.getDocumentClass()); try { - const assetOuputFolder = new File(outputFolderFurniture.path + "/" + className); - if (!assetOuputFolder.isDirectory()) { - assetOuputFolder.mkdirs(); - } else if (assetOuputFolder.list().length > 0) { - console.log("Furniture already exists or the directory is not empty!"); - return; - } - - const spriteSheetType = await spriteSheetConverter.generateSpriteSheet(habboAssetSwf, assetOuputFolder.path, "furniture"); + const spriteSheetType = await spriteSheetConverter.generateSpriteSheet(habboAssetSwf, outputFolderFurniture.path, "furniture"); if (spriteSheetType !== null) { - await furnitureConverter.fromHabboAsset(habboAssetSwf, assetOuputFolder.path, "furniture", spriteSheetType); + await furnitureConverter.fromHabboAsset(habboAssetSwf, outputFolderFurniture.path, "furniture", spriteSheetType); } } catch (e) { console.log("Furniture error: " + habboAssetSwf.getDocumentClass()); @@ -71,10 +71,26 @@ import FurnitureConverter from "./converters/furniture/FurnitureConverter"; console.log(`Parsed ${++count} furnitures`) } + if (config.getBoolean("convert.effect")) { + const effectDownloader = new EffectDownloader(config); + await effectDownloader.download(async function (habboAssetSwf: HabboAssetSWF) { + console.log("Attempt parsing figure: " + habboAssetSwf.getDocumentClass()); + + try { + const spriteSheetType = await spriteSheetConverter.generateSpriteSheet(habboAssetSwf, outputFolderFurniture.path, "effect"); + if (spriteSheetType !== null) { + await effectConverter.fromHabboAsset(habboAssetSwf, outputFolderEffect.path, "effect", spriteSheetType); + } + } catch (e) { + console.log(e); + console.log("Effect error: "+ habboAssetSwf.getDocumentClass()); + } + }); + } + console.log('finished!'); /* - outputFolderFurniture.rmdir({ recursive: true, force: true diff --git a/src/converters/ArchiveType.ts b/src/converters/ArchiveType.ts new file mode 100644 index 0000000..c8ec11f --- /dev/null +++ b/src/converters/ArchiveType.ts @@ -0,0 +1,9 @@ +import {SpriteSheetType} from "./util/SpriteSheetTypes"; + +export default interface ArchiveType { + spriteSheetType: SpriteSheetType, + imageData: { + name: string, + buffer: Buffer + } +} \ No newline at end of file diff --git a/src/converters/effect/EffectAnimationXMLTypes.ts b/src/converters/effect/EffectAnimationXMLTypes.ts new file mode 100644 index 0000000..8dcabc4 --- /dev/null +++ b/src/converters/effect/EffectAnimationXMLTypes.ts @@ -0,0 +1,522 @@ +export class AnimationXML { + private readonly _name: string; + private readonly _desc: string; + private readonly _resetOnToggle: boolean | undefined; + + private readonly _directions: Array; + private readonly _shadows: Array; + private readonly _adds: Array; + private readonly _removes: Array; + private readonly _sprites: Array; + private readonly _frames: Array; + private readonly _avatars: Array; + private readonly _overrides: Array; + + constructor(animationXML: any) { + const animation = animationXML.animation; + const attributes = animation.$; + + this._name = attributes.name; + this._desc = attributes.desc; + + if (attributes.resetOnToggle !== undefined) this._resetOnToggle = attributes.resetOnToggle === '1'; + + this._directions = new Array(); + if (animation.direction !== undefined) { + for (const direction of animation.direction) { + this._directions.push(new DirectionOffsetXML(direction)); + } + } + + this._shadows = new Array(); + if (animation.shadow !== undefined) { + for (const shadow of animation.shadow) { + this._shadows.push(new ShadowXML(shadow)); + } + } + + this._adds = new Array(); + if (animation.add !== undefined) { + for (const add of animation.add) { + this._adds.push(new AddXML(add)); + } + } + + this._removes = new Array(); + if (animation.remove !== undefined) { + for (const remove of animation.remove) { + this._removes.push(new RemoveXML(remove)); + } + } + + this._sprites = new Array(); + if (animation.sprite !== undefined) { + for (const sprite of animation.sprite) { + this._sprites.push(new SpriteXML(sprite)); + } + } + + this._frames = new Array(); + if (animation.frame !== undefined) { + for (const frame of animation.frame) { + this._frames.push(new FrameXML(frame)); + } + } + + this._avatars = new Array(); + if (animation.avatar !== undefined) { + for (const avatar of animation.avatar) { + this._avatars.push(new AvatarXML(avatar)); + } + } + + this._overrides = new Array(); + if (animation.override !== undefined) { + for (const override of animation.override) { + this._overrides.push(new OverrideXML(override)); + } + } + } + + get name(): string { + return this._name; + } + + get desc(): string { + return this._desc; + } + + get resetOnToggle(): boolean | undefined { + return this._resetOnToggle; + } + + get directions(): Array { + return this._directions; + } + + get shadows(): Array { + return this._shadows; + } + + get adds(): Array { + return this._adds; + } + + get removes(): Array { + return this._removes; + } + + get sprites(): Array { + return this._sprites; + } + + get frames(): Array { + return this._frames; + } + + get avatars(): Array { + return this._avatars; + } + + get overrides(): Array { + return this._overrides; + } +} + +export class OverrideXML { + private readonly _name: string; + private readonly _override: string; + + private readonly _frames: Array; + + constructor(overrideXML: any) { + const attributes = overrideXML.$; + + this._name = attributes.name; + this._override = attributes.override; + + this._frames = new Array(); + if (overrideXML.frame !== undefined) { + for (const frame of overrideXML.frame) { + this._frames.push(new FrameXML(frame)); + } + } + } + + get name(): string { + return this._name; + } + + get override(): string { + return this._override; + } + + get frames(): Array { + return this._frames; + } +} + +export class AvatarXML { + private readonly _ink: number; + private readonly _foreground: string; + private readonly _background: string; + + constructor(avatarXML: any) { + const attributes = avatarXML.$; + + this._ink = attributes.ink; + this._foreground = attributes.foreground; + this._background = attributes.background; + } + + get ink(): number { + return this._ink; + } + + get foreground(): string { + return this._foreground; + } + + get background(): string { + return this._background; + } +} + +export class SpriteXML { + private readonly _id: string; + private readonly _member: string; + private readonly _directions: number; + private readonly _staticY: number; + private readonly _ink: number; + + private readonly _directionList: Array; + + constructor(spriteXML: any) { + const attributes = spriteXML.$; + + this._id = attributes.id; + this._member = attributes.member; + this._directions = attributes.directions; + this._staticY = attributes.staticY; + this._ink = attributes.ink; + + this._directionList = new Array(); + if (spriteXML.direction !== undefined) { + for (const direction of spriteXML.direction) { + this._directionList.push(new DirectionXML(direction)); + } + } + } + + get id(): string { + return this._id; + } + + get member(): string { + return this._member; + } + + get directions(): number { + return this._directions; + } + + get staticY(): number { + return this._staticY; + } + + get ink(): number { + return this._ink; + } + + get directionList(): Array { + return this._directionList; + } +} + +export class DirectionXML { + private readonly _id: number; + private readonly _dx: number; + private readonly _dy: number; + private readonly _dz: number; + + constructor(directionXML: any) { + const attributes = directionXML.$; + + this._id = attributes.id; + this._dx = attributes.dx; + this._dy = attributes.dy; + this._dz = attributes.dz; + } + + get id(): number { + return this._id; + } + + get dx(): number { + return this._dx; + } + + get dy(): number { + return this._dy; + } + + get dz(): number { + return this._dz; + } +} + +export class RemoveXML { + private readonly _id: string; + + constructor(removeXML: any) { + this._id = removeXML.$.id; + } + + get id(): string { + return this._id; + } +} + +export class AddXML { + private readonly _id: string; + private readonly _align: string; + private readonly _blend: string; + private readonly _ink: number; + private readonly _base: string; + + constructor(addXML: any) { + const attributes = addXML.$; + + this._id = attributes.id; + this._align = attributes.align; + this._blend = attributes.blend; + this._ink = attributes.ink; + this._base = attributes.base; + } + + get id(): string { + return this._id; + } + + get align(): string { + return this._align; + } + + get blend(): string { + return this._blend; + } + + get ink(): number { + return this._ink; + } + + get base(): string { + return this._base; + } +} + +export class FrameXML { + private readonly _repeats: number | undefined; + private readonly _fxs: Array; + private readonly _bodyParts: Array; + + constructor(frameXML: any) { + if (frameXML.$ !== undefined) + this._repeats = frameXML.$.repeats; + + this._fxs = new Array(); + if (frameXML.fx !== undefined) { + for (const fx of frameXML.fx) { + this._fxs.push(new FxXML(fx)); + } + } + + this._bodyParts = new Array(); + if (frameXML.bodypart !== undefined) { + for (const bodypart of frameXML.bodypart) { + this._bodyParts.push(new BodyPartXML(bodypart)); + } + } + } + + get repeats(): number | undefined { + return this._repeats; + } + + get fxs(): Array { + return this._fxs; + } + + get bodyParts(): Array { + return this._bodyParts; + } +} + +export class BodyPartXML { + private readonly _id: string; + private readonly _frame: number; + private readonly _dx: number; + private readonly _dy: number; + private readonly _dz: number; + private readonly _dd: number; + private readonly _action: string; + private readonly _base: string; + + private readonly _items: Array; + + constructor(bodyPartXML: any) { + const attributes = bodyPartXML.$; + + this._id = attributes.id; + this._frame = attributes.frame; + this._dx = attributes.dx; + this._dy = attributes.dy; + this._dz = attributes.dz; + this._dd = attributes.dd; + this._action = attributes.action; + this._base = attributes.base; + + this._items = new Array(); + if (bodyPartXML.item !== undefined) { + for (const item of bodyPartXML.item) { + this._items.push(new ItemXML(item)); + } + } + } + + get id(): string { + return this._id; + } + + get frame(): number { + return this._frame; + } + + get dx(): number { + return this._dx; + } + + get dy(): number { + return this._dy; + } + + get dz(): number { + return this._dz; + } + + get dd(): number { + return this._dd; + } + + get action(): string { + return this._action; + } + + get base(): string { + return this._base; + } + + get items(): Array { + return this._items; + } +} + +export class ItemXML { + private readonly _id: string; + private readonly _base: string; + + constructor(itemXML: any) { + const attributes = itemXML.$; + + this._id = attributes.id; + this._base = attributes.base; + } + + get id(): string { + return this._id; + } + + get base(): string { + return this._base; + } +} + +export class FxXML { + private readonly _id: string; + private readonly _repeats: number; + private readonly _frame: number; + private readonly _dx: number; + private readonly _dy: number; + private readonly _dz: number; + private readonly _dd: number; + private readonly _action: string; + + constructor(fxXML: any) { + const attributes = fxXML.$; + + this._id = attributes.id; + this._repeats = attributes.repeats; + this._frame = attributes.frame; + this._dx = attributes.dx; + this._dy = attributes.dy; + this._dz = attributes.dz; + this._dd = attributes.dd; + this._action = attributes.action; + } + + get id(): string { + return this._id; + } + + get repeats(): number { + return this._repeats; + } + + get frame(): number { + return this._frame; + } + + get dx(): number { + return this._dx; + } + + get dy(): number { + return this._dy; + } + + get dz(): number { + return this._dz; + } + + get dd(): number { + return this._dd; + } + + get action(): string { + return this._action; + } +} + +export class ShadowXML { + private readonly _id: string; + + constructor(shadowXML: any) { + this._id = shadowXML.$.id; + } + + get id(): string { + return this._id; + } +} + +export class DirectionOffsetXML { + private readonly _offset: number; + + constructor(directionXML: any) { + this._offset = directionXML.$.offset; + } + + get offset(): number { + return this._offset; + } +} \ No newline at end of file diff --git a/src/converters/effect/EffectConverter.ts b/src/converters/effect/EffectConverter.ts new file mode 100644 index 0000000..277e462 --- /dev/null +++ b/src/converters/effect/EffectConverter.ts @@ -0,0 +1,80 @@ +import HabboAssetSWF from "../../swf/HabboAssetSWF"; +import DefineBinaryDataTag from "../../swf/tags/DefineBinaryDataTag"; +import ArchiveType from "../ArchiveType"; +import File from "../../utils/File"; +import NitroBundle from "../../utils/NitroBundle"; +import {EffectJson} from "./EffectTypes"; +import Configuration from "../../config/Configuration"; +import EffectJsonMapper from "./EffectJsonMapper"; + +const xml2js = require('xml2js'); +const parser = new xml2js.Parser(/* options */); + +const fs = require('fs').promises; + +export default class EffectConverter { + private readonly _effectJsonMapper: EffectJsonMapper; + + constructor(config: Configuration) { + this._effectJsonMapper = new EffectJsonMapper(); + } + + private static getBinaryData(habboAssetSWF: HabboAssetSWF, type: string, documentNameTwice: boolean) { + let binaryName: string = habboAssetSWF.getFullClassName(type, documentNameTwice); + let tag = habboAssetSWF.getBinaryTagByName(binaryName); + if (tag === null) { + binaryName = habboAssetSWF.getFullClassNameSnake(type, documentNameTwice, true); + tag = habboAssetSWF.getBinaryTagByName(binaryName); + } + + return tag; + } + + private static async getManifestXML(habboAssetSWF: HabboAssetSWF): Promise { + const binaryData: DefineBinaryDataTag | null = this.getBinaryData(habboAssetSWF, "manifest", false); + if (binaryData !== null) { + return await parser.parseStringPromise(binaryData.binaryData); + } + + return null; + } + + private static async getAnimationXML(habboAssetSWF: HabboAssetSWF): Promise { + const binaryData: DefineBinaryDataTag | null = this.getBinaryData(habboAssetSWF, "animation", false); + if (binaryData !== null) { + return await parser.parseStringPromise(binaryData.binaryData); + } + + return null; + } + + private async convertXML2JSON(habboAssetSWF: HabboAssetSWF): Promise { + const manifestXML = await EffectConverter.getManifestXML(habboAssetSWF); + const animationXML = await EffectConverter.getAnimationXML(habboAssetSWF); + + return this._effectJsonMapper.mapXML(habboAssetSWF, manifestXML, animationXML); + } + + public async fromHabboAsset(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string, archiveType: ArchiveType) { + const effectJson = await this.convertXML2JSON(habboAssetSWF); + if (effectJson !== null) { + effectJson.spritesheet = archiveType.spriteSheetType; + effectJson.type = type; + + const path = outputFolder + "/" + habboAssetSWF.getDocumentClass() + ".nitro"; + const assetOuputFolder = new File(path); + if (assetOuputFolder.exists()) { + console.log("Effect already exists or the directory is not empty!"); + + return; + } + + const nitroBundle = new NitroBundle(); + nitroBundle.addFile(habboAssetSWF.getDocumentClass() + ".json", Buffer.from(JSON.stringify(effectJson))); + nitroBundle.addFile(archiveType.imageData.name, archiveType.imageData.buffer); + + const buffer = await nitroBundle.toBufferAsync(); + await fs.writeFile(path, buffer); + } + } +} \ No newline at end of file diff --git a/src/converters/effect/EffectJsonMapper.ts b/src/converters/effect/EffectJsonMapper.ts new file mode 100644 index 0000000..0b99edd --- /dev/null +++ b/src/converters/effect/EffectJsonMapper.ts @@ -0,0 +1,338 @@ +import HabboAssetSWF from "../../swf/HabboAssetSWF"; +import { + Add, + Alias, + Aliases, + Animation, + Animations, + AssetJSON, + AssetsJSON, Avatar, + Bodypart, Direction, DirectionOffset, + EffectJson, + Frame, + Fx, Item, Override, Remove, Shadow, Sprite +} from "./EffectTypes"; +import EffectDownloader from "../../downloaders/EffectDownloader"; +import {ManifestXML} from "./EffectManifestXMLTypes"; +import SpriteSheetConverter from "../util/SpriteSheetConverter"; +import { + AnimationXML, + BodyPartXML +} from "./EffectAnimationXMLTypes"; + +export default class EffectJsonMapper { + public static readonly MUST_START_WITH: string = "h_"; + + public mapXML(habboAssetSWF: HabboAssetSWF, manifest: any, animation: any): EffectJson { + const name = habboAssetSWF.getDocumentClass(); + const result = {} as EffectJson; + result.name = name; + result.type = EffectDownloader.types.get(name) as string; + EffectJsonMapper.mapManifestXML(new ManifestXML(manifest), result); + EffectJsonMapper.mapAnimationXML(new AnimationXML(animation), result); + + return result; + } + + private static mapManifestXML(manifestXML: ManifestXML, output: EffectJson) { + const assets: AssetsJSON = {}; + + for (const assetXML of manifestXML.library.assets) { + if (assetXML.name.startsWith(this.MUST_START_WITH)) { + + const asset: AssetJSON = {} as any; + if (assetXML.param != undefined && assetXML.param.value !== undefined) { + asset.x = parseInt(assetXML.param.value.split(",")[0]); + asset.y = parseInt(assetXML.param.value.split(",")[1]); + } + if (SpriteSheetConverter.imageSource.has(assetXML.name)) { + asset.source = SpriteSheetConverter.imageSource.get(assetXML.name) as any; + } + + assets[assetXML.name] = asset; + } + } + + output.assets = assets; + + if (manifestXML.library.aliases.length > 0 || SpriteSheetConverter.imageSource.size > 0) { + const aliases: Aliases = {}; + for (const aliasXML of manifestXML.library.aliases) { + if (aliasXML.name.startsWith(this.MUST_START_WITH)) { + const alias: Alias = {} as any; + + alias.link = aliasXML.link; + if (aliasXML.fliph !== undefined) + alias.fliph = parseInt(aliasXML.fliph.toString()); + if (aliasXML.flipv !== undefined) + alias.flipv = parseInt(aliasXML.flipv.toString()); + + aliases[aliasXML.name] = alias; + } + } + + output.aliases = aliases; + } + } + + private static mapAnimationXML(animationXML: AnimationXML, output: EffectJson) { + const animations: Animations = {}; + const animation: Animation = {} as any; + animation.name = animationXML.name; + animation.desc = animationXML.desc; + animation.resetOnToggle = animationXML.resetOnToggle as any; + + const frames: Array = new Array(); + const avatars: Array = new Array(); + const directions: Array = new Array(); + const shadows: Array = new Array(); + const adds: Array = new Array(); + const removes: Array = new Array(); + const sprites: Array = new Array(); + const overrides: Array = new Array(); + if (animationXML.frames.length > 0) { + for (const frameXML of animationXML.frames) { + const fxs: Array = new Array(); + const bodyparts: Array = new Array(); + + const frame: Frame = {} as any; + if (frameXML.fxs.length > 0) { + for (const fxXML of frameXML.fxs) { + const fx: Fx = {} as any; + fx.action = fxXML.action; + + if (fxXML.dx !== undefined) + fx.dx = parseInt(fxXML.dx.toString()); + + if (fxXML.dy !== undefined) + fx.dy = parseInt(fxXML.dy.toString()); + + if (fxXML.dz !== undefined) + fx.dz = parseInt(fxXML.dz.toString()); + + if (fxXML.dd !== undefined) + fx.dd = parseInt(fxXML.dd.toString()); + + if (fxXML.frame !== undefined) + fx.frame = parseInt(fxXML.frame.toString()); + fx.id = fxXML.id; + + fxs.push(fx); + } + } + if (frameXML.bodyParts.length > 0) { + for (const bodypartXML of frameXML.bodyParts) { + const items: Array = new Array(); + const bodypart: Bodypart = {} as any; + if (bodypartXML.items.length > 0) { + for (const itemXML of bodypartXML.items) { + const item: Item = {} as any; + item.id = itemXML.id; + item.base = itemXML.base; + + items.push(item); + } + } + bodypart.action = bodypartXML.action; + + if (bodypartXML.dx !== undefined) + bodypart.dx = parseInt(bodypartXML.dx.toString()); + + if (bodypartXML.dy !== undefined) + bodypart.dy = parseInt(bodypartXML.dy.toString()); + + if (bodypartXML.dz !== undefined) + bodypart.dz = parseInt(bodypartXML.dz.toString()); + + if (bodypartXML.dd !== undefined) + bodypart.dd = parseInt(bodypartXML.dd.toString()); + + if (bodypartXML.frame !== undefined) + bodypart.frame = parseInt(bodypartXML.frame.toString()); + bodypart.id = bodypartXML.id; + bodypart.base = bodypartXML.base; + bodypart.items = items; + + bodyparts.push(bodypart); + } + } + if (frameXML.repeats !== undefined) frame.repeats = parseInt(frameXML.repeats.toString()); + frame.fxs = fxs; + frame.bodyparts = bodyparts; + frames.push(frame); + } + } + if (animationXML.avatars.length > 0) { + for (const avatarXML of animationXML.avatars) { + const avatar: Avatar = {} as any; + avatar.background = avatarXML.background; + avatar.foreground = avatarXML.foreground; + + if (avatarXML.ink !== undefined) + avatar.ink = parseInt(avatarXML.ink.toString()); + + avatars.push(avatar); + } + } + if (animationXML.directions.length > 0) { + for (const directionXML of animationXML.directions) { + const direction: DirectionOffset = {} as any; + direction.offset = parseInt(directionXML.offset.toString()); + + directions.push(direction); + } + } + if (animationXML.shadows.length > 0) { + for (const shadowXML of animationXML.shadows) { + const shadow: Shadow = {} as any; + shadow.id = shadowXML.id; + + shadows.push(shadow); + } + } + if (animationXML.adds.length > 0) { + for (const addXML of animationXML.adds) { + const add: Add = {} as any; + add.id = addXML.id; + add.align = addXML.align; + add.blend = addXML.blend; + + if (addXML.ink !== undefined) + add.ink = parseInt(addXML.ink.toString()); + add.base = addXML.base; + + adds.push(add); + } + } + if (animationXML.removes.length > 0) { + for (const removeXML of animationXML.removes) { + const remove: Remove = {} as any; + remove.id = removeXML.id; + + removes.push(remove); + } + } + if (animationXML.sprites.length > 0) { + for (const spriteXML of animationXML.sprites) { + const sprite: Sprite = {} as any; + const directions2: Array = new Array(); + if (spriteXML.directionList.length > 0) { + for (const directionXML of spriteXML.directionList) { + const direction: Direction = {} as any; + direction.id = parseInt(directionXML.id.toString()); + + if (directionXML.dx !== undefined) + direction.dx = parseInt(directionXML.dx.toString()); + + if (directionXML.dy !== undefined) + direction.dy = parseInt(directionXML.dy.toString()); + + if (directionXML.dz !== undefined) + direction.dz = parseInt(directionXML.dz.toString()); + + directions2.push(direction); + } + } + sprite.directionList = directions2; + + if (spriteXML.directions !== undefined) + sprite.directions = parseInt(spriteXML.directions.toString()); + sprite.id = spriteXML.id; + + if (spriteXML.ink !== undefined) + sprite.ink = parseInt(spriteXML.ink.toString()); + + if (spriteXML.member !== undefined) + sprite.member = spriteXML.member; + + if (spriteXML.staticY !== undefined) + sprite.staticY = parseInt(spriteXML.staticY.toString()); + sprites.push(sprite); + } + } + if (animationXML.overrides.length > 0) { + for (const overrideXML of animationXML.overrides) { + const override: Override = {} as any; + override.name = overrideXML.name; + override.override = overrideXML.override; + if (overrideXML.frames.length > 0) { + const overrideFrames: Array = new Array(); + for (const frameXML of overrideXML.frames) { + const fxs: Array = new Array(); + const bodyparts: Array = new Array(); + const frame: Frame = {} as any; + if (frameXML.fxs.length > 0) { + for (const fxXML of frameXML.fxs) { + const fx: Fx = {} as any; + fx.action = fxXML.action; + if (fxXML.dx !== undefined) + fx.dx = parseInt(fxXML.dx.toString()); + if (fxXML.dy !== undefined) + fx.dy = parseInt(fxXML.dy.toString()); + if (fxXML.dz !== undefined) + fx.dz = parseInt(fxXML.dz.toString()); + if (fxXML.dd !== undefined) + fx.dd = parseInt(fxXML.dd.toString()); + + if (fxXML.frame !== undefined) + fx.frame = parseInt(fxXML.frame.toString()); + fx.id = fxXML.id; + + fxs.push(fx); + } + } + if (frameXML.bodyParts.length > 0) { + for (const bodypartXML of frameXML.bodyParts) { + const items: Array = new Array(); + const bodypart: Bodypart = {} as any; + if (bodypartXML.items.length > 0) { + for (const itemXML of bodypartXML.items) { + const item: Item = {} as any; + item.id = itemXML.id; + item.base = itemXML.base; + + items.push(item); + } + } + bodypart.action = bodypartXML.action; + + if (bodypartXML.dx !== undefined) + bodypart.dx = parseInt(bodypartXML.dx.toString()); + if (bodypartXML.dy !== undefined) + bodypart.dy = parseInt(bodypartXML.dy.toString()); + if (bodypartXML.dz !== undefined) + bodypart.dz = parseInt(bodypartXML.dz.toString()); + if (bodypartXML.dd !== undefined) + bodypart.dd = parseInt(bodypartXML.dd.toString()); + + if (bodypartXML.frame !== undefined) + bodypart.frame = parseInt(bodypartXML.frame.toString()); + bodypart.id = bodypartXML.id; + bodypart.base = bodypartXML.base; + bodypart.items = items; + + bodyparts.push(bodypart); + } + } + frame.fxs = fxs; + frame.bodyparts = bodyparts; + overrideFrames.push(frame); + } + override.frames = overrideFrames; + overrides.push(override); + } + } + } + animation.frames = frames; + animation.shadows = shadows; + animation.adds = adds; + animation.directions = directions; + animation.avatars = avatars; + animation.removes = removes; + animation.sprites = sprites; + animation.overrides = overrides; + animations[output.name] = animation; + + output.animations = animations; + } +} \ No newline at end of file diff --git a/src/converters/effect/EffectManifestXMLTypes.ts b/src/converters/effect/EffectManifestXMLTypes.ts new file mode 100644 index 0000000..8df16af --- /dev/null +++ b/src/converters/effect/EffectManifestXMLTypes.ts @@ -0,0 +1,142 @@ +export class ManifestXML { + private readonly _library: LibraryXML; + + constructor(manifestXML: any) { + this._library = new LibraryXML(manifestXML.manifest.library[0]); + } + + get library(): LibraryXML { + return this._library; + } +} + +export class LibraryXML { + private readonly _name: string; + private readonly _version: string; + + private readonly _assets: Array; + private readonly _aliases: Array; + + constructor(libraryXML: any) { + const attributes = libraryXML.$; + this._name = attributes.id; + this._version = attributes.version; + + this._assets = new Array(); + if (libraryXML.assets !== undefined) { + for (const assetParent of libraryXML.assets) { + for (const asset of assetParent.asset) { + this._assets.push(new AssetXML(asset)); + } + } + } + + this._aliases = new Array(); + if (libraryXML.aliases !== undefined && Array.isArray(libraryXML.aliases)) { + for (const aliasParent of libraryXML.aliases) { + if (Array.isArray(aliasParent.alias)) { + for (const alias of aliasParent.alias) { + this._aliases.push(new AliasXML(alias)); + } + } + } + } + } + + get name(): string { + return this._name; + } + + get version(): string { + return this._version; + } + + get assets(): Array { + return this._assets; + } + + get aliases(): Array { + return this._aliases; + } +} + +export class AliasXML { + private readonly _name: string; + private readonly _link: string; + private readonly _fliph: number; + private readonly _flipv: number; + + constructor(aliasXML: any) { + const attributes = aliasXML.$; + + this._name = attributes.name; + this._link = attributes.link; + this._fliph = attributes.fliph; + this._flipv = attributes.flipv; + } + + get name(): string { + return this._name; + } + + get link(): string { + return this._link; + } + + get fliph(): number { + return this._fliph; + } + + get flipv(): number { + return this._flipv; + } +} + +export class AssetXML { + private readonly _name: string; + private readonly _mimeType: string; + private readonly _param: ParamXML | undefined; + + constructor(assetXML: any) { + const attributes = assetXML.$; + this._name = attributes.name; + this._mimeType = attributes.mimeType; + + if (assetXML.param !== undefined) { + for (const param of assetXML.param) { + this._param = new ParamXML(param); + } + } + } + + get name(): string { + return this._name; + } + + get mimeType(): string { + return this._mimeType; + } + + get param(): ParamXML | undefined { + return this._param; + } +} + +export class ParamXML { + private readonly _key: string; + private readonly _value: string; + + constructor(paramXML: any) { + const attributes = paramXML.$; + this._key = attributes.key; + this._value = attributes.value; + } + + get key(): string { + return this._key; + } + + get value(): string { + return this._value; + } +} \ No newline at end of file diff --git a/src/converters/effect/EffectTypes.ts b/src/converters/effect/EffectTypes.ts new file mode 100644 index 0000000..d96ba21 --- /dev/null +++ b/src/converters/effect/EffectTypes.ts @@ -0,0 +1,135 @@ +import {SpriteSheetType} from "../util/SpriteSheetTypes"; + +export interface EffectJson { + type: string, + name: string, + spritesheet: SpriteSheetType, + + assets: AssetsJSON, + aliases: Aliases, + animations: Animations +} + +export interface Animations { + [key: string]: Animation +} + +export interface Animation { + name: string, + desc: string, + resetOnToggle: boolean, + + directions: Array, + shadows: Array, + adds: Array, + removes: Array, + sprites: Array, + frames: Array, + avatars: Array, + overrides: Array +} + +export interface Override { + name: string, + override: string, + + frames: Array; +} + +export interface Avatar { + ink: number, + foreground: string, + background: string +} + +export interface Frame { + repeats: number, + + fxs: Array, + bodyparts: Array +} + +export interface Bodypart { + id: string, + frame: number, + dx: number, + dy: number, + dz: number, + dd: number, + action: string, + base: string, + + items: Array +} + +export interface Item { + id: string, + base: string +} + +export interface Fx { + id: string, + frame: number, + dx: number, + dy: number, + dz: number, + dd: number, + action: string +} + +export interface Sprite { + id: string, + member: string, + directions: number, + staticY: number, + ink: number, + + directionList: Array; +} + +export interface Direction { + id: number, + dx: number, + dy: number, + dz: number, +} + +export interface Remove { + id: string +} + +export interface Add { + id: string, + align: string, + blend: string, + ink: number, + base: string +} + +export interface Shadow { + id: string +} + +export interface DirectionOffset { + offset: number +} + +export interface Aliases { + [key: string]: Alias +} + +export interface Alias { + link: string, + fliph: number, + flipv: number +} + +export interface AssetsJSON { + [key: string]: AssetJSON; +} + +export interface AssetJSON { + source: string, + x: number, + y: number, +} \ No newline at end of file diff --git a/src/converters/figure/FigureConverter.ts b/src/converters/figure/FigureConverter.ts index fa92eab..16779ec 100644 --- a/src/converters/figure/FigureConverter.ts +++ b/src/converters/figure/FigureConverter.ts @@ -1,16 +1,15 @@ import HabboAssetSWF from "../../swf/HabboAssetSWF"; -import {SpriteSheetType} from "../util/SpriteSheetTypes"; import DefineBinaryDataTag from "../../swf/tags/DefineBinaryDataTag"; import Configuration from "../../config/Configuration"; import FigureJsonMapper from "./FigureJsonMapper"; import {FigureJson} from "./FigureJsonType"; import File from "../../utils/File"; +import ArchiveType from "../ArchiveType"; +import NitroBundle from "../../utils/NitroBundle"; const xml2js = require('xml2js'); const parser = new xml2js.Parser(/* options */); - const fs = require('fs').promises; -const {gzip} = require('node-gzip'); export default class FigureConverter { @@ -42,10 +41,10 @@ export default class FigureConverter { return null; } - public async fromHabboAsset(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string, spriteSheetType: SpriteSheetType) { + public async fromHabboAsset(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string, archiveType: ArchiveType) { const manifestJson = await this.convertXML2JSON(habboAssetSWF); if (manifestJson !== null) { - manifestJson.spritesheet = spriteSheetType; + manifestJson.spritesheet = archiveType.spriteSheetType; const path = outputFolder + "/" + habboAssetSWF.getDocumentClass() + ".nitro"; const assetOuputFolder = new File(path); @@ -55,8 +54,12 @@ export default class FigureConverter { return; } - const compressed = await gzip(JSON.stringify(manifestJson)); - await fs.writeFile(path, compressed); + const nitroBundle = new NitroBundle(); + nitroBundle.addFile(habboAssetSWF.getDocumentClass() + ".json", Buffer.from(JSON.stringify(manifestJson))); + nitroBundle.addFile(archiveType.imageData.name, archiveType.imageData.buffer); + + const buffer = await nitroBundle.toBufferAsync(); + await fs.writeFile(path, buffer); } } } \ No newline at end of file diff --git a/src/converters/furniture/FurnitureConverter.ts b/src/converters/furniture/FurnitureConverter.ts index 878689d..19dac13 100644 --- a/src/converters/furniture/FurnitureConverter.ts +++ b/src/converters/furniture/FurnitureConverter.ts @@ -1,16 +1,16 @@ import HabboAssetSWF from "../../swf/HabboAssetSWF"; -import {SpriteSheetType} from "../util/SpriteSheetTypes"; import DefineBinaryDataTag from "../../swf/tags/DefineBinaryDataTag"; import Configuration from "../../config/Configuration"; import FurniJsonMapper from "./FurniJsonMapper"; import {FurniJson} from "./FurniTypes"; import File from "../../utils/File"; +import ArchiveType from "../ArchiveType"; +import NitroBundle from "../../utils/NitroBundle"; const xml2js = require('xml2js'); const parser = new xml2js.Parser(/* options */); const fs = require('fs').promises; -const {gzip} = require('node-gzip'); export default class FurnitureConverter { @@ -50,7 +50,7 @@ export default class FurnitureConverter { } private static async getIndexXML(habboAssetSWF: HabboAssetSWF): Promise { - const binaryData: DefineBinaryDataTag | null = FurnitureConverter.getBinaryData(habboAssetSWF, "index", true); + const binaryData: DefineBinaryDataTag | null = FurnitureConverter.getBinaryData(habboAssetSWF, "index", false); if (binaryData !== null) { return await parser.parseStringPromise(binaryData.binaryData); } @@ -76,10 +76,10 @@ export default class FurnitureConverter { return this._furniJsonMapper.mapXML(assetXml, indexXml, logicXml, visualizationXml); } - public async fromHabboAsset(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string, spriteSheetType: SpriteSheetType) { + public async fromHabboAsset(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string, archiveType: ArchiveType) { const furnitureJson = await this.convertXML2JSON(habboAssetSWF); if (furnitureJson !== null) { - furnitureJson.spritesheet = spriteSheetType; + furnitureJson.spritesheet = archiveType.spriteSheetType; furnitureJson.type = type; const path = outputFolder + "/" + habboAssetSWF.getDocumentClass() + ".nitro"; @@ -90,8 +90,12 @@ export default class FurnitureConverter { return; } - const compressed = await gzip(JSON.stringify(furnitureJson)); - await fs.writeFile(path, compressed); + const nitroBundle = new NitroBundle(); + nitroBundle.addFile(habboAssetSWF.getDocumentClass() + ".json", Buffer.from(JSON.stringify(furnitureJson))); + nitroBundle.addFile(archiveType.imageData.name, archiveType.imageData.buffer); + + const buffer = await nitroBundle.toBufferAsync(); + await fs.writeFile(path, buffer); } } } \ No newline at end of file diff --git a/src/converters/util/SpriteSheetConverter.ts b/src/converters/util/SpriteSheetConverter.ts index 6869dd3..c22e91c 100644 --- a/src/converters/util/SpriteSheetConverter.ts +++ b/src/converters/util/SpriteSheetConverter.ts @@ -1,16 +1,14 @@ -import {SpriteSheetType} from "./SpriteSheetTypes"; - -const fs = require('fs').promises; let {packAsync} = require("free-tex-packer-core"); import HabboAssetSWF from "../../swf/HabboAssetSWF"; import SymbolClassTag from "../../swf/tags/SymbolClassTag"; import ImageTag from "../../swf/tags/ImageTag"; +import ArchiveType from "../ArchiveType"; export default class SpriteSheetConverter { - public static imageSource: Map = new Map(); + public static imageSource: Map = new Map(); - public async generateSpriteSheet(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string): Promise { + public async generateSpriteSheet(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string): Promise { const tagList: Array = habboAssetSWF.symbolTags(); const names: Array = new Array(); const tags: Array = new Array(); @@ -19,7 +17,7 @@ export default class SpriteSheetConverter { tags.push(...tag.tags); } - const images: Array<{ path: String, contents: Buffer }> = new Array<{ path: String, contents: Buffer }>(); + const images: Array<{ path: string, contents: Buffer }> = new Array<{ path: string, contents: Buffer }>(); const imageTags: Array = habboAssetSWF.imageTags(); for (const imageTag of imageTags) { @@ -67,7 +65,7 @@ export default class SpriteSheetConverter { return await this.packImages(habboAssetSWF.getDocumentClass(), outputFolder + "/", images); } - async packImages(documentClass: string, outputFolder: string, images: Array<{ path: String, contents: Buffer }>): Promise { + async packImages(documentClass: string, outputFolder: string, images: Array<{ path: string, contents: Buffer }>): Promise { let options = { textureName: documentClass, width: 1024, @@ -79,26 +77,33 @@ export default class SpriteSheetConverter { exporter: "Pixi" }; - let spriteSheetType: SpriteSheetType | null = null; - let base64 = ""; + const archiveType: ArchiveType = {} as any; + const imageData: { + name: string, + buffer: Buffer + } = {} as any; + try { const files = await packAsync(images, options); for (let item of files) { if (item.name.endsWith(".json")) { - spriteSheetType = JSON.parse(item.buffer.toString('utf8')); + archiveType.spriteSheetType = JSON.parse(item.buffer.toString('utf8')); } else { - base64 = item.buffer.toString("base64"); - //await fs.writeFile(outputFolder + item.name, item.buffer); + imageData.buffer = item.buffer; + imageData.name = item.name; } } - if (spriteSheetType === null) throw new Error("Failed to parse SpriteSheet. " + images[0].path); + if (archiveType.spriteSheetType === null) throw new Error("Failed to parse SpriteSheet. " + images[0].path); } catch (error) { console.log("Image Packing Error: "); console.log(error); } - if (spriteSheetType !== null) spriteSheetType.meta.image = base64; - return spriteSheetType; + if (archiveType.spriteSheetType !== null) { + archiveType.spriteSheetType.meta.image = imageData.name; + archiveType.imageData = imageData; + } + return archiveType; } } \ No newline at end of file diff --git a/src/downloaders/EffectDownloader.ts b/src/downloaders/EffectDownloader.ts new file mode 100644 index 0000000..e141ebb --- /dev/null +++ b/src/downloaders/EffectDownloader.ts @@ -0,0 +1,68 @@ +import Configuration from "../config/Configuration"; +import HabboAssetSWF from "../swf/HabboAssetSWF"; +import File from "../utils/File"; + +const fs = require("fs"); +const fetch = require('node-fetch'); +const xml2js = require('xml2js'); +const parser = new xml2js.Parser(/* options */); +const util = require('util'); + +const readFile = util.promisify(fs.readFile); + +export default class EffectDownloader { + private readonly _config: Configuration; + + constructor(config: Configuration) { + this._config = config; + } + + + public static types: Map = new Map(); + + public async download(callback: (habboAssetSwf: HabboAssetSWF) => Promise) { + const outputFolderEffect = this._config.getValue("output.folder.effect"); + const figureMap = await this.parseEffectMap(); + const map = figureMap.map; + + for (const lib of map.effect) { + const info = lib['$']; + const className: string = info.lib; + + //if (className !== 'Hoverboard' && className !== 'Staff' && className !== 'ZombieMask' && className !== 'ESredUntouchable') continue; + + const assetOutputFolder = new File(outputFolderEffect + "/" + className); + if (assetOutputFolder.exists()) { + continue; + } + + if (!EffectDownloader.types.has(className)) { + const url = this._config.getValue("dynamic.download.url.effect").replace("%className%", className); + if (!fs.existsSync(url)) { + console.log("SWF File does not exist: " + url); + continue; + } + + try { + const buffer: Buffer = await readFile(url); + const habboAssetSWF = new HabboAssetSWF(buffer); + await habboAssetSWF.setupAsync(); + + EffectDownloader.types.set(className, info.type); + await callback(habboAssetSWF); + } catch (e) { + console.log(className); + console.log(e); + } + } + } + } + + async parseEffectMap() { + const figureMapPath = this._config.getValue("effectmap.url"); + const figureFetch = await fetch(figureMapPath); + const figureMap = await figureFetch.text(); + + return await parser.parseStringPromise(figureMap); + } +} \ No newline at end of file diff --git a/src/downloaders/FigureDownloader.ts b/src/downloaders/FigureDownloader.ts index 452b4a4..a450f3a 100644 --- a/src/downloaders/FigureDownloader.ts +++ b/src/downloaders/FigureDownloader.ts @@ -19,7 +19,7 @@ export default class FigureDownloader { } - public static types: Map = new Map(); + public static types: Map = new Map(); public async download(callback: (habboAssetSwf: HabboAssetSWF) => Promise) { const outputFolderFigure = this._config.getValue("output.folder.figure"); diff --git a/src/utils/NitroBundle.ts b/src/utils/NitroBundle.ts new file mode 100644 index 0000000..cd7af7f --- /dev/null +++ b/src/utils/NitroBundle.ts @@ -0,0 +1,38 @@ +const ByteBuffer = require('bytebuffer'); +const {gzip} = require('node-gzip'); + +export default class NitroBundle { + private readonly _files: Map; + + constructor() { + this._files = new Map(); + } + + addFile(name: string, data: Buffer) { + this._files.set(name, data); + } + + async toBufferAsync() { + const buffer = new ByteBuffer(); + + buffer.writeUInt16(this._files.size); + + const iterator = this._files.entries(); + let result: IteratorResult<[string, Buffer]> = iterator.next(); + while (!result.done) { + const fileName = result.value[0]; + const file = result.value[1]; + + buffer.writeUint16(fileName.length); + buffer.writeString(fileName); + + const compressed = await gzip(file); + buffer.writeUint32(compressed.length); + buffer.append(compressed); + + result = iterator.next(); + } + + return buffer.flip().toBuffer(); + } +} \ No newline at end of file