Merge branch 'dev' into 'master'

Dev

See merge request nitro/nitro-converter!8
This commit is contained in:
Bill 2021-12-08 11:40:13 +00:00
commit f7f78d6fae
84 changed files with 1358 additions and 2170 deletions

View File

@ -2,13 +2,17 @@ import 'reflect-metadata';
import { container } from 'tsyringe';
import { Configuration } from './common/config/Configuration';
import { IConverter } from './common/converters/IConverter';
import { EffectConverter } from './converters/effect/EffectConverter';
import { ExternalTextsConverter } from './converters/externaltexts/ExternalTextsConverter';
import { FigureConverter } from './converters/figure/FigureConverter';
import { FigureDataConverter } from './converters/figuredata/FigureDataConverter';
import { FurnitureConverter } from './converters/furniture/FurnitureConverter';
import { PetConverter } from './converters/pet/PetConverter';
import { ProductDataConverter } from './converters/productdata/ProductDataConverter';
import { EffectConverter } from './converters/EffectConverter';
import { EffectMapConverter } from './converters/EffectMapConverter';
import { ExternalTextsConverter } from './converters/ExternalTextsConverter';
import { FigureConverter } from './converters/FigureConverter';
import { FigureDataConverter } from './converters/FigureDataConverter';
import { FigureMapConverter } from './converters/FigureMapConverter';
import { FurnitureConverter } from './converters/FurnitureConverter';
import { FurnitureDataConverter } from './converters/FurnitureDataConverter';
import { OldAssetConverter } from './converters/OldAssetConverter';
import { PetConverter } from './converters/PetConverter';
import { ProductDataConverter } from './converters/ProductDataConverter';
(async () =>
{
@ -18,13 +22,17 @@ import { ProductDataConverter } from './converters/productdata/ProductDataConver
await config.init();
const converters = [
FurnitureDataConverter,
FigureDataConverter,
ProductDataConverter,
ExternalTextsConverter,
EffectMapConverter,
FigureMapConverter,
FurnitureConverter,
FigureConverter,
EffectConverter,
FurnitureConverter,
PetConverter,
FigureDataConverter
OldAssetConverter
];
const [ arg1, arg2, ...rest ] = process.argv;

View File

@ -0,0 +1,59 @@
import { HabboAssetSWF } from '../swf/HabboAssetSWF';
import { FileUtilities } from '../utils/FileUtilities';
export class SWFDownloader
{
public static USES_REVISION: boolean = true;
public static LOG_DOWNLOADS: boolean = true;
public static async download(baseUrl: string, className: string, revision: number): Promise<HabboAssetSWF>
{
let url = baseUrl;
if(!url || !url.length) return;
if(SWFDownloader.USES_REVISION && (revision > -1)) url = url.replace('%revision%', revision.toString());
url = url.replace('%className%', className);
if(SWFDownloader.LOG_DOWNLOADS)
{
console.log();
console.log(`<Downloader> Downloading ${ className } from ${url}`);
}
const habboAssetSWF = await this.extractSWF(url);
if(!habboAssetSWF) return null;
return habboAssetSWF;
}
public static async downloadFromUrl(url: string, className: string, revision: number): Promise<HabboAssetSWF>
{
if(SWFDownloader.LOG_DOWNLOADS)
{
console.log();
console.log(`<Downloader> Downloading ${ className } from ${url}`);
}
const habboAssetSWF = await this.extractSWF(url);
if(!habboAssetSWF) return null;
return habboAssetSWF;
}
public static async extractSWF(url: string): Promise<HabboAssetSWF>
{
const buffer = await FileUtilities.readFileAsBuffer(url);
if(!buffer) return null;
const habboAssetSWF = new HabboAssetSWF(buffer);
await habboAssetSWF.setupAsync();
return habboAssetSWF;
}
}

View File

@ -1,20 +1,22 @@
import { packAsync } from 'free-tex-packer-core';
import { singleton } from 'tsyringe';
import { HabboAssetSWF } from '../../swf/HabboAssetSWF';
import { ImageBundle } from './ImageBundle';
import { SpriteBundle } from './SpriteBundle';
@singleton()
export class BundleProvider
{
public static imageSource: Map<string, string> = new Map();
public async generateSpriteSheet(habboAssetSWF: HabboAssetSWF): Promise<SpriteBundle>
public static async generateSpriteSheet(habboAssetSWF: HabboAssetSWF, convertCase: boolean = false): Promise<SpriteBundle>
{
const tagList = habboAssetSWF.symbolTags();
const names: string[] = [];
const tags: number[] = [];
let documentClass = habboAssetSWF.getDocumentClass();
if(convertCase) documentClass = (documentClass.replace(/(?:^|\.?)([A-Z])/g, (x,y) => ('_' + y.toLowerCase().replace(/^_/, ''))));
for(const tag of tagList)
{
names.push(...tag.names);
@ -39,7 +41,7 @@ export class BundleProvider
if(imageTag.className.indexOf('_32_') >= 0) continue;
BundleProvider.imageSource.set(names[i].substring(habboAssetSWF.getDocumentClass().length + 1), imageTag.className.substring(habboAssetSWF.getDocumentClass().length + 1));
BundleProvider.imageSource.set(names[i].substring(documentClass.length + 1), imageTag.className.substring(documentClass.length + 1));
}
}
@ -47,20 +49,24 @@ export class BundleProvider
if(imageTag.className.indexOf('_32_') >= 0) continue;
imageBundle.addImage(imageTag.className, imageTag.imgData);
let className = imageTag.className;
if(convertCase) className = ((className.replace(/(?:^|\.?)([A-Z])/g, (x,y) => ('_' + y.toLowerCase().replace(/^_/, '')))).substring(1));
imageBundle.addImage(className, imageTag.imgData);
}
if(!imageBundle.images.length) return null;
return await this.packImages(habboAssetSWF.getDocumentClass(), imageBundle);
return await this.packImages(documentClass, imageBundle, convertCase);
}
async packImages(documentClass: string, imageBundle: ImageBundle): Promise<SpriteBundle>
private static async packImages(documentClass: string, imageBundle: ImageBundle, convertCase: boolean = false): Promise<SpriteBundle>
{
const files = await packAsync(imageBundle.images, {
textureName: documentClass,
width: 3072,
height: 2048,
textureName: (convertCase ? documentClass.substring(1) : documentClass),
width: 10240,
height: 4320,
fixedSize: false,
allowRotation: false,
detectIdentical: true,
@ -86,6 +92,8 @@ export class BundleProvider
name: item.name,
buffer: item.buffer
};
if(convertCase) bundle.imageData.name = (documentClass.replace(/(?:^|\.?)([A-Z])/g, (x,y) => ('_' + y.toLowerCase().replace(/^_/, '')))).substring(1);
}
}

View File

@ -1,7 +1,7 @@
import { wrap } from 'bytebuffer';
import { writeFile } from 'fs/promises';
import { parseStringPromise } from 'xml2js';
import { IAssetData } from '../../mapping/json';
import { AnimationMapper, AssetMapper, IndexMapper, LogicMapper, ManifestMapper, VisualizationMapper } from '../../mapping/mappers';
import { HabboAssetSWF } from '../../swf/HabboAssetSWF';
import { DefineBinaryDataTag } from '../../swf/tags/DefineBinaryDataTag';
import { NitroBundle } from '../../utils/NitroBundle';
@ -10,24 +10,27 @@ import { Converter } from './Converter';
export class SWFConverter extends Converter
{
protected async fromHabboAsset(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string, assetData: IAssetData, spriteBundle: SpriteBundle): Promise<void>
private static removeComments(data: string): string
{
return data.replace(/<!--.*?-->/sg, '');
}
public static createNitroBundle(className: string, assetData: IAssetData, spriteBundle: SpriteBundle): NitroBundle
{
if(spriteBundle && (spriteBundle.spritesheet !== undefined)) assetData.spritesheet = spriteBundle.spritesheet;
const name = habboAssetSWF.getDocumentClass();
const path = outputFolder + '/' + name + '.nitro';
const nitroBundle = new NitroBundle();
nitroBundle.addFile((name + '.json'), Buffer.from(JSON.stringify(assetData)));
nitroBundle.addFile((className + '.json'), Buffer.from(JSON.stringify(assetData)));
if(spriteBundle && (spriteBundle.imageData !== undefined)) nitroBundle.addFile(spriteBundle.imageData.name, spriteBundle.imageData.buffer);
await writeFile(path, await nitroBundle.toBufferAsync());
return nitroBundle;
}
private static getBinaryData(habboAssetSWF: HabboAssetSWF, type: string, documentNameTwice: boolean): DefineBinaryDataTag
public static getBinaryData(habboAssetSWF: HabboAssetSWF, type: string, documentNameTwice: boolean, snakeCase: boolean = false): DefineBinaryDataTag
{
let binaryName = habboAssetSWF.getFullClassName(type, documentNameTwice);
let binaryName = habboAssetSWF.getFullClassName(type, documentNameTwice, snakeCase);
let tag = habboAssetSWF.getBinaryTagByName(binaryName);
if(!tag)
@ -39,64 +42,61 @@ export class SWFConverter extends Converter
return tag;
}
protected static async getManifestXML(habboAssetSWF: HabboAssetSWF): Promise<any>
public static async getManifestXML(habboAssetSWF: HabboAssetSWF, snakeCase: boolean = false): Promise<any>
{
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'manifest', false);
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'manifest', false, snakeCase);
if(!binaryData) return null;
return await parseStringPromise(binaryData.binaryData);
return await parseStringPromise(this.removeComments(binaryData.binaryData));
}
protected static async getIndexXML(habboAssetSWF: HabboAssetSWF): Promise<any>
public static async getIndexXML(habboAssetSWF: HabboAssetSWF, snakeCase: boolean = false): Promise<any>
{
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'index', false);
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'index', false, snakeCase);
if(!binaryData) return null;
//Will fix some malformed comments exception.
const removedComments: string = binaryData.binaryData.replace(/<!--.*?-->/sg, '');
return await parseStringPromise(removedComments);
return await parseStringPromise(this.removeComments(binaryData.binaryData));
}
protected static async getAssetsXML(habboAssetSWF: HabboAssetSWF): Promise<any>
public static async getAssetsXML(habboAssetSWF: HabboAssetSWF, snakeCase: boolean = false): Promise<any>
{
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'assets', true);
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'assets', true, snakeCase);
if(!binaryData) return null;
return await parseStringPromise(binaryData.binaryData);
return await parseStringPromise(this.removeComments(binaryData.binaryData));
}
protected static async getLogicXML(habboAssetSWF: HabboAssetSWF): Promise<any>
public static async getLogicXML(habboAssetSWF: HabboAssetSWF, snakeCase: boolean = false): Promise<any>
{
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'logic', true);
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'logic', true, snakeCase);
if(!binaryData) return null;
return await parseStringPromise(binaryData.binaryData);
return await parseStringPromise(this.removeComments(binaryData.binaryData));
}
protected static async getVisualizationXML(habboAssetSWF: HabboAssetSWF): Promise<any>
public static async getVisualizationXML(habboAssetSWF: HabboAssetSWF, snakeCase: boolean = false): Promise<any>
{
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'visualization', true);
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'visualization', true, snakeCase);
if(!binaryData) return null;
return await parseStringPromise(binaryData.binaryData);
return await parseStringPromise(this.removeComments(binaryData.binaryData));
}
protected static async getAnimationXML(habboAssetSWF: HabboAssetSWF): Promise<any>
public static async getAnimationXML(habboAssetSWF: HabboAssetSWF, snakeCase: boolean = false): Promise<any>
{
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'animation', false);
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, 'animation', false, snakeCase);
if(!binaryData) return null;
return await parseStringPromise(binaryData.binaryData);
return await parseStringPromise(this.removeComments(binaryData.binaryData));
}
protected static getPalette(habboAssetSWF: HabboAssetSWF, paletteName: string): [ number, number, number ][]
public static getPalette(habboAssetSWF: HabboAssetSWF, paletteName: string): [ number, number, number ][]
{
const binaryData = SWFConverter.getBinaryData(habboAssetSWF, paletteName, false);
@ -131,4 +131,65 @@ export class SWFConverter extends Converter
return paletteColors;
}
public static async mapXML2JSON(habboAssetSWF: HabboAssetSWF, assetType: string, snakeCase: boolean = false): Promise<IAssetData>
{
if(!habboAssetSWF) return null;
const output: IAssetData = {};
output.type = assetType;
const indexXML = await this.getIndexXML(habboAssetSWF, snakeCase);
if(indexXML) IndexMapper.mapXML(indexXML, output);
const manifestXML = await this.getManifestXML(habboAssetSWF, snakeCase);
if(manifestXML) ManifestMapper.mapXML(manifestXML, output);
const assetXML = await this.getAssetsXML(habboAssetSWF, snakeCase);
if(assetXML)
{
AssetMapper.mapXML(assetXML, output);
if(output.palettes !== undefined)
{
for(const paletteId in output.palettes)
{
const palette = output.palettes[paletteId];
const paletteColors = this.getPalette(habboAssetSWF, palette.source);
if(!paletteColors)
{
delete output.palettes[paletteId];
continue;
}
const rgbs: [ number, number, number ][] = [];
for(const rgb of paletteColors) rgbs.push([ rgb[0], rgb[1], rgb[2] ]);
palette.rgb = rgbs;
}
}
}
const animationXML = await this.getAnimationXML(habboAssetSWF, snakeCase);
if(animationXML) AnimationMapper.mapXML(animationXML, output);
const logicXML = await this.getLogicXML(habboAssetSWF, snakeCase);
if(logicXML) LogicMapper.mapXML(logicXML, output);
const visualizationXML = await this.getVisualizationXML(habboAssetSWF, snakeCase);
if(visualizationXML) VisualizationMapper.mapXML(visualizationXML, output);
return output;
}
}

View File

@ -12,13 +12,10 @@
"flash.dynamic.download.url": "",
"dynamic.download.furniture.url": "${flash.dynamic.download.url}%revision%/%className%.swf",
"external.variables.url": "https://www.habbo.com/gamedata/external_variables/1",
"external.texts.url": "${external.texts.txt}",
"convert.productdata": "1",
"convert.externaltexts": "1",
"convert.figure": "1",
"convert.figuredata": "1",
"convert.effect": "1",
"convert.furniture": "1",
"convert.pet": "1",
"external.texts.url": "",
"convert.figure": "0",
"convert.effect": "0",
"convert.furniture": "0",
"convert.pet": "0",
"misc.log_download_urls": "0"
}

View File

@ -0,0 +1,107 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { BundleProvider } from '../common/bundle/BundleProvider';
import { Configuration } from '../common/config/Configuration';
import { Converter } from '../common/converters/Converter';
import { SWFConverter } from '../common/converters/SWFConverter';
import { SWFDownloader } from '../common/SWFDownloader';
import { File } from '../utils/File';
import { FileUtilities } from '../utils/FileUtilities';
import { EffectMapConverter } from './EffectMapConverter';
@singleton()
export class EffectConverter extends Converter
{
public effectTypes: Map<string, string> = new Map();
constructor(
private readonly _effectMapConverter: EffectMapConverter,
private readonly _configuration: Configuration)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.effect')) return;
const now = Date.now();
const spinner = ora('Preparing Effects').start();
const baseUrl = this._configuration.getValue('dynamic.download.effect.url');
const effectMap = this._effectMapConverter.effectMap;
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'effect');
const classNames: string[] = [];
if(effectMap.effects !== undefined)
{
for(const library of effectMap.effects)
{
const className = library.lib;
if(classNames.indexOf(className) >= 0) continue;
classNames.push(className);
this.effectTypes.set(className, library.type);
}
}
for(const className of classNames)
{
try
{
const path = new File(directory.path + '/' + className + '.nitro');
if(path.exists()) continue;
const habboAssetSWF = await SWFDownloader.download(baseUrl, className, -1);
if(!habboAssetSWF)
{
spinner.text = 'Couldnt convert effect: ' + className;
spinner.render();
continue;
}
else
{
spinner.text = 'Converting: ' + className;
spinner.render();
}
const spriteBundle = await BundleProvider.generateSpriteSheet(habboAssetSWF);
const assetData = await SWFConverter.mapXML2JSON(habboAssetSWF, className);
if(assetData)
{
assetData.name = className;
assetData.type = this.effectTypes.get(className);
}
const nitroBundle = SWFConverter.createNitroBundle(habboAssetSWF.getDocumentClass(), assetData, spriteBundle);
await writeFile(path.path, await nitroBundle.toBufferAsync());
spinner.text = 'Finished: ' + className;
spinner.render();
}
catch (error)
{
spinner.text = `Error converting ${ className }: ${ error.message }`;
spinner.render();
continue;
}
}
console.log();
spinner.succeed(`Effects finished in ${ Date.now() - now }ms`);
}
}

View File

@ -0,0 +1,58 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { parseStringPromise } from 'xml2js';
import { Configuration } from '../common/config/Configuration';
import { Converter } from '../common/converters/Converter';
import { IEffectMap } from '../mapping/json';
import { EffectMapMapper } from '../mapping/mappers';
import { FileUtilities } from '../utils/FileUtilities';
@singleton()
export class EffectMapConverter extends Converter
{
public effectMap: IEffectMap = null;
constructor(
private readonly _configuration: Configuration)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
const now = Date.now();
const spinner = ora('Preparing EffectMap').start();
const url = this._configuration.getValue('effectmap.load.url');
const content = await FileUtilities.readFileAsString(url);
if(!content.startsWith('{'))
{
const xml = await parseStringPromise(content.replace(/&/g,'&amp;'));
this.effectMap = await this.mapXML2JSON(xml);
}
else
{
this.effectMap = JSON.parse(content);
}
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'gamedata');
const path = directory.path + '/EffectMap.json';
await writeFile(path, JSON.stringify(this.effectMap), 'utf8');
spinner.succeed(`EffectMap finished in ${ Date.now() - now }ms`);
}
private async mapXML2JSON(xml: any): Promise<IEffectMap>
{
if(!xml) return null;
const output: IEffectMap = {};
EffectMapMapper.mapXML(xml, output);
return output;
}
}

View File

@ -0,0 +1,61 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { Configuration } from '../common/config/Configuration';
import { Converter } from '../common/converters/Converter';
import { IExternalTexts } from '../mapping/json';
import { FileUtilities } from '../utils/FileUtilities';
@singleton()
export class ExternalTextsConverter extends Converter
{
public externalTexts: IExternalTexts = null;
constructor(
private readonly _configuration: Configuration)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
const now = Date.now();
const spinner = ora('Preparing ExternalTexts').start();
const url = this._configuration.getValue('external.texts.url');
const content = await FileUtilities.readFileAsString(url);
if(!content.startsWith('{'))
{
this.externalTexts = await this.mapText2JSON(content);
}
else
{
this.externalTexts = JSON.parse(content);
}
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'gamedata');
const path = directory.path + '/ExternalTexts.json';
await writeFile(path, JSON.stringify(this.externalTexts), 'utf8');
spinner.succeed(`ExternalTexts finished in ${ Date.now() - now }ms`);
}
private async mapText2JSON(text: string): Promise<IExternalTexts>
{
if(!text) return null;
const output: IExternalTexts = {};
const parts = text.split(/\n\r{1,}|\n{1,}|\r{1,}/mg);
for(const part of parts)
{
const [ key, value ] = part.split('=');
output[key] = value;
}
return output;
}
}

View File

@ -0,0 +1,105 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { BundleProvider } from '../common/bundle/BundleProvider';
import { Configuration } from '../common/config/Configuration';
import { SWFConverter } from '../common/converters/SWFConverter';
import { SWFDownloader } from '../common/SWFDownloader';
import { File } from '../utils/File';
import { FileUtilities } from '../utils/FileUtilities';
import { FigureMapConverter } from './FigureMapConverter';
@singleton()
export class FigureConverter
{
public figureTypes: Map<string, string> = new Map();
constructor(
private readonly _figureMapConverter: FigureMapConverter,
private readonly _configuration: Configuration)
{}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.figure')) return;
const now = Date.now();
const spinner = ora('Preparing Figure').start();
const baseUrl = this._configuration.getValue('dynamic.download.figure.url');
const figureMap = this._figureMapConverter.figureMap;
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'figure');
const classNames: string[] = [];
if(figureMap.libraries !== undefined)
{
for(const library of figureMap.libraries)
{
const className = library.id.split('*')[0];
if(className === 'hh_human_fx' || className === 'hh_pets') continue;
if(classNames.indexOf(className) >= 0) continue;
classNames.push(className);
this.figureTypes.set(className, library.parts[0].type);
}
}
for(const className of classNames)
{
try
{
const path = new File(directory.path + '/' + className + '.nitro');
if(path.exists()) continue;
const habboAssetSWF = await SWFDownloader.download(baseUrl, className, -1);
if(!habboAssetSWF)
{
spinner.text = 'Couldnt convert figure: ' + className;
spinner.render();
continue;
}
else
{
spinner.text = 'Converting: ' + className;
spinner.render();
}
const spriteBundle = await BundleProvider.generateSpriteSheet(habboAssetSWF);
const assetData = await SWFConverter.mapXML2JSON(habboAssetSWF, className);
if(assetData)
{
assetData.name = className;
assetData.type = this.figureTypes.get(className);
}
const nitroBundle = SWFConverter.createNitroBundle(habboAssetSWF.getDocumentClass(), assetData, spriteBundle);
await writeFile(path.path, await nitroBundle.toBufferAsync());
spinner.text = 'Finished: ' + className;
spinner.render();
}
catch (error)
{
spinner.text = `Error converting ${ className }: ${ error.message }`;
spinner.render();
continue;
}
}
console.log();
spinner.succeed(`Figures finished in ${ Date.now() - now }ms`);
}
}

View File

@ -0,0 +1,58 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { parseStringPromise } from 'xml2js';
import { Configuration } from '../common/config/Configuration';
import { Converter } from '../common/converters/Converter';
import { IFigureData } from '../mapping/json/figuredata/IFigureData';
import { FigureDataMapper } from '../mapping/mappers/FigureDataMapper';
import { FileUtilities } from '../utils/FileUtilities';
@singleton()
export class FigureDataConverter extends Converter
{
public figureData: IFigureData = null;
constructor(
private readonly _configuration: Configuration)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
const now = Date.now();
const spinner = ora('Preparing FigureData').start();
const url = this._configuration.getValue('figuredata.load.url');
const content = await FileUtilities.readFileAsString(url);
if(!content.startsWith('{'))
{
const xml = await parseStringPromise(content.replace(/&/g,'&amp;'));
this.figureData = await this.mapXML2JSON(xml);
}
else
{
this.figureData = JSON.parse(content);
}
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'gamedata');
const path = directory.path + '/FigureData.json';
await writeFile(path, JSON.stringify(this.figureData), 'utf8');
spinner.succeed(`FigureData finished in ${ Date.now() - now }ms`);
}
private async mapXML2JSON(xml: any): Promise<IFigureData>
{
if(!xml) return null;
const output: IFigureData = {};
FigureDataMapper.mapXML(xml, output);
return output;
}
}

View File

@ -0,0 +1,60 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { parseStringPromise } from 'xml2js';
import { Configuration } from '../common/config/Configuration';
import { Converter } from '../common/converters/Converter';
import { IFigureMap } from '../mapping/json';
import { FigureMapMapper } from '../mapping/mappers';
import { FileUtilities } from '../utils/FileUtilities';
import { Logger } from '../utils/Logger';
@singleton()
export class FigureMapConverter extends Converter
{
public figureMap: IFigureMap = null;
constructor(
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
const now = Date.now();
const spinner = ora('Preparing FigureMap').start();
const url = this._configuration.getValue('figuremap.load.url');
const content = await FileUtilities.readFileAsString(url);
if(!content.startsWith('{'))
{
const xml = await parseStringPromise(content.replace(/&/g,'&amp;'));
this.figureMap = await this.mapXML2JSON(xml);
}
else
{
this.figureMap = JSON.parse(content);
}
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'gamedata');
const path = directory.path + '/FigureMap.json';
await writeFile(path, JSON.stringify(this.figureMap), 'utf8');
spinner.succeed(`FigureMap finished in ${ Date.now() - now }ms`);
}
private async mapXML2JSON(xml: any): Promise<IFigureMap>
{
if(!xml) return null;
const output: IFigureMap = {};
FigureMapMapper.mapXML(xml, output);
return output;
}
}

View File

@ -0,0 +1,121 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { BundleProvider } from '../common/bundle/BundleProvider';
import { Configuration } from '../common/config/Configuration';
import { SWFConverter } from '../common/converters/SWFConverter';
import { SWFDownloader } from '../common/SWFDownloader';
import { IAssetData } from '../mapping/json';
import { File } from '../utils/File';
import { FileUtilities } from '../utils/FileUtilities';
import { FurnitureDataConverter } from './FurnitureDataConverter';
@singleton()
export class FurnitureConverter
{
public assets: Map<string, IAssetData> = new Map();
constructor(
private readonly _furniDataConverter: FurnitureDataConverter,
private readonly _configuration: Configuration)
{}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.furniture')) return;
const now = Date.now();
const spinner = ora('Preparing Furniture').start();
const baseUrl = this._configuration.getValue('dynamic.download.furniture.url');
const furniData = this._furniDataConverter.furnitureData;
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'furniture');
const classNames: string[] = [];
const revisions: number[] = [];
if(furniData.roomitemtypes)
{
if(furniData.roomitemtypes.furnitype)
{
for(const furniType of furniData.roomitemtypes.furnitype)
{
const className = furniType.classname.split('*')[0];
const revision = furniType.revision;
if(classNames.indexOf(className) >= 0) continue;
classNames.push(className);
revisions.push(revision);
}
}
}
if(furniData.wallitemtypes)
{
if(furniData.wallitemtypes.furnitype)
{
for(const furniType of furniData.wallitemtypes.furnitype)
{
const className = furniType.classname.split('*')[0];
const revision = furniType.revision;
if(classNames.indexOf(className) >= 0) continue;
classNames.push(className);
revisions.push(revision);
}
}
}
for(let i = 0; i < classNames.length; i++)
{
const className = classNames[i];
const revision = revisions[i];
try
{
const path = new File(directory.path + '/' + className + '.nitro');
if(path.exists()) continue;
const habboAssetSWF = await SWFDownloader.download(baseUrl, className, revision);
if(!habboAssetSWF)
{
spinner.text = 'Couldnt convert furni: ' + className;
spinner.render();
continue;
}
else
{
spinner.text = 'Couldnt convert furni: ' + className;
spinner.render();
}
const spriteBundle = await BundleProvider.generateSpriteSheet(habboAssetSWF);
const assetData = await SWFConverter.mapXML2JSON(habboAssetSWF, 'furniture');
const nitroBundle = SWFConverter.createNitroBundle(habboAssetSWF.getDocumentClass(), assetData, spriteBundle);
await writeFile(path.path, await nitroBundle.toBufferAsync());
spinner.text = 'Finished: ' + className;
spinner.render();
}
catch (error)
{
spinner.text = `Error converting ${ className }: ${ error.message }`;
spinner.render();
continue;
}
}
console.log();
spinner.succeed(`Furniture finished in ${ Date.now() - now }ms`);
}
}

View File

@ -0,0 +1,59 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { parseStringPromise } from 'xml2js';
import { Configuration } from '../common/config/Configuration';
import { Converter } from '../common/converters/Converter';
import { IFurnitureData } from '../mapping/json';
import { FurnitureDataMapper } from '../mapping/mappers';
import { FileUtilities } from '../utils/FileUtilities';
@singleton()
export class FurnitureDataConverter extends Converter
{
public furnitureData: IFurnitureData = null;
constructor(
private readonly _configuration: Configuration)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
const now = Date.now();
const spinner = ora('Preparing FurnitureData').start();
const url = this._configuration.getValue('furnidata.load.url');
const content = await FileUtilities.readFileAsString(url);
if(!content.startsWith('{'))
{
const xml = await parseStringPromise(content.replace(/&/g,'&amp;'));
const furnitureData = await this.mapXML2JSON(xml);
this.furnitureData = furnitureData;
}
else
{
this.furnitureData = JSON.parse(content);
}
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'gamedata');
const path = directory.path + '/FurnitureData.json';
await writeFile(path, JSON.stringify(this.furnitureData), 'utf8');
spinner.succeed(`FurnitureData finished in ${ Date.now() - now }ms`);
}
private async mapXML2JSON(xml: any): Promise<IFurnitureData>
{
if(!xml) return null;
const output: IFurnitureData = {};
FurnitureDataMapper.mapXML(xml, output);
return output;
}
}

View File

@ -0,0 +1,50 @@
import { writeFile } from 'fs/promises';
import { singleton } from 'tsyringe';
import { Configuration } from '../common/config/Configuration';
import { Converter } from '../common/converters/Converter';
import { File } from '../utils/File';
import { FileUtilities } from '../utils/FileUtilities';
import { Logger } from '../utils/Logger';
import { NitroBundle } from '../utils/NitroBundle';
@singleton()
export class OldAssetConverter extends Converter
{
constructor(
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
if(args.shift() !== 'old-asset') return;
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'generic');
const baseDirectory = FileUtilities.getDirectory(args.shift());
for(const className of args)
{
try
{
const path = new File(directory.path + '/' + className + '.nitro');
const jsonBuffer = await FileUtilities.readFileAsBuffer(baseDirectory.path + '/' + className + '/' + className + '.json');
const imageBuffer = await FileUtilities.readFileAsBuffer(baseDirectory.path + '/' + className + '/' + className + '.png');
const nitroBundle = new NitroBundle();
nitroBundle.addFile((className + '.json'), jsonBuffer);
nitroBundle.addFile((className + '.png'), imageBuffer);
await writeFile(path.path, await nitroBundle.toBufferAsync());
console.log('Finished converting: ' + className);
}
catch (error)
{
console.log('Error converting: ' + className);
}
}
}
}

View File

@ -0,0 +1,98 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { BundleProvider } from '../common/bundle/BundleProvider';
import { Configuration } from '../common/config/Configuration';
import { Converter } from '../common/converters/Converter';
import { SWFConverter } from '../common/converters/SWFConverter';
import { SWFDownloader } from '../common/SWFDownloader';
import { File } from '../utils/File';
import { FileUtilities } from '../utils/FileUtilities';
import { Logger } from '../utils/Logger';
@singleton()
export class PetConverter extends Converter
{
constructor(
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.pet')) return;
const now = Date.now();
const spinner = ora('Preparing Pets').start();
const baseUrl = this._configuration.getValue('dynamic.download.pet.url');
const classNames = this.getPetTypes();
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'pet');
for(const className of classNames)
{
try
{
const path = new File(directory.path + '/' + className + '.nitro');
if(path.exists()) continue;
const habboAssetSWF = await SWFDownloader.download(baseUrl, className, -1);
if(!habboAssetSWF)
{
spinner.text = 'Couldnt convert pet: ' + className;
spinner.render();
continue;
}
else
{
spinner.text = 'Converting: ' + className;
spinner.render();
}
const spriteBundle = await BundleProvider.generateSpriteSheet(habboAssetSWF);
const assetData = await SWFConverter.mapXML2JSON(habboAssetSWF, 'pet');
const nitroBundle = SWFConverter.createNitroBundle(habboAssetSWF.getDocumentClass(), assetData, spriteBundle);
await writeFile(path.path, await nitroBundle.toBufferAsync());
spinner.text = 'Finished: ' + className;
spinner.render();
}
catch (error)
{
spinner.text = `Error converting ${ className }: ${ error.message }`;
spinner.render();
continue;
}
}
console.log();
spinner.succeed(`Pets finished in ${ Date.now() - now }ms`);
}
private getPetTypes(): string[]
{
const petTypes: string[] = [];
const pets = this._configuration.getValue('pet.configuration');
if(pets)
{
const types = pets.split(',');
for(const type of types) petTypes.push(type);
}
return petTypes;
}
}

View File

@ -0,0 +1,86 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { Configuration } from '../common/config/Configuration';
import { Converter } from '../common/converters/Converter';
import { IProductData } from '../mapping/json';
import { FileUtilities } from '../utils/FileUtilities';
@singleton()
export class ProductDataConverter extends Converter
{
public productData: IProductData = null;
constructor(
private readonly _configuration: Configuration)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
const now = Date.now();
const spinner = ora('Preparing ProductData').start();
const url = this._configuration.getValue('productdata.load.url');
const content = await FileUtilities.readFileAsString(url);
if(!content.startsWith('{'))
{
const productData = await this.mapText2JSON(content);
this.productData = productData;
}
else
{
this.productData = JSON.parse(content);
}
const directory = FileUtilities.getDirectory(this._configuration.getValue('output.folder'), 'gamedata');
const path = directory.path + '/ProductData.json';
await writeFile(path, JSON.stringify(this.productData), 'utf8');
spinner.succeed(`ProductData finished in ${ Date.now() - now }ms`);
}
private async mapText2JSON(text: string): Promise<IProductData>
{
if(!text) return null;
const output: IProductData = {
productdata: {
product: []
}
};
text = text.replace(/"{1,}/g, '');
const parts = text.split(/\n\r{1,}|\n{1,}|\r{1,}/mg);
for(const part of parts)
{
const set = part.match(/\[+?((.)*?)\]/g);
if(set)
{
for(const entry of set)
{
let value = entry.replace(/\[{1,}/mg, '');
value = entry.replace(/\]{1,}/mg, '');
value = value.replace('[[', '');
value = value.replace('[', '');
const pieces = value.split(',');
const code = pieces.shift();
const name = pieces.shift();
const description = pieces.join(',');
output.productdata.product.push({ code, name, description });
}
}
}
return output;
}
}

View File

@ -1,135 +0,0 @@
import { createWriteStream } from 'fs';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { SWFConverter } from '../../common/converters/SWFConverter';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { FurnitureConverter } from '../furniture/FurnitureConverter';
import { FurnitureDataConverter } from '../furnituredata/FurnitureDataConverter';
@singleton()
export class CatalogConverter extends SWFConverter
{
constructor(
private readonly _furniDataConverter: FurnitureDataConverter,
private readonly _furniConverter: FurnitureConverter,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.catalog')) return;
const now = Date.now();
const spinner = ora('Building catalog').start();
const directory = this.getDirectory();
//await unlink(directory.path + '/catalog.txt');
const file = createWriteStream(directory.path + '/catalog.txt', {
flags: 'a'
});
file.write('INSERT INTO `furniture_definitions` (`id`, `sprite_id`, `public_name`, `product_name`, `type`, `logic`, `total_states`, `x`, `y`, `z`, `can_stack`, `can_walk`, `can_sit`, `can_lay`, `can_recycle`, `can_trade`, `can_group`, `can_sell`, `extra_data`, `date_created`, `date_updated`) VALUES');
try
{
const furnitureData = this._furniDataConverter.furnitureData;
const floorItems = furnitureData.roomitemtypes;
if(floorItems && floorItems.furnitype)
{
for(const furniture of floorItems.furnitype)
{
const assetData = this._furniConverter.assets.get(furniture.classname.split('*')[0]);
if(!assetData) continue;
let totalStates = 0;
if(assetData.visualizations)
{
for(const visualization of assetData.visualizations)
{
if(visualization.size !== 64) continue;
const animations = visualization.animations;
for(const animationKey in animations)
{
const animation = animations[animationKey];
if((animation.transitionTo !== undefined) || (animation.transitionFrom !== undefined)) continue;
totalStates++;
}
}
file.write(`(NULL, ${ furniture.id }, "${ furniture.name }", "${ furniture.classname }", 's', 'default', ${ totalStates }, ${ isNaN(assetData.dimensions.x) ? 0 : assetData.dimensions.x }, ${ isNaN(assetData.dimensions.y) ? 0 : assetData.dimensions.y }, ${ isNaN(assetData.dimensions.z) ? 0 : assetData.dimensions.z }, 0, ${ furniture.canstandon ? 1 : 0 }, ${ furniture.cansiton ? 1 : 0 }, ${ furniture.canlayon ? 1 : 0 }, 1, 1, 1, 1, NULL, '2021-03-21 22:58:36.000000', '2021-03-21 22:58:45.000000'), `);
}
}
}
const wallItems = furnitureData.wallitemtypes;
if(wallItems && wallItems.furnitype)
{
for(const furniture of wallItems.furnitype)
{
const assetData = this._furniConverter.assets.get(furniture.classname.split('*')[0]);
if(!assetData) continue;
let totalStates = 0;
for(const visualization of assetData.visualizations)
{
if(visualization.size !== 64) continue;
const animations = visualization.animations;
for(const animationKey in animations)
{
const animation = animations[animationKey];
if((animation.transitionTo !== undefined) || (animation.transitionFrom !== undefined)) continue;
totalStates++;
}
}
file.write(`(NULL, ${ furniture.id }, "${ furniture.name }", "${ furniture.classname }", 'i', 'default', ${ totalStates }, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, NULL, '2021-03-21 22:58:36.000000', '2021-03-21 22:58:45.000000'), `);
}
}
file.end();
spinner.succeed(`Catalog finished in ${ Date.now() - now }ms`);
}
catch (error)
{
spinner.fail('Catalog failed: ' + error.message);
}
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + 'catalog');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
return gameDataFolder;
}
}

View File

@ -1,99 +0,0 @@
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { BundleProvider } from '../../common/bundle/BundleProvider';
import { Configuration } from '../../common/config/Configuration';
import { SWFConverter } from '../../common/converters/SWFConverter';
import { IAssetData } from '../../mapping/json';
import { AnimationMapper, ManifestMapper } from '../../mapping/mappers';
import { HabboAssetSWF } from '../../swf/HabboAssetSWF';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { EffectDownloader } from './EffectDownloader';
@singleton()
export class EffectConverter extends SWFConverter
{
constructor(
private readonly _effectDownloader: EffectDownloader,
private readonly _configuration: Configuration,
private readonly _bundleProvider: BundleProvider,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.effect')) return;
const now = Date.now();
const spinner = ora('Preparing Effects').start();
const directory = this.getDirectory();
try
{
await this._effectDownloader.download(directory, async (habboAssetSwf: HabboAssetSWF, className: string) =>
{
if(!habboAssetSwf)
{
spinner.text = 'Couldnt convert effect: ' + className;
}
else
{
spinner.text = 'Parsing Effect: ' + habboAssetSwf.getDocumentClass();
}
spinner.render();
if(!habboAssetSwf) return;
const spriteBundle = await this._bundleProvider.generateSpriteSheet(habboAssetSwf);
const assetData = await this.mapXML2JSON(habboAssetSwf, className);
await this.fromHabboAsset(habboAssetSwf, directory.path, assetData.type, assetData, spriteBundle);
});
spinner.succeed(`Effects finished in ${ Date.now() - now }ms`);
}
catch (error)
{
spinner.fail('Effects failed: ' + error.message);
}
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + 'effect');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
return gameDataFolder;
}
private async mapXML2JSON(habboAssetSWF: HabboAssetSWF, assetType: string): Promise<IAssetData>
{
if(!habboAssetSWF) return null;
const output: IAssetData = {};
output.name = assetType;
output.type = EffectDownloader.EFFECT_TYPES.get(assetType);
const manifestXML = await EffectConverter.getManifestXML(habboAssetSWF);
if(manifestXML) ManifestMapper.mapXML(manifestXML, output);
const animationXML = await EffectConverter.getAnimationXML(habboAssetSWF);
if(animationXML) AnimationMapper.mapXML(animationXML, output);
return output;
}
}

View File

@ -1,99 +0,0 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { IEffectMap } from '../../mapping/json';
import { HabboAssetSWF } from '../../swf/HabboAssetSWF';
import File from '../../utils/File';
import { FileUtilities } from '../../utils/FileUtilities';
import { Logger } from '../../utils/Logger';
import { EffectMapConverter } from '../effectmap/EffectMapConverter';
@singleton()
export class EffectDownloader
{
public static EFFECT_TYPES: Map<string, string> = new Map();
constructor(
private readonly _effectMapConverter: EffectMapConverter,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{}
public async download(directory: File, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise<void>): Promise<void>
{
await this._effectMapConverter.convertAsync();
const effectMap = await this.parseEffectMap();
const classNames: string[] = [];
if(effectMap.effects !== undefined)
{
for(const library of effectMap.effects)
{
const className = library.lib;
const existingFile = new File(directory.path + '/' + className + '.nitro');
if(existingFile.exists()) continue;
if(classNames.indexOf(className) >= 0) continue;
classNames.push(className);
try
{
EffectDownloader.EFFECT_TYPES.set(className, library.type);
await this.extractEffect(className, callback);
}
catch (error)
{
console.log();
console.error(`Error parsing ${ className }: ` + error.message);
this._logger.logError(`Error parsing ${ className }: ` + error.message);
}
}
}
}
public async parseEffectMap(): Promise<IEffectMap>
{
const url = this._configuration.getValue('effectmap.load.url');
if(!url || !url.length) return null;
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading effect map from ${url}`);
const content = await FileUtilities.readFileAsString(url);
if(!content || !content.length) return null;
return (JSON.parse(content) as IEffectMap);
}
public async extractEffect(className: string, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise<void>): Promise<void>
{
let url = this._configuration.getValue('dynamic.download.effect.url');
if(!url || !url.length) return;
url = url.replace('%className%', className);
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading effect from ${url}`);
const buffer = await FileUtilities.readFileAsBuffer(url);
if(!buffer) return;
const newHabboAssetSWF = new HabboAssetSWF(buffer);
await newHabboAssetSWF.setupAsync();
await callback(newHabboAssetSWF, className);
}
}

View File

@ -1,101 +0,0 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { parseStringPromise } from 'xml2js';
import { Configuration } from '../../common/config/Configuration';
import { Converter } from '../../common/converters/Converter';
import { IEffectMap } from '../../mapping/json';
import { EffectMapMapper } from '../../mapping/mappers';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { EffectMapDownloader } from './EffectMapDownloader';
@singleton()
export class EffectMapConverter extends Converter
{
constructor(
private readonly _effectMapDownloader: EffectMapDownloader,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
return new Promise((resolve, reject) =>
{
const now = Date.now();
const spinner = ora('Preparing EffectMap').start();
const directory = this.getDirectory();
try
{
this._effectMapDownloader.download(async (content: string) =>
{
spinner.text = 'Parsing EffectMap';
spinner.render();
let effectMapString = content;
if(!effectMapString.startsWith('{'))
{
const xml = await parseStringPromise(effectMapString);
const effectMap = await this.mapXML2JSON(xml);
effectMapString = JSON.stringify(effectMap);
}
const path = directory.path + '/EffectMap.json';
await writeFile(path, effectMapString, 'utf8');
this._configuration.setValue('effectmap.load.url', path);
spinner.succeed(`EffectMap finished in ${ Date.now() - now }ms`);
resolve();
});
}
catch (error)
{
spinner.fail('EffectMap failed: ' + error.message);
reject(error);
}
});
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + '/gamedata');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
const jsonFolder = new File(gameDataFolder.path + '/json');
if(!jsonFolder.isDirectory()) jsonFolder.mkdirs();
return jsonFolder;
}
private async mapXML2JSON(xml: any): Promise<IEffectMap>
{
if(!xml) return null;
const output: IEffectMap = {};
EffectMapMapper.mapXML(xml, output);
return output;
}
}

View File

@ -1,36 +0,0 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { FileUtilities } from '../../utils/FileUtilities';
@singleton()
export class EffectMapDownloader
{
constructor(private readonly _configuration: Configuration)
{}
public async download(callback: (content: string) => Promise<void>): Promise<void>
{
const effectMap = await this.parseEffectMap();
if(!effectMap) throw new Error('invalid_effect_map');
callback(effectMap);
}
public async parseEffectMap(): Promise<string>
{
const url = this._configuration.getValue('effectmap.load.url');
if(!url || !url.length) return null;
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading effect map from ${url}`);
const content = await FileUtilities.readFileAsString(url);
if(!content || !content.length) return null;
return content;
}
}

View File

@ -1,104 +0,0 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { Converter } from '../../common/converters/Converter';
import { IExternalTexts } from '../../mapping/json';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { ExternalTextsDownloader } from './ExternalTextsDownloader';
@singleton()
export class ExternalTextsConverter extends Converter
{
constructor(
private readonly _externalTextsDownloader: ExternalTextsDownloader,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.externaltexts')) return;
return new Promise((resolve, reject) =>
{
const now = Date.now();
const spinner = ora('Preparing ExternalTexts').start();
const directory = this.getDirectory();
try
{
this._externalTextsDownloader.download(async (content: string) =>
{
spinner.text = 'Parsing ExternalTexts';
spinner.render();
let externalTextsString = content;
if(!externalTextsString.startsWith('{'))
{
const externalTexts = await this.mapText2JSON(externalTextsString);
externalTextsString = JSON.stringify(externalTexts);
}
const path = directory.path + '/ExternalTexts.json';
await writeFile(path, externalTextsString, 'utf8');
spinner.succeed(`ExternalTexts finished in ${ Date.now() - now }ms`);
resolve();
});
}
catch (error)
{
spinner.fail('ExternalTexts failed: ' + error.message);
reject(error);
}
});
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + '/gamedata');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
const jsonFolder = new File(gameDataFolder.path + '/json');
if(!jsonFolder.isDirectory()) jsonFolder.mkdirs();
return jsonFolder;
}
private async mapText2JSON(text: string): Promise<IExternalTexts>
{
if(!text) return null;
const output: IExternalTexts = {};
const parts = text.split(/\n\r{1,}|\n{1,}|\r{1,}/mg);
for(const part of parts)
{
const [ key, value ] = part.split('=');
output[key] = value;
}
return output;
}
}

View File

@ -1,36 +0,0 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { FileUtilities } from '../../utils/FileUtilities';
@singleton()
export class ExternalTextsDownloader
{
constructor(private readonly _configuration: Configuration)
{}
public async download(callback: (content: string) => Promise<void>): Promise<void>
{
const productData = await this.parseExternalTexts();
if(!productData) throw new Error('invalid_external_texts');
callback(productData);
}
public async parseExternalTexts(): Promise<string>
{
const url = this._configuration.getValue('external.texts.url');
if(!url || !url.length) return null;
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading external texts from ${url}`);
const content = await FileUtilities.readFileAsString(url);
if(!content || !content.length) return null;
return content;
}
}

View File

@ -1,95 +0,0 @@
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { BundleProvider } from '../../common/bundle/BundleProvider';
import { Configuration } from '../../common/config/Configuration';
import { SWFConverter } from '../../common/converters/SWFConverter';
import { IAssetData } from '../../mapping/json';
import { ManifestMapper } from '../../mapping/mappers';
import { HabboAssetSWF } from '../../swf/HabboAssetSWF';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { FigureDownloader } from './FigureDownloader';
@singleton()
export class FigureConverter extends SWFConverter
{
constructor(
private readonly _figureDownloader: FigureDownloader,
private readonly _configuration: Configuration,
private readonly _bundleProvider: BundleProvider,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.figure')) return;
const now = Date.now();
const spinner = ora('Preparing Figure').start();
const directory = this.getDirectory();
try
{
await this._figureDownloader.download(directory, async (habboAssetSwf: HabboAssetSWF, className: string) =>
{
if(!habboAssetSwf)
{
spinner.text = 'Couldnt convert figure: ' + className;
}
else
{
spinner.text = 'Parsing Figure: ' + habboAssetSwf.getDocumentClass();
}
spinner.render();
if(!habboAssetSwf) return;
const spriteBundle = await this._bundleProvider.generateSpriteSheet(habboAssetSwf);
const assetData = await this.mapXML2JSON(habboAssetSwf, className);
await this.fromHabboAsset(habboAssetSwf, directory.path, assetData.type, assetData, spriteBundle);
});
spinner.succeed(`Figures finished in ${ Date.now() - now }ms`);
}
catch (error)
{
spinner.fail('Figures failed: ' + error.message);
}
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + 'figure');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
return gameDataFolder;
}
private async mapXML2JSON(habboAssetSWF: HabboAssetSWF, assetType: string): Promise<IAssetData>
{
if(!habboAssetSWF) return null;
const output: IAssetData = {};
output.name = assetType;
output.type = FigureDownloader.FIGURE_TYPES.get(assetType);
const manifestXML = await FigureConverter.getManifestXML(habboAssetSWF);
if(manifestXML) ManifestMapper.mapXML(manifestXML, output);
return output;
}
}

View File

@ -1,101 +0,0 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { IFigureMap } from '../../mapping/json';
import { HabboAssetSWF } from '../../swf/HabboAssetSWF';
import File from '../../utils/File';
import { FileUtilities } from '../../utils/FileUtilities';
import { Logger } from '../../utils/Logger';
import { FigureMapConverter } from '../figuremap/FigureMapConverter';
@singleton()
export class FigureDownloader
{
public static FIGURE_TYPES: Map<string, string> = new Map();
constructor(
private readonly _figureMapConverter: FigureMapConverter,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{}
public async download(directory: File, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise<void>): Promise<void>
{
await this._figureMapConverter.convertAsync();
const figureMap = await this.parseFigureMap();
const classNames: string[] = [];
if(figureMap.libraries !== undefined)
{
for(const library of figureMap.libraries)
{
const className = library.id.split('*')[0];
const existingFile = new File(directory.path + '/' + className + '.nitro');
if(existingFile.exists()) continue;
if(className === 'hh_human_fx' || className === 'hh_pets') continue;
if(classNames.indexOf(className) >= 0) continue;
classNames.push(className);
try
{
FigureDownloader.FIGURE_TYPES.set(className, library.parts[0].type);
await this.extractFigure(className, callback);
}
catch (error)
{
console.log();
console.error(`Error parsing ${ className }: ` + error.message);
this._logger.logError(`Error parsing ${ className }: ` + error.message);
}
}
}
}
public async parseFigureMap(): Promise<IFigureMap>
{
const url = this._configuration.getValue('figuremap.load.url');
if(!url || !url.length) return null;
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading figure data from ${url}`);
const content = await FileUtilities.readFileAsString(url);
if(!content || !content.length) return null;
return (JSON.parse(content) as IFigureMap);
}
public async extractFigure(className: string, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise<void>): Promise<void>
{
let url = this._configuration.getValue('dynamic.download.figure.url');
if(!url || !url.length) return;
url = url.replace('%className%', className);
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading figure from ${url}`);
const buffer = await FileUtilities.readFileAsBuffer(url);
if(!buffer) return;
const newHabboAssetSWF = new HabboAssetSWF(buffer);
await newHabboAssetSWF.setupAsync();
await callback(newHabboAssetSWF, className);
}
}

View File

@ -1,103 +0,0 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { parseStringPromise } from 'xml2js';
import { Configuration } from '../../common/config/Configuration';
import { Converter } from '../../common/converters/Converter';
import { FigureDataMapper } from '../../mapping/mappers/FigureDataMapper';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { IFigureData } from './../../mapping/json/figuredata/IFigureData';
import { FigureDataDownloader } from './FigureDataDownloader';
@singleton()
export class FigureDataConverter extends Converter
{
constructor(
private readonly _figureDataDownloader: FigureDataDownloader,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.figuredata')) return;
return new Promise((resolve, reject) =>
{
const now = Date.now();
const spinner = ora('Preparing FigureData').start();
const directory = this.getDirectory();
try
{
this._figureDataDownloader.download(async (content: string) =>
{
spinner.text = 'Parsing FigureData';
spinner.render();
let figureDataString = content;
if(!figureDataString.startsWith('{'))
{
const xml = await parseStringPromise(figureDataString);
const figureData = await this.mapXML2JSON(xml);
figureDataString = JSON.stringify(figureData);
}
const path = directory.path + '/FigureData.json';
await writeFile(path, figureDataString, 'utf8');
this._configuration.setValue('figuredata.load.url', path);
spinner.succeed(`FigureData finished in ${ Date.now() - now }ms`);
resolve();
});
}
catch (error)
{
spinner.fail('FigureData failed: ' + error.message);
reject(error);
}
});
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + '/gamedata');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
const jsonFolder = new File(gameDataFolder.path + '/json');
if(!jsonFolder.isDirectory()) jsonFolder.mkdirs();
return jsonFolder;
}
private async mapXML2JSON(xml: any): Promise<IFigureData>
{
if(!xml) return null;
const output: IFigureData = {};
FigureDataMapper.mapXML(xml, output);
return output;
}
}

View File

@ -1,32 +0,0 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { FileUtilities } from '../../utils/FileUtilities';
@singleton()
export class FigureDataDownloader
{
constructor(private readonly _configuration: Configuration)
{}
public async download(callback: (content: string) => Promise<void>): Promise<void>
{
const figureData = await this.parseFigureData();
if(!figureData) throw new Error('invalid_figure_data');
callback(figureData);
}
public async parseFigureData(): Promise<string>
{
const url = this._configuration.getValue('figuredata.load.url');
if(!url || !url.length) return null;
const content = await FileUtilities.readFileAsString(url);
if(!content || !content.length) return null;
return content;
}
}

View File

@ -1,101 +0,0 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { parseStringPromise } from 'xml2js';
import { Configuration } from '../../common/config/Configuration';
import { Converter } from '../../common/converters/Converter';
import { IFigureMap } from '../../mapping/json';
import { FigureMapMapper } from '../../mapping/mappers';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { FigureMapDownloader } from './FigureMapDownloader';
@singleton()
export class FigureMapConverter extends Converter
{
constructor(
private readonly _figureMapDownloader: FigureMapDownloader,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
return new Promise((resolve, reject) =>
{
const now = Date.now();
const spinner = ora('Preparing FigureMap').start();
const directory = this.getDirectory();
try
{
this._figureMapDownloader.download(async (content: string) =>
{
spinner.text = 'Parsing FigureMap';
spinner.render();
let figureMapString = content;
if(!figureMapString.startsWith('{'))
{
const xml = await parseStringPromise(figureMapString);
const figureMap = await this.mapXML2JSON(xml);
figureMapString = JSON.stringify(figureMap);
}
const path = directory.path + '/FigureMap.json';
await writeFile(path, figureMapString, 'utf8');
this._configuration.setValue('figuremap.load.url', path);
spinner.succeed(`FigureMap finished in ${ Date.now() - now }ms`);
resolve();
});
}
catch (error)
{
spinner.fail('FigureMap failed: ' + error.message);
reject(error);
}
});
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + '/gamedata');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
const jsonFolder = new File(gameDataFolder.path + '/json');
if(!jsonFolder.isDirectory()) jsonFolder.mkdirs();
return jsonFolder;
}
private async mapXML2JSON(xml: any): Promise<IFigureMap>
{
if(!xml) return null;
const output: IFigureMap = {};
FigureMapMapper.mapXML(xml, output);
return output;
}
}

View File

@ -1,36 +0,0 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { FileUtilities } from '../../utils/FileUtilities';
@singleton()
export class FigureMapDownloader
{
constructor(private readonly _configuration: Configuration)
{}
public async download(callback: (content: string) => Promise<void>): Promise<void>
{
const figureMap = await this.parseFigureMap();
if(!figureMap) throw new Error('invalid_figure_map');
callback(figureMap);
}
public async parseFigureMap(): Promise<string>
{
const url = this._configuration.getValue('figuremap.load.url');
if(!url || !url.length) return null;
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading figure map from ${url}`);
const content = await FileUtilities.readFileAsString(url);
if(!content || !content.length) return null;
return content;
}
}

View File

@ -1,110 +0,0 @@
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { BundleProvider } from '../../common/bundle/BundleProvider';
import { Configuration } from '../../common/config/Configuration';
import { SWFConverter } from '../../common/converters/SWFConverter';
import { IAssetData } from '../../mapping/json';
import { AssetMapper, IndexMapper, LogicMapper, VisualizationMapper } from '../../mapping/mappers';
import { HabboAssetSWF } from '../../swf/HabboAssetSWF';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { FurnitureDownloader } from './FurnitureDownloader';
@singleton()
export class FurnitureConverter extends SWFConverter
{
public assets: Map<string, IAssetData> = new Map();
constructor(
private readonly _furniDownloader: FurnitureDownloader,
private readonly _configuration: Configuration,
private readonly _bundleProvider: BundleProvider,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(): Promise<void>
{
if(!this._configuration.getBoolean('convert.furniture')) return;
const now = Date.now();
const spinner = ora('Preparing Furniture').start();
const directory = this.getDirectory();
try
{
await this._furniDownloader.download(directory, async (habboAssetSwf: HabboAssetSWF, className: string) =>
{
if(!habboAssetSwf)
{
spinner.text = 'Couldnt convert furni: ' + className;
}
else
{
spinner.text = (`Parsing Furniture: ${ habboAssetSwf.getDocumentClass() } (${ (this._furniDownloader.totalFinished + 1) } / ${ this._furniDownloader.totalItems })`);
}
spinner.render();
if(!habboAssetSwf) return;
const spriteBundle = await this._bundleProvider.generateSpriteSheet(habboAssetSwf);
const assetData = await this.mapXML2JSON(habboAssetSwf, 'furniture');
await this.fromHabboAsset(habboAssetSwf, directory.path, 'furniture', assetData, spriteBundle);
this.assets.set(assetData.name, assetData);
});
spinner.succeed(`Furniture finished in ${ Date.now() - now }ms`);
}
catch (error)
{
spinner.fail('Furniture failed: ' + error.message);
}
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + 'furniture');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
return gameDataFolder;
}
private async mapXML2JSON(habboAssetSWF: HabboAssetSWF, assetType: string): Promise<IAssetData>
{
if(!habboAssetSWF) return null;
const output: IAssetData = {};
output.type = assetType;
const indexXML = await FurnitureConverter.getIndexXML(habboAssetSWF);
if(indexXML) IndexMapper.mapXML(indexXML, output);
const assetXML = await FurnitureConverter.getAssetsXML(habboAssetSWF);
if(assetXML) AssetMapper.mapXML(assetXML, output);
const logicXML = await FurnitureConverter.getLogicXML(habboAssetSWF);
if(logicXML) LogicMapper.mapXML(logicXML, output);
const visualizationXML = await FurnitureConverter.getVisualizationXML(habboAssetSWF);
if(visualizationXML) VisualizationMapper.mapXML(visualizationXML, output);
return output;
}
}

View File

@ -1,151 +0,0 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { IFurnitureData } from '../../mapping/json';
import { HabboAssetSWF } from '../../swf/HabboAssetSWF';
import File from '../../utils/File';
import { FileUtilities } from '../../utils/FileUtilities';
import { Logger } from '../../utils/Logger';
import { FurnitureDataConverter } from '../furnituredata/FurnitureDataConverter';
@singleton()
export class FurnitureDownloader
{
private _totalItems: number = 0;
private _totalFinished: number = 0;
constructor(
private readonly _furnitureDataConverter: FurnitureDataConverter,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{}
public async download(directory: File, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise<void>): Promise<void>
{
await this._furnitureDataConverter.convertAsync();
const furniData = await this.parseFurniData();
if(!furniData) throw new Error('invalid_furnidata');
const classNames: string[] = [];
const revisions: number[] = [];
if(furniData.roomitemtypes !== undefined)
{
if(furniData.roomitemtypes.furnitype !== undefined)
{
for(const furniType of furniData.roomitemtypes.furnitype)
{
const className = furniType.classname.split('*')[0];
const revision = furniType.revision;
const existingFile = new File(directory.path + '/' + className + '.nitro');
if(existingFile.exists()) continue;
if(classNames.indexOf(className) >= 0) continue;
classNames.push(className);
revisions.push(revision);
}
}
}
if(furniData.wallitemtypes !== undefined)
{
if(furniData.wallitemtypes.furnitype !== undefined)
{
for(const furniType of furniData.wallitemtypes.furnitype)
{
const className = furniType.classname.split('*')[0];
const revision = furniType.revision;
const existingFile = new File(directory.path + '/' + className + '.nitro');
if(existingFile.exists()) continue;
if(classNames.indexOf(className) >= 0) continue;
classNames.push(className);
revisions.push(revision);
}
}
}
this._totalItems = classNames.length;
this._totalFinished = 0;
while(this._totalFinished < this._totalItems)
{
const className = classNames[this._totalFinished];
const revision = revisions[this._totalFinished];
try
{
await this.extractFurniture(revision, className, callback);
}
catch (error)
{
console.log();
console.error(`Error parsing ${ className }: ` + error.message);
this._logger.logError(`Error parsing ${ className }: ` + error.message);
}
this._totalFinished++;
}
}
public async parseFurniData(): Promise<IFurnitureData>
{
const url = this._configuration.getValue('furnidata.load.url');
if(!url || !url.length) return null;
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading furniture data from ${url}`);
const content = await FileUtilities.readFileAsString(url);
if(!content || !content.length) return null;
return (JSON.parse(content) as IFurnitureData);
}
public async extractFurniture(revision: number, className: string, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise<void>): Promise<void>
{
let url = this._configuration.getValue('dynamic.download.furniture.url');
if(!url || !url.length) return;
//url = url.replace('%revision%', revision.toString());
url = url.replace('%className%', className);
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading furniture from ${url}`);
const buffer = await FileUtilities.readFileAsBuffer(url);
if(!buffer) return;
const newHabboAssetSWF = new HabboAssetSWF(buffer);
await newHabboAssetSWF.setupAsync();
await callback(newHabboAssetSWF, className);
}
public get totalItems(): number
{
return this._totalItems;
}
public get totalFinished(): number
{
return this._totalFinished;
}
}

View File

@ -1,110 +0,0 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { parseStringPromise } from 'xml2js';
import { Configuration } from '../../common/config/Configuration';
import { Converter } from '../../common/converters/Converter';
import { IFurnitureData } from '../../mapping/json';
import { FurnitureDataMapper } from '../../mapping/mappers';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { FurnitureDataDownloader } from './FurnitureDataDownloader';
@singleton()
export class FurnitureDataConverter extends Converter
{
public furnitureData: IFurnitureData = null;
constructor(
private readonly _furnitureDataDownloader: FurnitureDataDownloader,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
return new Promise((resolve, reject) =>
{
const now = Date.now();
const spinner = ora('Preparing FurnitureData').start();
const directory = this.getDirectory();
try
{
this._furnitureDataDownloader.download(async (content: string) =>
{
spinner.text = 'Parsing FurnitureData';
spinner.render();
let furnitureDataString = content;
if(!furnitureDataString.startsWith('{'))
{
const xml = await parseStringPromise(furnitureDataString
.replace(/&/g,'&amp;'));
const furnitureData = await this.mapXML2JSON(xml);
this.furnitureData = furnitureData;
furnitureDataString = JSON.stringify(furnitureData);
}
else
{
this.furnitureData = JSON.parse(furnitureDataString);
}
const path = directory.path + '/FurnitureData.json';
await writeFile(path, furnitureDataString, 'utf8');
this._configuration.setValue('furnidata.load.url', path);
spinner.succeed(`FurnitureData finished in ${ Date.now() - now }ms`);
resolve();
});
}
catch (error)
{
spinner.fail('FurnitureData failed: ' + error.message);
reject(error);
}
});
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + '/gamedata');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
const jsonFolder = new File(gameDataFolder.path + '/json');
if(!jsonFolder.isDirectory()) jsonFolder.mkdirs();
return jsonFolder;
}
private async mapXML2JSON(xml: any): Promise<IFurnitureData>
{
if(!xml) return null;
const output: IFurnitureData = {};
FurnitureDataMapper.mapXML(xml, output);
return output;
}
}

View File

@ -1,37 +0,0 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { FileUtilities } from '../../utils/FileUtilities';
@singleton()
export class FurnitureDataDownloader
{
constructor(private readonly _configuration: Configuration)
{
}
public async download(callback: (content: string) => Promise<void>): Promise<void>
{
const furnitureData = await this.parseFurnitureData();
if(!furnitureData) throw new Error('invalid_furniture_data');
callback(furnitureData);
}
public async parseFurnitureData(): Promise<string>
{
const url = this._configuration.getValue('furnidata.load.url');
if(!url || !url.length) return null;
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading furniture data from ${url}`);
const content = await FileUtilities.readFileAsString(url);
if(!content || !content.length) return null;
return content;
}
}

View File

@ -1,132 +0,0 @@
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { BundleProvider } from '../../common/bundle/BundleProvider';
import { Configuration } from '../../common/config/Configuration';
import { SWFConverter } from '../../common/converters/SWFConverter';
import { IAssetData } from '../../mapping/json';
import { AssetMapper, IndexMapper, LogicMapper, VisualizationMapper } from '../../mapping/mappers';
import { HabboAssetSWF } from '../../swf/HabboAssetSWF';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { PetDownloader } from './PetDownloader';
@singleton()
export class PetConverter extends SWFConverter
{
constructor(
private readonly _petDownloader: PetDownloader,
private readonly _configuration: Configuration,
private readonly _bundleProvider: BundleProvider,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.pet')) return;
const now = Date.now();
const spinner = ora('Preparing Pets').start();
const directory = this.getDirectory();
try
{
await this._petDownloader.download(directory, async (habboAssetSwf: HabboAssetSWF, className: string) =>
{
if(!habboAssetSwf)
{
spinner.text = 'Couldnt convert pet: ' + className;
}
else
{
spinner.text = 'Parsing Pet: ' + habboAssetSwf.getDocumentClass();
}
spinner.render();
if(!habboAssetSwf) return;
const spriteBundle = await this._bundleProvider.generateSpriteSheet(habboAssetSwf);
const assetData = await this.mapXML2JSON(habboAssetSwf, 'pet');
await this.fromHabboAsset(habboAssetSwf, directory.path, 'pet', assetData, spriteBundle);
});
spinner.succeed(`Pets finished in ${ Date.now() - now }ms`);
}
catch (error)
{
spinner.fail('Pet failed: ' + error.message);
}
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + 'pet');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
return gameDataFolder;
}
private async mapXML2JSON(habboAssetSWF: HabboAssetSWF, assetType: string): Promise<IAssetData>
{
if(!habboAssetSWF) return null;
const output: IAssetData = {};
output.type = assetType;
const indexXML = await PetConverter.getIndexXML(habboAssetSWF);
if(indexXML) IndexMapper.mapXML(indexXML, output);
const assetXML = await PetConverter.getAssetsXML(habboAssetSWF);
if(assetXML)
{
AssetMapper.mapXML(assetXML, output);
if(output.palettes !== undefined)
{
for(const paletteId in output.palettes)
{
const palette = output.palettes[paletteId];
const paletteColors = PetConverter.getPalette(habboAssetSWF, palette.source);
if(!paletteColors)
{
delete output.palettes[paletteId];
continue;
}
const rgbs: [ number, number, number ][] = [];
for(const rgb of paletteColors) rgbs.push([ rgb[0], rgb[1], rgb[2] ]);
palette.rgb = rgbs;
}
}
}
const logicXML = await PetConverter.getLogicXML(habboAssetSWF);
if(logicXML) LogicMapper.mapXML(logicXML, output);
const visualizationXML = await PetConverter.getVisualizationXML(habboAssetSWF);
if(visualizationXML) VisualizationMapper.mapXML(visualizationXML, output);
return output;
}
}

View File

@ -1,83 +0,0 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { HabboAssetSWF } from '../../swf/HabboAssetSWF';
import File from '../../utils/File';
import { FileUtilities } from '../../utils/FileUtilities';
import { Logger } from '../../utils/Logger';
@singleton()
export class PetDownloader
{
constructor(
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{}
public async download(directory: File, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise<void>): Promise<void>
{
const petTypes = await this.parsePetTypes();
if(!petTypes) throw new Error('invalid_pets');
const classNames: string[] = [];
for(const petType of petTypes)
{
const existingFile = new File(directory.path + '/' + petType + '.nitro');
if(existingFile.exists()) continue;
if(classNames.indexOf(petType) >= 0) continue;
classNames.push(petType);
try
{
await this.extractPet(petType, callback);
}
catch (error)
{
console.log();
console.error(`Error parsing ${ petType }: ` + error.message);
this._logger.logError(`Error parsing ${ petType }: ` + error.message);
}
}
}
public async parsePetTypes(): Promise<string[]>
{
const petTypes: string[] = [];
const pets = this._configuration.getValue('pet.configuration');
if(pets)
{
const types = pets.split(',');
for(const type of types) petTypes.push(type);
}
return petTypes;
}
public async extractPet(className: string, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise<void>): Promise<void>
{
let url = this._configuration.getValue('dynamic.download.pet.url');
if(!url || !url.length) return;
url = url.replace('%className%', className);
const buffer = await FileUtilities.readFileAsBuffer(url);
if(!buffer) return;
const newHabboAssetSWF = new HabboAssetSWF(buffer);
await newHabboAssetSWF.setupAsync();
await callback(newHabboAssetSWF, className);
}
}

View File

@ -1,127 +0,0 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { Converter } from '../../common/converters/Converter';
import { IProductData } from '../../mapping/json';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { ProductDataDownloader } from './ProductDataDownloader';
@singleton()
export class ProductDataConverter extends Converter
{
constructor(
private readonly _productDataDownloader: ProductDataDownloader,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(args: string[] = []): Promise<void>
{
if(!this._configuration.getBoolean('convert.productdata')) return;
return new Promise((resolve, reject) =>
{
const now = Date.now();
const spinner = ora('Preparing ProductData').start();
const directory = this.getDirectory();
try
{
this._productDataDownloader.download(async (content: string) =>
{
spinner.text = 'Parsing FurnitureData';
spinner.render();
let productDataString = content;
if(!productDataString.startsWith('{'))
{
const productData = await this.mapText2JSON(productDataString);
productDataString = JSON.stringify(productData);
}
const path = directory.path + '/ProductData.json';
await writeFile(path, productDataString, 'utf8');
spinner.succeed(`ProductData finished in ${ Date.now() - now }ms`);
resolve();
});
}
catch (error)
{
spinner.fail('ProductData failed: ' + error.message);
reject(error);
}
});
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + '/gamedata');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
const jsonFolder = new File(gameDataFolder.path + '/json');
if(!jsonFolder.isDirectory()) jsonFolder.mkdirs();
return jsonFolder;
}
private async mapText2JSON(text: string): Promise<IProductData>
{
if(!text) return null;
const output: IProductData = {
productdata: {
product: []
}
};
text = text.replace(/"{1,}/g, '');
const parts = text.split(/\n\r{1,}|\n{1,}|\r{1,}/mg);
for(const part of parts)
{
const set = part.match(/\[+?((.)*?)\]/g);
if(set)
{
for(const entry of set)
{
let value = entry.replace(/\[{1,}/mg, '');
value = entry.replace(/\]{1,}/mg, '');
value = value.replace('[[', '');
value = value.replace('[', '');
const pieces = value.split(',');
const code = pieces.shift();
const name = pieces.shift();
const description = pieces.join(',');
output.productdata.product.push({ code, name, description });
}
}
}
return output;
}
}

View File

@ -1,36 +0,0 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { FileUtilities } from '../../utils/FileUtilities';
@singleton()
export class ProductDataDownloader
{
constructor(private readonly _configuration: Configuration)
{}
public async download(callback: (content: string) => Promise<void>): Promise<void>
{
const productData = await this.parseProductData();
if(!productData) throw new Error('invalid_product_data');
callback(productData);
}
public async parseProductData(): Promise<string>
{
const url = this._configuration.getValue('productdata.load.url');
if(!url || !url.length) return null;
const logDownloads = this._configuration.getBoolean('misc.log_download_urls');
if(logDownloads) console.log(`<Downloader> Downloading product data from ${url}`);
const content = await FileUtilities.readFileAsString(url);
if(!content || !content.length) return null;
return content;
}
}

View File

@ -1,11 +1,8 @@
import { IAssetAnimation } from './animation';
import { IAsset } from './IAsset';
import { IAssetAlias } from './IAssetAlias';
import { IAssetDimension } from './IAssetDimension';
import { IAssetPalette } from './IAssetPalette';
import { IPlanetSystem } from './IPlanetSystem';
import { ISoundSample } from './ISoundSample';
import { IParticleSystem } from './particlesystem';
import { IAssetLogicData } from './logic/IAssetLogicData';
import { ISpritesheetData } from './spritesheet';
import { IAssetVisualizationData } from './visualization';
@ -14,15 +11,8 @@ export interface IAssetData {
name?: string;
visualizationType?: string;
logicType?: string;
maskType?: string;
credits?: string;
soundSample?: ISoundSample;
action?: { link?: string, startState?: number };
planetSystems?: IPlanetSystem[];
particleSystems?: IParticleSystem[];
spritesheet?: ISpritesheetData;
dimensions?: IAssetDimension;
directions?: number[];
logic?: IAssetLogicData;
assets?: { [index: string]: IAsset };
aliases?: { [index: string]: IAssetAlias };
animations?: { [index: string]: IAssetAnimation };

View File

@ -2,8 +2,7 @@ export * from './animation';
export * from './IAsset';
export * from './IAssetAlias';
export * from './IAssetData';
export * from './IAssetDimension';
export * from './IAssetPalette';
export * from './IPlanetSystem';
export * from './logic';
export * from './spritesheet';
export * from './visualization';

View File

@ -0,0 +1,4 @@
export interface ICustomVars
{
variables?: string[];
}

View File

@ -0,0 +1,17 @@
import { ICustomVars } from './IAssetLogicCustomVars';
import { IAssetLogicPlanetSystem } from './IAssetLogicPlanetSystem';
import { ISoundSample } from './ISoundSample';
import { IAssetLogicModel } from './model/IAssetLogicModel';
import { IParticleSystem } from './particlesystem';
export interface IAssetLogicData
{
model?: IAssetLogicModel;
maskType?: string;
credits?: string;
soundSample?: ISoundSample;
action?: { link?: string, startState?: number };
planetSystems?: IAssetLogicPlanetSystem[];
particleSystems?: IParticleSystem[];
customVars?: ICustomVars;
}

View File

@ -1,4 +1,4 @@
export interface IPlanetSystem
export interface IAssetLogicPlanetSystem
{
id?: number;
name?: string;

View File

@ -0,0 +1,6 @@
export * from './IAssetLogicCustomVars';
export * from './IAssetLogicData';
export * from './IAssetLogicPlanetSystem';
export * from './ISoundSample';
export * from './model';
export * from './particlesystem';

View File

@ -0,0 +1,7 @@
import { IAssetDimension } from './IAssetDimension';
export interface IAssetLogicModel
{
dimensions?: IAssetDimension;
directions?: number[];
}

View File

@ -0,0 +1,2 @@
export * from './IAssetDimension';
export * from './IAssetLogicModel';

View File

@ -14,6 +14,7 @@ export interface IAssetVisualizationData
colors?: { [index: string]: IAssetColor };
directions?: { [index: string]: IAssetVisualizationDirection };
animations?: { [index: string]: IAssetVisualAnimation };
postures?: { [index: string]: IAssetPosture };
gestures?: { [index: string]: IAssetGesture };
defaultPosture?: string;
postures?: { defaultPosture?: string, postures?: IAssetPosture[] };
gestures?: IAssetGesture[];
}

View File

@ -64,7 +64,7 @@ export class FurnitureDataMapper extends Mapper
furnitureType.name = typeXML.name;
furnitureType.description = typeXML.description;
furnitureType.adurl = typeXML.adurl;
furnitureType.offerid = typeXML.offerid;
furnitureType.offerid = typeXML.id;
furnitureType.buyout = typeXML.buyout;
furnitureType.rentofferid = typeXML.rentofferid;
furnitureType.rentbuyout = typeXML.rentbuyout;

View File

@ -1,5 +1,4 @@
import { IAssetData, IPlanetSystem } from '../../json';
import { IParticleSystem, IParticleSystemEmitter, IParticleSystemParticle, IParticleSystemSimulation } from '../../json/asset/particlesystem';
import { IAssetData, IAssetLogicData, IAssetLogicPlanetSystem, IParticleSystem, IParticleSystemEmitter, IParticleSystemParticle, IParticleSystemSimulation } from '../../json';
import { LogicXML, ParticleSystemEmitterXML, ParticleSystemObjectXML, ParticleSystemParticleXML, ParticleSystemSimulationXML, PlanetSystemObjectXML } from '../../xml';
import { Mapper } from './Mapper';
@ -9,18 +8,22 @@ export class LogicMapper extends Mapper
{
if(!logic || !output) return;
LogicMapper.mapLogicXML(new LogicXML(logic.objectData), output);
output.logic = {};
LogicMapper.mapLogicXML(new LogicXML(logic.objectData), output.logic);
}
private static mapLogicXML(xml: LogicXML, output: IAssetData): void
private static mapLogicXML(xml: LogicXML, output: IAssetLogicData): void
{
if(!xml || !output) return;
if(xml.model !== undefined)
{
output.model = {};
if(xml.model.dimensions !== undefined)
{
output.dimensions = {
output.model.dimensions = {
x: xml.model.dimensions.x,
y: xml.model.dimensions.y,
z: xml.model.dimensions.z
@ -40,23 +43,21 @@ export class LogicMapper extends Mapper
for(const direction of xml.model.directions) directions.push(parseInt(direction.id.toString()));
}
output.directions = directions;
output.model.directions = directions;
}
}
if(xml.action !== undefined)
{
output.action = {};
if(xml.action.link !== undefined)
{
if(!output.action) output.action = {};
output.action.link = xml.action.link;
}
if(xml.action.startState !== undefined)
{
if(!output.action) output.action = {};
output.action.startState = xml.action.startState;
}
}
@ -74,33 +75,39 @@ export class LogicMapper extends Mapper
}
if(xml.planetSystem !== undefined)
{
if(!output.planetSystems)
{
output.planetSystems = [];
}
if(xml.planetSystem.objects !== undefined) LogicMapper.mapPlanetSystemXML(xml.planetSystem.objects, output.planetSystems);
}
if(xml.particleSystem !== undefined)
{
if(!output.particleSystems)
{
output.particleSystems = [];
}
if(xml.particleSystem.objects !== undefined) LogicMapper.mapParticleSystemXML(xml.particleSystem.objects, output.particleSystems);
}
if(xml.customVars !== undefined)
{
output.customVars = {};
if(xml.customVars.variables !== undefined)
{
output.customVars.variables = [];
for(const customVar of xml.customVars.variables) output.customVars.variables.push(customVar);
}
}
}
private static mapPlanetSystemXML(xml: PlanetSystemObjectXML[], output: IPlanetSystem[]): void
private static mapPlanetSystemXML(xml: PlanetSystemObjectXML[], output: IAssetLogicPlanetSystem[]): void
{
if(!xml || !xml.length || !output) return;
for(const planetSystemObjectXML of xml)
{
const planetObject: IPlanetSystem = {};
const planetObject: IAssetLogicPlanetSystem = {};
if(planetSystemObjectXML.id !== undefined) planetObject.id = planetSystemObjectXML.id;
if(planetSystemObjectXML.name !== undefined) planetObject.name = planetSystemObjectXML.name;
@ -121,6 +128,11 @@ export class LogicMapper extends Mapper
for(const particleSystemXML of xml)
{
if(particleSystemXML.size !== undefined)
{
if([ 32 ].indexOf(particleSystemXML.size) >= 0) continue;
}
const particleObject: IParticleSystem = {};
if(particleSystemXML.size !== undefined) particleObject.size = particleSystemXML.size;

View File

@ -84,12 +84,16 @@ export class VisualizationMapper extends Mapper
}
if(visualizationDataXML.postures !== undefined)
{
if(visualizationDataXML.postures.length)
{
visualizationData.postures = {};
VisualizationMapper.mapVisualizationPostureXML(visualizationDataXML.postures, visualizationData.postures);
if(visualizationDataXML.defaultPosture !== undefined) visualizationData.postures.defaultPosture = visualizationDataXML.defaultPosture;
if(visualizationDataXML.postures.length)
{
visualizationData.postures.postures = [];
VisualizationMapper.mapVisualizationPostureXML(visualizationDataXML.postures, visualizationData.postures.postures);
}
}
@ -97,7 +101,7 @@ export class VisualizationMapper extends Mapper
{
if(visualizationDataXML.gestures.length)
{
visualizationData.gestures = {};
visualizationData.gestures = [];
VisualizationMapper.mapVisualizationGestureXML(visualizationDataXML.gestures, visualizationData.gestures);
}
@ -185,6 +189,26 @@ export class VisualizationMapper extends Mapper
}
}
private static requestNextInsertId(requestId: number, output: { [index: string]: IAssetVisualAnimation }): string
{
let id = requestId.toString();
if(!output[id]) return id;
let i = 1;
while(i < 6)
{
id += '_' + i;
if(!output[id]) return id;
i++;
}
return null;
}
private static mapVisualizationAnimationXML(xml: AnimationXML[], output: { [index: string]: IAssetVisualAnimation }): void
{
if(!xml || !xml.length || !output) return;
@ -208,7 +232,11 @@ export class VisualizationMapper extends Mapper
}
}
output[animationXML.id.toString()] = animation;
const id = this.requestNextInsertId(animationXML.id, output);
if(!id) continue;
output[id] = animation;
}
}
@ -321,7 +349,7 @@ export class VisualizationMapper extends Mapper
}
}
private static mapVisualizationPostureXML(xml: PostureXML[], output: { [index: string]: IAssetPosture }): void
private static mapVisualizationPostureXML(xml: PostureXML[], output: IAssetPosture[]): void
{
if(!xml || !xml.length || !output) return;
@ -332,11 +360,11 @@ export class VisualizationMapper extends Mapper
if(postureXML.id !== undefined) posture.id = postureXML.id;
if(postureXML.animationId !== undefined) posture.animationId = postureXML.animationId;
output[postureXML.id] = posture;
output.push(posture);
}
}
private static mapVisualizationGestureXML(xml: GestureXML[], output: { [index: string]: IAssetGesture }): void
private static mapVisualizationGestureXML(xml: GestureXML[], output: IAssetGesture[]): void
{
if(!xml || !xml.length || !output) return;
@ -347,7 +375,7 @@ export class VisualizationMapper extends Mapper
if(gestureXML.id !== undefined) gesture.id = gestureXML.id;
if(gestureXML.animationId !== undefined) gesture.animationId = gestureXML.animationId;
output[gestureXML.id] = gesture;
output.push(gesture);
}
}
}

View File

@ -3,4 +3,5 @@ export * from './AssetMapper';
export * from './IndexMapper';
export * from './LogicMapper';
export * from './ManifestMapper';
export * from './Mapper';
export * from './VisualizationMapper';

View File

@ -0,0 +1,29 @@
export class CustomVarsXML
{
private readonly _variables: string[];
constructor(xml: any)
{
const attributes = xml.$;
if((xml.variable !== undefined) && Array.isArray(xml.variable))
{
this._variables = [];
for(const variable of xml.variable)
{
const attributes = variable.$;
if(attributes !== undefined)
{
if(attributes.name !== undefined) this._variables.push(attributes.name);
}
}
}
}
public get variables(): string[]
{
return this._variables;
}
}

View File

@ -1,8 +1,9 @@
import { ActionXML } from './ActionXML';
import { CreditsXML } from './CreditsXML';
import { CustomVarsXML } from './CustomVarsXML';
import { MaskXML } from './MaskXML';
import { ModelXML } from './model/ModelXML';
import { ParticleSystemXML } from './ParticleSystemXML';
import { ModelXML } from './model';
import { ParticleSystemXML } from './particlesystem';
import { PlanetSystemXML } from './PlanetSystemXML';
import { SoundSampleXML } from './SoundSampleXML';
@ -16,6 +17,7 @@ export class LogicXML
private readonly _soundSample: SoundSampleXML;
private readonly _planetSystem: PlanetSystemXML;
private readonly _particleSystem: ParticleSystemXML;
private readonly _customVars: CustomVarsXML;
constructor(xml: any)
{
@ -48,7 +50,7 @@ export class LogicXML
if(xml.sound !== undefined)
{
if(xml.sound[0] !== undefined) this._soundSample = new SoundSampleXML(xml.sound[0].sample);
if(xml.sound[0] !== undefined) this._soundSample = new SoundSampleXML(xml.sound[0].sample[0]);
}
if(xml.planetsystem !== undefined)
@ -60,6 +62,11 @@ export class LogicXML
{
if(xml.particlesystems[0] !== undefined) this._particleSystem = new ParticleSystemXML(xml.particlesystems[0]);
}
if(xml.customvars !== undefined)
{
if(xml.customvars[0] !== undefined) this._customVars = new CustomVarsXML(xml.customvars[0]);
}
}
public get type(): string
@ -101,4 +108,9 @@ export class LogicXML
{
return this._particleSystem;
}
public get customVars(): CustomVarsXML
{
return this._customVars;
}
}

View File

@ -1,4 +1,4 @@
import { PlanetSystemObjectXML } from './PlanetSystemObjectXML';
import { PlanetSystemObjectXML } from './particlesystem/PlanetSystemObjectXML';
export class PlanetSystemXML
{
@ -6,16 +6,13 @@ export class PlanetSystemXML
constructor(xml: any)
{
if(xml.object !== undefined)
{
if(Array.isArray(xml.object))
if((xml.object !== undefined) && Array.isArray(xml.object))
{
this._objects = [];
for(const object of xml.object) this._objects.push(new PlanetSystemObjectXML(object));
}
}
}
public get objects(): PlanetSystemObjectXML[]
{

View File

@ -5,7 +5,7 @@ export class SoundSampleXML
constructor(xml: any)
{
const attributes = xml[0].$;
const attributes = xml.$;
if(attributes !== undefined)
{

View File

@ -1,13 +1,9 @@
export * from './ActionXML';
export * from './CreditsXML';
export * from './CustomVarsXML';
export * from './LogicXML';
export * from './MaskXML';
export * from './model';
export * from './ParticleSystemEmitterXML';
export * from './ParticleSystemObjectXML';
export * from './ParticleSystemParticleXML';
export * from './ParticleSystemSimulationXML';
export * from './ParticleSystemXML';
export * from './PlanetSystemObjectXML';
export * from './particlesystem';
export * from './PlanetSystemXML';
export * from './SoundSampleXML';

View File

@ -1,4 +1,4 @@
export class DimensionsXML
export class ModelDimensionsXML
{
private readonly _x: number;
private readonly _y: number;

View File

@ -1,21 +1,19 @@
import { DimensionsXML } from './DimensionsXML';
import { ModelDimensionsXML } from './ModelDimensionsXML';
import { ModelDirectionXML } from './ModelDirectionXML';
export class ModelXML
{
private readonly _dimensions: DimensionsXML;
private readonly _dimensions: ModelDimensionsXML;
private readonly _directions: ModelDirectionXML[];
constructor(xml: any)
{
if(xml.dimensions !== undefined)
{
if(xml.dimensions[0] !== undefined) this._dimensions = new DimensionsXML(xml.dimensions[0]);
if(xml.dimensions[0] !== undefined) this._dimensions = new ModelDimensionsXML(xml.dimensions[0]);
}
if(xml.directions !== undefined)
{
if(Array.isArray(xml.directions))
if((xml.directions !== undefined) && Array.isArray(xml.directions))
{
this._directions = [];
@ -25,9 +23,8 @@ export class ModelXML
}
}
}
}
public get dimensions(): DimensionsXML
public get dimensions(): ModelDimensionsXML
{
return this._dimensions;
}

View File

@ -1,2 +1,3 @@
export * from './DimensionsXML';
export * from './ModelDimensionsXML';
export * from './ModelDirectionXML';
export * from './ModelXML';

View File

@ -33,16 +33,13 @@ export class ParticleSystemEmitterXML
if(xml.simulation[0] !== undefined) this._simulation = new ParticleSystemSimulationXML(xml.simulation[0]);
}
if((xml.particles !== undefined) && (xml.particles[0] !== undefined))
{
if(Array.isArray(xml.particles[0].particle))
if((xml.particles !== undefined) && (xml.particles[0] !== undefined) && Array.isArray(xml.particles[0].particle))
{
this._particles = [];
for(const particle of xml.particles[0].particle) this._particles.push(new ParticleSystemParticleXML(particle));
}
}
}
public get id(): number
{

View File

@ -22,16 +22,13 @@ export class ParticleSystemObjectXML
if(attributes.bgcolor !== undefined) this._bgColor = attributes.bgcolor;
}
if(xml.emitter !== undefined)
{
if(Array.isArray(xml.emitter))
if((xml.emitter !== undefined) && Array.isArray(xml.emitter))
{
this._emitters = [];
for(const emitter of xml.emitter) this._emitters.push(new ParticleSystemEmitterXML(emitter));
}
}
}
public get size(): number
{

View File

@ -17,16 +17,13 @@ export class ParticleSystemParticleXML
if(attributes.fade !== undefined) this._fade = (attributes.fade === 'true');
}
if(xml.frame !== undefined)
{
if(Array.isArray(xml.frame))
if((xml.frame !== undefined) && Array.isArray(xml.frame))
{
this._frames = [];
for(const frame of xml.frame) this._frames.push(frame.$.name);
}
}
}
public get isEmitter(): boolean
{

View File

@ -6,16 +6,13 @@ export class ParticleSystemXML
constructor(xml: any)
{
if(xml.particlesystem !== undefined)
{
if(Array.isArray(xml.particlesystem))
if((xml.particlesystem !== undefined) && Array.isArray(xml.particlesystem))
{
this._objects = [];
for(const object of xml.particlesystem) this._objects.push(new ParticleSystemObjectXML(object));
}
}
}
public get objects(): ParticleSystemObjectXML[]
{

View File

@ -0,0 +1,6 @@
export * from './ParticleSystemEmitterXML';
export * from './ParticleSystemObjectXML';
export * from './ParticleSystemParticleXML';
export * from './ParticleSystemSimulationXML';
export * from './ParticleSystemXML';
export * from './PlanetSystemObjectXML';

View File

@ -18,35 +18,23 @@ export class ManifestLibraryXML
if(attributes.version !== undefined) this._version = attributes.version;
}
if(xml.assets !== undefined)
{
if(Array.isArray(xml.assets))
if((xml.assets !== undefined) && Array.isArray(xml.assets))
{
this._assets = [];
for(const assetParent of xml.assets)
{
if(Array.isArray(assetParent.asset))
{
for(const asset of assetParent.asset) this._assets.push(new ManifestLibraryAssetXML(asset));
}
}
if(Array.isArray(assetParent.asset)) for(const asset of assetParent.asset) this._assets.push(new ManifestLibraryAssetXML(asset));
}
}
if(xml.aliases !== undefined)
{
if(Array.isArray(xml.aliases))
if((xml.aliases !== undefined) && Array.isArray(xml.aliases))
{
this._aliases = [];
for(const aliasParent of xml.aliases)
{
if(Array.isArray(aliasParent.alias))
{
for(const alias of aliasParent.alias) this._aliases.push(new ManifestLibraryAliasXML(alias));
}
}
if(Array.isArray(aliasParent.alias)) for(const alias of aliasParent.alias) this._aliases.push(new ManifestLibraryAliasXML(alias));
}
}
}

View File

@ -14,16 +14,13 @@ export class VisualDirectionXML
if(attributes.id !== undefined) this._id = parseInt(attributes.id);
}
if(xml.layer !== undefined)
{
if(Array.isArray(xml.layer))
if((xml.layer !== undefined) && Array.isArray(xml.layer))
{
this._layers = [];
for(const layer of xml.layer) this._layers.push(new LayerXML(layer));
}
}
}
public get id(): number
{

View File

@ -15,6 +15,7 @@ export class VisualizationDataXML
private readonly _directions: VisualDirectionXML[];
private readonly _colors: ColorXML[];
private readonly _animations: AnimationXML[];
private readonly _defaultPosture: string;
private readonly _postures: PostureXML[];
private readonly _gestures: GestureXML[];
@ -29,99 +30,72 @@ export class VisualizationDataXML
if(attributes.angle !== undefined) this._angle = parseInt(attributes.angle);
}
if(xml.layers !== undefined)
{
if(Array.isArray(xml.layers))
if((xml.layers !== undefined) && Array.isArray(xml.layers))
{
this._layers = [];
for(const layerParent of xml.layers)
{
if(Array.isArray(layerParent.layer))
{
for(const layer of layerParent.layer) this._layers.push(new LayerXML(layer));
}
}
if(Array.isArray(layerParent.layer)) for(const layer of layerParent.layer) this._layers.push(new LayerXML(layer));
}
}
if(xml.directions !== undefined)
{
if(Array.isArray(xml.directions))
if((xml.directions !== undefined) && Array.isArray(xml.directions))
{
this._directions = [];
for(const directionParent of xml.directions)
{
if(Array.isArray(directionParent.direction))
{
for(const direction of directionParent.direction) this._directions.push(new VisualDirectionXML(direction));
}
}
if(Array.isArray(directionParent.direction)) for(const direction of directionParent.direction) this._directions.push(new VisualDirectionXML(direction));
}
}
if(xml.colors !== undefined)
{
if(Array.isArray(xml.colors))
if((xml.colors !== undefined) && Array.isArray(xml.colors))
{
this._colors = [];
for(const colorParent of xml.colors)
{
if(Array.isArray(colorParent.color))
{
for(const color of colorParent.color) this._colors.push(new ColorXML(color));
}
}
if(Array.isArray(colorParent.color)) for(const color of colorParent.color) this._colors.push(new ColorXML(color));
}
}
if(xml.animations !== undefined)
{
if(Array.isArray(xml.animations))
if((xml.animations !== undefined) && Array.isArray(xml.animations))
{
this._animations = [];
for(const animationParent of xml.animations)
{
if(Array.isArray(animationParent.animation))
{
for(const animation of animationParent.animation) this._animations.push(new AnimationXML(animation));
}
}
if(Array.isArray(animationParent.animation)) for(const animation of animationParent.animation) this._animations.push(new AnimationXML(animation));
}
}
if(xml.postures !== undefined)
if((xml.postures !== undefined) && Array.isArray(xml.postures))
{
if(Array.isArray(xml.postures))
const parent = xml.postures[0];
const attributes = parent.$;
if(attributes !== undefined)
{
if(attributes.defaultPosture !== undefined) this._defaultPosture = attributes.defaultPosture;
}
if(Array.isArray(parent.posture))
{
this._postures = [];
for(const postureParent of xml.postures)
{
if(Array.isArray(postureParent.posture))
{
for(const posture of postureParent.posture) this._postures.push(new PostureXML(posture));
}
}
for(const posture of parent.posture) this._postures.push(new PostureXML(posture));
}
}
if(xml.gestures !== undefined)
{
if(Array.isArray(xml.gestures))
if((xml.gestures !== undefined) && Array.isArray(xml.gestures))
{
this._gestures = [];
for(const gestureParent of xml.gestures)
{
if(Array.isArray(gestureParent.gesture))
{
for(const gesture of gestureParent.gesture) this._gestures.push(new GestureXML(gesture));
}
}
if(Array.isArray(gestureParent.gesture)) for(const gesture of gestureParent.gesture) this._gestures.push(new GestureXML(gesture));
}
}
}
@ -161,6 +135,11 @@ export class VisualizationDataXML
return this._animations;
}
public get defaultPosture(): string
{
return this._defaultPosture;
}
public get postures(): PostureXML[]
{
return this._postures;

View File

@ -14,20 +14,13 @@ export class VisualizationXML
if(attributes.type !== undefined) this._type = attributes.type;
}
if(xml.graphics !== undefined)
{
if(Array.isArray(xml.graphics))
if((xml.graphics !== undefined) && Array.isArray(xml.graphics))
{
this._visualizations = [];
for(const graphic of xml.graphics)
{
if(Array.isArray(graphic.visualization))
{
for(const visualization of graphic.visualization) this._visualizations.push(new VisualizationDataXML(visualization));
}
}
if(Array.isArray(graphic.visualization)) for(const visualization of graphic.visualization) this._visualizations.push(new VisualizationDataXML(visualization));
}
}
}

View File

@ -23,16 +23,13 @@ export class AnimationLayerXML
if(attributes.randomStart !== undefined) this._randomStart = parseInt(attributes.randomStart);
}
if(xml.frameSequence !== undefined)
{
if(Array.isArray(xml.frameSequence))
if((xml.frameSequence !== undefined) && Array.isArray(xml.frameSequence))
{
this._frameSequences = [];
for(const frameSequence of xml.frameSequence) this._frameSequences.push(new FrameSequenceXML(frameSequence));
}
}
}
public get id(): number
{

View File

@ -22,16 +22,13 @@ export class AnimationXML
if(attributes.randomStart !== undefined) this._randomStart = (attributes.randomStart === '1');
}
if(xml.animationLayer !== undefined)
{
if(Array.isArray(xml.animationLayer))
if((xml.animationLayer !== undefined) && Array.isArray(xml.animationLayer))
{
this._layers = [];
for(const animationLayer of xml.animationLayer) this._layers.push(new AnimationLayerXML(animationLayer));
}
}
}
public get id(): number
{

View File

@ -17,16 +17,13 @@ export class FrameSequenceXML
if(attributes.random !== undefined) this._random = parseInt(attributes.random);
}
if(xml.frame !== undefined)
{
if(Array.isArray(xml.frame))
if((xml.frame !== undefined) && Array.isArray(xml.frame))
{
this._frames = [];
for(const frame of xml.frame) this._frames.push(new FrameXML(frame));
}
}
}
public get loopCount(): number
{

View File

@ -23,19 +23,13 @@ export class FrameXML
if(attributes.randomY !== undefined) this._randomY = parseInt(attributes.randomY);
}
if(xml.offsets !== undefined)
{
if(Array.isArray(xml.offsets))
if((xml.offsets !== undefined) && Array.isArray(xml.offsets))
{
this._offsets = [];
for(const offsetParent of xml.offsets)
{
if(Array.isArray(offsetParent.offset))
{
for(const offset of offsetParent.offset) this._offsets.push(new FrameOffsetXML(offset));
}
}
if(Array.isArray(offsetParent.offset)) for(const offset of offsetParent.offset) this._offsets.push(new FrameOffsetXML(offset));
}
}
}

View File

@ -14,7 +14,7 @@ export class ColorXML
if(attributes.id !== undefined) this._id = parseInt(attributes.id);
}
if(xml.colorLayer !== undefined)
if((xml.colorLayer !== undefined) && Array.isArray(xml.colorLayer))
{
this._layers = [];

View File

@ -180,25 +180,30 @@ export class HabboAssetSWF
return streamTag;
}
public getFullClassName(type: string, documentNameTwice: boolean): string
public getFullClassName(type: string, documentNameTwice: boolean, snakeCase: boolean = false): string
{
return this.getFullClassNameSnake(type, documentNameTwice, false);
return this.getFullClassNameSnake(type, documentNameTwice, snakeCase);
}
public getFullClassNameSnake(type: string, documentNameTwice: boolean, snakeCase: boolean): string
public getFullClassNameSnake(type: string, documentNameTwice: boolean, snakeCase: boolean = false): string
{
let result: string = this.getDocumentClass() + '_';
let result: string = this.getDocumentClass();
if(documentNameTwice)
{
if(snakeCase)
{
//result += CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, this.swf.getDocumentClass()) + "_";
result = (result + (result.replace(/(?:^|\.?)([A-Z])/g, (x,y) => ('_' + y.toLowerCase().replace(/^_/, '')))) + '_');
}
else
{
result += this.getDocumentClass() + '_';
result += '_' + this.getDocumentClass() + '_';
}
}
else
{
result += '_';
}
return result + type;
}

View File

@ -1,8 +1,7 @@
import { existsSync, lstatSync, mkdirSync, readdirSync, RmOptions, rmSync } from 'fs';
export default class File
export class File
{
private readonly _path: string;
constructor(path: string)
@ -32,13 +31,13 @@ export default class File
return this.exists() && lstatSync(this._path).isDirectory();
}
get path(): string
{
return this._path;
}
public rmdir(options: RmOptions): void
{
return rmSync(this._path, options);
}
public get path(): string
{
return this._path;
}
}

View File

@ -1,11 +1,28 @@
import { readFile } from 'fs';
import * as fetch from 'node-fetch';
import { promisify } from 'util';
import { File } from './File';
const readFileAsync = promisify(readFile);
export class FileUtilities
{
public static getDirectory(baseFolderPath: string, childFolderName: string = null): File
{
let folder = new File(baseFolderPath);
if(!folder.isDirectory()) folder.mkdirs();
if(childFolderName)
{
folder = new File(folder.path + childFolderName);
if(!folder.isDirectory()) folder.mkdirs();
}
return folder;
}
public static async readFileAsBuffer(url: string): Promise<Buffer>
{
if(!url) return null;
@ -42,7 +59,7 @@ export class FileUtilities
if(url.startsWith('http'))
{
const data = await fetch.default(url);
if (data.status === 404) return null;
if(data.status === 404) return null;
if(data) content = await data.text();
}