src/chrome/content/mm7.js
author Jason Oster <parasyte@kodewerx.org>
Sat Mar 07 12:40:45 2009 -0700 (18 months ago)
changeset 17 c127f7d9c201
parent 1043c58f6d6171
child 202b56c4771d5c
permissions -rw-r--r--
Refactor rendering and stage construction
     1 /* ***** BEGIN LICENSE BLOCK *****
     2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
     3  *
     4  * The contents of this file are subject to the Mozilla Public License Version
     5  * 1.1 (the "License"); you may not use this file except in compliance with
     6  * the License. You may obtain a copy of the License at
     7  * http://www.mozilla.org/MPL/
     8  *
     9  * Software distributed under the License is distributed on an "AS IS" basis,
    10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
    11  * for the specific language governing rights and limitations under the
    12  * License.
    13  *
    14  * The Original Code is Syndrome.
    15  *
    16  * The Initial Developer of the Original Code is
    17  * Jason Oster <parasyte at kodewerx org>.
    18  * Portions created by the Initial Developer are Copyright (C) 2009
    19  * the Initial Developer. All Rights Reserved.
    20  *
    21  * Contributor(s):
    22  *
    23  * Alternatively, the contents of this file may be used under the terms of
    24  * either the GNU General Public License Version 2 or later (the "GPL"), or
    25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
    26  * in which case the provisions of the GPL or the LGPL are applicable instead
    27  * of those above. If you wish to allow use of your version of this file only
    28  * under the terms of either the GPL or the LGPL, and not to allow others to
    29  * use your version of this file under the terms of the MPL, indicate your
    30  * decision by deleting the provisions above and replace them with the notice
    31  * and other provisions required by the GPL or the LGPL. If you do not delete
    32  * the provisions above, a recipient may use your version of this file under
    33  * the terms of any one of the MPL, the GPL or the LGPL.
    34  *
    35  * ***** END LICENSE BLOCK ***** */
    36 
    37 
    38 /*
    39  * defaultTiles is an array of SNES CHR tiles;
    40  * one solid tile for each of the SNES palette's 16 colors
    41  * The stage graphics set is appended to this array after decompressing
    42  */
    43 const defaultTiles = [
    44     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    45     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    46     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    47     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    48     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    49     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    50     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    51     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    52     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    53     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    54     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    55     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    56     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    57     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    58     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    59     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    60     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    61     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    62     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    63     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    64     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    65     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    66     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    67     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    68     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    69     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    70     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    71     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    72     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    73     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    74     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    75     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    76     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    77     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    78     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    79     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    80     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    81     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    82     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    83     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    84     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    85     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    86     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    87     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    88     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    89     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    90     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    91     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
    92     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    93     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    94     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    95     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    96     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    97     0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
    98     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    99     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
   100     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
   101     0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
   102     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
   103     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
   104     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
   105     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
   106     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
   107     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
   108 ];
   109 
   110 // Mega Man 7 Editor constructor
   111 function mm7Editor(filepath, data, canvas) {
   112     this.snesROM(filepath, data);
   113     this.bgmap = null;
   114     this.fgmap = null;
   115     this.stage = 0;
   116     this.canvas = canvas;
   117 }
   118 
   119 
   120 // Mega Man 7 pointer constants
   121 mm7Editor.PTR_PALDATA =         0xC08456;   // Pointers to palette data
   122 mm7Editor.PTR_ROOMDATA =        0xC0914F;   // Pointers to room data
   123 /*
   124 mm7Editor.PTR_ROOMSIZE =        0xC09179;   // Pointers to room data sizes
   125 */
   126 mm7Editor.PTR_FGMAPDATA =       0xC09195;   // Pointers to foreground map data
   127 mm7Editor.PTR_BGMAPDATA =       0xC091B1;   // Pointers to background map data
   128 mm7Editor.PTR_BLOCKDATA =       0xC091CD;   // Pointers to block data
   129 /*
   130 mm7Editor.PTR_TYPEDATA =        0xC091F7;   // Pointers to "block\structure type" data
   131 */
   132 mm7Editor.PTR_CMPDATA =         0xC0E993;   // Pointers to compressed data
   133 mm7Editor.PTR_STAGEHEAD =       0xC0ED2B;   // Pointers to stage headers
   134 mm7Editor.PTR_STAGEPAL =        0xC0FB04;   // Pointers to stage palette headers
   135 mm7Editor.PTR_STRUCTDATA =      0xCA5600;   // Pointers to structure data
   136 
   137 
   138 // Error constants
   139 mm7Editor.ERR_DECOMP =          -2;         // Decompression error
   140 mm7Editor.ERR_GEN =             -1;         // General error
   141 mm7Editor.ERR_NONE =            0;          // Success/no error
   142 
   143 
   144 // Mega Man 7 Editor class
   145 mm7Editor.prototype = {
   146     // SNES tile map: Background
   147     bgmap: null,
   148 
   149     // SNES tile map: Foreground
   150     fgmap: null,
   151 
   152     // Mea Man 7 stage
   153     stage: 0,
   154 
   155     // Destination canvas
   156     canvas: null,
   157 
   158 
   159     // Data decompression
   160     decompress: function(index, length) {
   161         jsdump('decompress(' + index.toHex() + ', ' + length.toHex() + ')');
   162         if ((index < 0) || (index > 183)) {
   163             jsdump('decompress(): Index out of range: ' + index);
   164             return mm7Editor.ERR_DECOMP;
   165         }
   166 
   167         var ptr =       (mm7Editor.PTR_CMPDATA + (index * 5));
   168 
   169         var source =    this.getData24(ptr + 0);
   170         var size =      this.getData16(ptr + 3);
   171 
   172         // Error if the size we are given does not match the size in the compressed data header.
   173         if (size != length) {
   174             jsdump('decompress(): Size error: ' + length + ', ' + size);
   175             return mm7Editor.ERR_DECOMP;
   176         }
   177 
   178         var output =    new Array(size);
   179         var overflow =  new Array();
   180 
   181         var out = 0;
   182         while (out < size) {
   183             var ctrl = this.getData8(source++);
   184 
   185             for (var i = 0; i < 8; i++) {
   186                 if (ctrl & 0x80) {
   187                     var len = (this.getData8(source) >> 2); // Length of data to copy from moving window
   188                     var win = (((this.getData8(source) & 3) << 8) | this.getData8(source + 1)); // LZ window source
   189                     source += 2;
   190                     while (len--) {
   191                         if (out < size) output[out] = output[out - win];
   192                         else overflow[out - size] = output[out - win];
   193                         out++;
   194                     }
   195                 }
   196                 else {
   197                     if (out < size) output[out] = this.getData8(source);
   198                     else overflow[out - size] = this.getData8(source);
   199                     out++;
   200                     source++;
   201                 }
   202                 ctrl <<= 1;
   203             }
   204         }
   205         jsdump('decompress(): Decompressed data size: ' + size.toHex());
   206         if (out > size) {
   207             var overstr = '';
   208             for (i = 0; i < overflow.length; i++) {
   209                 overstr += overflow[i].toHex() + ' ';
   210             }
   211             jsdump('decompress(): overflow[' + overflow.length.toHex() + ']: ' + overstr);
   212         }
   213 
   214         return output;
   215     },
   216 
   217     // Load a palette index
   218     loadPal: function(index) {
   219         var pal = new Array(128);
   220 
   221         var ptr = (0x00C00000 | this.getData16(mm7Editor.PTR_PALDATA + (index * 2)));
   222         /* Note: ptr now points to palette headers
   223          *  byte-0: length
   224          *  byte-1 & byte-2: pointer to palette data
   225          *  byte-3: destination for this palette
   226          */
   227 
   228         var len = 0;
   229         while ((len = this.getData8(ptr++))) {
   230             var src = (0x00C40000 | this.getData16(ptr));
   231             ptr += 2;
   232 
   233             var dst = this.getData8(ptr++);
   234             for (var i = dst; i < (dst + len); i++) {
   235                 pal[i] = this.getData16(src);
   236                 src += 2;
   237             }
   238         }
   239 
   240         return pal;
   241     },
   242 
   243     // Load stage palette with sub-palette (used for swapping the palette mid-stage)
   244     loadStagePal: function(subpal) {
   245         if ((this.stage < 0) || (this.stage > 13)) return mm7Editor.ERR_GEN;
   246 
   247         var ptr = (mm7Editor.PTR_STAGEPAL + this.getData16(mm7Editor.PTR_STAGEPAL + (this.stage * 2)) + (subpal * 3));
   248         /* Note: ptr now points to the sub-palette header
   249          *  byte-0: palette index
   250          *  byte-1 & byte-2: background color (transparency)
   251          */
   252         var index = this.getData8(ptr++);
   253         var pal = this.loadPal(index);
   254         pal[0] = this.getData16(ptr);
   255 
   256         return pal;
   257     },
   258 
   259     // Load a tile set index
   260     loadTileSet: function(index) {
   261         ptr = (0x00C00000 | this.getData16(mm7Editor.PTR_STAGEHEAD + ((this.stage + 0x10) * 2)));
   262         /* Note: ptr now points to the stage graphics header:
   263          *  byte-0: Compression index
   264          *  byte-1 & byte-2: Decompressed data size
   265          *  byte-3: unknown
   266          *  byte-4: unknown
   267          *  byte-5: unknown
   268          */
   269         var cmp_index   = this.getData8 (ptr + 0);
   270         var cmp_length  = this.getData16(ptr + 1);
   271         var cmp_dest    = this.getData16(ptr + 3);
   272 
   273         // FIXME: Verify this is the correct way to handle this case ... works for Spring Man's stage
   274         var tiles = defaultTiles.slice(0);
   275         if (cmp_dest == 0x1100) {
   276             cmp_dest = 0x0200;
   277         }
   278         else {
   279             jsdump('buildStage(): Adjusting tile size to ' + cmp_dest.toHex() + ' bytes');
   280             for (var i = 0x0200; i < cmp_dest; i++) {
   281                 tiles[i] = 0;
   282             }
   283         }
   284         tiles.merge(cmp_dest, this.decompress(cmp_index, cmp_length));
   285 
   286         return tiles;
   287     },
   288 
   289     // Build stage images, tile maps, palettes, etc.
   290     buildStage: function(stage) {
   291         jsdump('buildStage(' + stage + ')');
   292         if ((stage < 0) || (stage > 13)) {
   293             jsdump('buildstage(): Stage error: ' + stage);
   294             return mm7Editor.ERR_GEN;
   295         }
   296         this.stage = stage;
   297 
   298         var ptr = 0;
   299 
   300 
   301         // Get foreground width and height
   302         ptr = (0x00C10000 | this.getData16(mm7Editor.PTR_FGMAPDATA + (this.stage * 2)));
   303         /* Note: ptr now points to the stage room data
   304          *  byte-0: stage width (in rooms)
   305          *  byte-1: stage height (in rooms)
   306          *  byte-2 to byte-n: room numbers (where n = (width * height) + 1)
   307          */
   308         var w = (this.getData8(ptr++) * 32); // Each room is 32 tiles wide
   309         var h = (this.getData8(ptr++) * 32); //          and 32 tiles high
   310 
   311         // Compile the fgmap
   312         this.fgmap = new snesTileMap(this.canvas, w, h, this.loadTileSet(0), this.loadStagePal(0));
   313 
   314         // Build tile maps
   315         for (var y = 0; y < h; y += 32) {       // Each room is 32 tiles high
   316             for (var x = 0; x < w; x += 32) {   //          and 32 tiles wide
   317                 this.buildRoom(this.fgmap, x, y, this.getData8(ptr++));
   318             }
   319         }
   320 
   321 /*
   322         // Get background width and height
   323         ptr = (0x00C10000 | this.getData16(mm7Editor.PTR_BGMAPDATA + (this.stage * 2)));
   324         /* Note: ptr now points to the stage room data
   325          *  byte-0: stage width (in rooms)
   326          *  byte-1: stage height (in rooms)
   327          *  byte-2 to byte-n: room numbers (where n = (width * height) + 1)
   328          * /
   329         w = (this.getData8(ptr++) * 32); // Each room is 32 tiles wide
   330         h = (this.getData8(ptr++) * 32); //          and 32 tiles high
   331 
   332         // Compile the bgmap
   333         this.bgmap = new snesTileMap(this.canvas, w, h, this.loadTileSet(0), this.loadStagePal(0));
   334 
   335         // Build tile maps
   336         for (var y = 0; y < h; y += 32) {       // Each room is 32 tiles high
   337             for (var x = 0; x < w; x += 32) {   //          and 32 tiles wide
   338                 this.buildRoom(this.bgmap, x, y, this.getData8(ptr++));
   339             }
   340         }
   341 */
   342         // Render background, then foreground
   343 //        this.bgmap.render(this.canvas);
   344 //        this.fgmap.render(this.canvas, true);
   345 
   346         // Resize canvas element
   347         this.canvas.setAttribute('width',  this.fgmap.w * 8);
   348         this.canvas.setAttribute('height', this.fgmap.h * 8);
   349 
   350         // Render only foreground
   351         // FIXME: Try doing putImageData after drawing every 256x256 pixel 'room': save memory, and probably feel like it's running faster?
   352         var ctx = this.canvas.getContext('2d');
   353         ctx.putImageData(this.fgmap.render(), 0, 0, 0, 0, (this.fgmap.w * 8), (this.fgmap.h * 8));
   354 
   355 
   356         return mm7Editor.ERR_NONE;
   357     },
   358 
   359     // Build a single room (made of 8x8 structures)
   360     buildRoom: function(map, x, y, num) {
   361         var ptr = (this.getData24(mm7Editor.PTR_ROOMDATA + (this.stage * 3)) + (num * 128)); // 128: each room is 8*8 blocks (each block is 2 bytes)
   362         var tx = (x + 32);
   363         var ty = (y + 32);
   364         var old_x = x;
   365 
   366         while (y < ty) {
   367             x = old_x;
   368             while (x < tx) {
   369                 this.buildStruct(map, x, y, this.getData16(ptr));
   370                 ptr += 2;
   371                 x += 4;
   372             }
   373             y += 4;
   374         }
   375     },
   376 
   377     // Build a single structure (made of 2x2 blocks)
   378     buildStruct: function(map, x, y, num) {
   379         var ptr = (mm7Editor.PTR_STRUCTDATA + this.getData24(mm7Editor.PTR_STRUCTDATA + (this.stage * 3)) + num);
   380         var tx = (x + 4);
   381         var ty = (y + 4);
   382         var old_x = x;
   383 
   384         while (y < ty) {
   385             x = old_x;
   386             while (x < tx) {
   387                 this.buildBlock(map, x, y, this.getData16(ptr));
   388                 ptr += 2;
   389                 x += 2;
   390             }
   391             y += 2;
   392         }
   393     },
   394 
   395     // Build a single block (made of 2x2 tiles)
   396     buildBlock: function(map, x, y, num) {
   397         var ptr = (this.getData24(mm7Editor.PTR_BLOCKDATA + (this.stage * 3)) + (num * 8)); // 8: each block is 2*2 tiles (each tile is 2 bytes)
   398         var tx = (x + 2);
   399         var ty = (y + 2);
   400         var old_x = x;
   401 
   402         while (y < ty) {
   403             x = old_x;
   404             while (x < tx) {
   405                 map.map[x + (y * map.w)] = this.getData16(ptr);
   406                 ptr += 2;
   407                 x++;
   408             }
   409             y++;
   410         }
   411     }
   412 
   413 };
   414 
   415 // Voodoo to inherit from the snesROM object
   416 mm7Editor.inherit(snesROM);