Effects are done

This commit is contained in:
SpreedBLood 2021-02-02 02:13:17 +01:00
parent bb1381c6db
commit d7cf4b5849
16 changed files with 1420 additions and 46 deletions

View File

@ -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

13
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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

View File

@ -0,0 +1,9 @@
import {SpriteSheetType} from "./util/SpriteSheetTypes";
export default interface ArchiveType {
spriteSheetType: SpriteSheetType,
imageData: {
name: string,
buffer: Buffer
}
}

View File

@ -0,0 +1,522 @@
export class AnimationXML {
private readonly _name: string;
private readonly _desc: string;
private readonly _resetOnToggle: boolean | undefined;
private readonly _directions: Array<DirectionOffsetXML>;
private readonly _shadows: Array<ShadowXML>;
private readonly _adds: Array<AddXML>;
private readonly _removes: Array<RemoveXML>;
private readonly _sprites: Array<SpriteXML>;
private readonly _frames: Array<FrameXML>;
private readonly _avatars: Array<AvatarXML>;
private readonly _overrides: Array<OverrideXML>;
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<DirectionOffsetXML>();
if (animation.direction !== undefined) {
for (const direction of animation.direction) {
this._directions.push(new DirectionOffsetXML(direction));
}
}
this._shadows = new Array<ShadowXML>();
if (animation.shadow !== undefined) {
for (const shadow of animation.shadow) {
this._shadows.push(new ShadowXML(shadow));
}
}
this._adds = new Array<AddXML>();
if (animation.add !== undefined) {
for (const add of animation.add) {
this._adds.push(new AddXML(add));
}
}
this._removes = new Array<RemoveXML>();
if (animation.remove !== undefined) {
for (const remove of animation.remove) {
this._removes.push(new RemoveXML(remove));
}
}
this._sprites = new Array<SpriteXML>();
if (animation.sprite !== undefined) {
for (const sprite of animation.sprite) {
this._sprites.push(new SpriteXML(sprite));
}
}
this._frames = new Array<FrameXML>();
if (animation.frame !== undefined) {
for (const frame of animation.frame) {
this._frames.push(new FrameXML(frame));
}
}
this._avatars = new Array<AvatarXML>();
if (animation.avatar !== undefined) {
for (const avatar of animation.avatar) {
this._avatars.push(new AvatarXML(avatar));
}
}
this._overrides = new Array<OverrideXML>();
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<DirectionOffsetXML> {
return this._directions;
}
get shadows(): Array<ShadowXML> {
return this._shadows;
}
get adds(): Array<AddXML> {
return this._adds;
}
get removes(): Array<RemoveXML> {
return this._removes;
}
get sprites(): Array<SpriteXML> {
return this._sprites;
}
get frames(): Array<FrameXML> {
return this._frames;
}
get avatars(): Array<AvatarXML> {
return this._avatars;
}
get overrides(): Array<OverrideXML> {
return this._overrides;
}
}
export class OverrideXML {
private readonly _name: string;
private readonly _override: string;
private readonly _frames: Array<FrameXML>;
constructor(overrideXML: any) {
const attributes = overrideXML.$;
this._name = attributes.name;
this._override = attributes.override;
this._frames = new Array<FrameXML>();
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<FrameXML> {
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<DirectionXML>;
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<DirectionXML>();
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<DirectionXML> {
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<FxXML>;
private readonly _bodyParts: Array<BodyPartXML>;
constructor(frameXML: any) {
if (frameXML.$ !== undefined)
this._repeats = frameXML.$.repeats;
this._fxs = new Array<FxXML>();
if (frameXML.fx !== undefined) {
for (const fx of frameXML.fx) {
this._fxs.push(new FxXML(fx));
}
}
this._bodyParts = new Array<BodyPartXML>();
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<FxXML> {
return this._fxs;
}
get bodyParts(): Array<BodyPartXML> {
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<ItemXML>;
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<ItemXML>();
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<ItemXML> {
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;
}
}

View File

@ -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<any> {
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<any> {
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<EffectJson | null> {
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);
}
}
}

View File

@ -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<Frame> = new Array<Frame>();
const avatars: Array<Avatar> = new Array<Avatar>();
const directions: Array<DirectionOffset> = new Array<DirectionOffset>();
const shadows: Array<Shadow> = new Array<Shadow>();
const adds: Array<Add> = new Array<Add>();
const removes: Array<Remove> = new Array<Remove>();
const sprites: Array<Sprite> = new Array<Sprite>();
const overrides: Array<Override> = new Array<Override>();
if (animationXML.frames.length > 0) {
for (const frameXML of animationXML.frames) {
const fxs: Array<Fx> = new Array<Fx>();
const bodyparts: Array<Bodypart> = new Array<BodyPartXML>();
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<Item> = new Array<Item>();
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<Direction> = new Array<Direction>();
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<Frame> = new Array<Frame>();
for (const frameXML of overrideXML.frames) {
const fxs: Array<Fx> = new Array<Fx>();
const bodyparts: Array<Bodypart> = new Array<Bodypart>();
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<Item> = new Array<Item>();
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;
}
}

View File

@ -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<AssetXML>;
private readonly _aliases: Array<AliasXML>;
constructor(libraryXML: any) {
const attributes = libraryXML.$;
this._name = attributes.id;
this._version = attributes.version;
this._assets = new Array<AssetXML>();
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<AliasXML>();
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<AssetXML> {
return this._assets;
}
get aliases(): Array<AliasXML> {
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;
}
}

View File

@ -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<DirectionOffset>,
shadows: Array<Shadow>,
adds: Array<Add>,
removes: Array<Remove>,
sprites: Array<Sprite>,
frames: Array<Frame>,
avatars: Array<Avatar>,
overrides: Array<Override>
}
export interface Override {
name: string,
override: string,
frames: Array<Frame>;
}
export interface Avatar {
ink: number,
foreground: string,
background: string
}
export interface Frame {
repeats: number,
fxs: Array<Fx>,
bodyparts: Array<Bodypart>
}
export interface Bodypart {
id: string,
frame: number,
dx: number,
dy: number,
dz: number,
dd: number,
action: string,
base: string,
items: Array<Item>
}
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<Direction>;
}
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,
}

View File

@ -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);
}
}
}

View File

@ -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<any> {
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);
}
}
}

View File

@ -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<String, String> = new Map<String, String>();
public static imageSource: Map<string, string> = new Map<string, string>();
public async generateSpriteSheet(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string): Promise<SpriteSheetType | null> {
public async generateSpriteSheet(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string): Promise<ArchiveType | null> {
const tagList: Array<SymbolClassTag> = habboAssetSWF.symbolTags();
const names: Array<string> = new Array<string>();
const tags: Array<number> = new Array<number>();
@ -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<ImageTag> = 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<SpriteSheetType | null> {
async packImages(documentClass: string, outputFolder: string, images: Array<{ path: string, contents: Buffer }>): Promise<ArchiveType | null> {
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;
}
}

View File

@ -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<string, string> = new Map<string, string>();
public async download(callback: (habboAssetSwf: HabboAssetSWF) => Promise<void>) {
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);
}
}

View File

@ -19,7 +19,7 @@ export default class FigureDownloader {
}
public static types: Map<String, String> = new Map<String, String>();
public static types: Map<string, string> = new Map<string, string>();
public async download(callback: (habboAssetSwf: HabboAssetSWF) => Promise<void>) {
const outputFolderFigure = this._config.getValue("output.folder.figure");

38
src/utils/NitroBundle.ts Normal file
View File

@ -0,0 +1,38 @@
const ByteBuffer = require('bytebuffer');
const {gzip} = require('node-gzip');
export default class NitroBundle {
private readonly _files: Map<string, Buffer>;
constructor() {
this._files = new Map<string, Buffer>();
}
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();
}
}