diff --git a/.gitignore b/.gitignore index 9b39010..668b69c 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ yarn-error.log testem.log /typings .git -error.log +*.log # System Files .DS_Store diff --git a/error.log b/error.log deleted file mode 100644 index e69de29..0000000 diff --git a/src/Main.ts b/src/Main.ts index d2bb7dc..265c991 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -2,21 +2,55 @@ import 'reflect-metadata'; import { container } from 'tsyringe'; import { Configuration } from './common/config/Configuration'; import { EffectConverter } from './converters/effect/EffectConverter'; +import { EffectMapConverter } from './converters/effectmap/EffectMapConverter'; import { FigureConverter } from './converters/figure/FigureConverter'; +import { FigureMapConverter } from './converters/figuremap/FigureMapConverter'; import { FurnitureConverter } from './converters/furniture/FurnitureConverter'; +import { FurnitureDataConverter } from './converters/furnituredata/FurnitureDataConverter'; import { PetConverter } from './converters/pet/PetConverter'; +import { ProductDataConverter } from './converters/productdata/ProductDataConverter'; (async () => { const config = container.resolve(Configuration); await config.init(); + if(config.getBoolean('convert.figuremap')) + { + const figureMapConverter = container.resolve(FigureMapConverter); + await figureMapConverter.convertAsync(); + } + + if(config.getBoolean('convert.effectmap')) + { + const effectMapConverter = container.resolve(EffectMapConverter); + await effectMapConverter.convertAsync(); + } + + if(config.getBoolean('convert.furnidata')) + { + const furniDataConverter = container.resolve(FurnitureDataConverter); + await furniDataConverter.convertAsync(); + } + + if(config.getBoolean('convert.productdata')) + { + const productDataConverter = container.resolve(ProductDataConverter); + await productDataConverter.convertAsync(); + } + if(config.getBoolean('convert.figure')) { const figureConverter = container.resolve(FigureConverter); await figureConverter.convertAsync(); } + if(config.getBoolean('convert.effect')) + { + const effectConverter = container.resolve(EffectConverter); + await effectConverter.convertAsync(); + } + if(config.getBoolean('convert.furniture')) { const furnitureConverter = container.resolve(FurnitureConverter); @@ -28,10 +62,4 @@ import { PetConverter } from './converters/pet/PetConverter'; const petConverter = container.resolve(PetConverter); await petConverter.convertAsync(); } - - if(config.getBoolean('convert.effect')) - { - const effectConverter = container.resolve(EffectConverter); - await effectConverter.convertAsync(); - } })(); diff --git a/src/common/config/Configuration.ts b/src/common/config/Configuration.ts index 0e184db..e291943 100644 --- a/src/common/config/Configuration.ts +++ b/src/common/config/Configuration.ts @@ -14,26 +14,22 @@ export class Configuration public async init(): Promise { - for(const key of Object.keys(configuration)) this._config.set(key, configuration[key]); + this.parseConfiguration(configuration); + + await this.loadExternalVariables(); + + this.parseConfiguration(configuration); } public async loadExternalVariables(): Promise { - const url = this.getValue('external_vars.url'); + const url = this.getValue('external.variables.url'); try { const content = await FileUtilities.readFileAsString(url); - const config: string[] = content.split('\n'); - for(const configEntry of config) - { - const configEntrySplit = configEntry.split('='); - const configKey = configEntrySplit[0]; - const configValue = configEntrySplit[1]; - - this._config.set(configKey, configValue); - } + this.parseExternalVariables(content); } catch (error) @@ -43,19 +39,106 @@ export class Configuration } } - public getBoolean(key: string): boolean + private parseConfiguration(content: Object): boolean { - return this._config.get(key) === '1'; + if(!content) return false; + + try + { + const regex = new RegExp(/\${(.*?)}/g); + + for(const key of Object.keys(configuration)) + { + if(this._config.get(key)) + { + if(!configuration[key].length) continue; + } + + this._config.set(key, this.interpolate(configuration[key], regex)); + } + + return true; + } + + catch (e) + { + console.log(); + console.error(e); + + return false; + } } - public getValue(key: string, value: string = ''): string + private parseExternalVariables(content: string): boolean { - if(this._config.has(key)) + if(!content || (content === '')) return false; + + try { - // @ts-ignore - return this._config.get(key); + const regex = new RegExp(/\${(.*?)}/g); + const lines: string[] = content.split('\n'); + + for(const line of lines) + { + const [ key, value ] = line.split('='); + + if(key.startsWith('landing.view')) continue; + + this._config.set(key, this.interpolate((value || ''), regex)); + } + + return true; + } + + catch (e) + { + console.log(); + console.error(e); + + return false; + } + } + + public interpolate(value: string, regex: RegExp = null): string + { + if(!regex) regex = new RegExp(/\${(.*?)}/g); + + const pieces = value.match(regex); + + if(pieces && pieces.length) + { + for(const piece of pieces) + { + const existing = this._config.get(this.removeInterpolateKey(piece)); + + if(existing) (value = value.replace(piece, existing)); + } } return value; } + + private removeInterpolateKey(value: string): string + { + return value.replace('${', '').replace('}', ''); + } + + public getValue(key: string, value: string = ''): string + { + if(this._config.has(key)) return this._config.get(key); + + return value; + } + + public setValue(key: string, value: string): void + { + this._config.set(key, value); + } + + public getBoolean(key: string): boolean + { + const value = this.getValue(key); + + return ((value === 'true') || (value === '1')); + } } diff --git a/src/common/converters/Converter.ts b/src/common/converters/Converter.ts new file mode 100644 index 0000000..41245b6 --- /dev/null +++ b/src/common/converters/Converter.ts @@ -0,0 +1,4 @@ +export class Converter +{ + +} diff --git a/src/common/converters/SWFConverter.ts b/src/common/converters/SWFConverter.ts index eae2aad..1827591 100644 --- a/src/common/converters/SWFConverter.ts +++ b/src/common/converters/SWFConverter.ts @@ -6,8 +6,9 @@ import { HabboAssetSWF } from '../../swf/HabboAssetSWF'; import { DefineBinaryDataTag } from '../../swf/tags/DefineBinaryDataTag'; import { NitroBundle } from '../../utils/NitroBundle'; import { SpriteBundle } from '../bundle/SpriteBundle'; +import { Converter } from './Converter'; -export class SWFConverter +export class SWFConverter extends Converter { protected async fromHabboAsset(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string, assetData: IAssetData, spriteBundle: SpriteBundle): Promise { diff --git a/src/config.json.example b/src/config.json.example deleted file mode 100644 index 5307ad6..0000000 --- a/src/config.json.example +++ /dev/null @@ -1,18 +0,0 @@ -{ - "output.folder.furniture": "/converted-assets/furniture/", - "output.folder.figure": "/converted-assets/figure/", - "output.folder.effect": "/converted-assets/effect/", - "output.folder.pet": "/converted-assets/pet/", - "furnidata.url": "/gamedata/json/furnidata", - "figuremap.url": "/gamedata/figuremap.xml", - "effectmap.url": "/gamedata/effectmap.xml", - "external_vars.url": "/gamedata/external_variables.txt", - "dynamic.download.url.furniture": "/dcr/hof_furni/%className%.swf", - "dynamic.download.url.figure": "/gordon/PRODUCTION/%className%.swf", - "dynamic.download.url.effect": "/gordon/PRODUCTION/%className%.swf", - "dynamic.download.url.pet": "/gordon/PRODUCTION/%className%.swf", - "convert.furniture": "1", - "convert.figure": "1", - "convert.effect": "1", - "convert.pet": "1" -} diff --git a/src/configuration.json.example b/src/configuration.json.example new file mode 100644 index 0000000..067e69a --- /dev/null +++ b/src/configuration.json.example @@ -0,0 +1,22 @@ +{ + "output.folder": "C:/nitro-converted-assets/", + "flash.client.url": "", + "furnidata.load.url": "", + "productdata.load.url": "", + "figuremap.load.url": "${flash.client.url}figuremap.xml", + "effectmap.load.url": "${flash.client.url}effectmap.xml", + "dynamic.download.pet.url": "${flash.client.url}%className%.swf", + "dynamic.download.figure.url": "${flash.client.url}%className%.swf", + "dynamic.download.effect.url": "${flash.client.url}%className%.swf", + "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", + "convert.furnidata": "1", + "convert.productdata": "1", + "convert.figuremap": "1", + "convert.effectmap": "1", + "convert.figure": "1", + "convert.effect": "1", + "convert.furniture": "1", + "convert.pet": "1" +} diff --git a/src/converters/effect/EffectConverter.ts b/src/converters/effect/EffectConverter.ts index 50a5b13..37ea378 100644 --- a/src/converters/effect/EffectConverter.ts +++ b/src/converters/effect/EffectConverter.ts @@ -7,7 +7,7 @@ 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 { Logger } from '../../utils/Logger'; import { EffectDownloader } from './EffectDownloader'; @singleton() @@ -28,9 +28,7 @@ export class EffectConverter extends SWFConverter const spinner = ora('Preparing Effects').start(); - const outputFolder = new File(this._configuration.getValue('output.folder.effect')); - - if(!outputFolder.isDirectory()) outputFolder.mkdirs(); + const directory = this.getDirectory(); try { @@ -43,7 +41,7 @@ export class EffectConverter extends SWFConverter const spriteBundle = await this._bundleProvider.generateSpriteSheet(habboAssetSwf); const assetData = await this.mapXML2JSON(habboAssetSwf, className); - await this.fromHabboAsset(habboAssetSwf, outputFolder.path, assetData.type, assetData, spriteBundle); + await this.fromHabboAsset(habboAssetSwf, directory.path, assetData.type, assetData, spriteBundle); }); spinner.succeed(`Effects finished in ${ Date.now() - now }ms`); @@ -55,23 +53,36 @@ export class EffectConverter extends SWFConverter } } + 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 { if(!habboAssetSWF) return null; - const assetData: IAssetData = {}; + const output: IAssetData = {}; - assetData.name = assetType; - assetData.type = EffectDownloader.EFFECT_TYPES.get(assetType); + output.name = assetType; + output.type = EffectDownloader.EFFECT_TYPES.get(assetType); const manifestXML = await EffectConverter.getManifestXML(habboAssetSWF); - if(manifestXML) ManifestMapper.mapXML(manifestXML, assetData); + if(manifestXML) ManifestMapper.mapXML(manifestXML, output); const animationXML = await EffectConverter.getAnimationXML(habboAssetSWF); - if(animationXML) AnimationMapper.mapXML(animationXML, assetData); + if(animationXML) AnimationMapper.mapXML(animationXML, output); - return assetData; + return output; } } diff --git a/src/converters/effect/EffectDownloader.ts b/src/converters/effect/EffectDownloader.ts index 3d2e1c1..e7651df 100644 --- a/src/converters/effect/EffectDownloader.ts +++ b/src/converters/effect/EffectDownloader.ts @@ -3,13 +3,16 @@ import { Configuration } from '../../common/config/Configuration'; import { IEffectMap } from '../../mapping/json'; import { HabboAssetSWF } from '../../swf/HabboAssetSWF'; import { FileUtilities } from '../../utils/FileUtilities'; +import { Logger } from '../../utils/Logger'; @singleton() export class EffectDownloader { public static EFFECT_TYPES: Map = new Map(); - constructor(private readonly _configuration: Configuration) + constructor( + private readonly _configuration: Configuration, + private readonly _logger: Logger) {} public async download(callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise): Promise @@ -38,6 +41,8 @@ export class EffectDownloader { console.log(); console.error(`Error parsing ${ className }: ` + error.message); + + this._logger.logError(`Error parsing ${ className }: ` + error.message); } } } @@ -45,7 +50,7 @@ export class EffectDownloader public async parseEffectMap(): Promise { - const url = this._configuration.getValue('effectmap.url'); + const url = this._configuration.getValue('effectmap.load.url'); if(!url || !url.length) return null; @@ -58,7 +63,7 @@ export class EffectDownloader public async extractEffect(className: string, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise): Promise { - let url = this._configuration.getValue('dynamic.download.url.effect'); + let url = this._configuration.getValue('dynamic.download.effect.url'); if(!url || !url.length) return; diff --git a/src/converters/effectmap/EffectMapConverter.ts b/src/converters/effectmap/EffectMapConverter.ts new file mode 100644 index 0000000..1218419 --- /dev/null +++ b/src/converters/effectmap/EffectMapConverter.ts @@ -0,0 +1,94 @@ +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(): Promise + { + const now = Date.now(); + + const spinner = ora('Preparing EffectMap').start(); + + const directory = this.getDirectory(); + + try + { + await 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`); + } + + catch (error) + { + spinner.fail('EffectMap 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 + '/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 + { + if(!xml) return null; + + const output: IEffectMap = {}; + + EffectMapMapper.mapXML(xml, output); + + return output; + } +} diff --git a/src/converters/effectmap/EffectMapDownloader.ts b/src/converters/effectmap/EffectMapDownloader.ts new file mode 100644 index 0000000..0cbb410 --- /dev/null +++ b/src/converters/effectmap/EffectMapDownloader.ts @@ -0,0 +1,32 @@ +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): Promise + { + const effectMap = await this.parseEffectMap(); + + if(!effectMap) throw new Error('invalid_effect_map'); + + callback(effectMap); + } + + public async parseEffectMap(): Promise + { + const url = this._configuration.getValue('effectmap.load.url'); + + if(!url || !url.length) return null; + + const content = await FileUtilities.readFileAsString(url); + + if(!content || !content.length) return null; + + return content; + } +} diff --git a/src/converters/figure/FigureConverter.ts b/src/converters/figure/FigureConverter.ts index e5b3404..bdd0c0e 100644 --- a/src/converters/figure/FigureConverter.ts +++ b/src/converters/figure/FigureConverter.ts @@ -7,7 +7,7 @@ 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 { Logger } from '../../utils/Logger'; import { FigureDownloader } from './FigureDownloader'; @singleton() @@ -28,9 +28,7 @@ export class FigureConverter extends SWFConverter const spinner = ora('Preparing Figure').start(); - const outputFolder = new File(this._configuration.getValue('output.folder.figure')); - - if(!outputFolder.isDirectory()) outputFolder.mkdirs(); + const directory = this.getDirectory(); try { @@ -43,7 +41,7 @@ export class FigureConverter extends SWFConverter const spriteBundle = await this._bundleProvider.generateSpriteSheet(habboAssetSwf); const assetData = await this.mapXML2JSON(habboAssetSwf, className); - await this.fromHabboAsset(habboAssetSwf, outputFolder.path, assetData.type, assetData, spriteBundle); + await this.fromHabboAsset(habboAssetSwf, directory.path, assetData.type, assetData, spriteBundle); }); spinner.succeed(`Figures finished in ${ Date.now() - now }ms`); @@ -55,19 +53,32 @@ export class FigureConverter extends SWFConverter } } + 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 { if(!habboAssetSWF) return null; - const assetData: IAssetData = {}; + const output: IAssetData = {}; - assetData.name = assetType; - assetData.type = FigureDownloader.FIGURE_TYPES.get(assetType); + output.name = assetType; + output.type = FigureDownloader.FIGURE_TYPES.get(assetType); const manifestXML = await FigureConverter.getManifestXML(habboAssetSWF); - if(manifestXML) ManifestMapper.mapXML(manifestXML, assetData); + if(manifestXML) ManifestMapper.mapXML(manifestXML, output); - return assetData; + return output; } } diff --git a/src/converters/figure/FigureDownloader.ts b/src/converters/figure/FigureDownloader.ts index 85a2a82..1876614 100644 --- a/src/converters/figure/FigureDownloader.ts +++ b/src/converters/figure/FigureDownloader.ts @@ -3,13 +3,16 @@ import { Configuration } from '../../common/config/Configuration'; import { IFigureMap } from '../../mapping/json'; import { HabboAssetSWF } from '../../swf/HabboAssetSWF'; import { FileUtilities } from '../../utils/FileUtilities'; +import { Logger } from '../../utils/Logger'; @singleton() export class FigureDownloader { public static FIGURE_TYPES: Map = new Map(); - constructor(private readonly _configuration: Configuration) + constructor( + private readonly _configuration: Configuration, + private readonly _logger: Logger) {} public async download(callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise): Promise @@ -40,6 +43,8 @@ export class FigureDownloader { console.log(); console.error(`Error parsing ${ className }: ` + error.message); + + this._logger.logError(`Error parsing ${ className }: ` + error.message); } } } @@ -47,7 +52,7 @@ export class FigureDownloader public async parseFigureMap(): Promise { - const url = this._configuration.getValue('figuremap.url'); + const url = this._configuration.getValue('figuremap.load.url'); if(!url || !url.length) return null; @@ -60,7 +65,7 @@ export class FigureDownloader public async extractFigure(className: string, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise): Promise { - let url = this._configuration.getValue('dynamic.download.url.figure'); + let url = this._configuration.getValue('dynamic.download.figure.url'); if(!url || !url.length) return; diff --git a/src/converters/figuremap/FigureMapConverter.ts b/src/converters/figuremap/FigureMapConverter.ts new file mode 100644 index 0000000..14a5fc2 --- /dev/null +++ b/src/converters/figuremap/FigureMapConverter.ts @@ -0,0 +1,94 @@ +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(): Promise + { + const now = Date.now(); + + const spinner = ora('Preparing FigureMap').start(); + + const directory = this.getDirectory(); + + try + { + await 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`); + } + + catch (error) + { + spinner.fail('FigureMap 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 + '/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 + { + if(!xml) return null; + + const output: IFigureMap = {}; + + FigureMapMapper.mapXML(xml, output); + + return output; + } +} diff --git a/src/converters/figuremap/FigureMapDownloader.ts b/src/converters/figuremap/FigureMapDownloader.ts new file mode 100644 index 0000000..179c72a --- /dev/null +++ b/src/converters/figuremap/FigureMapDownloader.ts @@ -0,0 +1,32 @@ +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): Promise + { + const figureMap = await this.parseFigureMap(); + + if(!figureMap) throw new Error('invalid_figure_map'); + + callback(figureMap); + } + + public async parseFigureMap(): Promise + { + const url = this._configuration.getValue('figuremap.load.url'); + + if(!url || !url.length) return null; + + const content = await FileUtilities.readFileAsString(url); + + if(!content || !content.length) return null; + + return content; + } +} diff --git a/src/converters/furniture/FurnitureConverter.ts b/src/converters/furniture/FurnitureConverter.ts index 1264ed6..e6808f3 100644 --- a/src/converters/furniture/FurnitureConverter.ts +++ b/src/converters/furniture/FurnitureConverter.ts @@ -7,7 +7,7 @@ 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 { Logger } from '../../utils/Logger'; import { FurnitureDownloader } from './FurnitureDownloader'; @singleton() @@ -28,9 +28,7 @@ export class FurnitureConverter extends SWFConverter const spinner = ora('Preparing Furniture').start(); - const outputFolder = new File(this._configuration.getValue('output.folder.furniture')); - - if(!outputFolder.isDirectory()) outputFolder.mkdirs(); + const directory = this.getDirectory(); try { @@ -43,7 +41,7 @@ export class FurnitureConverter extends SWFConverter const spriteBundle = await this._bundleProvider.generateSpriteSheet(habboAssetSwf); const assetData = await this.mapXML2JSON(habboAssetSwf, 'furniture'); - await this.fromHabboAsset(habboAssetSwf, outputFolder.path, 'furniture', assetData, spriteBundle); + await this.fromHabboAsset(habboAssetSwf, directory.path, 'furniture', assetData, spriteBundle); }); spinner.succeed(`Furniture finished in ${ Date.now() - now }ms`); @@ -55,30 +53,43 @@ export class FurnitureConverter extends SWFConverter } } + 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 { if(!habboAssetSWF) return null; - const assetData: IAssetData = {}; + const output: IAssetData = {}; - assetData.type = assetType; + output.type = assetType; const indexXML = await FurnitureConverter.getIndexXML(habboAssetSWF); - if(indexXML) IndexMapper.mapXML(indexXML, assetData); + if(indexXML) IndexMapper.mapXML(indexXML, output); const assetXML = await FurnitureConverter.getAssetsXML(habboAssetSWF); - if(assetXML) AssetMapper.mapXML(assetXML, assetData); + if(assetXML) AssetMapper.mapXML(assetXML, output); const logicXML = await FurnitureConverter.getLogicXML(habboAssetSWF); - if(logicXML) LogicMapper.mapXML(logicXML, assetData); + if(logicXML) LogicMapper.mapXML(logicXML, output); const visualizationXML = await FurnitureConverter.getVisualizationXML(habboAssetSWF); - if(visualizationXML) VisualizationMapper.mapXML(visualizationXML, assetData); + if(visualizationXML) VisualizationMapper.mapXML(visualizationXML, output); - return assetData; + return output; } } diff --git a/src/converters/furniture/FurnitureDownloader.ts b/src/converters/furniture/FurnitureDownloader.ts index 0c289e3..b448cf5 100644 --- a/src/converters/furniture/FurnitureDownloader.ts +++ b/src/converters/furniture/FurnitureDownloader.ts @@ -3,11 +3,14 @@ import { Configuration } from '../../common/config/Configuration'; import { IFurnitureData } from '../../mapping/json'; import { HabboAssetSWF } from '../../swf/HabboAssetSWF'; import { FileUtilities } from '../../utils/FileUtilities'; +import { Logger } from '../../utils/Logger'; @singleton() export class FurnitureDownloader { - constructor(private readonly _configuration: Configuration) + constructor( + private readonly _configuration: Configuration, + private readonly _logger: Logger) {} public async download(callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise): Promise @@ -39,7 +42,7 @@ export class FurnitureDownloader catch (error) { console.log(); - console.error(error); + console.error(`Error parsing ${ className }: ` + error.message); } } } @@ -66,7 +69,9 @@ export class FurnitureDownloader catch (error) { console.log(); - console.error(error); + console.error(`Error parsing ${ className }: ` + error.message); + + this._logger.logError(`Error parsing ${ className }: ` + error.message); } } } @@ -75,7 +80,7 @@ export class FurnitureDownloader public async parseFurniData(): Promise { - const url = this._configuration.getValue('furnidata.url'); + const url = this._configuration.getValue('furnidata.load.url'); if(!url || !url.length) return null; @@ -88,7 +93,7 @@ export class FurnitureDownloader public async extractFurniture(revision: number, className: string, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise): Promise { - let url = this._configuration.getValue('dynamic.download.url.furniture'); + let url = this._configuration.getValue('dynamic.download.furniture.url'); if(!url || !url.length) return; diff --git a/src/converters/furnituredata/FurnitureDataConverter.ts b/src/converters/furnituredata/FurnitureDataConverter.ts new file mode 100644 index 0000000..178f352 --- /dev/null +++ b/src/converters/furnituredata/FurnitureDataConverter.ts @@ -0,0 +1,94 @@ +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 +{ + constructor( + private readonly _furnitureDataDownloader: FurnitureDataDownloader, + private readonly _configuration: Configuration, + private readonly _logger: Logger) + { + super(); + } + + public async convertAsync(): Promise + { + const now = Date.now(); + + const spinner = ora('Preparing FurnitureData').start(); + + const directory = this.getDirectory(); + + try + { + await this._furnitureDataDownloader.download(async (content: string) => + { + spinner.text = 'Parsing FurnitureData'; + + spinner.render(); + + let furnitureDataString = content; + + if(!furnitureDataString.startsWith('{')) + { + const xml = await parseStringPromise(furnitureDataString); + + const furnitureData = await this.mapXML2JSON(xml); + + furnitureDataString = JSON.stringify(furnitureData); + } + + 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`); + } + + catch (error) + { + spinner.fail('FurnitureData 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 + '/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 + { + if(!xml) return null; + + const output: IFurnitureData = {}; + + FurnitureDataMapper.mapXML(xml, output); + + return output; + } +} diff --git a/src/converters/furnituredata/FurnitureDataDownloader.ts b/src/converters/furnituredata/FurnitureDataDownloader.ts new file mode 100644 index 0000000..53b7a2e --- /dev/null +++ b/src/converters/furnituredata/FurnitureDataDownloader.ts @@ -0,0 +1,32 @@ +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): Promise + { + const furnitureData = await this.parseFurnitureData(); + + if(!furnitureData) throw new Error('invalid_furniture_data'); + + callback(furnitureData); + } + + public async parseFurnitureData(): Promise + { + const url = this._configuration.getValue('furnidata.load.url'); + + if(!url || !url.length) return null; + + const content = await FileUtilities.readFileAsString(url); + + if(!content || !content.length) return null; + + return content; + } +} diff --git a/src/converters/pet/PetConverter.ts b/src/converters/pet/PetConverter.ts index fa63461..ae41aa0 100644 --- a/src/converters/pet/PetConverter.ts +++ b/src/converters/pet/PetConverter.ts @@ -7,7 +7,7 @@ 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 { Logger } from '../../utils/Logger'; import { PetDownloader } from './PetDownloader'; @singleton() @@ -28,9 +28,7 @@ export class PetConverter extends SWFConverter const spinner = ora('Preparing Pets').start(); - const outputFolder = new File(this._configuration.getValue('output.folder.pet')); - - if(!outputFolder.isDirectory()) outputFolder.mkdirs(); + const directory = this.getDirectory(); try { @@ -43,7 +41,7 @@ export class PetConverter extends SWFConverter const spriteBundle = await this._bundleProvider.generateSpriteSheet(habboAssetSwf); const assetData = await this.mapXML2JSON(habboAssetSwf, 'pet'); - await this.fromHabboAsset(habboAssetSwf, outputFolder.path, 'pet', assetData, spriteBundle); + await this.fromHabboAsset(habboAssetSwf, directory.path, 'pet', assetData, spriteBundle); }); spinner.succeed(`Pets finished in ${ Date.now() - now }ms`); @@ -55,35 +53,48 @@ export class PetConverter extends SWFConverter } } + 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 { if(!habboAssetSWF) return null; - const assetData: IAssetData = {}; + const output: IAssetData = {}; - assetData.type = assetType; + output.type = assetType; const indexXML = await PetConverter.getIndexXML(habboAssetSWF); - if(indexXML) IndexMapper.mapXML(indexXML, assetData); + if(indexXML) IndexMapper.mapXML(indexXML, output); const assetXML = await PetConverter.getAssetsXML(habboAssetSWF); if(assetXML) { - AssetMapper.mapXML(assetXML, assetData); + AssetMapper.mapXML(assetXML, output); - if(assetData.palettes !== undefined) + if(output.palettes !== undefined) { - for(const paletteId in assetData.palettes) + for(const paletteId in output.palettes) { - const palette = assetData.palettes[paletteId]; + const palette = output.palettes[paletteId]; const paletteColors = PetConverter.getPalette(habboAssetSWF, palette.source); if(!paletteColors) { - delete assetData.palettes[paletteId]; + delete output.palettes[paletteId]; continue; } @@ -99,12 +110,12 @@ export class PetConverter extends SWFConverter const logicXML = await PetConverter.getLogicXML(habboAssetSWF); - if(logicXML) LogicMapper.mapXML(logicXML, assetData); + if(logicXML) LogicMapper.mapXML(logicXML, output); const visualizationXML = await PetConverter.getVisualizationXML(habboAssetSWF); - if(visualizationXML) VisualizationMapper.mapXML(visualizationXML, assetData); + if(visualizationXML) VisualizationMapper.mapXML(visualizationXML, output); - return assetData; + return output; } } diff --git a/src/converters/pet/PetDownloader.ts b/src/converters/pet/PetDownloader.ts index f4f1d61..c1a174b 100644 --- a/src/converters/pet/PetDownloader.ts +++ b/src/converters/pet/PetDownloader.ts @@ -2,13 +2,13 @@ import { singleton } from 'tsyringe'; import { Configuration } from '../../common/config/Configuration'; import { HabboAssetSWF } from '../../swf/HabboAssetSWF'; import { FileUtilities } from '../../utils/FileUtilities'; -import Logger from '../../utils/Logger'; +import { Logger } from '../../utils/Logger'; @singleton() export class PetDownloader { constructor( - private readonly _config: Configuration, + private readonly _configuration: Configuration, private readonly _logger: Logger) {} @@ -34,18 +34,18 @@ export class PetDownloader catch (error) { console.log(); - console.error(error); + console.error(`Error parsing ${ petType }: ` + error.message); + + this._logger.logError(`Error parsing ${ petType }: ` + error.message); } } } public async parsePetTypes(): Promise { - await this._config.loadExternalVariables(); - const petTypes: string[] = []; - const pets = this._config.getValue('pet.configuration'); + const pets = this._configuration.getValue('pet.configuration'); if(pets) { @@ -59,7 +59,7 @@ export class PetDownloader public async extractPet(className: string, callback: (habboAssetSwf: HabboAssetSWF) => Promise): Promise { - let url = this._config.getValue('dynamic.download.url.pet'); + let url = this._configuration.getValue('dynamic.download.pet.url'); if(!url || !url.length) return; diff --git a/src/converters/productdata/ProductDataConverter.ts b/src/converters/productdata/ProductDataConverter.ts new file mode 100644 index 0000000..8a4a8f5 --- /dev/null +++ b/src/converters/productdata/ProductDataConverter.ts @@ -0,0 +1,118 @@ +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(): Promise + { + const now = Date.now(); + + const spinner = ora('Preparing ProductData').start(); + + const directory = this.getDirectory(); + + try + { + await 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`); + } + + catch (error) + { + spinner.fail('ProductData 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 + '/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 + { + 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; + } +} diff --git a/src/converters/productdata/ProductDataDownloader.ts b/src/converters/productdata/ProductDataDownloader.ts new file mode 100644 index 0000000..b769f75 --- /dev/null +++ b/src/converters/productdata/ProductDataDownloader.ts @@ -0,0 +1,32 @@ +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): Promise + { + const productData = await this.parseProductData(); + + if(!productData) throw new Error('invalid_product_data'); + + callback(productData); + } + + public async parseProductData(): Promise + { + const url = this._configuration.getValue('productdata.load.url'); + + if(!url || !url.length) return null; + + const content = await FileUtilities.readFileAsString(url); + + if(!content || !content.length) return null; + + return content; + } +} diff --git a/src/mapping/json/effectmap/IEffectMap.ts b/src/mapping/json/effectmap/IEffectMap.ts index 60893ee..5ada298 100644 --- a/src/mapping/json/effectmap/IEffectMap.ts +++ b/src/mapping/json/effectmap/IEffectMap.ts @@ -2,5 +2,5 @@ import { IEffectMapLibrary } from './IEffectMapLibrary'; export interface IEffectMap { - effects: IEffectMapLibrary[]; + effects?: IEffectMapLibrary[]; } diff --git a/src/mapping/json/effectmap/IEffectMapLibrary.ts b/src/mapping/json/effectmap/IEffectMapLibrary.ts index 9a00225..12f37af 100644 --- a/src/mapping/json/effectmap/IEffectMapLibrary.ts +++ b/src/mapping/json/effectmap/IEffectMapLibrary.ts @@ -1,7 +1,7 @@ export interface IEffectMapLibrary { - id: string; - lib: string; - type: string; - revision: number; + id?: string; + lib?: string; + type?: string; + revision?: number; } diff --git a/src/mapping/json/figuremap/IFigureMap.ts b/src/mapping/json/figuremap/IFigureMap.ts index 6a8280e..538be6c 100644 --- a/src/mapping/json/figuremap/IFigureMap.ts +++ b/src/mapping/json/figuremap/IFigureMap.ts @@ -2,5 +2,5 @@ import { IFigureMapLibrary } from './IFigureMapLibrary'; export interface IFigureMap { - libraries: IFigureMapLibrary[]; + libraries?: IFigureMapLibrary[]; } diff --git a/src/mapping/json/figuremap/IFigureMapLibrary.ts b/src/mapping/json/figuremap/IFigureMapLibrary.ts index 938c20a..b65977a 100644 --- a/src/mapping/json/figuremap/IFigureMapLibrary.ts +++ b/src/mapping/json/figuremap/IFigureMapLibrary.ts @@ -2,7 +2,7 @@ import { IFigureMapLibraryPart } from './IFigureMapLibraryPart'; export interface IFigureMapLibrary { - id: string; - revision: number; - parts: IFigureMapLibraryPart[]; + id?: string; + revision?: number; + parts?: IFigureMapLibraryPart[]; } diff --git a/src/mapping/json/figuremap/IFigureMapLibraryPart.ts b/src/mapping/json/figuremap/IFigureMapLibraryPart.ts index f9e5768..a15ecde 100644 --- a/src/mapping/json/figuremap/IFigureMapLibraryPart.ts +++ b/src/mapping/json/figuremap/IFigureMapLibraryPart.ts @@ -1,5 +1,5 @@ export interface IFigureMapLibraryPart { - id: number; - type: string; + id?: number; + type?: string; } diff --git a/src/mapping/json/furnidata/IFurnitureType.ts b/src/mapping/json/furnidata/IFurnitureType.ts deleted file mode 100644 index bb9664e..0000000 --- a/src/mapping/json/furnidata/IFurnitureType.ts +++ /dev/null @@ -1,28 +0,0 @@ -export interface IFurnitureType -{ - id: number; - classname: string; - revision: number; - category: string; - defaultdir: number; - xdim: number; - ydim: number; - partcolors: { color: string[] }; - name: string; - description: string; - adurl: string; - offerid: number; - buyout: boolean; - rentofferid: number; - rentbuyout: boolean; - bc: boolean; - excludeddynamic: boolean; - customparams: string; - specialtype: number; - canstandon: boolean; - cansiton: boolean; - canlayon: boolean; - furniline: string; - environment: string; - rare: boolean; -} diff --git a/src/mapping/json/furnidata/IFurnitureData.ts b/src/mapping/json/furnituredata/IFurnitureData.ts similarity index 79% rename from src/mapping/json/furnidata/IFurnitureData.ts rename to src/mapping/json/furnituredata/IFurnitureData.ts index 319683d..72bf298 100644 --- a/src/mapping/json/furnidata/IFurnitureData.ts +++ b/src/mapping/json/furnituredata/IFurnitureData.ts @@ -2,10 +2,10 @@ import { IFurnitureType } from './IFurnitureType'; export class IFurnitureData { - roomitemtypes: { + roomitemtypes?: { furnitype: IFurnitureType[] }; - wallitemtypes: { + wallitemtypes?: { furnitype: IFurnitureType[] }; } diff --git a/src/mapping/json/furnituredata/IFurnitureType.ts b/src/mapping/json/furnituredata/IFurnitureType.ts new file mode 100644 index 0000000..2959f61 --- /dev/null +++ b/src/mapping/json/furnituredata/IFurnitureType.ts @@ -0,0 +1,28 @@ +export interface IFurnitureType +{ + id?: number; + classname?: string; + revision?: number; + category?: string; + defaultdir?: number; + xdim?: number; + ydim?: number; + partcolors?: { color: string[] }; + name?: string; + description?: string; + adurl?: string; + offerid?: number; + buyout?: boolean; + rentofferid?: number; + rentbuyout?: boolean; + bc?: boolean; + excludeddynamic?: boolean; + customparams?: string; + specialtype?: number; + canstandon?: boolean; + cansiton?: boolean; + canlayon?: boolean; + furniline?: string; + environment?: string; + rare?: boolean; +} diff --git a/src/mapping/json/furnidata/index.ts b/src/mapping/json/furnituredata/index.ts similarity index 100% rename from src/mapping/json/furnidata/index.ts rename to src/mapping/json/furnituredata/index.ts diff --git a/src/mapping/json/index.ts b/src/mapping/json/index.ts index faf0cbe..ac7848f 100644 --- a/src/mapping/json/index.ts +++ b/src/mapping/json/index.ts @@ -1,4 +1,5 @@ export * from './asset'; export * from './effectmap'; export * from './figuremap'; -export * from './furnidata'; +export * from './furnituredata'; +export * from './productdata'; diff --git a/src/mapping/json/productdata/IProductData.ts b/src/mapping/json/productdata/IProductData.ts new file mode 100644 index 0000000..cab9ba8 --- /dev/null +++ b/src/mapping/json/productdata/IProductData.ts @@ -0,0 +1,8 @@ +import { IProductType } from './IProductType'; + +export interface IProductData +{ + productdata?: { + product: IProductType[] + }; +} diff --git a/src/mapping/json/productdata/IProductType.ts b/src/mapping/json/productdata/IProductType.ts new file mode 100644 index 0000000..02bfc98 --- /dev/null +++ b/src/mapping/json/productdata/IProductType.ts @@ -0,0 +1,6 @@ +export interface IProductType +{ + code: string; + name: string; + description: string; +} diff --git a/src/mapping/json/productdata/index.ts b/src/mapping/json/productdata/index.ts new file mode 100644 index 0000000..0772466 --- /dev/null +++ b/src/mapping/json/productdata/index.ts @@ -0,0 +1,2 @@ +export * from './IProductData'; +export * from './IProductType'; diff --git a/src/mapping/mappers/EffectMapMapper.ts b/src/mapping/mappers/EffectMapMapper.ts new file mode 100644 index 0000000..712b69b --- /dev/null +++ b/src/mapping/mappers/EffectMapMapper.ts @@ -0,0 +1,45 @@ +import { IEffectMap, IEffectMapLibrary } from '../json'; +import { EffectMapEffectXML, EffectMapXML } from '../xml'; +import { Mapper } from './asset/Mapper'; + +export class EffectMapMapper extends Mapper +{ + public static mapXML(xml: any, output: IEffectMap): void + { + if(!xml || !output) return; + + if(xml.map !== undefined) EffectMapMapper.mapEffectMapXML(new EffectMapXML(xml.map), output); + } + + private static mapEffectMapXML(xml: EffectMapXML, output: IEffectMap): void + { + if(!xml || !output) return; + + if(xml.effects !== undefined) + { + if(xml.effects.length) + { + output.effects = []; + + EffectMapMapper.mapEffectMapLibrariesXML(xml.effects, output.effects); + } + } + } + + private static mapEffectMapLibrariesXML(xml: EffectMapEffectXML[], output: IEffectMapLibrary[]): void + { + if(!xml || !xml.length || !output) return; + + for(const libraryXML of xml) + { + const library: IEffectMapLibrary = {}; + + if(libraryXML.id !== undefined) library.id = libraryXML.id; + if(libraryXML.lib !== undefined) library.lib = libraryXML.lib; + if(libraryXML.type !== undefined) library.type = libraryXML.type; + if(libraryXML.revision !== undefined) library.revision = libraryXML.revision; + + output.push(library); + } + } +} diff --git a/src/mapping/mappers/FigureMapMapper.ts b/src/mapping/mappers/FigureMapMapper.ts new file mode 100644 index 0000000..5c7e8e7 --- /dev/null +++ b/src/mapping/mappers/FigureMapMapper.ts @@ -0,0 +1,68 @@ +import { IFigureMap, IFigureMapLibrary, IFigureMapLibraryPart } from '../json'; +import { FigureLibraryPartXML, FigureLibraryXML, FigureMapXML } from '../xml'; +import { Mapper } from './asset/Mapper'; + +export class FigureMapMapper extends Mapper +{ + public static mapXML(xml: any, output: IFigureMap): void + { + if(!xml || !output) return; + + if(xml.map !== undefined) FigureMapMapper.mapFigureMapXML(new FigureMapXML(xml.map), output); + } + + private static mapFigureMapXML(xml: FigureMapXML, output: IFigureMap): void + { + if(!xml || !output) return; + + if(xml.libraries !== undefined) + { + if(xml.libraries.length) + { + output.libraries = []; + + FigureMapMapper.mapFigureMapLibrariesXML(xml.libraries, output.libraries); + } + } + } + + private static mapFigureMapLibrariesXML(xml: FigureLibraryXML[], output: IFigureMapLibrary[]): void + { + if(!xml || !xml.length || !output) return; + + for(const libraryXML of xml) + { + const library: IFigureMapLibrary = {}; + + if(libraryXML.id !== undefined) library.id = libraryXML.id; + if(libraryXML.revision !== undefined) library.revision = libraryXML.revision; + + if(libraryXML.parts !== undefined) + { + if(libraryXML.parts.length) + { + library.parts = []; + + FigureMapMapper.mapFigureMapLibraryPartsXML(libraryXML.parts, library.parts); + } + } + + output.push(library); + } + } + + private static mapFigureMapLibraryPartsXML(xml: FigureLibraryPartXML[], output: IFigureMapLibraryPart[]): void + { + if(!xml || !xml.length || !output) return; + + for(const libraryPartXML of xml) + { + const libraryPart: IFigureMapLibraryPart = {}; + + if(libraryPartXML.id !== undefined) libraryPart.id = libraryPartXML.id; + if(libraryPartXML.type !== undefined) libraryPart.type = libraryPartXML.type; + + output.push(libraryPart); + } + } +} diff --git a/src/mapping/mappers/FurnitureDataMapper.ts b/src/mapping/mappers/FurnitureDataMapper.ts new file mode 100644 index 0000000..725d140 --- /dev/null +++ b/src/mapping/mappers/FurnitureDataMapper.ts @@ -0,0 +1,90 @@ +import { IFurnitureData, IFurnitureType } from '../json'; +import { FurnitureDataXML, FurnitureTypeXML } from '../xml'; +import { Mapper } from './asset/Mapper'; + +export class FurnitureDataMapper extends Mapper +{ + public static mapXML(xml: any, output: IFurnitureData): void + { + if(!xml || !output) return; + + if(xml.furnidata !== undefined) FurnitureDataMapper.mapFurnitureDataXML(new FurnitureDataXML(xml.furnidata), output); + } + + private static mapFurnitureDataXML(xml: FurnitureDataXML, output: IFurnitureData): void + { + if(!xml || !output) return; + + if(xml.floorItems !== undefined) + { + if(xml.floorItems.length) + { + output.roomitemtypes = { + furnitype: [] + }; + + FurnitureDataMapper.mapFurnitureTypesXML(xml.floorItems, output.roomitemtypes.furnitype, 'floor'); + } + } + + if(xml.wallItems !== undefined) + { + if(xml.wallItems.length) + { + output.wallitemtypes = { + furnitype: [] + }; + + FurnitureDataMapper.mapFurnitureTypesXML(xml.wallItems, output.wallitemtypes.furnitype, 'wall'); + } + } + } + + private static mapFurnitureTypesXML(xml: FurnitureTypeXML[], output: IFurnitureType[], type: string): void + { + if(!xml || !xml.length || !output) return; + + for(const typeXML of xml) + { + const furnitureType: IFurnitureType = {}; + + furnitureType.id = typeXML.id; + furnitureType.classname = typeXML.classname; + furnitureType.revision = typeXML.revision; + furnitureType.category = typeXML.category; + + if(type === 'floor') + { + furnitureType.defaultdir = typeXML.defaultdir; + furnitureType.xdim = typeXML.xdim; + furnitureType.ydim = typeXML.ydim; + furnitureType.partcolors = typeXML.partcolors; + } + + furnitureType.name = typeXML.name; + furnitureType.description = typeXML.description; + furnitureType.adurl = typeXML.adurl; + furnitureType.offerid = typeXML.offerid; + furnitureType.buyout = typeXML.buyout; + furnitureType.rentofferid = typeXML.rentofferid; + furnitureType.rentbuyout = typeXML.rentbuyout; + furnitureType.bc = typeXML.bc; + furnitureType.excludeddynamic = typeXML.excludeddynamic; + furnitureType.customparams = typeXML.customparams; + furnitureType.specialtype = typeXML.specialtype; + + if(type === 'floor') + { + furnitureType.canstandon = typeXML.canstandon; + furnitureType.cansiton = typeXML.cansiton; + furnitureType.canlayon = typeXML.canlayon; + } + + furnitureType.furniline = typeXML.furniline; + furnitureType.environment = typeXML.environment; + furnitureType.rare = typeXML.rare; + + output.push(furnitureType); + } + } +} diff --git a/src/mapping/mappers/index.ts b/src/mapping/mappers/index.ts index ea2719d..5629e41 100644 --- a/src/mapping/mappers/index.ts +++ b/src/mapping/mappers/index.ts @@ -1 +1,4 @@ export * from './asset'; +export * from './EffectMapMapper'; +export * from './FigureMapMapper'; +export * from './FurnitureDataMapper'; diff --git a/src/mapping/xml/effectmap/EffectLibraryPartXML.ts b/src/mapping/xml/effectmap/EffectLibraryPartXML.ts deleted file mode 100644 index 2165f0e..0000000 --- a/src/mapping/xml/effectmap/EffectLibraryPartXML.ts +++ /dev/null @@ -1,26 +0,0 @@ -export class EffectLibraryPartXML -{ - private _id: number; - private _type: string; - - constructor(xml: any) - { - const attributes = xml.$; - - if(attributes) - { - if(attributes.id !== undefined) this._id = parseInt(attributes.id); - if(attributes.type !== undefined) this._type = attributes.type; - } - } - - public get id(): number - { - return this._id; - } - - public get type(): string - { - return this._type; - } -} diff --git a/src/mapping/xml/effectmap/EffectLibraryXML.ts b/src/mapping/xml/effectmap/EffectLibraryXML.ts deleted file mode 100644 index 3fa6d5f..0000000 --- a/src/mapping/xml/effectmap/EffectLibraryXML.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { EffectLibraryPartXML } from './EffectLibraryPartXML'; - -export class EffectLibraryXML -{ - private _id: string; - private _revision: number; - private _parts: EffectLibraryPartXML[]; - - constructor(xml: any) - { - const attributes = xml.$; - - if(attributes) - { - if(attributes.id !== undefined) this._id = attributes.id; - if(attributes.revision !== undefined) this._revision = parseInt(attributes.revision); - } - - if(xml.part !== undefined) - { - this._parts = []; - - for(const partId in xml.part) - { - const part = xml.part[partId]; - - this._parts.push(new EffectLibraryPartXML(part)); - } - } - } - - public get id(): string - { - return this._id; - } - - public get revision(): number - { - return this._revision; - } - - public get parts(): EffectLibraryPartXML[] - { - return this._parts; - } -} diff --git a/src/mapping/xml/effectmap/EffectMapEffectXML.ts b/src/mapping/xml/effectmap/EffectMapEffectXML.ts new file mode 100644 index 0000000..f48d8d0 --- /dev/null +++ b/src/mapping/xml/effectmap/EffectMapEffectXML.ts @@ -0,0 +1,41 @@ + +export class EffectMapEffectXML +{ + private _id: string; + private _lib: string; + private _type: string; + private _revision: number; + + constructor(xml: any) + { + const attributes = xml.$; + + if(attributes) + { + if(attributes.id !== undefined) this._id = attributes.id; + if(attributes.lib !== undefined) this._lib = attributes.lib; + if(attributes.type !== undefined) this._type = attributes.type; + if(attributes.revision !== undefined) this._revision = parseInt(attributes.revision); + } + } + + public get id(): string + { + return this._id; + } + + public get lib(): string + { + return this._lib; + } + + public get type(): string + { + return this._type; + } + + public get revision(): number + { + return this._revision; + } +} diff --git a/src/mapping/xml/effectmap/EffectMapXML.ts b/src/mapping/xml/effectmap/EffectMapXML.ts index 64e2c8b..dd14cbb 100644 --- a/src/mapping/xml/effectmap/EffectMapXML.ts +++ b/src/mapping/xml/effectmap/EffectMapXML.ts @@ -1,29 +1,24 @@ -import { EffectLibraryXML } from './EffectLibraryXML'; +import { EffectMapEffectXML } from './EffectMapEffectXML'; export class EffectMapXML { - private _librares: EffectLibraryXML[]; + private _effect: EffectMapEffectXML[]; constructor(xml: any) { - if(xml.map !== undefined) + if(xml.effect !== undefined) { - if(xml.map.lib !== undefined) + if(Array.isArray(xml.effect)) { - this._librares = []; + this._effect = []; - for(const lib in xml.map.lib) - { - const library = xml.map.lib[lib]; - - this._librares.push(new EffectLibraryXML(library)); - } + for(const library of xml.effect) this._effect.push(new EffectMapEffectXML(library)); } } } - public get libraries(): EffectLibraryXML[] + public get effects(): EffectMapEffectXML[] { - return this._librares; + return this._effect; } } diff --git a/src/mapping/xml/effectmap/index.ts b/src/mapping/xml/effectmap/index.ts index 1cd7092..61bec71 100644 --- a/src/mapping/xml/effectmap/index.ts +++ b/src/mapping/xml/effectmap/index.ts @@ -1,3 +1,2 @@ -export * from './EffectLibraryPartXML'; -export * from './EffectLibraryXML'; +export * from './EffectMapEffectXML'; export * from './EffectMapXML'; diff --git a/src/mapping/xml/figuremap/FigureLibraryXML.ts b/src/mapping/xml/figuremap/FigureLibraryXML.ts index 5b7b66a..3522491 100644 --- a/src/mapping/xml/figuremap/FigureLibraryXML.ts +++ b/src/mapping/xml/figuremap/FigureLibraryXML.ts @@ -18,13 +18,16 @@ export class FigureLibraryXML if(xml.part !== undefined) { - this._parts = []; - - for(const partId in xml.part) + if(Array.isArray(xml.part)) { - const part = xml.part[partId]; + this._parts = []; - this._parts.push(new FigureLibraryPartXML(part)); + for(const partId in xml.part) + { + const part = xml.part[partId]; + + this._parts.push(new FigureLibraryPartXML(part)); + } } } } diff --git a/src/mapping/xml/figuremap/FigureMapXML.ts b/src/mapping/xml/figuremap/FigureMapXML.ts index e475e88..722d5b5 100644 --- a/src/mapping/xml/figuremap/FigureMapXML.ts +++ b/src/mapping/xml/figuremap/FigureMapXML.ts @@ -6,15 +6,15 @@ export class FigureMapXML constructor(xml: any) { - if(xml.map !== undefined) + if(xml.lib !== undefined) { - if(xml.map.lib !== undefined) + if(Array.isArray(xml.lib)) { this._librares = []; - for(const lib in xml.map.lib) + for(const lib in xml.lib) { - const library = xml.map.lib[lib]; + const library = xml.lib[lib]; this._librares.push(new FigureLibraryXML(library)); } diff --git a/src/mapping/xml/furnidata/FurnitureDataXML.ts b/src/mapping/xml/furnituredata/FurnitureDataXML.ts similarity index 61% rename from src/mapping/xml/furnidata/FurnitureDataXML.ts rename to src/mapping/xml/furnituredata/FurnitureDataXML.ts index 8bb8cc7..a0deb45 100644 --- a/src/mapping/xml/furnidata/FurnitureDataXML.ts +++ b/src/mapping/xml/furnituredata/FurnitureDataXML.ts @@ -13,13 +13,13 @@ export class FurnitureDataXML { this._floorItems = []; - for(const roomitemtype in xml.roomitemtypes) + for(const roomitemtype of xml.roomitemtypes) { - const furniTypes = xml.roomitemtypes[roomitemtype]; + const furniTypes = roomitemtype.furnitype; if(furniTypes !== undefined) { - for(const furniType in furniTypes) this._floorItems.push(new FurnitureTypeXML('floor', furniType)); + if(Array.isArray(furniTypes)) for(const furniType of furniTypes) this._floorItems.push(new FurnitureTypeXML('floor', furniType)); } } } @@ -28,13 +28,13 @@ export class FurnitureDataXML { this._wallItems = []; - for(const wallitemtype in xml.wallitemtypes) + for(const wallitemtype of xml.wallitemtypes) { - const furniTypes = xml.wallitemtypes[wallitemtype]; + const furniTypes = wallitemtype.furnitype; if(furniTypes !== undefined) { - for(const furniType in furniTypes) this._wallItems.push(new FurnitureTypeXML('wall', furniType)); + if(Array.isArray(furniTypes)) for(const furniType in furniTypes) this._wallItems.push(new FurnitureTypeXML('wall', furniType)); } } } diff --git a/src/mapping/xml/furnidata/FurnitureTypeXML.ts b/src/mapping/xml/furnituredata/FurnitureTypeXML.ts similarity index 100% rename from src/mapping/xml/furnidata/FurnitureTypeXML.ts rename to src/mapping/xml/furnituredata/FurnitureTypeXML.ts diff --git a/src/mapping/xml/furnidata/index.ts b/src/mapping/xml/furnituredata/index.ts similarity index 100% rename from src/mapping/xml/furnidata/index.ts rename to src/mapping/xml/furnituredata/index.ts diff --git a/src/mapping/xml/index.ts b/src/mapping/xml/index.ts index faf0cbe..8a7330a 100644 --- a/src/mapping/xml/index.ts +++ b/src/mapping/xml/index.ts @@ -1,4 +1,4 @@ export * from './asset'; export * from './effectmap'; export * from './figuremap'; -export * from './furnidata'; +export * from './furnituredata'; diff --git a/src/utils/FileUtilities.ts b/src/utils/FileUtilities.ts index 1661e4e..263792c 100644 --- a/src/utils/FileUtilities.ts +++ b/src/utils/FileUtilities.ts @@ -12,6 +12,8 @@ export class FileUtilities let content: Buffer = null; + if(url.startsWith('//')) url = ('https:' + url); + if(url.startsWith('http')) { const data = await fetch.default(url); @@ -33,6 +35,8 @@ export class FileUtilities let content = null; + if(url.startsWith('//')) url = ('https:' + url); + if(url.startsWith('http')) { const data = await fetch.default(url); diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts index 5db9762..b185fe3 100644 --- a/src/utils/Logger.ts +++ b/src/utils/Logger.ts @@ -1,22 +1,19 @@ -import { createWriteStream, existsSync } from 'fs'; -import { appendFile } from 'fs/promises'; +import { WriteStream } from 'fs'; import { singleton } from 'tsyringe'; @singleton() -export default class Logger +export class Logger { + private _fileName: string = `error-${ Date.now() }.log`; + private _writeStream: WriteStream = null; constructor() { - if(!existsSync('error.log')) - { - const createStream = createWriteStream('error.log'); - createStream.end(); - } + } - public logErrorAsync(message: string): Promise + public logError(message: string): void { - return appendFile('error.log', message + '\n'); + // } }