From 5488f25310c6db6e00cf7bf2ae39a891e8105781 Mon Sep 17 00:00:00 2001 From: SpreedBLood Date: Thu, 28 Jan 2021 09:12:04 +0100 Subject: [PATCH] Initial --- .gitignore | 4 + config.ini | 19 + package-lock.json | 775 ++++++++++++++++++ package.json | 20 + src/Main.ts | 75 ++ src/config/Configuration.ts | 36 + src/converters/figure/FigureConverter.ts | 62 ++ src/converters/figure/FigureJsonMapper.ts | 85 ++ src/converters/figure/FigureType.ts | 20 + src/converters/furniture/FurniJsonMapper.ts | 186 +++++ src/converters/furniture/FurniTypes.ts | 170 ++++ .../furniture/FurnitureConverter.ts | 85 ++ src/converters/util/SpriteSheetConverter.ts | 103 +++ src/converters/util/SpriteSheetTypes.ts | 55 ++ src/downloaders/FigureDownloader.ts | 69 ++ src/downloaders/FurnitureDownloader.ts | 92 +++ src/swf/HabboAssetSWF.ts | 188 +++++ src/swf/tags/CharacterTag.ts | 20 + src/swf/tags/DefineBinaryDataTag.ts | 43 + src/swf/tags/ITag.ts | 3 + src/swf/tags/ImageTag.ts | 37 + src/swf/tags/SymbolClassTag.ts | 42 + src/utils/CustomIterator.ts | 54 ++ src/utils/File.ts | 33 + tsconfig.json | 14 + 25 files changed, 2290 insertions(+) create mode 100644 .gitignore create mode 100644 config.ini create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/Main.ts create mode 100644 src/config/Configuration.ts create mode 100644 src/converters/figure/FigureConverter.ts create mode 100644 src/converters/figure/FigureJsonMapper.ts create mode 100644 src/converters/figure/FigureType.ts create mode 100644 src/converters/furniture/FurniJsonMapper.ts create mode 100644 src/converters/furniture/FurniTypes.ts create mode 100644 src/converters/furniture/FurnitureConverter.ts create mode 100644 src/converters/util/SpriteSheetConverter.ts create mode 100644 src/converters/util/SpriteSheetTypes.ts create mode 100644 src/downloaders/FigureDownloader.ts create mode 100644 src/downloaders/FurnitureDownloader.ts create mode 100644 src/swf/HabboAssetSWF.ts create mode 100644 src/swf/tags/CharacterTag.ts create mode 100644 src/swf/tags/DefineBinaryDataTag.ts create mode 100644 src/swf/tags/ITag.ts create mode 100644 src/swf/tags/ImageTag.ts create mode 100644 src/swf/tags/SymbolClassTag.ts create mode 100644 src/utils/CustomIterator.ts create mode 100644 src/utils/File.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8d5a7a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.idea +rare_dragonlamp \ No newline at end of file diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..c659e84 --- /dev/null +++ b/config.ini @@ -0,0 +1,19 @@ +output.folder.furniture=/home/user/WebstormProjects/sites/assets.nitro.se/game/dcr/endrit/furniture/ +output.folder.figure=/home/user/WebstormProjects/sites/assets.nitro.se/game/gordon/figure-new1/ +output.folder.effect=/home/user/WebstormProjects/sites/assets.nitro.se/game/effect/ +output.folder.pet=/home/user/WebstormProjects/sites/assets.nitro.se/game/pet/ +furnidata.url=http://assets.nitro.se/game/gamedata/furnidata-entry.xml +figuremap.url=http://assets.nitro.se/game/gordon/PRODUCTION-201701242205-837386173/figuremap.xml +effectmap.url=http://assets.nitro.se/game/gordon/PRODUCTION-201701242205-837386173/effectmap.xml +external_vars.url=http://assets.nitro.se/game/gamedata/external_variables.txt +dynamic.download.url.furniture=/home/user/WebstormProjects/sites/assets.nitro.se/game/dcr/endrit/hof_furni/%className%.swf +dynamic.download.url.figure=/home/user/WebstormProjects/sites/assets.nitro.se/game/gordon/PRODUCTION-201701242205-837386173/%className%.swf +dynamic.download.url.effect=http://assets.nitro.se/game/gordon/PRODUCTION-201701242205-837386173/%className%.swf +dynamic.download.url.pet=http://assets.nitro.se/game/gordon/PRODUCTION-201701242205-837386173/%className%.swf +convert.furniture=0 +convert.figure=1 +convert.effect=1 +convert.pet=1 +figure.rotation.enabled=0 +figure.skip.non-existing.asset.images=0 +convert.threads=4 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4cd6a97 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,775 @@ +{ + "name": "nitroassetconverter", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@jvitela/mustache-wax": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@jvitela/mustache-wax/-/mustache-wax-1.0.3.tgz", + "integrity": "sha512-5M5p8d9YQwEDSa0oLoeCZ8ECiM2ZJLKxI/D0pDByiNBJw+4Tizjk/NMIjGx7IoJOGBnWcfHX3Pwd6m/MpMHoGA==" + }, + "@types/node": { + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==" + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "amdefine": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-0.1.1.tgz", + "integrity": "sha1-tcdcUyBS3M1qOcAGTHcsjVegbNI=" + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bignumber.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", + "integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=" + }, + "bl": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", + "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", + "requires": { + "readable-stream": "~1.0.26" + } + }, + "bmp-js": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.0.3.tgz", + "integrity": "sha1-ZBE+nHzxICs3btYHvzBibr5XsYo=" + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" + }, + "concat-frames": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/concat-frames/-/concat-frames-1.0.3.tgz", + "integrity": "sha1-z+moFvJce3WWPZn+8vSqBFj7+Zs=", + "requires": { + "pixel-stream": "^1.0.3" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=" + }, + "exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=" + }, + "exif-reader": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/exif-reader/-/exif-reader-1.0.3.tgz", + "integrity": "sha512-tWMBj1+9jUSibgR/kv/GQ/fkR0biaN9GEZ5iPdf7jFeH//d2bSzgPoaWf1OfMv4MXFD4upwvpCCyeMvSyLWSfA==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "free-tex-packer-core": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/free-tex-packer-core/-/free-tex-packer-core-0.3.2.tgz", + "integrity": "sha512-CauShorQ77Pgm+u2YalXX6HyR0rnkuccS3lIKCZpnBeAjcUV6SXCwswUGS8cbrvS5/dIwM5UeqFu02X86PPMkg==", + "requires": { + "@jvitela/mustache-wax": "^1.0.1", + "jimp": "^0.2.28", + "maxrects-packer": "^2.5.0", + "mustache": "^2.3.0", + "tinify": "^1.5.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ip-regex": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-1.0.3.tgz", + "integrity": "sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0=" + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jimp": { + "version": "0.2.28", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.2.28.tgz", + "integrity": "sha1-3VKak3GQ9ClXp5N9Gsw6d2KZbqI=", + "requires": { + "bignumber.js": "^2.1.0", + "bmp-js": "0.0.3", + "es6-promise": "^3.0.2", + "exif-parser": "^0.1.9", + "file-type": "^3.1.0", + "jpeg-js": "^0.2.0", + "load-bmfont": "^1.2.3", + "mime": "^1.3.4", + "mkdirp": "0.5.1", + "pixelmatch": "^4.0.0", + "pngjs": "^3.0.0", + "read-chunk": "^1.0.1", + "request": "^2.65.0", + "stream-to-buffer": "^0.1.0", + "tinycolor2": "^1.1.2", + "url-regex": "^3.0.0" + } + }, + "jpeg-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz", + "integrity": "sha1-U+RI7J0mPmgyZkZ+lELSxaLvVII=" + }, + "jpg-stream": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/jpg-stream/-/jpg-stream-1.1.2.tgz", + "integrity": "sha1-TboVnZ0ZNo3yExj2SM7pgKcr5Ac=", + "requires": { + "exif-reader": "^1.0.0", + "pixel-stream": "^1.0.3" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "load-bmfont": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", + "requires": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "lzma-purejs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/lzma-purejs/-/lzma-purejs-0.9.3.tgz", + "integrity": "sha1-yJF+iUsbTbXIZbkn34ZO3edZzN4=", + "requires": { + "amdefine": "~0.1.0", + "commander": "~2.0.0" + } + }, + "maxrects-packer": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/maxrects-packer/-/maxrects-packer-2.7.2.tgz", + "integrity": "sha512-akd5IRLPqQeWlpJyRJyfYq86VB05zzbMIdyTgLxRk4z1H0A8g4oTJW31Yo6zO9piSRsFNYdzmgudW7J2g1gEhQ==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" + }, + "mime-types": { + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "requires": { + "mime-db": "1.45.0" + } + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mustache": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", + "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==" + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "node-gzip": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/node-gzip/-/node-gzip-1.1.2.tgz", + "integrity": "sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU=" + }, + "parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY=" + }, + "parse-bmfont-xml": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", + "requires": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.4.5" + } + }, + "parse-headers": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz", + "integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "phin": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" + }, + "pixel-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pixel-stream/-/pixel-stream-1.0.3.tgz", + "integrity": "sha1-U+jFSyHVUIOTtTvLMrZKd1Xx+l4=", + "requires": { + "shallow-copy": "0.0.1" + } + }, + "pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=", + "requires": { + "pngjs": "^3.0.0" + } + }, + "png-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/png-stream/-/png-stream-1.0.5.tgz", + "integrity": "sha1-W8cWh+qJWUJ+lQ5Sx8yknipvBMY=", + "requires": { + "bl": "^0.9.3", + "buffer-crc32": "^0.2.3", + "buffer-equal": "^0.0.1", + "pixel-stream": "^1.0.3" + } + }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "promise-nodeify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/promise-nodeify/-/promise-nodeify-3.0.1.tgz", + "integrity": "sha512-ghsSuzZXJX8iO7WVec2z7GI+Xk/EyiD+JZK7AZKhUqYfpLa/Zs4ylUD+CwwnKlG6G3HnkUPMAi6PO7zeqGKssg==" + }, + "proxying-agent": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/proxying-agent/-/proxying-agent-2.4.0.tgz", + "integrity": "sha512-b9vDqIcViJZVsWPpQlp9Py74u+Wqd0a+kMkkg7zX58mwNtrNbOChNlRTM7lUrlpiwNzyJCV8+5D8rnZYLDFh7Q==" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "read-chunk": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-1.0.1.tgz", + "integrity": "sha1-X2jKswfmY/GZk1J9m1icrORmEZQ=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stream-to": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-to/-/stream-to-0.2.2.tgz", + "integrity": "sha1-hDBgmNhf25kLn6MAsbPM9V6O8B0=" + }, + "stream-to-array": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", + "integrity": "sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=", + "requires": { + "any-promise": "^1.1.0" + } + }, + "stream-to-buffer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stream-to-buffer/-/stream-to-buffer-0.1.0.tgz", + "integrity": "sha1-JnmdkDqyAlyb1VCsRxcbAPjdgKk=", + "requires": { + "stream-to": "~0.2.0" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "swf-extract": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/swf-extract/-/swf-extract-1.1.0.tgz", + "integrity": "sha1-DS6Q01lKFu9ly8hfuEEiRhdz9t0=", + "requires": { + "concat-frames": "^1.0.3", + "jpg-stream": "^1.1.1", + "lzma-purejs": "~0.9.3", + "png-stream": "^1.0.5", + "stream-to-array": "^2.3.0" + } + }, + "tinify": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/tinify/-/tinify-1.5.0.tgz", + "integrity": "sha1-a6LqfognyEXSY947RvTbe61VWSA=", + "requires": { + "promise-nodeify": ">= 0.1", + "proxying-agent": ">= 2.1" + } + }, + "tinycolor2": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", + "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "url-regex": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-3.2.0.tgz", + "integrity": "sha1-260eDJ4p4QXdCx8J9oYvf9tIJyQ=", + "requires": { + "ip-regex": "^1.0.1" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "requires": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha1-qQKekp09vN7RafPG4oI42VpdWig=" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1a5b183 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "nitroassetconverter", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@types/node": "^14.14.22", + "free-tex-packer-core": "^0.3.2", + "lodash": "^4.17.20", + "node-fetch": "^2.6.1", + "node-gzip": "^1.1.2", + "swf-extract": "^1.1.0", + "xml2js": "^0.4.23" + } +} diff --git a/src/Main.ts b/src/Main.ts new file mode 100644 index 0000000..7db9bfd --- /dev/null +++ b/src/Main.ts @@ -0,0 +1,75 @@ +import Configuration from "./config/Configuration"; +import FigureDownloader from "./downloaders/FigureDownloader"; +import HabboAssetSWF from "./swf/HabboAssetSWF"; +import SpriteSheetConverter from "./converters/util/SpriteSheetConverter"; +import FigureConverter from "./converters/figure/FigureConverter"; +import File from "./utils/File"; +import FurnitureDownloader from "./downloaders/FurnitureDownloader"; +import FurnitureConverter from "./converters/furniture/FurnitureConverter"; + +(async () => { + const config = new Configuration(); + await config.init(); + + const outputFolderFigure = new File(config.getValue("output.folder.figure")); + if (!outputFolderFigure.isDirectory()) { + outputFolderFigure.mkdirs(); + } + + const outputFolderFurniture = new File(config.getValue("output.folder.furniture")); + if (!outputFolderFurniture.isDirectory()) { + outputFolderFurniture.mkdirs(); + } + + const spriteSheetConverter = new SpriteSheetConverter(); + const figureConverter = new FigureConverter(config); + const furnitureConverter= new FurnitureConverter(config); + + if (config.getBoolean("convert.figure")) { + const figureDownloader = new FigureDownloader(config); + await figureDownloader.download(async function (habboAssetSwf: HabboAssetSWF) { + + console.log("Attempt parsing figure: " + habboAssetSwf.getDocumentClass()); + + try { + const spriteSheetType = await spriteSheetConverter.generateSpriteSheet(habboAssetSwf, outputFolderFigure.path, "figure"); + if (spriteSheetType !== null) + await figureConverter.fromHabboAsset(habboAssetSwf, outputFolderFigure.path, "figure", spriteSheetType); + + } catch (e) { + console.log("Figure error: " + habboAssetSwf.getDocumentClass()); + } + }); + } + + let count = 0; + + if (config.getBoolean("convert.furniture")) { + const furnitureDownloader = new FurnitureDownloader(config); + await furnitureDownloader.download(async function (habboAssetSwf: HabboAssetSWF, className: string) { + //console.log("Attempt parsing furniture: " + habboAssetSwf.getDocumentClass()); + + try { + const assetOuputFolder = new File(outputFolderFurniture.path + "/" + className); + if (!assetOuputFolder.isDirectory()) { + assetOuputFolder.mkdirs(); + } else if (assetOuputFolder.list().length > 0) { + console.log("Furniture already exists or the directory is not empty!"); + return; + } + + const spriteSheetType = await spriteSheetConverter.generateSpriteSheet(habboAssetSwf, assetOuputFolder.path, "furniture"); + if (spriteSheetType !== null) { + await furnitureConverter.fromHabboAsset(habboAssetSwf, assetOuputFolder.path, "furniture", spriteSheetType); + } + } catch (e) { + console.log("Furniture error: " + habboAssetSwf.getDocumentClass()); + console.log(e); + } + }); + + console.log(`Parsed ${count} furnitures`) + } + + console.log('finished!'); +})() \ No newline at end of file diff --git a/src/config/Configuration.ts b/src/config/Configuration.ts new file mode 100644 index 0000000..daa0441 --- /dev/null +++ b/src/config/Configuration.ts @@ -0,0 +1,36 @@ +const fs = require('fs/promises'); + +export default class Configuration { + + private readonly _config: Map; + + constructor() { + this._config = new Map(); + } + + async init() { + const content = await fs.readFile("/home/user/git/nitro-asset-converter-node/config.ini"); + + const config: string[] = content.toString("utf-8").split("\n"); + for (const configEntry of config) { + const configEntrySplit = configEntry.split("="); + const configKey = configEntrySplit[0]; + const configValue = configEntrySplit[1]; + + this._config.set(configKey, configValue); + } + } + + public getBoolean(key: string): boolean { + return this._config.get(key) === "1"; + } + + public getValue(key: string, value: string = ""): string { + if (this._config.has(key)) { + // @ts-ignore + return this._config.get(key); + } + + return value; + } +} \ No newline at end of file diff --git a/src/converters/figure/FigureConverter.ts b/src/converters/figure/FigureConverter.ts new file mode 100644 index 0000000..53e203e --- /dev/null +++ b/src/converters/figure/FigureConverter.ts @@ -0,0 +1,62 @@ +import HabboAssetSWF from "../../swf/HabboAssetSWF"; +import {SpriteSheetType} from "../util/SpriteSheetTypes"; +import DefineBinaryDataTag from "../../swf/tags/DefineBinaryDataTag"; +import Configuration from "../../config/Configuration"; +import FigureJsonMapper from "./FigureJsonMapper"; +import {FigureJson} from "./FigureType"; +import File from "../../utils/File"; + +const xml2js = require('xml2js'); +const parser = new xml2js.Parser(/* options */); + +const fs = require('fs').promises; +const {gzip} = require('node-gzip'); + +export default class FigureConverter { + + private readonly _figureJsonMapper: FigureJsonMapper; + + constructor(config: Configuration) { + this._figureJsonMapper = new FigureJsonMapper(config); + } + + private getBinaryData(habboAssetSWF: HabboAssetSWF, type: string, documentNameTwice: boolean) { + let binaryName: string = habboAssetSWF.getFullClassName(type, documentNameTwice); + let tag = habboAssetSWF.getBinaryTagByName(binaryName); + if (tag === null) { + binaryName = habboAssetSWF.getFullClassNameSnake(type, documentNameTwice, true); + tag = habboAssetSWF.getBinaryTagByName(binaryName); + } + + return tag; + } + + private async convertXML2JSON(habboAssetSWF: HabboAssetSWF): Promise { + const manifestXML: DefineBinaryDataTag | null = this.getBinaryData(habboAssetSWF, "manifest", false); + if (manifestXML !== null) { + const result = await parser.parseStringPromise(manifestXML.binaryData); + + return this._figureJsonMapper.mapXML(habboAssetSWF, result); + } + + return null; + } + + public async fromHabboAsset(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string, spriteSheetType: SpriteSheetType) { + const manifestJson = await this.convertXML2JSON(habboAssetSWF); + if (manifestJson !== null) { + manifestJson.spritesheet = spriteSheetType; + + const path = outputFolder + "/" + habboAssetSWF.getDocumentClass() + ".nitro"; + const assetOuputFolder = new File(path); + if (assetOuputFolder.exists()) { + console.log("Furniture already exists or the directory is not empty!"); + + return; + } + + const compressed = await gzip(JSON.stringify(manifestJson)); + await fs.writeFile(path, compressed); + } + } +} \ No newline at end of file diff --git a/src/converters/figure/FigureJsonMapper.ts b/src/converters/figure/FigureJsonMapper.ts new file mode 100644 index 0000000..94f2caa --- /dev/null +++ b/src/converters/figure/FigureJsonMapper.ts @@ -0,0 +1,85 @@ +import Configuration from "../../config/Configuration"; +import HabboAssetSWF from "../../swf/HabboAssetSWF"; +import {FigureAsset, FigureAssets, FigureJson} from "./FigureType"; +import FigureDownloader from "../../downloaders/FigureDownloader"; +import SpriteSheetConverter from "../util/SpriteSheetConverter"; + +export default class FigureJsonMapper { + + private static MUST_START_WITH: string = "h_"; + + private readonly _config: Configuration; + + constructor(config: Configuration) { + this._config = config; + } + + + public mapXML(habboAssetSWF: HabboAssetSWF, manifestXML: any): FigureJson { + const name = habboAssetSWF.getDocumentClass(); + + return { + name: name, + type: FigureDownloader.types.get(name) as string, + assets: this.mapManifestXML(habboAssetSWF, manifestXML), + spritesheet: null as any + }; + } + + private mapManifestXML(habboAssetSWF: HabboAssetSWF, manifestXML: any): FigureAssets { + const assets: any = {}; + + const libraries = manifestXML.manifest.library; + for (const library of libraries) { + for (const assetObj of library.assets) { + for (const asset of assetObj.asset) { + const assetInfo = asset['$']; + const name = assetInfo.name; + + if (name.startsWith(FigureJsonMapper.MUST_START_WITH)) { + let hasImage = false; + for (const imageTag of habboAssetSWF.imageTags()) { + if (imageTag.className.includes(name)) { + hasImage = true; + } + } + + if (hasImage || !this._config.getBoolean("figure.skip.non-existing.asset.images")) { + const figureAsset: FigureAsset = {} as any; + figureAsset.name = name; + figureAsset.x = asset.param[0]['$'].value.split(',')[0]; + figureAsset.y = asset.param[0]['$'].value.split(',')[1]; + + if (SpriteSheetConverter.imageSource.has(name)) { + figureAsset.source = SpriteSheetConverter.imageSource.get(name) as string; + } + + assets[name] = figureAsset; + /*FigureJSON.Asset asset = new FigureJSON.Asset(); + if (FFConverter.getConfig().getBoolean("figure.rotation.enabled")) { + String[] names = assetXML.getName().split("_"); + if (this.isInteger(names[4])) { + String firstName = names[0] + "_" + names[1] + "_" + names[2] + "_" + names[3] + "_%ROTATION%_" + names[5]; + Integer rotation = Integer.parseInt(names[4]); + if (rotation >= 0 && rotation < 8) { + if (assetRotations.containsKey(firstName)) { + assetRotations.get(firstName).add(rotation); + } else { + List rotations = new ArrayList(); + rotations.add(rotation); + assetRotations.put(firstName, rotations); + } + } + } + }*/ + } else { + console.log("Image " + name + " did not decompile for some reason"); + } + } + } + } + } + + return assets; + } +} \ No newline at end of file diff --git a/src/converters/figure/FigureType.ts b/src/converters/figure/FigureType.ts new file mode 100644 index 0000000..b4e4d24 --- /dev/null +++ b/src/converters/figure/FigureType.ts @@ -0,0 +1,20 @@ +import {SpriteSheetType} from "../util/SpriteSheetTypes"; + +export interface FigureJson { + type: string, + name: string, + spritesheet: SpriteSheetType, + assets: FigureAssets +} + +export interface FigureAssets { + [key: string]: FigureAsset +} + +export interface FigureAsset { + name: string, + source: string, + x: number, + y: number, + flipH: boolean +} \ No newline at end of file diff --git a/src/converters/furniture/FurniJsonMapper.ts b/src/converters/furniture/FurniJsonMapper.ts new file mode 100644 index 0000000..ce47b74 --- /dev/null +++ b/src/converters/furniture/FurniJsonMapper.ts @@ -0,0 +1,186 @@ +import { + Action, + Direction, + Directions, + FurniAsset, + FurniAssets, + FurniJson, + Layer, + Visualization, + VisualizationLayers +} from "./FurniTypes"; +import SpriteSheetConverter from "../util/SpriteSheetConverter"; + +export default class FurniJsonMapper { + private static readonly VISUALIZATION_DEFAULT_SIZE = 64; + + private static readonly VISUALIZATION_ICON_SIZE = 1; + + public mapXML(assetsXML: any, indexXML: any, logicXML: any, visualizationXML: any): FurniJson { + const furniJson: FurniJson = {} as any; + + furniJson.assets = this.mapAssetsXML(assetsXML) as any; + + this.mapIndexXML(indexXML, furniJson); + this.mapLogicXML(logicXML, furniJson); + this.mapVisualizationXML(visualizationXML, furniJson); + //console.log(furniJson); + + return furniJson; + } + + + private mapAssetsXML(assetsXML: any): FurniAssets | null { + const assets: FurniAssets = {} as any; + + for (const asset of assetsXML.assets.asset) { + const attributes = asset['$']; + if (!attributes.name.includes("_32_")) { + const furniAsset: FurniAsset = {} as any; + + if (attributes.source !== undefined) { + furniAsset.source = attributes.source; + if (SpriteSheetConverter.imageSource.has(attributes.source)) { + furniAsset.source = SpriteSheetConverter.imageSource.get(attributes.source) as string; + } + } + + if (SpriteSheetConverter.imageSource.has(attributes.name)) { + furniAsset.source = SpriteSheetConverter.imageSource.get(attributes.name) as string; + } + + furniAsset.x = attributes.x; + furniAsset.y = attributes.y; + furniAsset.flipH = attributes.flipH === "1"; + assets[attributes.name] = furniAsset; + } + } + return Object.keys(assets).length > 0 ? assets : null; + } + + private mapIndexXML(indexXML: any, output: FurniJson) { + const attributes = indexXML.object['$']; + output.name = attributes.type; + output.logicType = attributes.logic; + output.visualizationType = attributes.visualization; + } + + private mapLogicXML(logicXML: any, output: FurniJson) { + const objectData = logicXML.objectData; + const attributes = objectData['$']; + + const model = objectData.model[0]; + const dimensions = model.dimensions[0]['$']; + output.dimensions = { + x: parseInt(dimensions.x), + y: parseInt(dimensions.y), + z: parseFloat(dimensions.z) + } + + const directions: Array = []; + if (model.directions === undefined) { + directions.push(0); + } else { + for (const directionObj of model.directions) { + const direction = directionObj.direction; + for (const dir of direction) { + const dirAttributes = dir['$']; + directions.push(dirAttributes); + } + } + } + + if (model.action !== undefined) { + const action: Action = {} as any; + } + + output.directions = directions; + } + + private mapVisualizationXML(visualizationXML: any, output: FurniJson) { + const visualizationsArray: Visualization[] = []; + + const visualizationData = visualizationXML.visualizationData; + const attributes = visualizationData['$']; + + const visualizations = visualizationData.graphics; + for (const visualizationArr of visualizations) { + for (const visualization of visualizationArr.visualization) { + const attributes = visualization['$']; + + if (attributes.size == FurniJsonMapper.VISUALIZATION_DEFAULT_SIZE || attributes.size == FurniJsonMapper.VISUALIZATION_ICON_SIZE) { + const visualizationType: Visualization = {} as any; + visualizationType.angle = attributes.angle; + visualizationType.layerCount = attributes.layerCount; + visualizationType.size = attributes.size; + + this.mapVisualizationLayersXML(visualization, visualizationType); + this.mapVisualizationDirectionXML(visualization, visualizationType); + + visualizationsArray.push(visualizationType); + } + } + } + + output.visualizations = visualizationsArray; + } + + private mapVisualizationColorXML(visualization: any, visualizationType: Visualization) { + if (visualization.colors !== undefined && visualization.colors.length > 0) { + + } + } + + private mapVisualizationDirectionXML(visualization: any, visualizationType: Visualization) { + if (visualization.directions !== undefined && visualization.directions.length > 0) { + const directions: Directions = {} as any; + for (const directionParent of visualization.directions) { + for (const direction of directionParent.direction) { + const attributes = direction['$']; + const directionType: Direction = {} as any; + directionType.id = attributes.id; + + if (direction.layers !== undefined && direction.layers.length > 0) { + directionType.layers = this.generateLayers(direction, visualizationType); + } + + directions[directionType.id] = directionType; + } + } + + if (Object.keys(directions).length > 0) visualizationType.directions = directions; + } + } + + private mapVisualizationLayersXML(visualization: any, visualizationType: Visualization) { + if (visualization.layers !== undefined && visualization.layers.length > 0) { + for (const layerEntry of visualization.layers) { + const layer = layerEntry.layer; + visualizationType.layers = this.generateLayers(layer, visualizationType); + } + } + } + + private generateLayers(visualization: any, visualizationType: Visualization): VisualizationLayers { + const visualizationLayers: VisualizationLayers = {} as any; + for (const layerEntry of visualization.layers) { + const layer = layerEntry.layer; + const layerAttributes = layer['$']; + + const layerType: Layer = { + id: layerAttributes.id, + alpha: layerAttributes.alpha, + ink: layerAttributes.ink, + tag: layerAttributes.tag, + x: layerAttributes.x, + y: layerAttributes.y, + z: layerAttributes.z, + } as any; + if (layerAttributes.ignoreMouse !== undefined) layerType.ignoreMouse = layerAttributes.ignoreMouse === '1'; + + visualizationLayers[layerAttributes.id] = layerType; + } + + return visualizationLayers; + } +} \ No newline at end of file diff --git a/src/converters/furniture/FurniTypes.ts b/src/converters/furniture/FurniTypes.ts new file mode 100644 index 0000000..acd2ca9 --- /dev/null +++ b/src/converters/furniture/FurniTypes.ts @@ -0,0 +1,170 @@ +import {SpriteSheetType} from "../util/SpriteSheetTypes"; + +export interface FurniJson { + type: string, + name: string, + visualizationType: string, + logicType: string, + maskType: string, + credits: string, + + spritesheet: SpriteSheetType, + + dimensions: Dimensions, + action: Action; + directions: number[], + assets: FurniAssets, + visualizations: Visualization[] +} + +export interface Visualization { + layerCount: number, + angle: number, + size: number, + + layers: VisualizationLayers, + directions: Directions, + colors: Colors, + animations: Animations, + postures: Postures; + gestures: Gestures +} + +export interface Gestures { + [key: string]: Gesture +} + +export interface Gesture { + id: string, + animationId: number +} + +export interface Postures { + [key: string]: Posture +} + +export interface Posture { + id: string, + animationId: number +} + +export interface Offset { + direction: number, + x: number, + y: number +} + +export interface Frame { + id: number, + x: number, + y: number, + randomX: number, + randomY: number, + + offsets: Offset[] +} + +export interface Frames { + [key: number]: Frame +} + +export interface FrameSequence { + loopCount: number, + random: number, + + frames: Frames +} + +export interface FrameSequences { + [key: number]: FrameSequence +} + +export interface AnimationLayer { + id: number, + loopCount: number, + frameRepeat: number, + random: number, + + frameSequences: FrameSequences +} + +export interface AnimationLayers { + [key: number]: AnimationLayer +} + +export interface Animations { + [key: number]: Animation +} + +export interface Animation { + id: number, + transitionTo: number, + transitionFrom: number, + immediateChangeFrom: string, + + layers: AnimationLayers; +} + +export interface ColorLayers { + [key: number]: ColorLayer +} + +export interface ColorLayer { + id: number, + color: number +} + +export interface Colors { + [key: number]: Color +} + +export interface Color { + id: number; + layers: ColorLayers; +} + +export interface Directions { + [key: number]: Direction +} + +export interface Direction { + id: number; + layers: VisualizationLayers; +} + +export interface VisualizationLayers { + [key: number]: Layer +} + +export interface Layer { + id: number, + alpha: number, + x: number, + y: number, + z: number, + ink: string, + tag: string, + ignoreMouse: boolean +} + +export interface Action { + link: string, + startState: number +} + +export interface Dimensions { + x: number, + y: number, + z: number +} + +export interface FurniAssets { + [key: string]: FurniAsset +} + +export interface FurniAsset { + source: string, + x: number, + y: number, + flipH: boolean +} \ No newline at end of file diff --git a/src/converters/furniture/FurnitureConverter.ts b/src/converters/furniture/FurnitureConverter.ts new file mode 100644 index 0000000..a5df835 --- /dev/null +++ b/src/converters/furniture/FurnitureConverter.ts @@ -0,0 +1,85 @@ +import HabboAssetSWF from "../../swf/HabboAssetSWF"; +import {SpriteSheetType} from "../util/SpriteSheetTypes"; +import DefineBinaryDataTag from "../../swf/tags/DefineBinaryDataTag"; +import Configuration from "../../config/Configuration"; +import FurniJsonMapper from "./FurniJsonMapper"; +import {FurniJson} from "./FurniTypes"; + +const xml2js = require('xml2js'); +const parser = new xml2js.Parser(/* options */); + +const fs = require('fs').promises; + +export default class FurnitureConverter { + + private readonly _furniJsonMapper: FurniJsonMapper; + + constructor(config: Configuration) { + this._furniJsonMapper = new FurniJsonMapper(); + } + + private static getBinaryData(habboAssetSWF: HabboAssetSWF, type: string, documentNameTwice: boolean) { + let binaryName: string = habboAssetSWF.getFullClassName(type, documentNameTwice); + let tag = habboAssetSWF.getBinaryTagByName(binaryName); + if (tag === null) { + binaryName = habboAssetSWF.getFullClassNameSnake(type, documentNameTwice, true); + tag = habboAssetSWF.getBinaryTagByName(binaryName); + } + + return tag; + } + + private static async getAssetsXML(habboAssetSWF: HabboAssetSWF): Promise { + const binaryData: DefineBinaryDataTag | null = FurnitureConverter.getBinaryData(habboAssetSWF, "assets", true); + if (binaryData !== null) { + return await parser.parseStringPromise(binaryData.binaryData); + } + + return null; + } + + private static async getLogicXML(habboAssetSWF: HabboAssetSWF): Promise { + const binaryData: DefineBinaryDataTag | null = FurnitureConverter.getBinaryData(habboAssetSWF, "logic", true); + if (binaryData !== null) { + return await parser.parseStringPromise(binaryData.binaryData); + } + + return null; + } + + private static async getIndexXML(habboAssetSWF: HabboAssetSWF): Promise { + const binaryData: DefineBinaryDataTag | null = FurnitureConverter.getBinaryData(habboAssetSWF, "index", true); + if (binaryData !== null) { + return await parser.parseStringPromise(binaryData.binaryData); + } + + return null; + } + + private static async getVisualizationXML(habboAssetSWF: HabboAssetSWF): Promise { + const binaryData: DefineBinaryDataTag | null = FurnitureConverter.getBinaryData(habboAssetSWF, "visualization", true); + if (binaryData !== null) { + return await parser.parseStringPromise(binaryData.binaryData); + } + + return null; + } + + private async convertXML2JSON(habboAssetSWF: HabboAssetSWF): Promise { + const assetXml = await FurnitureConverter.getAssetsXML(habboAssetSWF); + const logicXml = await FurnitureConverter.getLogicXML(habboAssetSWF); + const indexXml = await FurnitureConverter.getIndexXML(habboAssetSWF); + const visualizationXml = await FurnitureConverter.getVisualizationXML(habboAssetSWF); + + return this._furniJsonMapper.mapXML(assetXml, indexXml, logicXml, visualizationXml); + } + + public async fromHabboAsset(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string, spriteSheetType: SpriteSheetType) { + const furnitureJson = await this.convertXML2JSON(habboAssetSWF); + if (furnitureJson !== null) { + furnitureJson.spritesheet = spriteSheetType; + + await fs.writeFile(outputFolder + "/" + habboAssetSWF.getDocumentClass() + ".json", JSON.stringify(furnitureJson)); + } + } +} \ No newline at end of file diff --git a/src/converters/util/SpriteSheetConverter.ts b/src/converters/util/SpriteSheetConverter.ts new file mode 100644 index 0000000..d5b7af9 --- /dev/null +++ b/src/converters/util/SpriteSheetConverter.ts @@ -0,0 +1,103 @@ +import {SpriteSheetType} from "./SpriteSheetTypes"; + +const fs = require('fs').promises; +let {packAsync} = require("free-tex-packer-core"); + +import HabboAssetSWF from "../../swf/HabboAssetSWF"; +import SymbolClassTag from "../../swf/tags/SymbolClassTag"; +import ImageTag from "../../swf/tags/ImageTag"; + +export default class SpriteSheetConverter { + public static imageSource: Map = new Map(); + + public async generateSpriteSheet(habboAssetSWF: HabboAssetSWF, outputFolder: string, type: string): Promise { + const tagList: Array = habboAssetSWF.symbolTags(); + const names: Array = new Array(); + const tags: Array = new Array(); + for (const tag of tagList) { + names.push(...tag.names); + tags.push(...tag.tags); + } + + const images: Array<{ path: String, contents: Buffer }> = new Array<{ path: String, contents: Buffer }>(); + + const imageTags: Array = habboAssetSWF.imageTags(); + for (const imageTag of imageTags) { + if (tags.includes(imageTag.characterID)) { + for (let i = 0; i < tags.length; i++) { + if (tags[i] == imageTag.characterID) { + if ( + (names[i].includes("_64_") && type === "furniture") || + (names[i].includes("_icon_") && type === "furniture") || + (names[i].includes("_h_") && (type === "figure" || type === "effect")) || + (names[i].includes("_64_") && type === "pet")) { + + if (names[i] !== imageTag.className) { + SpriteSheetConverter.imageSource.set(names[i].substring(habboAssetSWF.getDocumentClass().length + 1), imageTag.className.substring(habboAssetSWF.getDocumentClass().length + 1)); + if ((imageTag.className.includes("_32_") && type === "furniture") || + (imageTag.className.includes("_sh_") && (type === "figure" || type === "effect")) || + (imageTag.className.includes("_32_") && type === "pet")) { + images.push({ + path: imageTag.className, + contents: imageTag.imgData + }); + } + } + } + } + } + } + + if ((imageTag.className.includes("_64_") && type === "furniture") || + (imageTag.className.includes("_icon_") && type === "furniture") || + (imageTag.className.includes("_h_") && (type === "figure" || type === "effect")) || + (imageTag.className.includes("_64_") && type === "pet")) { + images.push({ + path: imageTag.className, + contents: imageTag.imgData + }); + } + + } + + if (images.length === 0) { + return null; + } + + return await this.packImages(habboAssetSWF.getDocumentClass(), outputFolder + "/", images); + } + + async packImages(documentClass: string, outputFolder: string, images: Array<{ path: String, contents: Buffer }>): Promise { + let options = { + textureName: documentClass, + width: 1024, + height: 1024, + fixedSize: false, + allowRotation: true, + detectIdentical: true, + allowTrim: true, + exporter: "Pixi" + }; + + let spriteSheetType: SpriteSheetType | null = null; + let base64 = ""; + try { + const files = await packAsync(images, options); + for (let item of files) { + if (item.name.endsWith(".json")) { + spriteSheetType = JSON.parse(item.buffer.toString('utf8')); + } else { + base64 = item.buffer.toString("base64"); + //await fs.writeFile(outputFolder + item.name, item.buffer); + } + } + + if (spriteSheetType === null) throw new Error("Failed to parse SpriteSheet. " + images[0].path); + } catch (error) { + console.log("Error: " + error); + } + + if (spriteSheetType !== null) spriteSheetType.meta.imageb64 = base64; + return spriteSheetType; + } +} \ No newline at end of file diff --git a/src/converters/util/SpriteSheetTypes.ts b/src/converters/util/SpriteSheetTypes.ts new file mode 100644 index 0000000..9b4277e --- /dev/null +++ b/src/converters/util/SpriteSheetTypes.ts @@ -0,0 +1,55 @@ +export interface SpriteSheetType { + frames: SpriteSheetFrames, + meta: SpriteSheetMeta +} + +export interface SpriteSheetFrames { + [key: string]: SpriteSheetFrame +} + +export interface SpriteSheetFrame { + frame: SpriteSheetFrameDimensions, + rotated: boolean, + trimmed: boolean, + spriteSourceSize: SpriteSourceSize + sourceSize: SourceSize, + pivot: FramePivot +} + +export interface FramePivot { + x: number, + y: number +} + +export interface SourceSize { + w: number, + h: number +} + +export interface SpriteSourceSize { + x: number, + y: number, + w: number, + h: number +} + +export interface SpriteSheetFrameDimensions { + x: number, + y: number, + w: number, + h: number +} + +export interface SpriteSheetMeta { + app: string, + version: string, + imageb64: string, + format: string, + size: SpriteSheetSize, + scale: number +} + +export interface SpriteSheetSize { + w: number, + h: number +} \ No newline at end of file diff --git a/src/downloaders/FigureDownloader.ts b/src/downloaders/FigureDownloader.ts new file mode 100644 index 0000000..452b4a4 --- /dev/null +++ b/src/downloaders/FigureDownloader.ts @@ -0,0 +1,69 @@ +import Configuration from "../config/Configuration"; +import HabboAssetSWF from "../swf/HabboAssetSWF"; +import File from "../utils/File"; + +const fs = require("fs"); +const fetch = require('node-fetch'); +const xml2js = require('xml2js'); +const parser = new xml2js.Parser(/* options */); +const util = require('util'); + +const readFile = util.promisify(fs.readFile); + +export default class FigureDownloader { + + private readonly _config: Configuration; + + constructor(config: Configuration) { + this._config = config; + } + + + public static types: Map = new Map(); + + public async download(callback: (habboAssetSwf: HabboAssetSWF) => Promise) { + const outputFolderFigure = this._config.getValue("output.folder.figure"); + const figureMap = await this.parseFigureMap(); + const map = figureMap.map; + + for (const lib of map.lib) { + const info = lib['$']; + const className: string = info.id.split("\\*")[0]; + if (className === "hh_human_fx") { + continue; + } + + const assetOutputFolder = new File(outputFolderFigure + "/" + className); + if (assetOutputFolder.exists()) { + continue; + } + + if (!FigureDownloader.types.has(className)) { + if (className !== "jacket_U_snowwar4_team1" && + className !== "jacket_U_snowwar4_team2") { //TODO: Figure out why snowstorm assets aren't converting... + + const url = this._config.getValue("dynamic.download.url.figure").replace("%className%", className); + if (!fs.existsSync(url)) { + console.log("SWF File does not exist: " + url); + return; + } + + const buffer: Buffer = await readFile(url); + const habboAssetSWF = new HabboAssetSWF(buffer); + await habboAssetSWF.setupAsync(); + + FigureDownloader.types.set(className, lib.part[0]['$'].type); + await callback(habboAssetSWF); + } + } + } + } + + async parseFigureMap() { + const figureMapPath = this._config.getValue("figuremap.url"); + const figureFetch = await fetch(figureMapPath); + const figureMap = await figureFetch.text(); + + return await parser.parseStringPromise(figureMap); + } +} \ No newline at end of file diff --git a/src/downloaders/FurnitureDownloader.ts b/src/downloaders/FurnitureDownloader.ts new file mode 100644 index 0000000..1479741 --- /dev/null +++ b/src/downloaders/FurnitureDownloader.ts @@ -0,0 +1,92 @@ +import HabboAssetSWF from "../swf/HabboAssetSWF"; +import Configuration from "../config/Configuration"; +import {type} from "os"; +import File from "../utils/File"; + +const fs = require("fs"); +const fetch = require('node-fetch'); +const xml2js = require('xml2js'); +const parser = new xml2js.Parser(/* options */); +const util = require('util'); + +const readFile = util.promisify(fs.readFile); + +export default class FurnitureDownloader { + + private readonly _config: Configuration; + + constructor(config: Configuration) { + this._config = config; + } + + public async download(callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise) { + + const outputFolderFurniture = new File(this._config.getValue("output.folder.furniture")); + + const furniDataObj = await this.parseFurniData(); + const furniData = furniDataObj.furnidata; + + const roomitemtypes = furniData.roomitemtypes; + const wallitemtypes = furniData.wallitemtypes; + + for (const roomItem of roomitemtypes) { + for (const furnitype of roomItem.furnitype) { + const attributes = furnitype['$']; + const className = attributes.classname.split("\*")[0]; + const revision = furnitype.revision[0]; + + const assetOuputFolder = new File(outputFolderFurniture.path + "/" + className); + if (assetOuputFolder.isDirectory()) { + continue; + } + + await this.extractFurniture(revision, className, callback); + } + } + + for (const wallItem of wallitemtypes) { + for (const furnitype of wallItem.furnitype) { + const attributes = furnitype['$']; + const className = attributes.classname.split("\*")[0]; + const revision = furnitype.revision[0]; + + const assetOuputFolder = new File(outputFolderFurniture + "/" + className); + if (assetOuputFolder.isDirectory()) { + continue; + } + + await this.extractFurniture(revision, className, callback); + } + } + } + + async extractFurniture(revision: string, className: string, callback: (habboAssetSwf: HabboAssetSWF, className: string) => Promise) { + if (className !== "rare_dragonlamp") return; + + const url = this._config.getValue("dynamic.download.url.furniture").replace("%revision%", revision).replace("%className%", className); + const file = new File(url); + if (!file.exists()) { + console.log("SWF File does not exist: " + file.path); + return; + } + + try { + const buffer: Buffer = await readFile(url); + const habboAssetSWF = new HabboAssetSWF(buffer); + await habboAssetSWF.setupAsync(); + + await callback(habboAssetSWF, className); + } catch (e) { + console.log("Error with furniture: " + url); + console.log(e); + } + } + + async parseFurniData() { + const furniDataFetch = this._config.getValue("furnidata.url"); + const furniFetch = await fetch(furniDataFetch); + const furniData = await furniFetch.text(); + + return await parser.parseStringPromise(furniData); + } +} \ No newline at end of file diff --git a/src/swf/HabboAssetSWF.ts b/src/swf/HabboAssetSWF.ts new file mode 100644 index 0000000..63ca029 --- /dev/null +++ b/src/swf/HabboAssetSWF.ts @@ -0,0 +1,188 @@ +import SymbolClassTag from "./tags/SymbolClassTag"; +import ImageTag from "./tags/ImageTag"; +import ITag from "./tags/ITag"; +import CustomIterator from "../utils/CustomIterator"; +import CharacterTag from "./tags/CharacterTag"; +import DefineBinaryDataTag from "./tags/DefineBinaryDataTag"; + +const {readFromBufferP, extractImages} = require('swf-extract'); + +export interface Tag { + code: number, + length: number, + rawData: Buffer +} + +export interface SWFFrameSize { + x: number, + y: number, + width: number, + height: number +} + +export interface SWFFileLength { + compressed: number, + uncompressed: number +} + +export interface SWF { + tags: Array, + version: number, + fileLength: SWFFileLength, + frameSize: SWFFrameSize, + frameRate: number, + frameCount: number +} + +export default class HabboAssetSWF { + + private swf: SWF | null; + + private readonly _tags: Array; + + private _documentClass: string | null = null; + + constructor(private readonly _buffer: Buffer) { + this.swf = null; + + this._tags = new Array(); + } + + public async setupAsync() { + this.swf = await readFromBufferP(this._buffer); + + if (this.swf === null) throw new Error("SWF Can't be null!"); + + for (const tag of this.swf.tags) { + if (tag.code === 76) { + this._tags.push(new SymbolClassTag(tag)); + } + + if (tag.code === 87) { + this._tags.push(new DefineBinaryDataTag(tag)); + } + } + + const images = await Promise.all(extractImages(this.swf.tags)); + for (const image of images) { + const imgObj: any = image; + this._tags.push(new ImageTag({ + code: imgObj.code, + characterID: imgObj.characterId, + imgType: imgObj.imgType, + imgData: imgObj.imgData + })); + } + + this.assignClassesToSymbols(); + } + + public imageTags(): Array { + return this._tags.filter((tag: ITag) => tag instanceof ImageTag).map(x => x as ImageTag); + } + + public symbolTags(): Array { + return this._tags.filter((tag: ITag) => tag instanceof SymbolClassTag).map(x => x as SymbolClassTag) + } + + private binaryTags(): Array { + return this._tags.filter((tag: ITag) => tag instanceof DefineBinaryDataTag).map(x => x as DefineBinaryDataTag); + } + + public getBinaryTagByName(name: string): DefineBinaryDataTag | null { + const streamTag = this.binaryTags() + .filter(tag => tag.className === name)[0]; + + if (streamTag === undefined) return null; + + return streamTag; + } + + private assignClassesToSymbols() { + const classes: Map = new Map(); + + let iterator: CustomIterator = new CustomIterator(this._tags); + + while (true) { + let t: ITag; + do { + if (!iterator.hasNext()) { + iterator = new CustomIterator(this._tags); + + while (iterator.hasNext()) { + t = iterator.next(); + if (t instanceof CharacterTag) { + const ct = t as CharacterTag; + + if (classes.has(ct.characterId)) { + // @ts-ignore + ct.className = classes.get(ct.characterId); + } + } + } + + return; + } + + t = iterator.next(); + } while (!(t instanceof SymbolClassTag)); + + const sct = t as SymbolClassTag; + + for (let i = 0; i < sct.tags.length; ++i) { + if (!classes.has(sct.tags[i]) && !Array.from(classes.values()).includes(sct.names[i])) { + classes.set(sct.tags[i], sct.names[i]); + } + } + } + } + + public getFullClassName(type: string, documentNameTwice: boolean): string { + return this.getFullClassNameSnake(type, documentNameTwice, false); + } + + public getFullClassNameSnake(type: string, documentNameTwice: boolean, snakeCase: boolean): string { + if (this.swf === null) throw new Error("SWF Can't be null!"); + + let result: string = this.getDocumentClass() + "_"; + if (documentNameTwice) { + if (snakeCase) { + //result += CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, this.swf.getDocumentClass()) + "_"; + } else { + result += this.getDocumentClass() + "_"; + } + } + + return result + type; + } + + public getDocumentClass(): string { + if (this._documentClass !== null) return this._documentClass; + + let iterator: CustomIterator = new CustomIterator(this._tags); + + while (true) { + let t: ITag; + do { + if (!iterator.hasNext()) { + return ""; + } + + t = iterator.next(); + } while (!(t instanceof SymbolClassTag)); + + const sc = t as SymbolClassTag; + + for (let i = 0; i < sc.tags.length; ++i) { + if (sc.tags[i] == 0) { + this._documentClass = sc.names[i]; + return this._documentClass; + } + } + } + } + + public setDocumentClass(documentClass: string) { + this._documentClass = documentClass; + } +} \ No newline at end of file diff --git a/src/swf/tags/CharacterTag.ts b/src/swf/tags/CharacterTag.ts new file mode 100644 index 0000000..e6b0a0d --- /dev/null +++ b/src/swf/tags/CharacterTag.ts @@ -0,0 +1,20 @@ +export default abstract class CharacterTag { + private _className: string = ""; + private _characterId: number = -1; + + get className(): string { + return this._className; + } + + set className(value: string) { + this._className = value; + } + + get characterId(): number { + return this._characterId; + } + + set characterId(value: number) { + this._characterId = value; + } +} \ No newline at end of file diff --git a/src/swf/tags/DefineBinaryDataTag.ts b/src/swf/tags/DefineBinaryDataTag.ts new file mode 100644 index 0000000..28ada3e --- /dev/null +++ b/src/swf/tags/DefineBinaryDataTag.ts @@ -0,0 +1,43 @@ +import {Tag} from "../HabboAssetSWF"; +import ITag from "./ITag"; +import CharacterTag from "./CharacterTag"; + +const {SWFBuffer} = require('swf-extract/swf-buffer'); + +export default class DefineBinaryDataTag extends CharacterTag implements ITag { + + private readonly _tag: number; + private readonly _reserved: number; + private readonly _binaryData: string; + + constructor(tag: Tag) { + super(); + + const swfBuffer = new SWFBuffer(tag.rawData); + this._tag = swfBuffer.readUIntLE(16); + this._reserved = swfBuffer.readUIntLE(32); + const start = 6; //short 2 + int 4 + const end = tag.rawData.length; + const binary = tag.rawData.slice(start, end); + + this._binaryData = binary.toString("utf-8"); + + this.characterId = this._tag; + } + + get code(): number { + return 87; + } + + get tag(): number { + return this._tag; + } + + get reserved(): number { + return this._reserved; + } + + get binaryData(): string { + return this._binaryData; + } +} \ No newline at end of file diff --git a/src/swf/tags/ITag.ts b/src/swf/tags/ITag.ts new file mode 100644 index 0000000..7ffb142 --- /dev/null +++ b/src/swf/tags/ITag.ts @@ -0,0 +1,3 @@ +export default interface ITag { + code: number; +} \ No newline at end of file diff --git a/src/swf/tags/ImageTag.ts b/src/swf/tags/ImageTag.ts new file mode 100644 index 0000000..c9791b0 --- /dev/null +++ b/src/swf/tags/ImageTag.ts @@ -0,0 +1,37 @@ +import ITag from "./ITag"; +import CharacterTag from "./CharacterTag"; + +export default class ImageTag extends CharacterTag implements ITag { + + private readonly _code: number; + private readonly _characterID: number; + private readonly _imgType: string; + private readonly _imgData: Buffer; + + constructor(image: { code: number, characterID: number, imgType: string, imgData: Buffer }) { + super(); + + this._code = image.code; + this._characterID = image.characterID; + this._imgType = image.imgType; + this._imgData = image.imgData; + + this.characterId = this._characterID; + } + + get code(): number { + return this._code; + }; + + get characterID(): number { + return this._characterID; + } + + get imgType(): string { + return this._imgType; + } + + get imgData(): Buffer { + return this._imgData; + } +} \ No newline at end of file diff --git a/src/swf/tags/SymbolClassTag.ts b/src/swf/tags/SymbolClassTag.ts new file mode 100644 index 0000000..3cacb14 --- /dev/null +++ b/src/swf/tags/SymbolClassTag.ts @@ -0,0 +1,42 @@ +import {Tag} from "../HabboAssetSWF"; +import ITag from "./ITag"; + +const {SWFBuffer} = require('swf-extract/swf-buffer'); + +export default class SymbolClassTag implements ITag { + + private readonly _tags: Array; + private readonly _names: Array; + + constructor(tag: Tag) { + this._tags = []; + this._names = []; + + this.init(tag); + } + + init(tag: Tag) { + const swfBuffer = new SWFBuffer(tag.rawData); + + const numSymbols = swfBuffer.readUIntLE(16); + for (let i = 0; i < numSymbols; i++) { + const tagId = swfBuffer.readUIntLE(16); + const tagName = swfBuffer.readString("utf-8"); + + this._tags.push(tagId); + this._names.push(tagName); + } + } + + get tags(): Array { + return this._tags; + } + + get names(): Array { + return this._names; + } + + get code(): number { + return 76; + } +} \ No newline at end of file diff --git a/src/utils/CustomIterator.ts b/src/utils/CustomIterator.ts new file mode 100644 index 0000000..355fc48 --- /dev/null +++ b/src/utils/CustomIterator.ts @@ -0,0 +1,54 @@ +export default class CustomIterator { + + private idx: number; + private readonly top: number; + private readonly keys: Array; + private readonly arr: boolean; + private readonly collection: Array; + + constructor(collection: Array) { + if (this.dontIterate(collection)) { + throw new Error('Oh you nasty man, I won\'t iterate over that (' + collection + ')!'); + } + + this.arr = this.isArray(collection); + this.idx = 0; + this.top = 0; + this.keys = []; + if (this.arr) { + this.top = collection.length; + } else { + for (const prop in collection) { + this.keys.push(prop); + } + } + + this.collection = collection; + } + + isArray(candidate: any) { + return candidate && + typeof candidate === 'object' && + typeof candidate.length === 'number' && + typeof candidate.splice === 'function' && + !(candidate.propertyIsEnumerable('length')); + } + + dontIterate(collection: any) { + // put some checks chere for stuff that isn't iterable (yet) + return (!collection || typeof collection === 'number' || typeof collection === 'boolean'); + } + + public next(): TType { + if (!this.hasNext()) { + throw new Error('Oh you nasty man. I have no more elements.'); + } + const elem = this.arr ? this.collection[this.idx] : this.collection[this.keys[this.idx]]; + ++this.idx; + return elem; + }; + + public hasNext() { + return this.arr ? this.idx <= this.top : this.idx <= this.keys.length; + }; +} \ No newline at end of file diff --git a/src/utils/File.ts b/src/utils/File.ts new file mode 100644 index 0000000..f0bc5c3 --- /dev/null +++ b/src/utils/File.ts @@ -0,0 +1,33 @@ +const fs = require("fs"); + +export default class File { + + private readonly _path: string; + + constructor(path: string) { + this._path = path; + } + + public exists(): boolean { + return fs.existsSync(this._path); + } + + public mkdirs(): void { + return fs.mkdirSync(this._path); + } + + public list(): string[] { + const test = fs.readdirSync(this._path); + console.log(test); + + return test; + } + + public isDirectory(): boolean { + return this.exists() && fs.lstatSync(this._path).isDirectory() + } + + get path(): string { + return this._path; + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..310853b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "lib": ["es6"], + "allowJs": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "noImplicitAny": true, + "esModuleInterop": true, + "resolveJsonModule": true + } +} \ No newline at end of file