From 6349a4f0d2debeccd1eb84d03b72cf2a3c892dc4 Mon Sep 17 00:00:00 2001 From: SpreedBLood Date: Tue, 2 Feb 2021 04:43:38 +0100 Subject: [PATCH] Pets --- config.ini | 4 +- src/Main.ts | 29 +- src/config/Configuration.ts | 15 +- src/converters/pet/PetConverter.ts | 100 ++++ src/converters/pet/PetJsonMapper.ts | 414 ++++++++++++++++ src/converters/pet/PetTypes.ts | 175 +++++++ src/converters/pet/PetXMLTypes.ts | 290 +++++++++++ src/converters/pet/RGB.ts | 23 + src/converters/pet/VisualizationXMLTypes.ts | 517 ++++++++++++++++++++ src/downloaders/PetDownloader.ts | 50 ++ src/swf/tags/DefineBinaryDataTag.ts | 6 + 11 files changed, 1618 insertions(+), 5 deletions(-) create mode 100644 src/converters/pet/PetConverter.ts create mode 100644 src/converters/pet/PetJsonMapper.ts create mode 100644 src/converters/pet/PetTypes.ts create mode 100644 src/converters/pet/PetXMLTypes.ts create mode 100644 src/converters/pet/RGB.ts create mode 100644 src/converters/pet/VisualizationXMLTypes.ts create mode 100644 src/downloaders/PetDownloader.ts diff --git a/config.ini b/config.ini index 44ebc9e..13af61b 100644 --- a/config.ini +++ b/config.ini @@ -9,8 +9,8 @@ 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=/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 +dynamic.download.url.pet=/home/user/WebstormProjects/sites/assets.nitro.se/game/gordon/PRODUCTION-201701242205-837386173/%className%.swf +convert.furniture=0 convert.figure=0 convert.effect=1 convert.pet=1 diff --git a/src/Main.ts b/src/Main.ts index 9f6c5d1..f5af61b 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -8,6 +8,8 @@ import FurnitureDownloader from "./downloaders/FurnitureDownloader"; import FurnitureConverter from "./converters/furniture/FurnitureConverter"; import EffectConverter from "./converters/effect/EffectConverter"; import EffectDownloader from "./downloaders/EffectDownloader"; +import PetDownloader from "./downloaders/PetDownloader"; +import PetConverter from "./converters/pet/PetConverter"; (async () => { const config = new Configuration(); @@ -28,10 +30,16 @@ import EffectDownloader from "./downloaders/EffectDownloader"; outputFolderEffect.mkdirs(); } + const outputFolderPet = new File(config.getValue("output.folder.pet")); + if (!outputFolderPet.isDirectory()) { + outputFolderPet.mkdirs(); + } + const spriteSheetConverter = new SpriteSheetConverter(); const figureConverter = new FigureConverter(config); const furnitureConverter = new FurnitureConverter(config); const effectConverter = new EffectConverter(config); + const petConverter = new PetConverter(); if (config.getBoolean("convert.figure")) { const figureDownloader = new FigureDownloader(config); @@ -74,7 +82,7 @@ import EffectDownloader from "./downloaders/EffectDownloader"; if (config.getBoolean("convert.effect")) { const effectDownloader = new EffectDownloader(config); await effectDownloader.download(async function (habboAssetSwf: HabboAssetSWF) { - console.log("Attempt parsing figure: " + habboAssetSwf.getDocumentClass()); + console.log("Attempt parsing effect: " + habboAssetSwf.getDocumentClass()); try { const spriteSheetType = await spriteSheetConverter.generateSpriteSheet(habboAssetSwf, outputFolderFurniture.path, "effect"); @@ -83,7 +91,24 @@ import EffectDownloader from "./downloaders/EffectDownloader"; } } catch (e) { console.log(e); - console.log("Effect error: "+ habboAssetSwf.getDocumentClass()); + console.log("Effect error: " + habboAssetSwf.getDocumentClass()); + } + }); + } + + if (config.getBoolean("convert.pet")) { + const petDownloader = new PetDownloader(config); + await petDownloader.download(async function (habboAssetSwf: HabboAssetSWF) { + console.log("Attempt parsing pet: " + habboAssetSwf.getDocumentClass()); + + try { + const spriteSheetType = await spriteSheetConverter.generateSpriteSheet(habboAssetSwf, outputFolderPet.path, "pet"); + if (spriteSheetType !== null) { + await petConverter.fromHabboAsset(habboAssetSwf, outputFolderPet.path, "pet", spriteSheetType); + } + } catch (e) { + console.log(e); + console.log("Effect error: " + habboAssetSwf.getDocumentClass()); } }); } diff --git a/src/config/Configuration.ts b/src/config/Configuration.ts index daa0441..879f855 100644 --- a/src/config/Configuration.ts +++ b/src/config/Configuration.ts @@ -1,4 +1,5 @@ const fs = require('fs/promises'); +const fetch = require('node-fetch'); export default class Configuration { @@ -11,7 +12,11 @@ export default class Configuration { async init() { const content = await fs.readFile("/home/user/git/nitro-asset-converter-node/config.ini"); - const config: string[] = content.toString("utf-8").split("\n"); + this.parseContent(content.toString("utf-8")); + } + + private parseContent(content: string) { + const config: string[] = content.split("\n"); for (const configEntry of config) { const configEntrySplit = configEntry.split("="); const configKey = configEntrySplit[0]; @@ -33,4 +38,12 @@ export default class Configuration { return value; } + + public async loadExternalVariables(): Promise { + const url = this.getValue("external_vars.url"); + const fetchData = await fetch(url); + const textData = await fetchData.text(); + + this.parseContent(textData); + } } \ No newline at end of file diff --git a/src/converters/pet/PetConverter.ts b/src/converters/pet/PetConverter.ts new file mode 100644 index 0000000..5cf49ce --- /dev/null +++ b/src/converters/pet/PetConverter.ts @@ -0,0 +1,100 @@ +import HabboAssetSWF from "../../swf/HabboAssetSWF"; +import ArchiveType from "../ArchiveType"; +import File from "../../utils/File"; +import NitroBundle from "../../utils/NitroBundle"; +import {FurniJson} from "../furniture/FurniTypes"; +import DefineBinaryDataTag from "../../swf/tags/DefineBinaryDataTag"; +import PetJsonMapper from "./PetJsonMapper"; + +const xml2js = require('xml2js'); +const parser = new xml2js.Parser(/* options */); + +const fs = require('fs').promises; + +export default class PetConverter { + + private readonly _petJsonMapper: PetJsonMapper; + + constructor() { + this._petJsonMapper = new PetJsonMapper(); + } + + 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 getAssetsXML(habboAssetSWF: HabboAssetSWF): Promise { + const binaryData: DefineBinaryDataTag | null = PetConverter.getBinaryData(habboAssetSWF, "assets", true); + if (binaryData !== null) { + return await parser.parseStringPromise(binaryData.binaryData); + } + + return null; + } + + private static async getLogicXML(habboAssetSWF: HabboAssetSWF): Promise { + const binaryData: DefineBinaryDataTag | null = PetConverter.getBinaryData(habboAssetSWF, "logic", true); + if (binaryData !== null) { + return await parser.parseStringPromise(binaryData.binaryData); + } + + return null; + } + + private static async getIndexXML(habboAssetSWF: HabboAssetSWF): Promise { + const binaryData: DefineBinaryDataTag | null = PetConverter.getBinaryData(habboAssetSWF, "index", false); + if (binaryData !== null) { + return await parser.parseStringPromise(binaryData.binaryData); + } + + return null; + } + + private static async getVisualizationXML(habboAssetSWF: HabboAssetSWF): Promise { + const binaryData: DefineBinaryDataTag | null = PetConverter.getBinaryData(habboAssetSWF, "visualization", true); + if (binaryData !== null) { + return await parser.parseStringPromise(binaryData.binaryData); + } + + return null; + } + + private async convertXML2JSON(habboAssetSWF: HabboAssetSWF): Promise { + const assetXml = await PetConverter.getAssetsXML(habboAssetSWF); + const logicXml = await PetConverter.getLogicXML(habboAssetSWF); + const indexXml = await PetConverter.getIndexXML(habboAssetSWF); + const visualizationXml = await PetConverter.getVisualizationXML(habboAssetSWF); + + return this._petJsonMapper.mapXML(habboAssetSWF, assetXml, indexXml, logicXml, visualizationXml); + } + + public async fromHabboAsset(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string, archiveType: ArchiveType) { + const petJson = await this.convertXML2JSON(habboAssetSWF); + if (petJson !== null) { + petJson.spritesheet = archiveType.spriteSheetType; + petJson.type = type; + + const path = outputFolder + "/" + habboAssetSWF.getDocumentClass() + ".nitro"; + const assetOutputFolder = new File(path); + if (assetOutputFolder.exists()) { + console.log("Pet already exists or the directory is not empty!"); + + return; + } + + const nitroBundle = new NitroBundle(); + nitroBundle.addFile(habboAssetSWF.getDocumentClass() + ".json", Buffer.from(JSON.stringify(petJson))); + 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/pet/PetJsonMapper.ts b/src/converters/pet/PetJsonMapper.ts new file mode 100644 index 0000000..dd9f315 --- /dev/null +++ b/src/converters/pet/PetJsonMapper.ts @@ -0,0 +1,414 @@ +import SpriteSheetConverter from "../util/SpriteSheetConverter"; +import { + Action, Animation, AnimationLayer, AnimationLayers, Animations, Color, ColorLayer, ColorLayers, Colors, + Direction, + Directions, Frame, Frames, FrameSequence, FrameSequences, Gesture, Gestures, + Layer, Offset, Palette, + PetAsset, + PetAssets, + PetJson, Posture, Postures, + Visualization, + VisualizationLayers +} from "./PetTypes"; +import {AssetsXML, IndexXML, LogicXML} from "./PetXMLTypes"; +import { + AnimationLayerXML, + AnimationXML, + ColorXML, + LayerXML, + VisualizationDataXML, + VisualizationXML +} from "./VisualizationXMLTypes"; +import HabboAssetSWF from "../../swf/HabboAssetSWF"; +import RGB from "./RGB"; + +const ByteBuffer = require('bytebuffer'); + +export default class PetJsonMapper { + + + private static readonly VISUALIZATION_DEFAULT_SIZE = 64; + + private static readonly VISUALIZATION_ICON_SIZE = 1; + + public mapXML(habboAssetSWF: HabboAssetSWF, assets: any, indexXML: any, logic: any, visualization: any): PetJson { + const petJson: PetJson = {} as any; + + this.mapAssetsXML(habboAssetSWF, new AssetsXML(assets), petJson); + PetJsonMapper.mapIndexXML(new IndexXML(indexXML.object), petJson); + PetJsonMapper.mapLogicXML(new LogicXML(logic.objectData), petJson); + PetJsonMapper.mapVisualizationXML(new VisualizationDataXML(visualization.visualizationData), petJson); + + return petJson; + } + + + private mapAssetsXML(habboAssetSWF: HabboAssetSWF, assetsXML: AssetsXML, output: PetJson) { + const assets: PetAssets = {} as any; + + for (const asset of assetsXML.assets) { + if (!asset.name.includes("_32_")) { + const petAsset: PetAsset = {} as any; + + if (asset.source !== undefined) { + petAsset.source = asset.source; + if (SpriteSheetConverter.imageSource.has(asset.source)) { + petAsset.source = SpriteSheetConverter.imageSource.get(asset.source) as string; + } + } + + if (SpriteSheetConverter.imageSource.has(asset.name)) { + petAsset.source = SpriteSheetConverter.imageSource.get(asset.name) as string; + } + + petAsset.x = parseInt(asset.x.toString()); + petAsset.y = parseInt(asset.y.toString()); + petAsset.flipH = asset.flipH as any; + petAsset.usesPalette = asset.usesPalette as any; + assets[asset.name] = petAsset; + } + } + + const palettes: Array = new Array(); + if (assetsXML.palettes.length > 0) { + for (const paletteXML of assetsXML.palettes) { + const palette: Palette = {} as any; + palette.id = parseInt(paletteXML.id.toString()); + palette.source = paletteXML.source; + palette.color1 = paletteXML.color1; + palette.color2 = paletteXML.color2; + + const paletteColors = this.getPalette(habboAssetSWF, paletteXML.source); + + const RGB: Array> = new Array>(); + if (paletteColors !== null) + for (const rgb of paletteColors) { + const rgbs: Array = new Array(); + rgbs.push(rgb.r); + rgbs.push(rgb.g); + rgbs.push(rgb.b); + RGB.push(rgbs); + } + + palette.rgb = RGB; + + palettes.push(palette); + } + } + + if (Object.keys(assets).length > 0) { + output.assets = assets; + } + + output.palettes = palettes; + } + + private getPalette(habboAssetSWF: HabboAssetSWF, paletteName: string): Array | null { + const binaryData = this.getBinaryData(habboAssetSWF, paletteName, false); + if (binaryData !== null) { + const byteBuffer = ByteBuffer.wrap(binaryData); + const paletteColors: Array = new Array(); + + try { + let R = 0; + let G = 0; + let B = 0; + let counter = 1; + while ((binaryData.length - byteBuffer.offset) > 0) { + if (counter == 1) { + R = byteBuffer.readUInt8(); + } else if (counter == 2) { + G = byteBuffer.readUInt8(); + } else if (counter == 3) { + B = byteBuffer.readUInt8(); + paletteColors.push(new RGB(R, G, B)); + counter = 0; + } + counter++; + } + + return paletteColors; + } catch (err) { + console.log(err); + } + } + + return null; + } + + + private getBinaryData(habboAssetSWF: HabboAssetSWF, type: string, documentNameTwice: boolean): Buffer | null { + let binaryName = habboAssetSWF.getFullClassName(type, documentNameTwice); + let tag = habboAssetSWF.getBinaryTagByName(binaryName); + if (tag === null) { + binaryName = habboAssetSWF.getFullClassNameSnake(type, documentNameTwice, true); + tag = habboAssetSWF.getBinaryTagByName(binaryName); + } + if (tag === null) { + return null; + } + + return tag.binaryDataBuffer; + } + + private static mapIndexXML(indexXML: IndexXML, output: PetJson) { + output.name = indexXML.type; + output.logicType = indexXML.logic; + output.visualizationType = indexXML.visualization; + } + + private static mapLogicXML(logicXML: LogicXML, output: PetJson) { + output.dimensions = { + x: parseInt(logicXML.model.dimensions.x.toString()), + y: parseInt(logicXML.model.dimensions.y.toString()), + z: parseInt(logicXML.model.dimensions.z.toString()) + } + + const directions: Array = []; + if (logicXML.model.directions.length === 0) { + directions.push(0); + } else { + for (const direction of logicXML.model.directions) { + directions.push(parseInt(direction.id.toString())); + } + } + + if (logicXML.mask !== undefined) { + output.maskType = logicXML.mask.type; + } + + output.directions = directions; + } + + private static mapVisualizationXML(visualizationData: VisualizationDataXML, output: PetJson) { + const visualizationsArray: Array = new Array(); + + for (const visualization of visualizationData.visualizations) { + if (visualization.size == PetJsonMapper.VISUALIZATION_DEFAULT_SIZE || visualization.size == PetJsonMapper.VISUALIZATION_ICON_SIZE) { + const visualizationJson: Visualization = {} as any; + visualizationJson.angle = parseInt(visualization.angle.toString()); + visualizationJson.layerCount = parseInt(visualization.layerCount.toString()); + visualizationJson.size = parseInt(visualization.size.toString()); + + PetJsonMapper.mapVisualizationLayerXML(visualization, visualizationJson); + PetJsonMapper.mapVisualizationDirectionXML(visualization, visualizationJson); + PetJsonMapper.mapVisualizationColorXML(visualization, visualizationJson); + PetJsonMapper.mapVisualizationAnimationXML(visualization, visualizationJson); + PetJsonMapper.mapVisualizationPostureXML(visualization, visualizationJson); + PetJsonMapper.mapVisualizationGestureXML(visualization, visualizationJson); + + visualizationsArray.push(visualizationJson); + } + } + + output.visualizations = visualizationsArray; + } + + private static mapVisualizationLayerXML(visXML: VisualizationXML, output: Visualization) { + if (visXML.layers.length > 0) { + output.layers = PetJsonMapper.mapVisualizationLayersXML(visXML.layers); + } + } + + private static mapVisualizationLayersXML(layersXML: Array): VisualizationLayers { + const layers: VisualizationLayers = {}; + for (const layerXML of layersXML) { + const layer: Layer = {} as any; + if (layer.alpha !== undefined) + layer.alpha = parseInt(layerXML.alpha.toString()); + layer.ink = layerXML.ink; + layer.tag = layerXML.tag; + + if (layerXML.x !== undefined) + layer.x = parseInt(layerXML.x.toString()); + if (layerXML.y !== undefined) + layer.y = parseInt(layerXML.y.toString()); + if (layerXML.z !== undefined) + layer.z = parseInt(layerXML.z.toString()); + layer.ignoreMouse = layerXML.ignoreMouse as any; + + layers[layerXML.id] = layer; + } + + return layers; + } + + private static mapVisualizationDirectionXML(visXML: VisualizationXML, output: Visualization) { + if (visXML.directions.length > 0) { + const directions: Directions = {} as any; + for (const directionXML of visXML.directions) { + const direction: Direction = {} as any; + if (directionXML.layers.length > 0) { + direction.layers = PetJsonMapper.mapVisualizationLayersXML(directionXML.layers); + } + + directions[directionXML.id] = direction; + } + + if (Object.keys(directions).length > 0) { + output.directions = directions; + } + } + } + + private static mapVisualizationColorXML(visXML: VisualizationXML, output: Visualization) { + if (visXML.colors.length > 0) { + const colors: Colors = {}; + for (const colorXML of visXML.colors) { + if (colorXML.layers.length > 0) { + const color: Color = {} as any; + color.layers = PetJsonMapper.mapVisualizationColorLayerXML(colorXML); + + colors[colorXML.id] = color; + } + } + + if (Object.keys(colors).length > 0) { + output.colors = colors; + } + } + } + + private static mapVisualizationColorLayerXML(colorXML: ColorXML): ColorLayers { + const colorLayers: ColorLayers = {}; + for (const colorLayerXML of colorXML.layers) { + const colorLayer: ColorLayer = {} as any; + colorLayer.color = parseInt(colorLayerXML.color, 16); + + colorLayers[colorLayerXML.id] = colorLayer; + } + + return colorLayers; + } + + private static mapVisualizationAnimationXML(visXML: VisualizationXML, output: Visualization) { + if (visXML.animations.length > 0) { + const animations: Animations = {}; + for (const animationXML of visXML.animations) { + if (animationXML.layers.length > 0) { + const animation: Animation = {} as any; + + if (animationXML.transitionTo !== undefined) + animation.transitionTo = parseInt(animationXML.transitionTo.toString()); + if (animationXML.transitionFrom !== undefined) + animation.transitionFrom = parseInt(animationXML.transitionFrom.toString()); + animation.immediateChangeFrom = animationXML.immediateChangeFrom; + + animation.layers = PetJsonMapper.mapVisualizationAnimationLayerXML(animationXML); + + animations[animationXML.id] = animation; + } + } + + if (Object.keys(animations).length > 0) { + output.animations = animations; + } + } + } + + private static mapVisualizationAnimationLayerXML(animationXML: AnimationXML): AnimationLayers { + const animationLayers: AnimationLayers = {}; + for (const animationLayerXML of animationXML.layers) { + const animationLayer: AnimationLayer = {} as any; + if (animationLayerXML.frameRepeat !== undefined) + animationLayer.frameRepeat = parseInt(animationLayerXML.frameRepeat.toString()); + if (animationLayerXML.loopCount !== undefined) + animationLayer.loopCount = parseInt(animationLayerXML.loopCount.toString()); + if (animationLayerXML.random !== undefined) + animationLayer.random = parseInt(animationLayerXML.random.toString()); + + if (animationLayerXML.frameSequences.length > 0) { + animationLayer.frameSequences = PetJsonMapper.mapVisualizationFrameSequenceXML(animationLayerXML); + animationLayers[animationLayerXML.id] = animationLayer; + } + } + + return animationLayers; + } + + private static mapVisualizationFrameSequenceXML(animationLayerXML: AnimationLayerXML): FrameSequences { + const frameSequences: FrameSequences = {}; + let frameSequenceCount = 0; + for (const frameSequenceXML of animationLayerXML.frameSequences) { + const frameSequence: FrameSequence = {} as any; + + if (frameSequenceXML.frames.length > 0) { + let frameId = 0; + const frames: Frames = {}; + for (const frameXML of frameSequenceXML.frames) { + const frame: Frame = {} as any; + if (frameXML.x !== undefined) + frame.x = parseInt(frameXML.x.toString()); + if (frameXML.y !== undefined) + frame.y = parseInt(frameXML.y.toString()); + if (frameXML.randomX !== undefined) + frame.randomX = parseInt(frameXML.randomX.toString()); + if (frameXML.randomY !== undefined) + frame.randomY = parseInt(frameXML.randomY.toString()); + if (frameXML.id === "NaN") { + frame.id = 0; + } else { + frame.id = parseInt(frameXML.id); + } + if (frameXML.offsets.length > 0) { + const offsets: Array = new Array(); + for (const offsetXML of frameXML.offsets) { + const offset: Offset = {} as any; + offset.direction = parseInt(offsetXML.direction.toString()); + if (offsetXML.x !== undefined) + offset.x = parseInt(offsetXML.x.toString()); + if (offsetXML.y !== undefined) + offset.y = parseInt(offsetXML.y.toString()); + offsets.push(offset); + } + frame.offsets = offsets; + } + frames[frameId] = frame; + frameId++; + } + if (frameSequenceXML.loopCount !== undefined) + frameSequence.loopCount = parseInt(frameSequenceXML.loopCount.toString()); + if (frameSequenceXML.random !== undefined) + frameSequence.random = parseInt(frameSequenceXML.random.toString()); + frameSequence.frames = frames; + frameSequences[frameSequenceCount] = frameSequence; + } + frameSequenceCount++; + } + + return frameSequences; + } + + private static mapVisualizationPostureXML(visXML: VisualizationXML, output: Visualization) { + if (visXML.postures.length > 0) { + const postures: Postures = {}; + for (const postureXML of visXML.postures) { + const posture: Posture = {} as any; + posture.id = postureXML.id; + posture.animationId = parseInt(postureXML.animationId.toString()); + + postures[postureXML.id] = posture; + } + + if (Object.keys(postures).length > 0) { + output.postures = postures; + } + } + } + + private static mapVisualizationGestureXML(visXML: VisualizationXML, output: Visualization) { + if (visXML.gestures.length > 0) { + const gestures: Gestures = {}; + for (const gestureXML of visXML.gestures) { + const gesture: Gesture = {} as any; + gesture.id = gestureXML.id; + gesture.animationId = parseInt(gestureXML.animationId.toString()); + gestures[gestureXML.id] = gesture; + } + + if (Object.keys(gestures).length > 0) { + output.gestures = gestures; + } + } + } +} \ No newline at end of file diff --git a/src/converters/pet/PetTypes.ts b/src/converters/pet/PetTypes.ts new file mode 100644 index 0000000..c4550a3 --- /dev/null +++ b/src/converters/pet/PetTypes.ts @@ -0,0 +1,175 @@ +import {SpriteSheetType} from "../util/SpriteSheetTypes"; + +export interface PetJson { + type: string, + name: string, + visualizationType: string, + logicType: string, + maskType: string, + credits: string, + + spritesheet: SpriteSheetType, + + dimensions: Dimensions, + action: Action; + directions: number[], + assets: PetAssets, + palettes: Array, + visualizations: Visualization[] +} + +export interface Palette { + id: number, + source: string, + color1: string, + color2: string, + + rgb: Array>; +} + +export interface Visualization { + layerCount: number, + angle: number, + size: number, + + layers: VisualizationLayers, + directions: Directions, + colors: Colors, + animations: Animations, + postures: Postures; + gestures: Gestures +} + +export interface Gestures { + [key: string]: Gesture +} + +export interface Gesture { + id: string, + animationId: number +} + +export interface Postures { + [key: string]: Posture +} + +export interface Posture { + id: string, + animationId: number +} + +export interface Offset { + direction: number, + x: number, + y: number +} + +export interface Frame { + id: number, + x: number, + y: number, + randomX: number, + randomY: number, + + offsets: Offset[] +} + +export interface Frames { + [key: number]: Frame +} + +export interface FrameSequence { + loopCount: number, + random: number, + + frames: Frames +} + +export interface FrameSequences { + [key: number]: FrameSequence +} + +export interface AnimationLayer { + loopCount: number, + frameRepeat: number, + random: number, + + frameSequences: FrameSequences +} + +export interface AnimationLayers { + [key: number]: AnimationLayer +} + +export interface Animations { + [key: number]: Animation +} + +export interface Animation { + transitionTo: number, + transitionFrom: number, + immediateChangeFrom: string, + + layers: AnimationLayers; +} + +export interface ColorLayers { + [key: number]: ColorLayer +} + +export interface ColorLayer { + color: number +} + +export interface Colors { + [key: number]: Color +} + +export interface Color { + layers: ColorLayers; +} + +export interface Directions { + [key: number]: Direction +} + +export interface Direction { + layers: VisualizationLayers; +} + +export interface VisualizationLayers { + [key: number]: Layer +} + +export interface Layer { + alpha: number, + x: number, + y: number, + z: number, + ink: string, + tag: string, + ignoreMouse: boolean +} + +export interface Action { + link: string, + startState: number +} + +export interface Dimensions { + x: number, + y: number, + z: number +} + +export interface PetAssets { + [key: string]: PetAsset +} + +export interface PetAsset { + source: string, + x: number, + y: number, + flipH: boolean, + usesPalette: boolean, +} \ No newline at end of file diff --git a/src/converters/pet/PetXMLTypes.ts b/src/converters/pet/PetXMLTypes.ts new file mode 100644 index 0000000..ed9225c --- /dev/null +++ b/src/converters/pet/PetXMLTypes.ts @@ -0,0 +1,290 @@ +export class AssetsXML { + private readonly _assets: Array; + private readonly _palettes: Array; + + constructor(assetsXML: any) { + this._assets = new Array(); + + for (const asset of assetsXML.assets.asset) { + this._assets.push(new AssetXML(asset)); + } + + this._palettes = new Array(); + if (assetsXML.assets.palette !== undefined) + for (const palette of assetsXML.assets.palette) { + this._palettes.push(new PaletteXML(palette)); + } + } + + get assets(): Array { + return this._assets; + } + + get palettes(): Array { + return this._palettes; + } +} + +export class PaletteXML { + private readonly _id: number; + private readonly _source: string; + private readonly _color1: string; + private readonly _color2: string; + + constructor(paletteXML: any) { + const attributes = paletteXML.$; + + this._id = attributes.id; + this._source = attributes.source; + this._color1 = attributes.color1; + this._color2 = attributes.color2; + } + + get id(): number { + return this._id; + } + + get source(): string { + return this._source; + } + + get color1(): string { + return this._color1; + } + + get color2(): string { + return this._color2; + } +} + +export class AssetXML { + private readonly _name: string; + private readonly _source: string | undefined; + private readonly _x: number; + private readonly _y: number; + private readonly _flipH: boolean | undefined; + private readonly _usesPalette: number | undefined; + + constructor(asset: any) { + const attributes = asset.$; + + this._name = attributes.name; + + if (attributes.source !== undefined) + this._source = attributes.source; + + this._x = attributes.x; + + this._y = attributes.y; + + if (attributes.flipH !== undefined) + this._flipH = attributes.flipH === '1'; + + if (attributes.usesPalette !== undefined) + this._usesPalette = attributes.usesPalette; + } + + get name(): string { + return this._name; + } + + get source(): string | undefined { + return this._source; + } + + get x(): number { + return this._x; + } + + get y(): number { + return this._y; + } + + get flipH(): boolean | undefined { + return this._flipH; + } + + get usesPalette(): number | undefined { + return this._usesPalette; + } +} + +export class IndexXML { + private readonly _type: string; + private readonly _visualization: string; + private readonly _logic: string; + + constructor(indexXML: any) { + const attributes = indexXML.$; + + this._type = attributes.type; + this._visualization = attributes.visualization; + this._logic = attributes.logic; + } + + get type(): string { + return this._type; + } + + get visualization(): string { + return this._visualization; + } + + get logic(): string { + return this._logic; + } +} + +export class LogicXML { + private readonly _type: string; + private readonly _model: ModelXML; + private readonly _action: ActionXML | undefined; + private readonly _mask: MaskXML | undefined; + private readonly _credits: CreditsXML | undefined; + + constructor(logicXML: any) { + const attributes = logicXML.$; + this._type = attributes.type; + + this._model = new ModelXML(logicXML.model[0]); + if (logicXML.action !== undefined) + this._action = new ActionXML(logicXML.action[0]); + + if (logicXML.mask !== undefined) + this._mask = new MaskXML(logicXML.mask[0]); + + if (logicXML.credits !== undefined) + this._credits = new CreditsXML(logicXML.credits[0]); + } + + get type(): string { + return this._type; + } + + get model(): ModelXML { + return this._model; + } + + get action(): ActionXML | undefined { + return this._action; + } + + get mask(): MaskXML | undefined { + return this._mask; + } + + get credits(): CreditsXML | undefined { + return this._credits; + } +} + +export class ModelXML { + private readonly _dimensions: DimensionsXML; + private readonly _directions: Array; + + constructor(modelXML: any) { + this._dimensions = new DimensionsXML(modelXML.dimensions[0]); + this._directions = new Array(); + + if (Array.isArray(modelXML.directions)) { + for (const directionParent of modelXML.directions) { + if (Array.isArray(directionParent.direction)) { + for (const direction of directionParent.direction) { + this._directions.push(new DirectionXML(direction.$)); + } + } else { + console.log(directionParent.direction); + } + } + } + } + + get dimensions(): DimensionsXML { + return this._dimensions; + } + + get directions(): Array { + return this._directions; + } +} + +export class DimensionsXML { + private readonly _x: number; + private readonly _y: number; + private readonly _z: number; + + constructor(dimensionsXML: any) { + const attributes = dimensionsXML.$; + + this._x = attributes.x; + this._y = attributes.y; + this._z = attributes.z; + } + + get x(): number { + return this._x; + } + + get y(): number { + return this._y; + } + + get z(): number { + return this._z; + } +} + +export class DirectionXML { + private readonly _id: number; + + constructor(directionXML: any) { + this._id = directionXML.id; + } + + get id(): number { + return this._id; + } +} + +export class ActionXML { + private readonly _link: string; + private readonly _startState: number; + + constructor(actionXML: any) { + const attributes = actionXML.$; + this._link = attributes.link; + this._startState = attributes.startState; + } + + get link(): string { + return this._link; + } + + get startState(): number { + return this._startState; + } +} + +export class MaskXML { + private readonly _type: string; + + constructor(maskXML: any) { + this._type = maskXML.$.type; + } + + get type(): string { + return this._type; + } +} + +export class CreditsXML { + private readonly _value: string; + + constructor(creditsXML: any) { + this._value = creditsXML.$.value; + } + + get value(): string { + return this._value; + } +} \ No newline at end of file diff --git a/src/converters/pet/RGB.ts b/src/converters/pet/RGB.ts new file mode 100644 index 0000000..00d5354 --- /dev/null +++ b/src/converters/pet/RGB.ts @@ -0,0 +1,23 @@ +export default class RGB { + private readonly _r: number; + private readonly _g: number; + private readonly _b: number; + + constructor(r: number, g: number, b: number) { + this._r = r; + this._g = g; + this._b = b; + } + + get r(): number { + return this._r; + } + + get g(): number { + return this._g; + } + + get b(): number { + return this._b; + } +} \ No newline at end of file diff --git a/src/converters/pet/VisualizationXMLTypes.ts b/src/converters/pet/VisualizationXMLTypes.ts new file mode 100644 index 0000000..0956cb1 --- /dev/null +++ b/src/converters/pet/VisualizationXMLTypes.ts @@ -0,0 +1,517 @@ +export class VisualizationDataXML { + + private readonly _type: string; + private readonly _visualizations: Array; + + constructor(visualizationXML: any) { + this._type = visualizationXML.$.type; + this._visualizations = new Array(); + + if (Array.isArray(visualizationXML.graphics)) { + for (const graphic of visualizationXML.graphics) { + for (const visualization of graphic.visualization) { + this._visualizations.push(new VisualizationXML(visualization)); + } + } + } + } + + get type(): string { + return this._type; + } + + get visualizations(): Array { + return this._visualizations; + } +} + +export class VisualizationXML { + + private readonly _size: number; + private readonly _layerCount: number; + private readonly _angle: number; + + private readonly _layers: Array; + private readonly _directions: Array; + private readonly _colors: Array; + private readonly _animations: Array; + private readonly _postures: Array; + private readonly _gestures: Array; + + constructor(visualizationXML: any) { + const attributes = visualizationXML.$; + this._size = attributes.size; + this._layerCount = attributes.layerCount; + this._angle = attributes.angle; + + this._layers = new Array(); + if (visualizationXML.layers !== undefined) + for (const layerParent of visualizationXML.layers) { + if (Array.isArray(layerParent.layer)) { + for (const layer of layerParent.layer) { + this._layers.push(new LayerXML(layer)); + } + } + } + + this._directions = new Array(); + if (visualizationXML.directions !== undefined) { + for (const directionParent of visualizationXML.directions) { + if (Array.isArray(directionParent.direction)) { + for (const direction of directionParent.direction) { + this._directions.push(new DirectionXML(direction)); + } + } + } + } + + this._colors = new Array(); + if (visualizationXML.colors !== undefined) { + for (const colorParent of visualizationXML.colors) { + if (Array.isArray(colorParent.color)) { + for (const color of colorParent.color) { + this._colors.push(new ColorXML(color)); + } + } + } + } + + this._animations = new Array(); + if (visualizationXML.animations !== undefined) { + for (const animationParent of visualizationXML.animations) { + if (Array.isArray(animationParent.animation)) { + for (const animation of animationParent.animation) { + this._animations.push(new AnimationXML(animation)); + } + } + } + } + + this._postures = new Array(); + if (visualizationXML.postures !== undefined) { + for (const postureParent of visualizationXML.postures) { + if (Array.isArray(postureParent.posture)) { + for (const posture of postureParent.posture) { + this._postures.push(new PostureXML(posture)); + } + } + } + } + this._gestures = new Array(); + if (visualizationXML.gestures !== undefined) { + for (const gestureParent of visualizationXML.gestures) { + if (Array.isArray(gestureParent.gesture)) { + for (const gesture of gestureParent.gesture) { + this._gestures.push(new GestureXML(gesture)); + } + } + } + } + } + + get size(): number { + return this._size; + } + + get layerCount(): number { + return this._layerCount; + } + + get angle(): number { + return this._angle; + } + + get layers(): Array { + return this._layers; + } + + get directions(): Array { + return this._directions; + } + + get colors(): Array { + return this._colors; + } + + get animations(): Array { + return this._animations; + } + + get postures(): Array { + return this._postures; + } + + get gestures(): Array { + return this._gestures; + } +} + +export class LayerXML { + private readonly _id: number; + private readonly _alpha: number; + private readonly _x: number; + private readonly _y: number; + private readonly _z: number; + private readonly _ink: string; + private readonly _tag: string; + private readonly _ignoreMouse: boolean | undefined; + + constructor(layerXML: any) { + const attributes = layerXML.$; + + this._id = attributes.id; + this._alpha = attributes.alpha; + this._x = attributes.x; + this._y = attributes.y; + this._z = attributes.z; + this._ink = attributes.ink; + this._tag = attributes.tag; + if (attributes.ignoreMouse !== undefined) { + this._ignoreMouse = attributes.ignoreMouse === '1'; + } + } + + get id(): number { + return this._id; + } + + get alpha(): number { + return this._alpha; + } + + get x(): number { + return this._x; + } + + get y(): number { + return this._y; + } + + get z(): number { + return this._z; + } + + get ink(): string { + return this._ink; + } + + get tag(): string { + return this._tag; + } + + get ignoreMouse(): boolean | undefined { + return this._ignoreMouse; + } +} + +export class DirectionXML { + private readonly _id: number; + private readonly _layers: Array; + + constructor(directionXML: any) { + this._id = directionXML.$.id; + + this._layers = new Array(); + if (directionXML.layer !== undefined) { + for (const layer of directionXML.layer) { + this._layers.push(new LayerXML(layer)); + } + } + } + + get id(): number { + return this._id; + } + + get layers(): Array { + return this._layers; + } +} + +export class ColorXML { + private readonly _id: number; + private readonly _layers: Array; + + constructor(colorXML: any) { + this._id = colorXML.$.id; + + this._layers = new Array(); + for (const colorLayer of colorXML.colorLayer) { + this._layers.push(new ColorLayerXML(colorLayer)); + } + } + + + get id(): number { + return this._id; + } + + get layers(): Array { + return this._layers; + } +} + +export class ColorLayerXML { + private readonly _id: number; + private readonly _color: string; + + constructor(colorLayerXML: any) { + const attributes = colorLayerXML.$; + this._id = attributes.id; + this._color = attributes.color; + } + + get id(): number { + return this._id; + } + + get color(): string { + return this._color; + } +} + +export class AnimationXML { + private readonly _id: number; + private readonly _transitionTo: number; + private readonly _transitionFrom: number; + private readonly _immediateChangeFrom: string; + private readonly _layers: Array; + + constructor(animationXML: any) { + const attributes = animationXML.$; + this._id = attributes.id; + this._transitionTo = attributes.transitionTo; + this._transitionFrom = attributes.transitionFrom; + this._immediateChangeFrom = attributes.immediateChangeFrom; + + this._layers = new Array(); + if (animationXML.animationLayer !== undefined) { + for (const animationLayer of animationXML.animationLayer) { + this._layers.push(new AnimationLayerXML(animationLayer)) + } + } + } + + get id(): number { + return this._id; + } + + get transitionTo(): number { + return this._transitionTo; + } + + get transitionFrom(): number { + return this._transitionFrom; + } + + get immediateChangeFrom(): string { + return this._immediateChangeFrom; + } + + get layers(): Array { + return this._layers; + } +} + +export class AnimationLayerXML { + private readonly _id: number; + private readonly _loopCount: number; + private readonly _frameRepeat: number; + private readonly _random: number; + private readonly _randomStart: number; + + private readonly _frameSequences: Array; + + constructor(animationLayerXML: any) { + const attributes = animationLayerXML.$; + this._id = attributes.id; + this._loopCount = attributes.loopCount; + this._frameRepeat = attributes.frameRepeat; + this._random = attributes.random; + this._randomStart = attributes.randomStart; + + this._frameSequences = new Array(); + if (animationLayerXML.frameSequence !== undefined) { + for (const frameSequence of animationLayerXML.frameSequence) { + this._frameSequences.push(new FrameSequenceXML(frameSequence)); + } + } + } + + get id(): number { + return this._id; + } + + get loopCount(): number { + return this._loopCount; + } + + get frameRepeat(): number { + return this._frameRepeat; + } + + get random(): number { + return this._random; + } + + get randomStart(): number { + return this._randomStart; + } + + get frameSequences(): Array { + return this._frameSequences; + } +} + +export class FrameSequenceXML { + private readonly _loopCount: number; + private readonly _random: number; + + private readonly _frames: Array; + + constructor(frameSequenceXML: any) { + let attributes = frameSequenceXML.$; + if (attributes === undefined) attributes = {}; + + this._loopCount = attributes.loopCount; + this._random = attributes.random; + + this._frames = new Array(); + if (frameSequenceXML.frame !== undefined) { + for (const frame of frameSequenceXML.frame) { + this._frames.push(new FrameXML(frame)); + } + } + } + + get loopCount(): number { + return this._loopCount; + } + + get random(): number { + return this._random; + } + + get frames(): Array { + return this._frames; + } +} + +export class FrameXML { + private readonly _id: string; + private readonly _x: number; + private readonly _y: number; + private readonly _randomX: number; + private readonly _randomY: number; + + private readonly _offsets: Array; + + constructor(frameXML: any) { + const attributes = frameXML.$; + + this._id = attributes.id; + this._x = attributes.x; + this._y = attributes.y; + this._randomX = attributes.randomX; + this._randomY = attributes.randomY; + + this._offsets = new Array(); + if (frameXML.offsets !== undefined) { + for (const offsetParent of frameXML.offsets) { + for (const offset of offsetParent.offset) { + this._offsets.push(new OffsetXML(offset)); + } + } + } + } + + get id(): string { + return this._id; + } + + get x(): number { + return this._x; + } + + get y(): number { + return this._y; + } + + get randomX(): number { + return this._randomX; + } + + get randomY(): number { + return this._randomY; + } + + get offsets(): Array { + return this._offsets; + } +} + +export class OffsetXML { + private readonly _direction: number; + private readonly _x: number; + private readonly _y: number; + + constructor(offsetXML: any) { + const attributes = offsetXML.$; + + this._direction = attributes.direction; + this._x = attributes.x; + this._y = attributes.y; + } + + get direction(): number { + return this._direction; + } + + get x(): number { + return this._x; + } + + get y(): number { + return this._y; + } +} + +export class PostureXML { + private readonly _id: string; + private readonly _animationId: number; + + constructor(postureXML: any) { + const attributes = postureXML.$; + + this._id = attributes.id; + this._animationId = attributes.animationId; + } + + get id(): string { + return this._id; + } + + get animationId(): number { + return this._animationId; + } +} + +export class GestureXML { + private readonly _id: string; + private readonly _animationId: number; + + constructor(gestureXML: any) { + const attributes = gestureXML.$; + + this._id = attributes.id; + this._animationId = attributes.animationId; + } + + get id(): string { + return this._id; + } + + get animationId(): number { + return this._animationId; + } +} \ No newline at end of file diff --git a/src/downloaders/PetDownloader.ts b/src/downloaders/PetDownloader.ts new file mode 100644 index 0000000..4ca2951 --- /dev/null +++ b/src/downloaders/PetDownloader.ts @@ -0,0 +1,50 @@ +import Configuration from "../config/Configuration"; +import HabboAssetSWF from "../swf/HabboAssetSWF"; +import File from "../utils/File"; + +const util = require('util'); +const fs = require("fs"); +const readFile = util.promisify(fs.readFile); + +export default class PetDownloader { + private readonly _config: Configuration; + + constructor(config: Configuration) { + this._config = config; + } + + public async download(callback: (habboAssetSwf: HabboAssetSWF) => Promise) { + const outputFolderPet = new File(this._config.getValue("output.folder.pet")); + await this._config.loadExternalVariables(); + + const pets = this._config.getValue("pet.configuration"); + if (pets !== "") { + const itemClassNames: Array = new Array(); + const petNames: string[] = pets.split(","); + + for (const pet of petNames) { + const petOutputFolder = new File(outputFolderPet.path + "/" + pet); + if (petOutputFolder.isDirectory()) { + continue; + } + + if (!itemClassNames.includes(pet)) { + const url = this._config.getValue("dynamic.download.url.pet").replace("%className%", pet); + const file = new File(url); + if (!file.exists()) { + console.log("SWF File does not exist: " + file.path); + continue; + } + + const buffer: Buffer = await readFile(url); + const habboAssetSWF = new HabboAssetSWF(buffer); + await habboAssetSWF.setupAsync(); + + await callback(habboAssetSWF); + } + + itemClassNames.push(pet); + } + } + } +} \ No newline at end of file diff --git a/src/swf/tags/DefineBinaryDataTag.ts b/src/swf/tags/DefineBinaryDataTag.ts index 28ada3e..c7e959d 100644 --- a/src/swf/tags/DefineBinaryDataTag.ts +++ b/src/swf/tags/DefineBinaryDataTag.ts @@ -9,6 +9,7 @@ export default class DefineBinaryDataTag extends CharacterTag implements ITag { private readonly _tag: number; private readonly _reserved: number; private readonly _binaryData: string; + private readonly _binaryDataBuffer: Buffer; constructor(tag: Tag) { super(); @@ -21,6 +22,7 @@ export default class DefineBinaryDataTag extends CharacterTag implements ITag { const binary = tag.rawData.slice(start, end); this._binaryData = binary.toString("utf-8"); + this._binaryDataBuffer = binary; this.characterId = this._tag; } @@ -40,4 +42,8 @@ export default class DefineBinaryDataTag extends CharacterTag implements ITag { get binaryData(): string { return this._binaryData; } + + get binaryDataBuffer(): Buffer { + return this._binaryDataBuffer; + } } \ No newline at end of file