├── lib ├── config │ ├── min_info.js │ ├── dun_info.js │ ├── cel_info.js │ └── cl2_info.js ├── pal.js ├── til.js ├── sol.js ├── cl2.js ├── min.js ├── cel.js ├── dun.js └── cel_decode.js ├── index.js ├── package.json ├── LICENSE └── README.md /lib/config/min_info.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pillar counts per-pillar for the different files. 3 | */ 4 | module.exports = { 5 | 'l1': 10, 6 | 'l2': 10, 7 | 'l3': 10, 8 | 'l4': 16, 9 | 'town': 16 10 | }; 11 | -------------------------------------------------------------------------------- /lib/config/dun_info.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Start coordinates (column/row) for the DUN files. 3 | */ 4 | module.exports = { 5 | 'sector1s': [46, 46], 6 | 'sector2s': [46, 0], 7 | 'sector3s': [0, 46], 8 | 'sector4s': [0, 0] 9 | }; 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Dun: require('./lib/dun'), 3 | Til: require('./lib/til'), 4 | Min: require('./lib/min'), 5 | Cel: require('./lib/cel'), 6 | Cl2: require('./lib/cl2'), 7 | Pal: require('./lib/pal'), 8 | Sol: require('./lib/sol') 9 | }; 10 | -------------------------------------------------------------------------------- /lib/config/cel_info.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'strytell': { 3 | w: 96, 4 | h: 96, 5 | header: 10 6 | }, 7 | 'chest1': { 8 | w: 96, 9 | h: 96, 10 | header: 10 11 | }, 12 | 'chest2': { 13 | w: 96, 14 | h: 96, 15 | header: 10 16 | }, 17 | 'chest3': { 18 | w: 96, 19 | h: 96, 20 | header: 10 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /lib/config/cl2_info.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'wlsat': { 3 | w: 128, 4 | h: 128 5 | }, 6 | 'phalla': { 7 | w: 128, 8 | h: 128 9 | }, 10 | 'phalld': { 11 | w: 128, 12 | h: 128 13 | }, 14 | 'phallh': { 15 | w: 128, 16 | h: 128 17 | }, 18 | 'phalln': { 19 | w: 128, 20 | h: 128 21 | }, 22 | 'phallw': { 23 | w: 128, 24 | h: 128 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diablo-file-formats", 3 | "version": "0.1.0", 4 | "description": "Parsing of Diablo 1 file formats.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "diablo", 11 | "file format" 12 | ], 13 | "homepage": "https://github.com/doggan/diablo-file-format", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/doggan/diablo-file-format.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/doggan/diablo-file-format/issues" 20 | }, 21 | "author": { 22 | "name": "Shyam Guthikonda", 23 | "url": "http://shy.am" 24 | }, 25 | "license": "MIT", 26 | "jshintConfig": { 27 | "node": true, 28 | "unused": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/pal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * PAL files are the color palettes used to render images. 5 | * Partial transparency is not supported. 6 | */ 7 | function PalFile(colors) { 8 | this.colors = colors; 9 | } 10 | 11 | var COLOR_COUNT = 256; 12 | 13 | function _load(buffer, path) { 14 | if (buffer.length != (COLOR_COUNT * 3)) { 15 | console.error('Invalid PAL file size [' + buffer.length + '] for ' + path); 16 | return null; 17 | } 18 | 19 | var colors = new Array(COLOR_COUNT); 20 | var offset = 0; 21 | for (var i = 0; i < COLOR_COUNT; i++) { 22 | colors[i] = { 23 | r: buffer.readUInt8(offset), 24 | g: buffer.readUInt8(offset + 1), 25 | b: buffer.readUInt8(offset + 2), 26 | a: 0xFF 27 | }; 28 | 29 | offset += 3; 30 | } 31 | 32 | return new PalFile(colors); 33 | } 34 | 35 | module.exports = { 36 | load: _load 37 | }; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Shyam Guthikonda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | diablo-file-formats 2 | ================== 3 | Handles parsing of binary files found within Diablo 1 MPQ archives, and the creation of convenient run-time data structures. 4 | 5 | Currently supported file formats: 6 | * ```.cel``` 7 | * ```.cl2``` (partial) 8 | * ```.pal``` 9 | * ```.dun``` 10 | * ```.til``` 11 | * ```.min``` 12 | * ```.sol``` 13 | 14 | ## Installation 15 | ``` bash 16 | npm install diablo-file-formats 17 | ``` 18 | 19 | ## Usage 20 | ```javascript 21 | // 1). Require the package. 22 | var DFormats = require('diablo-file-formats'); 23 | 24 | // 2). Read the file. 25 | var path = 'levels/towndata/town.pal'; 26 | fs.readFile(path, function read(err, buffer) { 27 | // 3). Parse the file. 28 | var palFile = DFormats.Pal.load(buffer, path); 29 | 30 | // 4). Do something with the file. 31 | for (var i = 0; i < palFile.colors; i++) { 32 | console.log(palFile.colors[i]); 33 | } 34 | }); 35 | 36 | ``` 37 | 38 | ## Protip 39 | Use along with MPQ readers like [mech-mpq](https://www.npmjs.org/package/mech-mpq) or [mpq-server](https://www.npmjs.org/package/mpq-server) to dynamically extract and parse the MPQ archive (without having to pre-extract the contents). 40 | 41 | ## Acknowledgments 42 | Special thanks to @mewmew for the [blizzconv](https://github.com/mewrnd/blizzconv) project, which served as an invaluable reference. 43 | -------------------------------------------------------------------------------- /lib/til.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * TIL files contain indices for rendering the pillars found in the MIN files. 5 | * 6 | * Each 8 byte sequence in the TIL file describes the 4 quadrants of a square, 7 | * at 2 bytes each. Each square quadrant index is an index into the pillar 8 | * array of the MIN file. 9 | * 10 | * The square is arranged as follows: 11 | * 12 | * top [0] 13 | * /\ 14 | * left [2] /\/\ right [1] 15 | * \/\/ 16 | * \/ 17 | * bottom [3] 18 | */ 19 | function TilFile(squares) { 20 | this.squares = squares; 21 | } 22 | 23 | function _load(buffer, tilPath) { 24 | // 4 indices at 2 bytes a piece. 25 | var squareSizeInBytes = 4 * 2; 26 | 27 | if ((buffer.length % squareSizeInBytes) !== 0) { 28 | console.error('Invalid TIL file size [' + buffer.length + '] for ' + tilPath); 29 | return null; 30 | } 31 | 32 | var squares = new Array(buffer.length / squareSizeInBytes); 33 | 34 | var offset = 0; 35 | for (var i = 0; i < squares.length; i++) { 36 | squares[i] = [ 37 | buffer.readUInt16LE(offset), // top 38 | buffer.readUInt16LE(offset + 2), // right 39 | buffer.readUInt16LE(offset + 4), // left 40 | buffer.readUInt16LE(offset + 6) // bottom 41 | ]; 42 | 43 | offset += 8; 44 | } 45 | 46 | return new TilFile(squares); 47 | } 48 | 49 | module.exports = { 50 | load: _load, 51 | 52 | SQUARE_TOP: 0, 53 | SQUARE_RIGHT: 1, 54 | SQUARE_LEFT: 2, 55 | SQUARE_BOTTOM: 3 56 | }; 57 | -------------------------------------------------------------------------------- /lib/sol.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pathLib = require('path'); 4 | 5 | /** 6 | * SOL files contain meta information about pillars, such as 7 | * collision and transparency properties. 8 | * 9 | * The usage of some of the bits are currently unknown. 10 | */ 11 | function SolFile(data, path) { 12 | this.data = data; 13 | this.path = path; 14 | 15 | // levels/towndata/town.sol -> town 16 | this.name = pathLib.basename(path, '.sol'); 17 | } 18 | 19 | var CHECK_COLLISION = 0x01; 20 | // var CHECK_0x02 = 0x02; 21 | var CHECK_COLLISION_RANGE = 0x04; 22 | var CHECK_TRANSPARENCY = 0x08; 23 | // var CHECK_0x10 = 0x10; 24 | // var CHECK_0x20 = 0x20; 25 | // var CHECK_0x40 = 0x40; 26 | // var CHECK_0x80 = 0x80; 27 | 28 | SolFile.prototype = { 29 | /** 30 | * Returns true if the block has collision and should 31 | * not be passable by player / enemies. 32 | */ 33 | isCollision: function(pillarIndex) { 34 | return ((this.data[pillarIndex] & CHECK_COLLISION) !== 0); 35 | }, 36 | 37 | /** 38 | * Returns true if the block has 'range' collision, for things 39 | * like missiles and summoning of monsters. 40 | */ 41 | isCollisionRange: function(pillarIndex) { 42 | return ((this.data[pillarIndex] & CHECK_COLLISION_RANGE) !== 0); 43 | }, 44 | 45 | /** 46 | * Returns true if the block allows for transparency. 47 | */ 48 | allowTransparency: function(pillarIndex) { 49 | return ((this.data[pillarIndex] & CHECK_TRANSPARENCY) !== 0); 50 | } 51 | }; 52 | 53 | function _load(buffer, path) { 54 | var solData = new Array(buffer.length); 55 | var offset = 0; 56 | for (var i = 0; i < solData.length; i++) { 57 | solData[i] = buffer.readUInt8(offset++); 58 | } 59 | 60 | return new SolFile(solData, path); 61 | } 62 | 63 | module.exports = { 64 | load: _load 65 | }; 66 | -------------------------------------------------------------------------------- /lib/cl2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pathLib = require('path'), 4 | Cel = require('./cel'), 5 | cel_decode = require('./cel_decode'), 6 | cl2Config = require('./config/cl2_info'); 7 | 8 | function Cl2File(celFiles, path) { 9 | this.celFiles = celFiles; 10 | this.path = path; 11 | 12 | // plrgfx/warrior/wls/wlsst.cl2 -> wlsst 13 | this.name = pathLib.basename(this.path, '.cl2'); 14 | } 15 | 16 | var ARCHIVE_IMAGE_COUNT = 8; 17 | 18 | function getFrameDimensions(cl2Name) { 19 | var entry = cl2Config[cl2Name]; 20 | if (typeof entry == 'undefined') { 21 | return [96, 96]; 22 | } 23 | 24 | return [entry.w, entry.h]; 25 | } 26 | 27 | Cl2File.prototype.decodeFrames = function(palFile) { 28 | var frameDimensions = getFrameDimensions(this.name); 29 | var frameWidth = frameDimensions[0]; 30 | var frameHeight = frameDimensions[1]; 31 | 32 | // Decode all frames within each image with the appropriate decoder. 33 | var decodedImages = new Array(this.celFiles.length); 34 | for (var i = 0; i < decodedImages.length; i++) { 35 | var frames = this.celFiles[i].frames; 36 | var decodedFrames = new Array(frames.length); 37 | for (var j = 0; j < decodedFrames.length; j++) { 38 | var frameData = frames[j]; 39 | var frameDecoder = cel_decode.getCl2FrameDecoder(); 40 | decodedFrames[j] = frameDecoder(frameData, frameWidth, frameHeight, palFile); 41 | } 42 | 43 | decodedImages[i] = decodedFrames; 44 | } 45 | 46 | return decodedImages; 47 | }; 48 | 49 | function _load(buffer, cl2Path) { 50 | // TODO: archives only 51 | if (buffer.readUInt32LE(0) !== 32) { 52 | console.log('not an archive file!!'); 53 | return null; 54 | } 55 | 56 | var i; 57 | var offset = 0; 58 | 59 | // Header offsets. 60 | var headerOffsets = new Array(ARCHIVE_IMAGE_COUNT); 61 | for (i = 0; i < headerOffsets.length; i++) { 62 | headerOffsets[i] = buffer.readUInt32LE(offset); 63 | offset += 4; 64 | } 65 | 66 | // Process the images. Each image content is a CEL file. 67 | var celFiles = new Array(headerOffsets.length); 68 | for (i = 0; i < celFiles.length; i++) { 69 | var imageBuffer = buffer.slice(headerOffsets[i]); 70 | celFiles[i] = Cel.load(imageBuffer, cl2Path); 71 | } 72 | 73 | return new Cl2File(celFiles, cl2Path); 74 | } 75 | 76 | module.exports = { 77 | load: _load 78 | }; 79 | -------------------------------------------------------------------------------- /lib/min.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | minConfig = require('./config/min_info'); 5 | 6 | /** 7 | * MIN files contain information on how to arrange the frames 8 | * in a CEL image, in order to form a vertical pillar. This allows 9 | * for 'tall' configurations of images, such as for trees and roofs. 10 | * 11 | * A single pillar is arranged in the following configuration, 12 | * vertically from top to bottom. Depending on the specific file, 13 | * MIN files will have either 16 or 10 blocks. 14 | * 15 | * +----+----+ 16 | * | 0 | 1 | 17 | * +----+----+ 18 | * | 2 | 3 | 19 | * +----+----+ 20 | * | 4 | 5 | 21 | * +----+----+ 22 | * | 6 | 7 | 23 | * +----+----+ 24 | * | 8 | 9 | 25 | * +----+----+ 26 | * | 10 | 11 | 27 | * +----+----+ 28 | * | 12 | 13 | 29 | * +----+----+ 30 | * | 14 | 15 | 31 | * +----+----+ 32 | */ 33 | function MinFile(pillars) { 34 | this.pillars = pillars; 35 | } 36 | 37 | function getBlockCountPerPillar(minName) { 38 | var pillarCount = minConfig[minName]; 39 | if (typeof pillarCount == 'undefined') { 40 | console.error('Unhandled MIN file: ' + minName); 41 | return 0; 42 | } 43 | 44 | return pillarCount; 45 | } 46 | 47 | var BYTE_COUNT_PER_BLOCK = 2; 48 | 49 | function _load(buffer, minPath) { 50 | // levels/towndata/town.min -> town 51 | var minName = path.basename(minPath, '.min'); 52 | var blockCountPerPillar = getBlockCountPerPillar(minName); 53 | var totalBlockCount = buffer.length / BYTE_COUNT_PER_BLOCK; 54 | 55 | // There should be exactly the correct # of bytes for creating the pillars. 56 | if ((buffer.length % BYTE_COUNT_PER_BLOCK) !== 0 || 57 | (totalBlockCount % blockCountPerPillar) !== 0) { 58 | console.error('Invalid MIN file size [' + buffer.length + '] for ' + minPath); 59 | return null; 60 | } 61 | 62 | var pillars = new Array(totalBlockCount / blockCountPerPillar); 63 | 64 | var offset = 0; 65 | for (var p = 0; p < pillars.length; p++) { 66 | var pillarBlocks = new Array(blockCountPerPillar); 67 | for (var i = 0; i < blockCountPerPillar; i++) { 68 | // Read in the raw block value. 69 | var rawBlockValue = buffer.readUInt16LE(offset); 70 | offset += BYTE_COUNT_PER_BLOCK; 71 | 72 | // Parse the block value for easy usage. 73 | var frameNumPlus1 = (rawBlockValue & 0x0FFF); 74 | if (frameNumPlus1 !== 0) { 75 | pillarBlocks[i] = { 76 | frameNum: frameNumPlus1 - 1, 77 | type: (rawBlockValue & 0x7000) >>> 12 78 | }; 79 | } else { 80 | pillarBlocks[i] = null; 81 | } 82 | } 83 | 84 | pillars[p] = pillarBlocks; 85 | } 86 | 87 | return new MinFile(pillars); 88 | } 89 | 90 | module.exports = { 91 | load: _load 92 | }; 93 | -------------------------------------------------------------------------------- /lib/cel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var cel_decode = require('./cel_decode'), 4 | celConfig = require('./config/cel_info'), 5 | pathLib = require('path'); 6 | 7 | /** 8 | * CEL files hold image data. Similar to GIF images, they 9 | * contain multiple frames and use color palettes (PAL). They are used 10 | * for things like animations, effects, and tile sets. 11 | * 12 | * There are many different optimizations that are applied to the individual 13 | * CEL types and frames in order to improve data compression, so the 14 | * decoding of CEL files can be tricky and very specialized. 15 | */ 16 | function CelFile(frames, path, name) { 17 | this.frames = frames; 18 | this.path = path; 19 | this.name = name; 20 | } 21 | 22 | function getFrameDimensions(celName) { 23 | var entry = celConfig[celName]; 24 | if (typeof entry == 'undefined') { 25 | return [32, 32]; 26 | } 27 | 28 | return [entry.w, entry.h]; 29 | } 30 | 31 | function getFrameHeaderSize(celName) { 32 | var entry = celConfig[celName]; 33 | if (typeof entry == 'undefined') { 34 | return 0; 35 | } 36 | 37 | return entry.header; 38 | } 39 | 40 | /** 41 | * Decode all the frames within the CEL file, using the PAL file 42 | * to perform color palette lookup. This function constructs the 43 | * per-frame color data, which is used for rendering. 44 | */ 45 | CelFile.prototype.decodeFrames = function(palFile) { 46 | var frameDimensions = getFrameDimensions(this.name); 47 | var frameWidth = frameDimensions[0]; 48 | var frameHeight = frameDimensions[1]; 49 | 50 | // Decode each frame with the appropriate decoder. 51 | var frames = this.frames; 52 | var decodedFrames = new Array(frames.length); 53 | for (var i = 0; i < frames.length; i++) { 54 | var frameData = frames[i]; 55 | var frameDecoder = cel_decode.getCelFrameDecoder(this.name, frameData, i); 56 | decodedFrames[i] = frameDecoder(frameData, frameWidth, frameHeight, palFile); 57 | } 58 | 59 | return decodedFrames; 60 | }; 61 | 62 | function _load(buffer, celPath) { 63 | var offset = 0; 64 | var frameCount = buffer.readUInt32LE(offset); 65 | 66 | // Frame offsets. 67 | // frameCount # of offsets + 1 for the endOffset 68 | var frameOffsets = new Array(frameCount + 1); 69 | for (var i = 0; i < frameOffsets.length; i++) { 70 | frameOffsets[i] = buffer.readUInt32LE(offset += 4); 71 | } 72 | 73 | // levels/towndata/town.cel -> town 74 | var celName = pathLib.basename(celPath, '.cel'); 75 | 76 | var headerSize = getFrameHeaderSize(celName); 77 | 78 | var frames = new Array(frameCount); 79 | for (i = 0; i < frames.length; i++) { 80 | var frameStart = frameOffsets[i] + headerSize; 81 | var frameEnd = frameOffsets[i + 1]; 82 | var frameSize = frameEnd - frameStart; 83 | 84 | var frameData = new Array(frameSize); 85 | for (var j = 0; j < frameData.length; j++) { 86 | frameData[j] = buffer.readUInt8(frameStart + j); 87 | } 88 | 89 | frames[i] = frameData; 90 | } 91 | 92 | return new CelFile(frames, celPath, celName); 93 | } 94 | 95 | module.exports = { 96 | load: _load 97 | }; 98 | -------------------------------------------------------------------------------- /lib/dun.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | Til = require('./til'), 5 | dunConfig = require('./config/dun_info'); 6 | 7 | /** 8 | * DUN files contain information for arranging the squares of a TIL file. 9 | * Multiple DUN files can be pieced together to form entire levels. 10 | * For example, the 'town' level is a combination of 4 DUN files. 11 | * 12 | * In addition, DUN files also provide information regarding dungeon 13 | * monsters and object ids. 14 | */ 15 | function DunFile(startCoord, rawPillarData, dunName) { 16 | this.startCol = startCoord[0]; 17 | this.startRow = startCoord[1]; 18 | this.fileName = dunName; 19 | 20 | this.rawPillarData = rawPillarData; 21 | this.rawColCount = this.rawPillarData.length; 22 | this.rawRowCount = (this.rawColCount > 0) ? this.rawPillarData[0].length : 0; 23 | 24 | this.pillarData = null; // Initialized during unpack operation. 25 | this.colCount = this.rawColCount * 2; 26 | this.rowCount = this.rawRowCount * 2; 27 | } 28 | 29 | /** 30 | * Unpacks the pillar data of the DUN file using the TIL file to perform 31 | * square index lookups. DUN file can be used without unpacking, but 32 | * this operation will convert it to an easier to use format. 33 | */ 34 | DunFile.prototype.unpack = function(tilFile) { 35 | // Already unpacked? 36 | if (this.pillarData !== null) { 37 | return; 38 | } 39 | 40 | // Pre-allocate. 41 | this.pillarData = new Array(this.colCount); 42 | for (var i = 0; i < this.pillarData.length; i++) { 43 | this.pillarData[i] = new Array(this.rowCount); 44 | } 45 | 46 | // Parse pillar data. 47 | var row = 0; 48 | for (i = 0; i < this.rawRowCount; i++) { 49 | var col = 0; 50 | for (var j = 0; j < this.rawColCount; j++) { 51 | var squareIndexPlus1 = this.rawPillarData[j][i]; 52 | if (squareIndexPlus1 !== 0) { 53 | var square = tilFile.squares[squareIndexPlus1 - 1]; 54 | 55 | this.pillarData[col][row] = square[Til.SQUARE_TOP]; 56 | this.pillarData[col + 1][row] = square[Til.SQUARE_RIGHT]; 57 | this.pillarData[col][row + 1] = square[Til.SQUARE_LEFT]; 58 | this.pillarData[col + 1][row + 1] = square[Til.SQUARE_BOTTOM]; 59 | } else { 60 | this.pillarData[col][row] = null; 61 | this.pillarData[col + 1][row] = null; 62 | this.pillarData[col][row + 1] = null; 63 | this.pillarData[col + 1][row + 1] = null; 64 | } 65 | 66 | col += 2; 67 | } 68 | row += 2; 69 | } 70 | }; 71 | 72 | function getStartCoord(dunName) { 73 | var startCoord = dunConfig[dunName]; 74 | if (typeof startCoord == 'undefined') { 75 | console.error('Unhandled DUN file: ' + dunName); 76 | return [0, 0]; 77 | } 78 | 79 | return startCoord; 80 | } 81 | 82 | function _load(buffer, dunPath) { 83 | var offset = 0; 84 | var colCount = buffer.readUInt16LE(offset); 85 | var rowCount = buffer.readUInt16LE(offset += 2); 86 | 87 | // Pre-allocate. 88 | var rawPillarData = new Array(colCount); 89 | for (var i = 0; i < rawPillarData.length; i++) { 90 | rawPillarData[i] = new Array(rowCount); 91 | } 92 | 93 | // Parse pillar data. 94 | for (i = 0; i < rowCount; i++) { 95 | for (var j = 0; j < colCount; j++) { 96 | rawPillarData[j][i] = buffer.readUInt16LE(offset += 2); 97 | } 98 | } 99 | 100 | // levels/towndata/sector1s.dun -> sector1s 101 | var dunName = path.basename(dunPath, '.dun'); 102 | var startCoord = getStartCoord(dunName); 103 | 104 | return new DunFile(startCoord, rawPillarData, dunName); 105 | } 106 | 107 | module.exports = { 108 | load: _load 109 | }; 110 | -------------------------------------------------------------------------------- /lib/cel_decode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var BYTES_PER_PIXEL = 4; // RGBA 4 | var TRANSPARENT_COLOR = { r: 0, g: 0, b: 0, a: 0 }; 5 | 6 | /** 7 | * Utility for setting pixels. 8 | */ 9 | function getPixelSetter() { 10 | var offset = 0; 11 | return function(colors, c) { 12 | colors[offset] = c.r; 13 | colors[offset + 1] = c.g; 14 | colors[offset + 2] = c.b; 15 | colors[offset + 3] = c.a; 16 | offset += 4; 17 | }; 18 | } 19 | 20 | /** 21 | * Returns true if the image is a plain 32x32. 22 | */ 23 | function isType0(celName, frameNum) { 24 | // These special frames are type 1. 25 | switch (celName) { 26 | case 'l1': 27 | switch (frameNum) { 28 | case 148: case 159: case 181: case 186: case 188: 29 | return false; 30 | } 31 | break; 32 | case 'l2': 33 | switch (frameNum) { 34 | case 47: case 1397: case 1399: case 1411: 35 | return false; 36 | } 37 | break; 38 | case 'l4': 39 | switch (frameNum) { 40 | case 336: case 639: 41 | return false; 42 | } 43 | break; 44 | case 'town': 45 | switch (frameNum) { 46 | case 2328: case 2367: case 2593: 47 | return false; 48 | } 49 | } 50 | 51 | return true; 52 | } 53 | 54 | /** 55 | * Returns true if the image is a less-than (<) shape. 56 | */ 57 | function isType2or4(frameData) { 58 | var zeroPositions = [0, 1, 8, 9, 24, 25, 48, 49, 80, 81, 120, 121, 168, 169, 224, 225]; 59 | for (var i = 0; i < zeroPositions.length; i++) { 60 | if (frameData[zeroPositions[i]] !== 0) { 61 | return false; 62 | } 63 | } 64 | 65 | return true; 66 | } 67 | 68 | /** 69 | * Returns true if the image is a greater-than (>) shape. 70 | */ 71 | function isType3or5(frameData) { 72 | var zeroPositions = [2, 3, 14, 15, 34, 35, 62, 63, 98, 99, 142, 143, 194, 195, 254, 255]; 73 | for (var i = 0; i < zeroPositions.length; i++) { 74 | if (frameData[zeroPositions[i]] !== 0) { 75 | return false; 76 | } 77 | } 78 | 79 | return true; 80 | } 81 | 82 | /** 83 | * Type0 corresponds to plain 32x32 images with no transparency. 84 | * 85 | * 1) Range through the frame, one byte at the time. 86 | * - Each byte corresponds to a color index of the palette. 87 | * - Set one regular pixel per byte, using the color index to locate the 88 | * color in the palette. 89 | */ 90 | function DecodeFrameType0(frameData, width, height, palFile) { 91 | var colors = new Uint8Array(width * height * BYTES_PER_PIXEL); 92 | var setPixel = getPixelSetter(); 93 | for (var i = 0; i < frameData.length; i++) { 94 | setPixel(colors, palFile.colors[frameData[i]]); 95 | } 96 | 97 | return { 98 | width: width, 99 | height: height, 100 | colors: colors 101 | }; 102 | } 103 | 104 | /** 105 | * Type1 corresponds to 32x32 images with arbitrary color / transparency data. 106 | * 107 | * 1). Read one byte (chunkSize). 108 | * 2). If chunkSize >= 128: 109 | * - Set (256 - chunkSize) # of transparent pixels. 110 | * 3). Else (chunkSize < 128): 111 | * - Set chunkSize # of regular pixels, using the color index to locate 112 | * the color in the palette. 113 | * - Increment the frame counter for each byte read. 114 | * 4). Repeat from 1 till end of frame data. 115 | */ 116 | function DecodeFrameType1(frameData, width, height, palFile) { 117 | var colors = new Uint8Array(width * height * BYTES_PER_PIXEL); 118 | var setPixel = getPixelSetter(); 119 | 120 | var j; 121 | for (var i = 0; i < frameData.length; /*i++*/) { 122 | var chunkSize = frameData[i++]; 123 | if (chunkSize & 0x80) { 124 | // Transparent chunk. 125 | chunkSize = 256 - chunkSize; 126 | for (j = 0; j < chunkSize; j++) { 127 | setPixel(colors, TRANSPARENT_COLOR); 128 | } 129 | } else { 130 | // Regular (colored) chunk. 131 | for (j = 0; j < chunkSize; j++) { 132 | setPixel(colors, palFile.colors[frameData[i++]]); 133 | } 134 | } 135 | } 136 | 137 | return { 138 | width: width, 139 | height: height, 140 | colors: colors 141 | }; 142 | } 143 | 144 | /** 145 | * Type2 corresponds to 32x32 images of left facing triangles (<). 146 | * 147 | * 1) Dump one line of 32 pixels at the time. 148 | * - The illustration below tells if a pixel is transparent or regular. 149 | * - Only regular and zero (transparent) pixels are explicitly stored in 150 | * the frame content. All other pixels of the illustration are 151 | * implicitly transparent. 152 | * 153 | * Below is an illustration of the 32x32 image, where a space represents an 154 | * implicit transparent pixel, a '0' represents an explicit transparent pixel 155 | * and an 'x' represents an explicit regular pixel. 156 | * 157 | * Note: The output image will be "upside-down" compared to the illustration. 158 | * 159 | * +--------------------------------+ 160 | * | | 161 | * | 00xx| 162 | * | xxxx| 163 | * | 00xxxxxx| 164 | * | xxxxxxxx| 165 | * | 00xxxxxxxxxx| 166 | * | xxxxxxxxxxxx| 167 | * | 00xxxxxxxxxxxxxx| 168 | * | xxxxxxxxxxxxxxxx| 169 | * | 00xxxxxxxxxxxxxxxxxx| 170 | * | xxxxxxxxxxxxxxxxxxxx| 171 | * | 00xxxxxxxxxxxxxxxxxxxxxx| 172 | * | xxxxxxxxxxxxxxxxxxxxxxxx| 173 | * | 00xxxxxxxxxxxxxxxxxxxxxxxxxx| 174 | * | xxxxxxxxxxxxxxxxxxxxxxxxxxxx| 175 | * |00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 176 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 177 | * |00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 178 | * | xxxxxxxxxxxxxxxxxxxxxxxxxxxx| 179 | * | 00xxxxxxxxxxxxxxxxxxxxxxxxxx| 180 | * | xxxxxxxxxxxxxxxxxxxxxxxx| 181 | * | 00xxxxxxxxxxxxxxxxxxxxxx| 182 | * | xxxxxxxxxxxxxxxxxxxx| 183 | * | 00xxxxxxxxxxxxxxxxxx| 184 | * | xxxxxxxxxxxxxxxx| 185 | * | 00xxxxxxxxxxxxxx| 186 | * | xxxxxxxxxxxx| 187 | * | 00xxxxxxxxxx| 188 | * | xxxxxxxx| 189 | * | 00xxxxxx| 190 | * | xxxx| 191 | * | 00xx| 192 | * +--------------------------------+ 193 | */ 194 | function DecodeFrameType2(frameData, width, height, palFile) { 195 | var colors = new Uint8Array(width * height * BYTES_PER_PIXEL); 196 | var setPixel = getPixelSetter(); 197 | 198 | var decodeCounts = [0, 4, 4, 8, 8, 12, 12, 16, 16, 20, 20, 24, 24, 28, 28, 32, 32, 32, 28, 28, 24, 24, 20, 20, 16, 16, 12, 12, 8, 8, 4, 4]; 199 | var frameReadOffset = 0; 200 | for (var i = 0; i < decodeCounts.length; i++) { 201 | var zeroCount = ((i % 2) === 0) ? 0 : 2; 202 | var decodeCount = decodeCounts[i]; 203 | decodeLineTransparencyLeft(colors, frameData, frameReadOffset, decodeCount, zeroCount, palFile, setPixel); 204 | frameReadOffset += decodeCount; 205 | } 206 | 207 | return { 208 | width: width, 209 | height: height, 210 | colors: colors 211 | }; 212 | } 213 | 214 | /** 215 | * Type3 corresponds to 32x32 images of right facing triangles (>). 216 | * 217 | * 1) Dump one line of 32 pixels at the time. 218 | * - The illustration below tells if a pixel is transparent or regular. 219 | * - Only regular and zero (transparent) pixels are explicitly stored in 220 | * the frame content. All other pixels of the illustration are 221 | * implicitly transparent. 222 | * 223 | * Below is an illustration of the 32x32 image, where a space represents an 224 | * implicit transparent pixel, a '0' represents an explicit transparent pixel 225 | * and an 'x' represents an explicit regular pixel. 226 | * 227 | * Note: The output image will be "upside-down" compared to the illustration. 228 | * 229 | * +--------------------------------+ 230 | * | | 231 | * |xx00 | 232 | * |xxxx | 233 | * |xxxxxx00 | 234 | * |xxxxxxxx | 235 | * |xxxxxxxxxx00 | 236 | * |xxxxxxxxxxxx | 237 | * |xxxxxxxxxxxxxx00 | 238 | * |xxxxxxxxxxxxxxxx | 239 | * |xxxxxxxxxxxxxxxxxx00 | 240 | * |xxxxxxxxxxxxxxxxxxxx | 241 | * |xxxxxxxxxxxxxxxxxxxxxx00 | 242 | * |xxxxxxxxxxxxxxxxxxxxxxxx | 243 | * |xxxxxxxxxxxxxxxxxxxxxxxxxx00 | 244 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxx | 245 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx00| 246 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 247 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx00| 248 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxx | 249 | * |xxxxxxxxxxxxxxxxxxxxxxxxxx00 | 250 | * |xxxxxxxxxxxxxxxxxxxxxxxx | 251 | * |xxxxxxxxxxxxxxxxxxxxxx00 | 252 | * |xxxxxxxxxxxxxxxxxxxx | 253 | * |xxxxxxxxxxxxxxxxxx00 | 254 | * |xxxxxxxxxxxxxxxx | 255 | * |xxxxxxxxxxxxxx00 | 256 | * |xxxxxxxxxxxx | 257 | * |xxxxxxxxxx00 | 258 | * |xxxxxxxx | 259 | * |xxxxxx00 | 260 | * |xxxx | 261 | * |xx00 | 262 | * +--------------------------------+ 263 | */ 264 | function DecodeFrameType3(frameData, width, height, palFile) { 265 | var colors = new Uint8Array(width * height * BYTES_PER_PIXEL); 266 | var setPixel = getPixelSetter(); 267 | 268 | var decodeCounts = [0, 4, 4, 8, 8, 12, 12, 16, 16, 20, 20, 24, 24, 28, 28, 32, 32, 32, 28, 28, 24, 24, 20, 20, 16, 16, 12, 12, 8, 8, 4, 4]; 269 | var frameReadOffset = 0; 270 | for (var i = 0; i < decodeCounts.length; i++) { 271 | var zeroCount = ((i % 2) === 0) ? 0 : 2; 272 | var decodeCount = decodeCounts[i]; 273 | decodeLineTransparencyRight(colors, frameData, frameReadOffset, decodeCount, zeroCount, palFile, setPixel); 274 | frameReadOffset += decodeCount; 275 | } 276 | 277 | return { 278 | width: width, 279 | height: height, 280 | colors: colors 281 | }; 282 | } 283 | 284 | /** 285 | * Type4 corresponds to 32x32 images of left facing triangles (<), with 286 | * an additional section of the image filled in with solid colors. 287 | * 288 | * 1) Dump one line of 32 pixels at the time. 289 | * - The illustration below tells if a pixel is transparent or regular. 290 | * - Only regular and zero (transparent) pixels are explicitly stored in 291 | * the frame content. All other pixels of the illustration are 292 | * implicitly transparent. 293 | * 294 | * Below is an illustration of the 32x32 image, where a space represents an 295 | * implicit transparent pixel, a '0' represents an explicit transparent pixel 296 | * and an 'x' represents an explicit regular pixel. 297 | * 298 | * Note: The output image will be "upside-down" compared to the illustration. 299 | * 300 | * +--------------------------------+ 301 | * | 00xx| 302 | * | xxxx| 303 | * | 00xxxxxx| 304 | * | xxxxxxxx| 305 | * | 00xxxxxxxxxx| 306 | * | xxxxxxxxxxxx| 307 | * | 00xxxxxxxxxxxxxx| 308 | * | xxxxxxxxxxxxxxxx| 309 | * | 00xxxxxxxxxxxxxxxxxx| 310 | * | xxxxxxxxxxxxxxxxxxxx| 311 | * | 00xxxxxxxxxxxxxxxxxxxxxx| 312 | * | xxxxxxxxxxxxxxxxxxxxxxxx| 313 | * | 00xxxxxxxxxxxxxxxxxxxxxxxxxx| 314 | * | xxxxxxxxxxxxxxxxxxxxxxxxxxxx| 315 | * |00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 316 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 317 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 318 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 319 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 320 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 321 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 322 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 323 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 324 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 325 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 326 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 327 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 328 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 329 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 330 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 331 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 332 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 333 | * +--------------------------------+ 334 | */ 335 | function DecodeFrameType4(frameData, width, height, palFile) { 336 | var colors = new Uint8Array(width * height * BYTES_PER_PIXEL); 337 | var setPixel = getPixelSetter(); 338 | 339 | var decodeCounts = [4, 4, 8, 8, 12, 12, 16, 16, 20, 20, 24, 24, 28, 28, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32]; 340 | var frameReadOffset = 0; 341 | for (var i = 0; i < decodeCounts.length; i++) { 342 | var zeroCount = 0; 343 | switch (i) { 344 | case 0: case 2: case 4: case 6: case 8: case 10: case 12: case 14: 345 | zeroCount = 2; 346 | } 347 | var decodeCount = decodeCounts[i]; 348 | decodeLineTransparencyLeft(colors, frameData, frameReadOffset, decodeCount, zeroCount, palFile, setPixel); 349 | frameReadOffset += decodeCount; 350 | } 351 | 352 | return { 353 | width: width, 354 | height: height, 355 | colors: colors 356 | }; 357 | } 358 | 359 | /** 360 | * Type5 corresponds to 32x32 images of right facing triangles (>), with 361 | * an additional section of the image filled in with solid colors. 362 | * 363 | * 1) Dump one line of 32 pixels at the time. 364 | * - The illustration below tells if a pixel is transparent or regular. 365 | * - Only regular and zero (transparent) pixels are explicitly stored in 366 | * the frame content. All other pixels of the illustration are 367 | * implicitly transparent. 368 | * 369 | * Below is an illustration of the 32x32 image, where a space represents an 370 | * implicit transparent pixel, a '0' represents an explicit transparent pixel 371 | * and an 'x' represents an explicit regular pixel. 372 | * 373 | * Note: The output image will be "upside-down" compared to the illustration. 374 | * 375 | * +--------------------------------+ 376 | * |xx00 | 377 | * |xxxx | 378 | * |xxxxxx00 | 379 | * |xxxxxxxx | 380 | * |xxxxxxxxxx00 | 381 | * |xxxxxxxxxxxx | 382 | * |xxxxxxxxxxxxxx00 | 383 | * |xxxxxxxxxxxxxxxx | 384 | * |xxxxxxxxxxxxxxxxxx00 | 385 | * |xxxxxxxxxxxxxxxxxxxx | 386 | * |xxxxxxxxxxxxxxxxxxxxxx00 | 387 | * |xxxxxxxxxxxxxxxxxxxxxxxx | 388 | * |xxxxxxxxxxxxxxxxxxxxxxxxxx00 | 389 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxx | 390 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx00| 391 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 392 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 393 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 394 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 395 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 396 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 397 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 398 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 399 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 400 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 401 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 402 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 403 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 404 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 405 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 406 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 407 | * |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 408 | * +--------------------------------+ 409 | */ 410 | function DecodeFrameType5(frameData, width, height, palFile) { 411 | var colors = new Uint8Array(width * height * BYTES_PER_PIXEL); 412 | var setPixel = getPixelSetter(); 413 | 414 | var decodeCounts = [4, 4, 8, 8, 12, 12, 16, 16, 20, 20, 24, 24, 28, 28, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32]; 415 | var frameReadOffset = 0; 416 | for (var i = 0; i < decodeCounts.length; i++) { 417 | var zeroCount = 0; 418 | switch (i) { 419 | case 0: case 2: case 4: case 6: case 8: case 10: case 12: case 14: 420 | zeroCount = 2; 421 | } 422 | var decodeCount = decodeCounts[i]; 423 | decodeLineTransparencyRight(colors, frameData, frameReadOffset, decodeCount, zeroCount, palFile, setPixel); 424 | frameReadOffset += decodeCount; 425 | } 426 | 427 | return { 428 | width: width, 429 | height: height, 430 | colors: colors 431 | }; 432 | } 433 | 434 | /** 435 | * Type6 corresponds to an image with arbitrary color / transparency data. 436 | * Both transparent and color data use run-length encoding for compression. 437 | * 438 | * 1). Read one byte (chunkSize). 439 | * 2). If chunkSize >= 128: 440 | * - Subtract chunkSize from 256 441 | * 2a). If chunkSize <= 65, read that many bytes. 442 | * - Each byte read corresponds to a color index in the palette. 443 | * - Set the pixel color to this color. 444 | * 2b). Else (chunkSize > 65), subtract 65 and read one byte. 445 | * - The byte corresponds to a color index in the palette. 446 | * - Set chunkSize # of pixels to this color. 447 | * 3). Else (chunkSize < 128): 448 | * - Set chunkSize # of transparent pixels. 449 | * 4). Repeat from 1 till end of frame data. 450 | */ 451 | function DecodeFrameType6(frameData, width, height, palFile) { 452 | var colors = new Uint8Array(width * height * BYTES_PER_PIXEL); 453 | var setPixel = getPixelSetter(); 454 | 455 | // TODO: ?? investigate where this is coming from 456 | var headerSizeSkip = 10; 457 | 458 | var j; 459 | for (var i = headerSizeSkip; i < frameData.length; /*i++*/) { 460 | var chunkSize = frameData[i++]; 461 | if (chunkSize & 0x80) { 462 | // Regular (colored) chunks. 463 | chunkSize = 256 - chunkSize; 464 | 465 | if (chunkSize <= 65) { 466 | for (j = 0; j < chunkSize; j++) { 467 | setPixel(colors, palFile.colors[frameData[i++]]); 468 | } 469 | } else { 470 | // Run-length encoding (repeat the same color). 471 | chunkSize -= 65; 472 | var color = palFile.colors[frameData[i++]]; 473 | for (j = 0; j < chunkSize; j++) { 474 | setPixel(colors, color); 475 | } 476 | } 477 | } else { 478 | // Transparent chunk. 479 | for (j = 0; j < chunkSize; j++) { 480 | setPixel(colors, TRANSPARENT_COLOR); 481 | } 482 | } 483 | } 484 | 485 | return { 486 | width: width, 487 | height: height, 488 | colors: colors 489 | }; 490 | } 491 | 492 | /** 493 | * Decodes a line of the frame, where decodeCount is the total # of 494 | * explicit pixels, and zeroCount is the total # of explicit transparent 495 | * pixels, and the rest of the line is implictly transparent. 496 | * 497 | * Each line is assumed to have a width of 32 pixels. 498 | */ 499 | function decodeLineTransparencyLeft(colors, frameData, frameReadOffset, decodeCount, zeroCount, palFile, setPixel) { 500 | // Implicit transparent pixels. 501 | for (var i = decodeCount; i < 32; i++) { 502 | setPixel(colors, TRANSPARENT_COLOR); 503 | } 504 | 505 | // Explicit transparent pixels (zeroes). 506 | for (i = 0; i < zeroCount; i++) { 507 | setPixel(colors, TRANSPARENT_COLOR); 508 | } 509 | 510 | // Explicit regular pixels. 511 | for (i = zeroCount; i < decodeCount; i++) { 512 | setPixel(colors, palFile.colors[frameData[frameReadOffset + i]]); 513 | } 514 | } 515 | 516 | /** 517 | * Decodes a line of the frame, where decodeCount is the total # of 518 | * explicit pixels, and zeroCount is the total # of explicit transparent 519 | * pixels, and the rest of the line is implictly transparent. 520 | * 521 | * Each line is assumed to have a width of 32 pixels. 522 | */ 523 | function decodeLineTransparencyRight(colors, frameData, frameReadOffset, decodeCount, zeroCount, palFile, setPixel) { 524 | // Total # of explicit pixels. 525 | var regularCount = decodeCount - zeroCount; 526 | 527 | // Implicit transparent pixels. 528 | for (var i = 0; i < regularCount; i++) { 529 | setPixel(colors, palFile.colors[frameData[frameReadOffset + i]]); 530 | } 531 | 532 | // Explicit transparent pixels (zeroes). 533 | for (i = 0; i < zeroCount; i++) { 534 | setPixel(colors, TRANSPARENT_COLOR); 535 | } 536 | 537 | // Explicit regular pixels. 538 | for (i = decodeCount; i < 32; i++) { 539 | setPixel(colors, TRANSPARENT_COLOR); 540 | } 541 | } 542 | 543 | /** 544 | * Gets the appropriate frame decoder for the particular frame. 545 | */ 546 | function _getCelFrameDecoder(celName, frameData, frameNum) { 547 | var frameSize = frameData.length; 548 | 549 | switch (celName) { 550 | case 'l1': case 'l2': case 'l3': case 'l4': case 'town': 551 | // Some regular (type 1) CEL images just happen to have a frame size of 552 | // exactly 0x220, 0x320 or 0x400. Therefore the isType* functions are 553 | // required to figure out the appropriate decoding function. 554 | switch (frameSize) { 555 | case 0x400: 556 | if (isType0(celName, frameNum)) { 557 | return DecodeFrameType0; 558 | } 559 | break; 560 | case 0x220: 561 | if (isType2or4(frameData)) { 562 | return DecodeFrameType2; 563 | } else if (isType3or5(frameData)) { 564 | return DecodeFrameType3; 565 | } 566 | break; 567 | case 0x320: 568 | if (isType2or4(frameData)) { 569 | return DecodeFrameType4; 570 | } else if (isType3or5(frameData)) { 571 | return DecodeFrameType5; 572 | } 573 | } 574 | } 575 | 576 | return DecodeFrameType1; 577 | } 578 | 579 | function _getCl2FrameDecoder() { 580 | return DecodeFrameType6; 581 | } 582 | 583 | module.exports = { 584 | getCelFrameDecoder: _getCelFrameDecoder, 585 | getCl2FrameDecoder: _getCl2FrameDecoder 586 | }; 587 | --------------------------------------------------------------------------------