├── .gitignore ├── LICENSE.txt ├── README.md ├── index.js ├── lib ├── swf-buffer.js └── swf-tags.js ├── package.json └── test ├── test.spec.js └── test.swf /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Rafael Leal Dias 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SWF Reader 2 | 3 | A simple [node][nodejs] module for reading [SWF format][swf-format]. 4 | 5 | ## Installation 6 | 7 | ```sh 8 | $ npm install swf-reader 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | var SWFReader = require('swf-reader'); 15 | 16 | SWFReader.read( 'swf_path.swf', function(err, swf) { 17 | if ( err ) { 18 | // handle error 19 | ... 20 | } 21 | console.log(swf); 22 | }); 23 | ``` 24 | 25 | ## SWFReader.read(file, callback) 26 | 27 | Returns a [SWF Object](#swf-object) to `callback` function. If it's not possible to read the SWF, an error object is passed as the first argument of `callback`. 28 | 29 | ## SWFReader.readSync(file) 30 | 31 | Returns a [SWF Object](#swf-object) to the caller. If it's not possible to read the SWF, an Exception is thrown. 32 | 33 | The `file` parameter of both methods may be either a file path or a buffer of the SWF file. 34 | 35 | ## SWF Object 36 | 37 | The SWF Object method has the following properties : 38 | 39 | * `version`: The SWF version. 40 | * `fileLength`: An Object containing the following properties : 41 | * `compressed`: The SWF compressed size in bytes. 42 | * `uncompressed`: The SWF uncompressed size in bytes. 43 | * `frameSize`: An Object containing the `width` and `height` of the SWF. 44 | * `frameRate`: The SWF framerate. 45 | * `frameCount`: Number of frames in the SWF. 46 | * `backgroundColor`: The background color of the SWF in the format `#XXXXXX`. 47 | * `fileAttributes`: FileAtributtes defines characteristics of the SWF file. 48 | * `useNetwork`: If `true`, the SWF file is given network file access when loaded locally. 49 | * `as3`: If `true`, the SWF uses ActionScript 3.0. Otherwise it uses ActionScript 1.0 or 2.0. 50 | * `hasMetaData`: If `true`, the SWF file contains the Metadata tag. 51 | * `useGPU`: If `true`, the SWF file uses GPU compositing features when drawing graphics. 52 | * `useDirectBlit`: If `true`, the SWF file uses hardware acceleration to blit graphics to the screen. 53 | * `metadata`: The metadata describes the SWF file to and external process. 54 | * `tags`: An array of `tag`. Each item in the array is an object with a `header` property with the folowing properties: 55 | * `code`: A number indicating the type of the tag. (see [SWF format][swf-format] for more information) 56 | * `length`: The length of the tag in bytes. 57 | 58 | ## Running test 59 | 60 | To run the test invoke the following command within the repo directory : 61 | 62 | ```sh 63 | $ npm test 64 | ``` 65 | 66 | ## Todo 67 | 68 | * Read tags fields. 69 | * Write in tags block. 70 | 71 | ## Contributors 72 | 73 | Author: [Rafael Leal Dias][rdleal-git] 74 | 75 | ## License 76 | 77 | MIT 78 | 79 | [nodejs]: http://www.nodejs.org 80 | [swf-format]: http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/swf/pdf/swf-file-format-spec.pdf 81 | [rdleal-git]: https://github.com/rafaeldias 82 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple module for reading SWF properties 3 | * 4 | * (c) 2014 Rafael Leal Dias 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var fs = require('fs') 10 | , zlib = require('zlib') 11 | , lzma = require('lzma-purejs') 12 | , SWFBuffer = require('./lib/swf-buffer') 13 | , SWFTags = require('./lib/swf-tags') 14 | , SWFReader = exports; 15 | 16 | function readSWFTags(buff, swf) { 17 | var tags = [] 18 | , tag 19 | , tagHeader 20 | , flag 21 | , l 22 | , sc 23 | , fc; 24 | 25 | /* Reads TagCodeAndLength from Tag's RECORDHEADER */ 26 | while( (tagHeader = buff.readTagCodeAndLength()) ) { 27 | tag = { 28 | header : tagHeader 29 | }; 30 | switch( tagHeader.code ) { 31 | case SWFTags.FileAttributes : 32 | flag = buff.readUIntLE(32); 33 | fileAttrs = {} 34 | 35 | fileAttrs.useNetwork = tag.useNetwork = !!(flag & 0x1); 36 | fileAttrs.as3 = tag.as3 = !!(flag & 0x8); 37 | fileAttrs.hasMetaData = tag.hasMetaData = !!(flag & 0x10); 38 | fileAttrs.useGPU = tag.useGPU = !!(flag & 0x20); 39 | fileAttrs.useDirectBit = tag.useDirectBlit = !!(flag & 0x40); 40 | 41 | swf.fileAttributes = fileAttrs; 42 | break; 43 | case SWFTags.Metadata : 44 | swf.metadata = tag.metadata = buff.readString() 45 | break; 46 | case SWFTags.SetBackgroundColor : 47 | tag.RGB = buff.readRGB(); 48 | swf.backgroundColor = '#' + (tag.RGB[0]*65536 + tag.RGB[1]*256 + tag.RGB[0]).toString(16); 49 | break; 50 | case SWFTags.Protect : 51 | swf.protect = tagHeader.length && buff.readString(); 52 | break; 53 | case SWFTags.DefineSceneAndFrameLabelData : 54 | sc = tag.sceneCount = buff.readEncodedU32(); 55 | tag.scenes = []; 56 | 57 | while (sc--) 58 | tag.scenes.push({ 59 | offset : buff.readEncodedU32(), 60 | name : buff.readString() 61 | }); 62 | 63 | fc = tag.frameLabelCount = buff.readEncodedU32(); 64 | tag.labels = []; 65 | 66 | while (fc--) 67 | tag.labels.push({ 68 | frameNum : buff.readEncodedU32(), 69 | frameLabel : buff.readString() 70 | }); 71 | break; 72 | /** 73 | * DefineShape4 extends the capabilities of 74 | * DefineShape3 by using a new line style 75 | * record in the shape 76 | */ 77 | //case SWFTags.DefineShape4 : 78 | // /* id for this character */ 79 | // tag.ShapeId = buff.readUIntLE(16); 80 | // /* bounds of the shape */ 81 | // tag.ShapeBounds = buff.readRect(); 82 | // /* bounds of the shape, excluding the strokes */ 83 | // tag.EdgeBounds = buff.readRect(); 84 | // /* reserved, must be 0 */ 85 | // if (0 !== buff.readBits(5)) 86 | // throw new Error('Reserved bit used.'); 87 | // /* if 1, use fill winding. >= SWF 10 */ 88 | // if (swf.version >= 10) 89 | // tag.UsesFillWindingRule = buff.readBits(1); 90 | // /** 91 | // * if 1, shape contains at least one 92 | // * non-scaling stroke. 93 | // */ 94 | // tag.UsesNonScallingStrokes = buff.readBits(1); 95 | // /** 96 | // * if 1, shape contains at least one 97 | // * scaling stroke 98 | // */ 99 | // tag.UsesScalingStrokes = buff.readBits(1); 100 | // tag.shapes = buff.readShapeWithStyle(); 101 | // break; 102 | case SWFTags.FrameLabel : 103 | tag.name = buff.readString() 104 | l = Buffer.byteLength(tag.name); 105 | /* check if it's an named anchor */ 106 | if (l & (tagHeader.length - 1) != l) 107 | tag.anchor = buff.readUInt8(); 108 | break; 109 | case SWFTags.DefineSprite : 110 | tag.SpriteID = buff.readUIntLE(16); 111 | tag.FrameCount = buff.readUIntLE(16); 112 | tag.ControlTags = readSWFTags(buff, swf); 113 | break; 114 | case SWFTags.ExportAssets : 115 | tag.count = buff.readUIntLE(16); 116 | tag.assets = []; 117 | 118 | l = 0; 119 | 120 | while (l++ < tag.count) 121 | tag.assets.push({ 122 | id : buff.readUIntLE(16), 123 | name : buff.readString() 124 | }); 125 | break; 126 | case SWFTags.ImportAssets : 127 | /** 128 | * URL where the source SWF file can be found 129 | */ 130 | tag.url = buff.readString(); 131 | /** 132 | * Number of assets to import 133 | */ 134 | tag.count = buff.readUIntLE(16); 135 | tag.assets = []; 136 | 137 | l = 0; 138 | 139 | while (l++ < tag.count) 140 | tag.assets.push({ 141 | /** 142 | * Character ID for the l-th item 143 | * in importing SWF file 144 | */ 145 | id : buff.readUIntLE(16), 146 | /** 147 | * Identifies for the l-th 148 | * imported character 149 | */ 150 | name : buff.readString() 151 | }); 152 | break; 153 | case SWFTags.ImportAssets2 : 154 | tag.url = buff.readString(); 155 | 156 | if ( !(1 === buff.readUInt8() && 0 === buff.readUInt8()) ) { 157 | throw new Error('Reserved bits for ImportAssets2 used'); 158 | } 159 | 160 | tag.count = buff.readUIntLE(16); 161 | tag.assets = []; 162 | 163 | l = 0; 164 | 165 | while (l++ < tag.count) 166 | tag.assets({ 167 | id : buff.readUIntLE(16), 168 | name : buff.readString() 169 | }); 170 | break; 171 | case SWFTags.EnableDebbuger : 172 | tag.password = buff.readString() 173 | break; 174 | case SWFTags.EnableDebugger2 : 175 | if (0 !== buff.readUIntLE(16)) { 176 | throw new Error('Reserved bit for EnableDebugger2 used.'); 177 | } 178 | tag.password = buff.readString() 179 | break; 180 | case SWFTags.ScriptLimits : 181 | /** 182 | * Maximum recursion Depth 183 | */ 184 | tag.maxRecursionDepth = buff.readUIntLE(16); 185 | /** 186 | * Maximum ActionScript processing time before script 187 | * stuck dialog box displays 188 | */ 189 | tag.scriptTimeoutSeconds = buff.readUIntLE(16); 190 | break; 191 | case SWFTags.SymbolClass : 192 | tag.numSymbols = buff.readUIntLE(16); 193 | tag.symbols = []; 194 | 195 | l = 0; 196 | 197 | while (l++ < tag.numSymbols) 198 | tag.symbols.push({ 199 | id : buff.readUIntLE(16), 200 | name : buff.readString() 201 | }); 202 | break; 203 | case SWFTags.DefineScalingGrid : 204 | tag.characterId = buff.readUIntLE(16); 205 | tag.splitter = buff.readRect(); 206 | break; 207 | case SWFTags.setTabIndex : 208 | tag.depth = buff.readUIntLE(16); 209 | tag.tabIndex = buff.readUIntLE(16); 210 | break; 211 | default: 212 | tag.data = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length); 213 | buff.pointer += tagHeader.length; 214 | break; 215 | } 216 | tags.push(tag); 217 | } 218 | return tags; 219 | } 220 | 221 | /** 222 | * Reads tags and their contents, passaing a SWF object to callback 223 | * 224 | * @param {SWFBuffer} buff 225 | * @param {Buffer} compressed_buff 226 | * @param {function} callback 227 | * @api private 228 | * 229 | */ 230 | function readSWFBuff(buff, compressed_buff, next) { 231 | buff.seek(3);// start 232 | 233 | var swf = { 234 | version : buff.readUInt8(), 235 | fileLength : { 236 | compressed : compressed_buff.length, 237 | uncompressed : buff.readUIntLE(32) 238 | }, 239 | frameSize : buff.readRect(), // Returns a RECT object. i.e : { x : 0, y : 0, width : 200, height: 300 } 240 | frameRate : buff.readUIntLE(16)/256, 241 | frameCount : buff.readUIntLE(16) 242 | } 243 | , isSync = 'function' !== typeof next; 244 | 245 | try { 246 | swf.tags = readSWFTags(buff, swf); 247 | } catch(e) { 248 | if (isSync) throw e; 249 | return next(e); 250 | } 251 | 252 | return isSync && swf || next( null, swf ); 253 | } 254 | 255 | /** 256 | * Concat SWF Header with uncompressed Buffer 257 | * 258 | * @param {Buffer|ArrayBuffer} buff 259 | * @param {Buffer|ArrayBuffer} swf 260 | */ 261 | function concatSWFHeader(buff, swf) { 262 | return Buffer.concat([swf.slice(0, 8), buff]); 263 | } 264 | 265 | /** 266 | * Uncompress SWF and start reading it 267 | * 268 | * @param {Buffer|ArrayBuffer} swf 269 | * @param {function} callback 270 | * 271 | */ 272 | function uncompress(swf, next) { 273 | var compressed_buff = swf.slice(8) 274 | , uncompressed_buff 275 | , isSync = 'function' !== typeof next 276 | , e; 277 | 278 | // uncompress buffer 279 | switch( swf[0] ) { 280 | case 0x43 : // zlib compressed 281 | if (isSync) { 282 | uncompressed_buff = concatSWFHeader(zlib.unzipSync(compressed_buff), swf); 283 | return readSWFBuff(new SWFBuffer(uncompressed_buff), swf); 284 | } 285 | 286 | zlib.unzip( compressed_buff, function(err, result) { 287 | if ( err ) { 288 | next(err); 289 | return; 290 | } 291 | uncompressed_buff = concatSWFHeader(result, swf); 292 | readSWFBuff(new SWFBuffer(uncompressed_buff), swf, next); 293 | }); 294 | break; 295 | case 0x46 : // uncompressed 296 | return readSWFBuff(new SWFBuffer( swf ), swf, next); 297 | break; 298 | case 0x5a : // LZMA compressed 299 | uncompressed_buff = Buffer.concat([swf.slice(0, 8), lzma.decompressFile(compressed_buff)]); 300 | 301 | return readSWFBuff(new SWFBuffer(uncompressed_buff), swf, next); 302 | break; 303 | default : 304 | e = new Error('Unknown SWF compressions'); 305 | 306 | if (isSync) { 307 | throw e; 308 | } else { 309 | next(e); 310 | } 311 | }; 312 | }; 313 | 314 | /** 315 | * Check if file is Buffer or ArrayBuffer 316 | * 317 | * @param {Buffer|ArrayBuffer) b 318 | * @api private 319 | * 320 | */ 321 | function isBuffer(b) { 322 | return typeof Buffer !== "undefined" && Buffer.isBuffer(b) || b instanceof ArrayBuffer; 323 | } 324 | 325 | /* Exposes Tags constants */ 326 | SWFReader.TAGS = SWFTags; 327 | 328 | /** 329 | * Reads SWF file 330 | * 331 | * @param {String|Buffer}} file 332 | * @param {function} next - if not a function, uses synchronous algorithm 333 | * @api public 334 | * 335 | */ 336 | SWFReader.read = SWFReader.readSync = function(file, next) { 337 | if (isBuffer(file)) { 338 | /* File is already a buffer */ 339 | return uncompress(file, next); 340 | } else { 341 | /* Get the buffer */ 342 | if ('function' === typeof next) { 343 | fs.readFile(file, function(err, swf) { 344 | if ( err ) { 345 | next(err); 346 | return; 347 | } 348 | uncompress(swf, next); 349 | }); 350 | } else { 351 | return uncompress(fs.readFileSync(file)); 352 | } 353 | } 354 | }; 355 | -------------------------------------------------------------------------------- /lib/swf-buffer.js: -------------------------------------------------------------------------------- 1 | 2 | var RECORDHEADER_LENTH_FULL = 0x3f 3 | // null-character 4 | , EOS = 0x00 5 | , styleCountExt = 0xFF; 6 | 7 | function readStyleArray(buffer, next) { 8 | var styleArrayCount = buffer.readUInt8() 9 | , styles = []; 10 | 11 | if (styleArrayCount === styleCountExt) 12 | styleArrayCount = buffer.readUIntLE(16); 13 | 14 | for (var i = 0; i < styleArrayCount; i++) 15 | styles.push(next(buffer)); 16 | 17 | return styles; 18 | } 19 | 20 | function readFillStyle(buffer) { 21 | var type = buffer.readUInt8() 22 | , fillStyle = { 23 | /** 24 | * 0x00 = solid 25 | * 0x10 = linear gradient fill 26 | * 0x12 = radial gradient fill 27 | * 0x13 = focal radial gradient fill (SWF 8 or later) 28 | * 0x40 = repeating bitmap fill 29 | * 0x41 = clipped bitmap fill 30 | * 0x42 = non-smoothed repeating bitmap 31 | * 0x43 = non-smoothed clipped bitmap 32 | */ 33 | fillStyleType : type 34 | }; 35 | 36 | switch (type) { 37 | case 0x00: 38 | fillStyle.color = buffer.readRGBA(); 39 | break; 40 | case 0x10, 0x12, 0x13: 41 | console.log('Gradient'); 42 | break; 43 | case 0x40, 0x41, 0x42, 0x43: 44 | fillStyle.bitmapId = buffer.readUIntLE(16); 45 | break; 46 | } 47 | 48 | return fillStyle; 49 | } 50 | 51 | function readLineStyle(buffer) { 52 | return { 53 | width: buffer.readUIntLE(16)/20, 54 | color: buffer.readRGBA() 55 | }; 56 | } 57 | 58 | function readShapeRecords(buffer) { 59 | var shapeRecords = [] 60 | , typeFlag = buffer.readBits(1) 61 | , shapeRecord 62 | , eos; 63 | 64 | while ((eos = buffer.readBits(5))) { 65 | if (0 === typeFlag) { 66 | shaperecord = { 67 | type: 'STYLECHANGERECORD' 68 | }; 69 | } 70 | } 71 | 72 | return shapeRecords; 73 | } 74 | 75 | /** 76 | * 77 | * Constructor of SWFBuffer object 78 | * 79 | * @param {Buffer} buffer 80 | * @return Instance of SWFBuffer 81 | */ 82 | 83 | function SWFBuffer( buffer ) { 84 | if ( !Buffer.isBuffer( buffer ) ) { 85 | throw new Error('Invalid buffer'); 86 | } 87 | this.buffer = buffer; 88 | this.pointer = 0; 89 | this.position = 1; 90 | this.current = 0; 91 | this.length = buffer.length; 92 | } 93 | 94 | /** 95 | * Reads unsigned 16 or 32 Little Endian Bits 96 | * and advance pointer to next bits / 8 bytes 97 | * 98 | * @param {Number} bits 99 | * @return {Number} Value read from buffer 100 | */ 101 | 102 | SWFBuffer.prototype.readUIntLE = function( bits ) { 103 | var value = 0; 104 | try { 105 | value = this.buffer['readUInt' + bits + 'LE'](this.pointer); 106 | this.pointer += bits / 8; 107 | } catch ( e ) { 108 | throw e; 109 | } 110 | return value; 111 | }; 112 | 113 | /** 114 | * Reads unsigned 8 bit from the buffer 115 | * 116 | * @return {Number} Value read from buffer 117 | */ 118 | 119 | SWFBuffer.prototype.readUInt8 = function() { 120 | return this.buffer.readUInt8( this.pointer++ ); 121 | }; 122 | 123 | /** 124 | * Reads 32-bit unsigned integers value encoded (1-5 bytes) 125 | * 126 | * @return {Number} 32-bit unsigned integer 127 | */ 128 | 129 | SWFBuffer.prototype.readEncodedU32 = function() { 130 | var i = 5 131 | , result = 0 132 | , nb; 133 | 134 | do 135 | result += (nb = this.nextByte()); 136 | while((nb & 128) && --i); 137 | 138 | return result; 139 | }; 140 | 141 | /** 142 | * Reads an encoded data from buffer and returns a 143 | * string using the specified character set. 144 | * 145 | * @param {String} encoding - defaults to 'utf8' 146 | * @returns {String} Decoded string 147 | */ 148 | 149 | SWFBuffer.prototype.readString = function(encoding) { 150 | var init = this.pointer; 151 | while(this.readUInt8() !== EOS); 152 | return this.buffer.toString(encoding || 'utf8', init, this.pointer - 1); 153 | }; 154 | 155 | /** 156 | * Reads RGB value 157 | * 158 | * @return {Array} Array of RGB value 159 | */ 160 | 161 | SWFBuffer.prototype.readRGB = function() { 162 | return [this.readUInt8(), this.readUInt8(), this.readUInt8()]; 163 | }; 164 | 165 | /** 166 | * Reads RGBA value 167 | * 168 | * @return {Array} Array of RGBA value 169 | */ 170 | 171 | SWFBuffer.prototype.readRGBA = function() { 172 | var rgba = this.readRGB(); 173 | rgba.push(this.readUInt8()); 174 | return rgba; 175 | } 176 | 177 | /** 178 | * Reads ShapeWithStyle structure 179 | * used by the DefineShape tag. 180 | * 181 | * @return ShapeWithStyle structure 182 | */ 183 | SWFBuffer.prototype.readShapeWithStyle = function() { 184 | return { 185 | fillStyles : readStyleArray(this, readFillStyle), 186 | lineStyles : readStyleArray(this, readLineStyle), 187 | numFillBits : this.readBits(4), 188 | numLineBits : this.readBits(4), 189 | shapeRecords: readShapeRecords(this) 190 | } 191 | }; 192 | 193 | /** 194 | * Reads RECORDHEADER from next tag in the buffer 195 | * 196 | * @return {Object} Tag code and length 197 | */ 198 | 199 | SWFBuffer.prototype.readTagCodeAndLength = function() { 200 | var n = this.readUIntLE(16) 201 | , tagType = n >> 6 202 | , tagLength = n & RECORDHEADER_LENTH_FULL; 203 | 204 | if ( n === 0 ) 205 | return false; 206 | 207 | if ( tagLength === RECORDHEADER_LENTH_FULL ) 208 | tagLength = this.readUIntLE(32); 209 | 210 | return { code : tagType, length : tagLength }; 211 | }; 212 | 213 | /** 214 | * Reads RECT format 215 | * 216 | * @return {Object} x, y, width and height of the RECT 217 | */ 218 | 219 | SWFBuffer.prototype.readRect = function() { 220 | 221 | this.start(); 222 | 223 | var NBits = this.readBits(5) 224 | , Xmin = this.readBits(NBits, true)/20 225 | , Xmax = this.readBits(NBits, true)/20 226 | , Ymin = this.readBits(NBits, true)/20 227 | , Ymax = this.readBits(NBits, true)/20; 228 | 229 | return { 230 | x : Xmin, 231 | y : Ymin, 232 | width : (Xmax > Xmin ? Xmax - Xmin : Xmin - Xmax), 233 | height : (Ymax > Ymin ? Ymax - Ymin : Ymin - Ymax) 234 | }; 235 | 236 | } 237 | 238 | /** 239 | * Sets internal pointer to the specified position; 240 | * 241 | * @param {Number} pos 242 | */ 243 | 244 | SWFBuffer.prototype.seek = function( pos ) { 245 | this.pointer = pos % this.buffer.length; 246 | }; 247 | 248 | /** 249 | * Resets position and sets current to next Byte in buffer 250 | */ 251 | SWFBuffer.prototype.start = function() { 252 | this.current = this.nextByte(); 253 | this.position = 1; 254 | }; 255 | 256 | /** 257 | * Gets next Byte in the buffer and Increment internal pointer 258 | * 259 | * @return {Number} Next byte in buffer 260 | */ 261 | 262 | SWFBuffer.prototype.nextByte = function() { 263 | return this.pointer > this.buffer.length ? null : this.buffer[ this.pointer++ ]; 264 | }; 265 | 266 | /** 267 | * Reads b bits from current byte in buffer 268 | * 269 | * @param {Number} b 270 | * @return {Number} Bits read from buffer 271 | */ 272 | 273 | SWFBuffer.prototype.readBits = function( b, signed ) { 274 | var n = 0 275 | , r = 0 276 | , sign = signed && ++n && ((this.current >> (8-this.position++)) & 1) ? -1 : 1; 277 | 278 | while( n++ < b ) { 279 | if ( this.position > 8 ) this.start(); 280 | 281 | r = (r << 1 ) + ((this.current >> (8-this.position++)) & 1); 282 | } 283 | return sign * r; 284 | }; 285 | 286 | /* Exposes class */ 287 | exports = module.exports = SWFBuffer; 288 | -------------------------------------------------------------------------------- /lib/swf-tags.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines constants on exports object 3 | * 4 | * @param {String} name 5 | * @param {Mixed} value 6 | */ 7 | 8 | function define(name, value) { 9 | Object.defineProperty(exports, name, { 10 | value : value, 11 | enumerable : true 12 | }); 13 | } 14 | 15 | /* SWF Tags Type */ 16 | 17 | define('End', 0); 18 | define('ShowFrame', 1); 19 | define('DefineShape', 2); 20 | define('PlaceObject', 4); 21 | define('RemoveObject', 5); 22 | define('DefineBits', 6); 23 | define('DefineButton', 7); 24 | define('JPEGTables', 8); 25 | define('SetBackgroundColor', 9); 26 | define('DefineFont', 10); 27 | define('DefineText', 11); 28 | define('DoAction', 12); 29 | define('DefineFontInfo', 13); 30 | define('DefineSound', 14); 31 | define('StartSound', 15); 32 | define('DefineButtonSound', 17); 33 | define('SoundStreamHead', 18); 34 | define('SoundStreamBlock', 19); 35 | define('DefineBitsLossless', 20); 36 | define('DefineBitsJPEG2', 21); 37 | define('DefineShape2', 22); 38 | define('DefineButtonCxform', 23); 39 | define('Protect', 24); 40 | define('PlaceObject2', 26); 41 | define('RemoveObject2', 28); 42 | define('DefineShape3', 32); 43 | define('DefineText2', 33); 44 | define('DefineButton2', 34); 45 | define('DefineBitsJPEG3', 35); 46 | define('DefineBitsLossless2', 36); 47 | define('DefineEditText', 37); 48 | define('DefineSprite', 39); 49 | define('SerialNumber', 41); 50 | define('FrameLabel', 43); 51 | define('SoundStreamHead2', 45); 52 | define('DefineMorphShape', 46); 53 | define('DefineFont2', 48); 54 | define('ExportAssets', 56); 55 | define('ImportAssets', 57); 56 | define('EnableDebugger', 58); 57 | define('DoInitAction', 59); 58 | define('DefineVideoStream', 60); 59 | define('VideoFrame', 61); 60 | define('DefineFontInfo2', 62); 61 | define('EnableDebugger2', 64); 62 | define('ScriptLimits', 65); 63 | define('SetTabIndex', 66); 64 | define('FileAttributes', 69); 65 | define('PlaceObject3', 70); 66 | define('ImportAssets2', 71); 67 | define('DefineFontAlignZones', 73); 68 | define('CSMTextSettings', 74); 69 | define('DefineFont3', 75); 70 | define('SymbolClass', 76); 71 | define('Metadata', 77); 72 | define('DefineScalingGrid', 78); 73 | define('DoABC', 82); 74 | define('DefineShape4', 83); 75 | define('DefineMorphShape2', 84); 76 | define('DefineSceneAndFrameLabelData', 86); 77 | define('DefineBinaryData', 87); 78 | define('DefineFontName', 88); 79 | define('StartSound2', 89); 80 | define('DefineBitsJPEG4', 90); 81 | define('DefineFont4', 91); 82 | //define('TagMax' (DefineFont4 + 1) 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swf-reader", 3 | "version": "1.0.0", 4 | "description": "A simple node module for reading SWF format", 5 | "main": "index.js", 6 | "dependencies": { 7 | "lzma-purejs": "~0.9.3" 8 | }, 9 | "directories": { 10 | "test": "test" 11 | }, 12 | "scripts": { 13 | "test": "TEST_FILE='test/test.swf' node test/test.spec.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/rafaeldias/swf-reader" 18 | }, 19 | "keywords": [ 20 | "swf", 21 | "flash", 22 | "adobe" 23 | ], 24 | "author": "Rafael Leal Dias ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/rafaeldias/swf-reader/issues" 28 | }, 29 | "homepage": "https://github.com/rafaeldias/swf-reader" 30 | } 31 | -------------------------------------------------------------------------------- /test/test.spec.js: -------------------------------------------------------------------------------- 1 | var SWFReader = require('../index') 2 | , fs = require('fs') 3 | , SWFInfo = function(swf) { 4 | console.log('SWF version : ' + swf.version); 5 | console.log('SWF size : ' + swf.frameSize.width + 'x' + swf.frameSize.height); 6 | console.log('SWF background : ' + swf.backgroundColor); 7 | console.log('SWF compressed bytes : ' + swf.fileLength.compressed + ', uncompressed bytes : ' + swf.fileLength.uncompressed); 8 | console.log('SWF frameRate : ' + swf.frameRate); 9 | console.log('SWF frames : ' + swf.frameCount); 10 | console.log('Test OK.'); 11 | }; 12 | 13 | if ( process.env.TEST_FILE ) { 14 | console.log('Testing file path...'); 15 | SWFReader.read(process.env.TEST_FILE, function(err, swf){ 16 | if ( err ) { 17 | console.log(err); 18 | return; 19 | } 20 | 21 | SWFInfo(swf); 22 | 23 | console.log('Testing buffer...'); 24 | 25 | fs.readFile(process.env.TEST_FILE, function(err, buff) { 26 | if ( err ) { 27 | console.log(err); 28 | return; 29 | } 30 | 31 | SWFReader.read(buff, function(err, swf) { 32 | if ( err ) { 33 | console.log(err); 34 | return; 35 | } 36 | 37 | SWFInfo(swf); 38 | 39 | console.log('Testing Sync...'); 40 | SWFInfo(SWFReader.readSync(buff)); 41 | 42 | 43 | console.log('Tags:'); 44 | console.log(swf.tags); 45 | }); 46 | }); 47 | }); 48 | }else{ 49 | throw new Error("TEST_FILE env var not set to .swf"); 50 | } 51 | -------------------------------------------------------------------------------- /test/test.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafaeldias/swf-reader/b52ad60b83ba6fcb0c189174a6bca10be63455c0/test/test.swf --------------------------------------------------------------------------------