UNPKG

@maniajs/gbxparser

Version:
436 lines (359 loc) 13.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _bufferReader = require('buffer-reader'); var _bufferReader2 = _interopRequireDefault(_bufferReader); var _events = require('events'); var _fs = require('fs'); var fs = _interopRequireWildcard(_fs); var _map = require('./map'); var _map2 = _interopRequireDefault(_map); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** * Map Parser * @class MapParser * @type {MapParser} * * @property {BufferReader} parser * @property {{thumb:boolean}} options */ var MapParser = function (_EventEmitter) { _inherits(MapParser, _EventEmitter); /** * Parse Map File. * @param {string|Buffer} fd Could be a file (string) or a buffer. * @param {{thumb:boolean, body:boolean}} [options] Provide extra options, Turn thumb off/on.. Turn body parsing off/on. */ function MapParser(fd, options) { _classCallCheck(this, MapParser); var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(MapParser).call(this)); _this.map = new _map2.default(); options = options || {}; options.thumb = typeof options.thumb === 'undefined' ? true : options.thumb; // Parse thumb by default! options.body = typeof options.body === 'undefined' ? true : options.body; // Parse thumb by default! _this.options = options; if (fd instanceof Buffer) { _this.buffer = fd; } else if (typeof fd === 'string') { _this.file = fd; } else { throw new Error('Please provide a buffer or file path!'); } return _this; } /** * Parse the Map file. * After completing it will emit events on the Parser itself. * * @returns {Promise} */ _createClass(MapParser, [{ key: 'parse', value: function parse() { var _this2 = this; return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) { if (!_this2.buffer && _this2.file) { fs.readFile(_this2.file, function (err, data) { if (err) return reject(err); return resolve(data); }); } else { return resolve(_this2.buffer); } }).then(function (buffer) { _this2.parser = new _bufferReader2.default(buffer); try { _this2.parser.move(9); var classId = _this2.parser.nextUInt32LE(); if (classId !== (0x3 << 24 | 0x43 << 12)) { // engineid << 24, classid << 12. return reject(new Error('Map is not a valid map class!')); } return _this2._parseHeader(); } catch (err) { return reject(err); } }).then(function () { // Parse body if (_this2.options.body) { return _this2._parseBody(); } return Promise.resolve(); }).then(function () { // Done, return resolving with the map object. return resolve(_this2.map); }).catch(function (err) { return reject(err); }); }); } /** * Parse Header. * @returns {Promise} * @private */ }, { key: '_parseHeader', value: function _parseHeader() { var _this3 = this; var headerLength = this.parser.nextUInt32LE(); var chunkCount = this.parser.nextUInt32LE(); var headerChunks = {}; // Parse chunks (extract from header). for (var chunkNr = 0; chunkNr < chunkCount; chunkNr++) { headerChunks[this.parser.nextUInt32LE()] = this.parser.nextUInt32LE() & ~0x80000000; } return Object.keys(headerChunks).reduce(function (promise, chunkId) { return promise.then(function () { return _this3._parseChunk(chunkId, headerChunks[chunkId]); }); }, Promise.resolve()); } /** * Parse Body (data section). * @returns {Promise} * @private */ }, { key: '_parseBody', value: function _parseBody() { return Promise.resolve(); // Not implemented. var instanceCount = this.parser.nextUInt32LE(); var externalInstanceCount = this.parser.nextUInt32LE(); if (externalInstanceCount !== 0) { return Promise.reject('Body parsing failed, external instances and dependencies not implemented in the parser!'); } // TODO: Implement body parsing. // TODO: LZO decompression. } /** * Parse Chunk (will have the switch in it to forward to internal methods) * * @param {string} id String format with decimal of chunkid. * @param {number} size Size in bytes of chunk. * @returns {Promise} * @private */ }, { key: '_parseChunk', value: function _parseChunk(id, size) { // Clear Lookback this.parser.resetLookBackStrings(); switch (id) { // ======== HEADER ========= case '50606082': // 0x03043002 return this._parseC1(size); case '50606083': // 0x03043003 return this._parseC2(size); case '50606084': // 0x03043004 return this._parseC3(size); case '50606085': // 0x03043005 return this._parseC4(size); case '50606087': // 0x03043007 return this._parseC5(size); case '50606088': // 0x03043008 return this._parseC6(size); // ========= DATA =========== default: try { this.parser.move(size); // Skip by default (when not found). return Promise.resolve(); } catch (err) { return Promise.reject(err); } } } // Chunk Parsers........... /** * Parse First Chunk, time and attribute information in it. * Part name 'basic'. * Chunk '50606082' (0x03043002) * * @param size * @returns {Promise} * @private */ }, { key: '_parseC1', value: function _parseC1(size) { // Header try { var version = this.parser.nextUInt8(); if (typeof this.debug === 'function') this.debug('Chunk (0x03043002), Version: ' + version); // times, price, lap, etc. this.parser.move(4); this.map.time.bronze = this.parser.nextUInt32LE(); this.map.time.silver = this.parser.nextUInt32LE(); this.map.time.gold = this.parser.nextUInt32LE(); this.map.time.author = this.parser.nextUInt32LE(); this.map.price = this.parser.nextUInt32LE(); this.map.isMultilap = this.parser.nextUInt32LE() === 1; this.map.type = this.parser.nextUInt32LE(); this.parser.move(4); this.map.authorScore = this.parser.nextUInt32LE(); this.map.editor = this.parser.nextUInt32LE() === 1 ? 'simple' : 'advanced'; this.parser.move(4); this.map.checkpoints = this.parser.nextUInt32LE(); this.map.laps = this.parser.nextUInt32LE(); return Promise.resolve(); } catch (err) { return Promise.reject(err); } } /** * Parse Second Chunk, extended map informations, mostly general/technical. * Part name 'basic'. * Chunk '50606083' (0x03043003) * * @param size * @returns {Promise} * @private */ }, { key: '_parseC2', value: function _parseC2(size) { // Header try { var version = this.parser.nextUInt8(); if (typeof this.debug === 'function') this.debug('Chunk (0x03043003), Version: ' + version); this.map.uid = this.parser.nextLookBackString(); this.map.environment = this.parser.nextLookBackString(); this.map.author.login = this.parser.nextLookBackString(); this.map.name = this.parser.nextGbxString(); this.parser.move(5); this.parser.nextGbxString(); // I don't want that on my baguette! this.map.mood = this.parser.nextLookBackString(); this.map.decorationEnvironment.id = this.parser.nextLookBackString(); this.map.decorationEnvironment.author = this.parser.nextLookBackString(); this.parser.move(4 * 4 + 16); this.map.mapType = this.parser.nextGbxString(); this.map.style = this.parser.nextGbxString(); this.parser.move(9); this.map.title = this.parser.nextLookBackString(); return Promise.resolve(); } catch (err) { return Promise.reject(err); } } /** * Parse Chunk 3. (skip) * Chunk '50606084' (0x03043004) * * @param size * @returns {Promise} * @private */ }, { key: '_parseC3', value: function _parseC3(size) { // Header try { var version = this.parser.nextUInt8(); if (typeof this.debug === 'function') this.debug('Chunk (0x03043004), Version: ' + version); this.parser.move(size - 1); return Promise.resolve(); } catch (err) { return Promise.reject(err); } } /** * Parse Chunk 4. Header XML * Chunk '50606085' (0x03043005) * * @param size * @returns {Promise} * @private */ }, { key: '_parseC4', value: function _parseC4(size) { // Header try { if (typeof this.debug === 'function') this.debug('Chunk (0x03043005)'); this.map.xml = this.parser.nextGbxString(); return Promise.resolve(); } catch (err) { return Promise.reject(err); } } /** * Parse Chunk 5. Thumb + Comment * Chunk '50606087' (0x03043007) * * @param size * @returns {Promise} * @private */ }, { key: '_parseC5', value: function _parseC5(size) { // Header try { if (typeof this.debug === 'function') this.debug('Chunk (0x03043007)'); if (this.parser.nextUInt32LE() === 1) { // Has Thumb. var thumbSize = this.parser.nextUInt32LE(); this.parser.move(15); // Begin thumb xml tag if (this.options.thumb) { this.map.thumb = this.parser.nextBuffer(thumbSize); } else { this.parser.move(thumbSize); } this.parser.move(16); // </Thumbnail.jpg> this.parser.move(10); // <Comments> var commentSize = this.parser.nextUInt32LE(); this.map.comment = ''; if (commentSize > 0) { this.map.comment = this.parser.nextString(commentSize); } this.parser.move(11); // </Comments> } else { this.parser.move(size - 4); } return Promise.resolve(); } catch (err) { return Promise.reject(err); } } /** * Parse Chunk 6. Author information. * Chunk '50606088' (0x03043008) * * @param size * @returns {Promise} * @private */ }, { key: '_parseC6', value: function _parseC6(size) { // Header try { var version = this.parser.nextUInt32LE(); if (typeof this.debug === 'function') this.debug('Chunk (0x03043008), Version: ' + version); this.map.author.version = this.parser.nextUInt32LE(); this.map.author.login = this.parser.nextGbxString(); this.map.author.nickname = this.parser.nextGbxString(); this.map.author.zone = this.parser.nextGbxString(); this.map.author.extra = this.parser.nextGbxString(); return Promise.resolve(); } catch (err) { return Promise.reject(err); } } }]); return MapParser; }(_events.EventEmitter); exports.default = MapParser;